Skip to content

Instantly share code, notes, and snippets.

@sebdelsol
Last active June 24, 2025 12:44
Show Gist options
  • Save sebdelsol/494c897446deefe6995d8e38adc0a4b6 to your computer and use it in GitHub Desktop.
Save sebdelsol/494c897446deefe6995d8e38adc0a4b6 to your computer and use it in GitHub Desktop.
KOReader userpatch to show info in the file browser title with autorefresh_time
-- **MOVED to https://github.com/sebdelsol/KOReader.patches**
-- KOReader userpatch to show info in the file manager title bar
-- based on https://gist.github.com/hius07/c53bc1ed00e0490cb1a0709c5ed6e735#file-2-fm-title-info-lua
-- Menu added in the File browser menu (1st icon) to change all the settings and rearrange the items in the title bar
-- Items added: Custom text, Brightness Level, Warmth Level, Up time, Time spent awake, Time in suspend
-- Settings added: Auto refresh clock, Custom separator, Number of spaces around separator, Show wifi when disabled, Show frontlight when off, Show path
-- Fix screen rotation (path was disappearing)
-- Arrange items reorders the items the menu
local BD = require("ui/bidi")
local Device = require("device")
local FileManager = require("apps/filemanager/filemanager")
local FileManagerMenu = require("apps/filemanager/filemanagermenu")
local FileManagerMenuOrder = require("ui/elements/filemanager_menu_order")
local InputDialog = require("ui/widget/inputdialog")
local NetworkMgr = require("ui/network/manager")
local SortWidget = require("ui/widget/sortwidget")
local SpinWidget = require("ui/widget/spinwidget")
local UIManager = require("ui/uimanager")
local datetime = require("datetime")
local time = require("ui/time")
local T = require("ffi/util").template
local _ = require("gettext")
local Screen = Device.screen
local logger = require("logger")
-- Title bar config
local separators = {
bar = "|",
bullet = "•",
dot = "·",
en_dash = "-",
em_dash = "—",
none = "",
}
local config_default = {
-- items
show = {
wifi = true,
memory = false,
storage = false,
custom_text = false,
clock = true,
battery = true,
frontlight = false,
frontlight_warmth = false,
up_time = false,
awake_time = false,
suspend_time = false,
},
-- items order
order = {
"wifi",
"memory",
"storage",
"custom_text",
"clock",
"battery",
"frontlight",
"frontlight_warmth",
"up_time",
"awake_time",
"suspend_time",
},
-- settings
custom_text = "KOReader",
separator = "dot",
separator_space = 1,
separator_custom = "*",
show_path = true,
auto_refresh_clock = true,
wifi_show_disabled = false,
frontlight_show_off = true,
bold = false,
}
local function load_and_update_config()
local config = G_reader_settings:readSetting("filemanager_title_bar", config_default)
local updated = false
local function compare_and_update_config(config_to_browse, config_to_compare)
for setting, setting_value in pairs(config_to_browse) do
if type(setting_value) ~= "table" then -- only the settings
if config_to_compare[setting] == nil then -- missing ?
updated = true
if config_to_compare == config then -- missing in config: add it
config[setting] = setting_value
else -- missing in config_default: remove it
config[setting] = nil
end
end
end
end
for item, item_value in pairs(config_to_browse.show) do
if config_to_compare.show[item] == nil then -- missing ?
updated = true
if config_to_compare == config then -- missing in config: add it
config.show[item] = item_value
table.insert(config.order, item)
else -- missing in config_default: remove it
config.show[item] = nil
for i, _item in ipairs(config.order) do
if _item == item then
table.remove(config.order, i)
break
end
end
end
end
end
end
compare_and_update_config(config, config_default)
compare_and_update_config(config_default, config)
if updated then logger.info("Updated title bar config", config) end
separators.custom = config.separator_custom
return config
end
local config = load_and_update_config()
-- Title bar update
local genItemText = {
custom_text = function() return config.custom_text end,
clock = function() return datetime.secondsToHour(os.time(), G_reader_settings:isTrue("twelve_hour_clock")) end,
wifi = function() return NetworkMgr:isWifiOn() and "" or (config.wifi_show_disabled and "") end,
frontlight_warmth = function()
if Device:hasNaturalLight() then
local prefix = "💡"
local powerd = Device:getPowerDevice()
if powerd:isFrontlightOn() then
local warmth = powerd:frontlightWarmth()
if warmth then return (prefix .. "%d%%"):format(warmth) end
else
return config.frontlight_show_off and T(_("%1Off"), prefix)
end
end
end,
frontlight = function()
if Device:hasFrontlight() then
local prefix = "☼"
local powerd = Device:getPowerDevice()
if powerd:isFrontlightOn() then
if Device:isCervantes() or Device:isKobo() then
return (prefix .. "%d%%"):format(powerd:frontlightIntensity())
else
return (prefix .. "%d"):format(powerd:frontlightIntensity())
end
else
return config.frontlight_show_off and T(_("%1Off"), prefix)
end
end
end,
battery = function()
if Device:hasBattery() then
local powerd = Device:getPowerDevice()
local batt_lvl = powerd:getCapacity()
local batt_symbol = powerd:getBatterySymbol(powerd:isCharged(), powerd:isCharging(), batt_lvl)
local text = BD.wrap(batt_symbol) .. BD.wrap(batt_lvl .. "%")
if Device:hasAuxBattery() and powerd:isAuxBatteryConnected() then
local aux_batt_lvl = powerd:getAuxCapacity()
local aux_batt_symbol =
powerd:getBatterySymbol(powerd:isAuxCharged(), powerd:isAuxCharging(), aux_batt_lvl)
text = text .. " " .. BD.wrap("+") .. BD.wrap(aux_batt_symbol) .. BD.wrap(aux_batt_lvl .. "%")
end
return text
end
end,
up_time = function(self)
local SystemStat = self:_getSystemStat()
if SystemStat then
local uptime = time.boottime_or_realtime_coarse() - SystemStat.start_monotonic_time
return "⏻" .. datetime.secondsToClockDuration("modern", time.to_s(uptime), true, false, true) -- ⏽
end
end,
awake_time = function(self)
local SystemStat = self:_getSystemStat()
if SystemStat and (Device:canSuspend() or Device:canStandby()) then
local uptime = time.boottime_or_realtime_coarse() - SystemStat.start_monotonic_time
local suspend = Device:canSuspend() and Device.total_suspend_time or 0
local standby = Device:canStandby() and Device.total_standby_time or 0
local awake = uptime - suspend - standby
return "☀️" .. datetime.secondsToClockDuration("modern", time.to_s(awake), true, false, true) -- ☀☉⚡☀️
end
end,
suspend_time = function(self)
local SystemStat = self:_getSystemStat()
if SystemStat and Device:canSuspend() then
local suspend = Device.total_suspend_time
return "⏾" .. datetime.secondsToClockDuration("modern", time.to_s(suspend), true, false, true) -- ⏾💤😴⏸️
end
end,
storage = function(self)
local SystemStat = self:_getSystemStat()
if SystemStat then
SystemStat.kv_pairs = {}
SystemStat:appendStorageInfo()
return SystemStat.kv_pairs[3][2] -- available storage
end
end,
memory = function()
local statm = io.open("/proc/self/statm", "r")
if statm then
local _, rss = statm:read("*number", "*number")
statm:close()
return rss and ("%d"):format(math.floor(rss / 256))
end
end,
}
-- get SystemStat
function FileManager:_getSystemStat()
if self.systemstat then
local userpatch = require("userpatch")
return userpatch.getUpValue(self.systemstat.addToMainMenu, "SystemStat")
end
end
-- handle close : need to remove schedule update for clock auto refresh
local orig_FileManager_onClose = FileManager.onClose
FileManager.onClose = function(self)
orig_FileManager_onClose(self)
UIManager:unschedule(self.updateTitleBarTitle)
end
-- handle screen rotation
local orig_FileManager_onSetRotationMode = FileManager.onSetRotationMode
FileManager.onSetRotationMode = function(self, mode)
orig_FileManager_onSetRotationMode(self, mode)
self:updateTitleBarTitle()
end
-- redraw the title bar
function FileManager:updateTitleBarTitle()
if self.title_bar == nil then return end
local titlebar_texts = {}
for _, item in ipairs(config.order) do
if config.show[item] then
local text = genItemText[item](self)
if text then table.insert(titlebar_texts, text) end
end
end
-- logger.info("FileManager:updateTitleBarTitle CALLED")
local font_has_changed
local not_bold_font = self.title_bar.info_text_face
if config.bold then
font_has_changed = self.title_bar.title_face == not_bold_font
else
font_has_changed = self.title_bar.title_face ~= not_bold_font
end
if font_has_changed then
if config.bold then
self.title_bar.title_face = nil
self.title_bar.bottom_v_padding = self._title_bar_bottom_v_padding_saved
else
self.title_bar.title_face = not_bold_font
self.title_bar.bottom_v_padding = self._title_bar_bottom_v_padding_saved + Screen:scaleBySize(5)
end
self.title_bar:clear()
self.title_bar:init()
end
local spaces = string.rep(" ", config.separator_space)
local seperator = spaces .. (separators[config.separator] or "") .. spaces
self.title_bar:setTitle(table.concat(titlebar_texts, seperator))
self:updateTitleBarPath(config.show_path and self._title_bar_path_saved or "")
-- autorefresh time
UIManager:unschedule(self.updateTitleBarTitle)
if config.show.clock and config.auto_refresh_clock then
UIManager:scheduleIn(61 - tonumber(os.date("%S")), self.updateTitleBarTitle, self)
end
end
FileManager.onNetworkConnected = FileManager.updateTitleBarTitle
FileManager.onNetworkDisconnected = FileManager.updateTitleBarTitle
FileManager.onCharging = FileManager.updateTitleBarTitle
FileManager.onNotCharging = FileManager.updateTitleBarTitle
FileManager.onResume = FileManager.updateTitleBarTitle
FileManager.onTimeFormatChanged = FileManager.updateTitleBarTitle
FileManager.onFrontlightStateChanged = FileManager.updateTitleBarTitle
function FileManager:onPathChanged(path)
-- logger.info("FileManager:onPathChanged CALLED")
if not self._title_bar_bottom_v_padding_saved then
self._title_bar_bottom_v_padding_saved = self.title_bar.bottom_v_padding
end
self._title_bar_path_saved = path
self:updateTitleBarTitle() -- updateTitleBarPath done in updateTitleBarTitle
end
-- Title bar menu
function FileManagerMenu:_title_bar_input_text(touchmenu_instance, title, input, onChanged)
local text_dialog
text_dialog = InputDialog:new {
title = title,
input = input,
buttons = {
{
{
text = _("Cancel"),
id = "close",
callback = function() UIManager:close(text_dialog) end,
},
{
text = _("Set"),
is_enter_default = true,
callback = function()
onChanged(text_dialog:getInputText())
UIManager:close(text_dialog)
touchmenu_instance:updateItems()
self.ui:updateTitleBarTitle()
end,
},
},
},
}
UIManager:show(text_dialog)
text_dialog:onShowKeyboard()
end
-- Separators
local setting_menu_texts
function FileManagerMenu:_set_title_bar_separator_space(touchmenu_instance)
local spin_widget = SpinWidget:new {
title_text = setting_menu_texts.separator_space(true),
value = config.separator_space,
value_min = 1,
value_step = 1,
value_max = 5,
callback = function(spin)
config.separator_space = spin.value
touchmenu_instance:updateItems()
self.ui:updateTitleBarTitle()
end,
}
UIManager:show(spin_widget)
end
function FileManagerMenu:_set_title_bar_custom_separator_text(touchmenu_instance)
self:_title_bar_input_text(touchmenu_instance, _("Enter a custom separator"), separators.custom, function(text)
config.separator_custom = text
separators.custom = text
end)
end
local separator_menu_texts = {
dot = _("Dot"),
bullet = _("Bullet"),
en_dash = _("En dash"),
em_dash = _("Em dash"),
bar = _("Vertical bar"),
none = _("No separator"),
custom = function(concise) return concise and _("Custom separator") or _("Custom separator (long-press to edit)") end,
}
local separators_menu_help_text_funcs = {
custom = FileManagerMenu._set_title_bar_custom_separator_text,
}
local separators_order = {
"dot",
"bullet",
"en_dash",
"em_dash",
"bar",
"custom",
"none",
}
local function get_separator_menu_texts(separator, concise)
text = separator_menu_texts[separator]
if type(text) == "function" then text = text(concise) end
local spaces = string.rep(" ", config.separator_space)
local seperator = separators[config.separator] or ""
return T(text .. " '" .. spaces .. "%1" .. spaces .. "'", separators[separator])
end
function FileManagerMenu:_get_title_bar_separators_submenu()
local items = {}
for _, separator in ipairs(separators_order) do
local help_text_func = separators_menu_help_text_funcs[separator]
table.insert(items, {
text_func = function() return get_separator_menu_texts(separator) end,
help_text_func = help_text_func
and function(touchmenu_instance) help_text_func(self, touchmenu_instance) end,
checked_func = function() return config.separator == separator end,
callback = function(touchmenu_instance)
config.separator = separator
touchmenu_instance:updateItems()
self.ui:updateTitleBarTitle()
end,
})
end
items[#items].separator = true
table.insert(items, {
text_func = setting_menu_texts.separator_space,
keep_menu_open = true,
callback = function(touchmenu_instance) self:_set_title_bar_separator_space(touchmenu_instance) end,
})
return items
end
-- items
function FileManagerMenu:_set_title_bar_custom_text(touchmenu_instance)
self:_title_bar_input_text(
touchmenu_instance,
_("Enter a custom text"),
config.custom_text or "",
function(text) config.custom_text = text end
)
end
local item_menu_texts = {
wifi = _("Wifi"),
memory = _("Memory"),
storage = _("Storage"),
clock = _("Clock"),
battery = _("Battery"),
frontlight = _("Brightness level"),
frontlight_warmth = _("Warmth level"),
up_time = _("Up time"),
awake_time = _("Time spent awake"),
suspend_time = _("Time in suspend"),
custom_text = function(concise)
return concise and _("Custom text") or T(_("Custom text (long-press to edit): '%1'"), config.custom_text)
end,
}
local item_menu_help_texts = {
memory = _("RAM used, MiB"),
storage = _("Free storage, requires SystemStat plugin"),
custom_text = FileManagerMenu._set_title_bar_custom_text,
}
function FileManagerMenu:_title_bar_arrange_items(item_table_rebuild)
return {
text = _("Arrange title bar items"),
keep_menu_open = true,
enabled_func = function()
local show_count = 0 -- count shown items
for _, item in ipairs(config.order) do
show_count = show_count + (config.show[item] and 1 or 0)
if show_count >= 1 then return true end
end
return false
end,
callback = function(touchmenu_instance)
local item_table = {}
for _, item in ipairs(config.order) do
local text = item_menu_texts[item]
table.insert(item_table, {
text = type(text) == "function" and text(true) or text,
orig_item = item,
dim = not config.show[item],
})
end
local sort_item
sort_item = SortWidget:new {
title = _("Arrange title bar items"),
item_table = item_table,
callback = function()
for i, item in ipairs(item_table) do
config.order[i] = item.orig_item
end
if item_table_rebuild then -- rebuild the item_table, so the items in the menu are reordered
touchmenu_instance.item_table = item_table_rebuild()
touchmenu_instance:updateItems()
end
self.ui:updateTitleBarTitle()
UIManager:setDirty(nil, "ui")
end,
}
UIManager:show(sort_item)
end,
}
end
function FileManagerMenu:_get_title_bar_item(item)
local text = item_menu_texts[item]
local help_text = item_menu_help_texts[item]
return {
text = type(text) == "string" and text,
text_func = type(text) == "function" and function() return text() end,
help_text = type(help_text) == "string" and help_text,
help_text_func = type(help_text) == "function"
and function(touchmenu_instance) help_text(self, touchmenu_instance) end,
checked_func = function() return config.show[item] end,
callback = function(touchmenu_instance)
config.show[item] = not config.show[item]
touchmenu_instance:updateItems()
self.ui:updateTitleBarTitle()
end,
}
end
-- settings
setting_menu_texts = { -- already declared above
auto_refresh_clock = _("Auto refresh clock"),
show_path = _("Show file browser path"),
wifi_show_disabled = _("Show wifi status even when disabled"),
frontlight_show_off = _("Show frontlight when off"),
bold = _("Bold font"),
separator = function() return T(_("Item separator: %1"), get_separator_menu_texts(config.separator, true)) end,
custom_text = function() return T(_("Custom text: '%1'"), config.custom_text) end,
separator_space = function(concise)
return concise and _("Number of spaces around separator")
or T(_("Number of spaces around separator: %1"), config.separator_space)
end,
}
local setting_menu_callback = {
custom_text = FileManagerMenu._set_title_bar_custom_text,
separator_space = FileManagerMenu._set_title_bar_separator_space,
}
local setting_submenu_items = {
separator = FileManagerMenu._get_title_bar_separators_submenu,
}
local setting_menu_enable_func = {
auto_refresh_clock = function() return config.show.clock end,
wifi_show_disabled = function() return config.show.wifi end,
frontlight_show_off = function() return config.show.frontlight or config.show.frontlight_warmth end,
custom_text = function() return config.show.custom_text end,
}
local setting_menu_order = {
"bold",
"wifi_show_disabled",
"frontlight_show_off",
"auto_refresh_clock",
"show_path",
"custom_text",
"separator",
}
function FileManagerMenu:_get_title_bar_setting(setting)
local text = setting_menu_texts[setting]
local submenu = setting_submenu_items[setting]
local enabled_func = setting_menu_enable_func[setting]
local callback_func = setting_menu_callback[setting]
return {
text = type(text) == "string" and text,
text_func = type(text) == "function" and function() return text() end,
enabled_func = enabled_func and function() return enabled_func() end,
checked_func = not (submenu or callback_func) and function() return config[setting] end,
callback = not submenu and function(touchmenu_instance)
if callback_func then
callback_func(self, touchmenu_instance)
else
config[setting] = not config[setting]
touchmenu_instance:updateItems()
end
self.ui:updateTitleBarTitle()
end,
keep_menu_open = callback_func ~= nil,
sub_item_table = submenu and submenu(self),
}
end
-- Patch title bar menu
local orig_FileManagerMenu_setUpdateItemTable = FileManagerMenu.setUpdateItemTable
FileManagerMenu.setUpdateItemTable = function(self)
orig_FileManagerMenu_setUpdateItemTable(self)
local buttons = FileManagerMenuOrder["KOMenu:menu_buttons"]
for i, button in ipairs(buttons) do
if button == "filemanager_settings" then
logger.info("Add title bar options in the file manager settings menu")
local setting_table = {}
for _, setting in ipairs(setting_menu_order) do
table.insert(setting_table, self:_get_title_bar_setting(setting))
end
local sub_item_table_func
sub_item_table_func = function()
local _table = {
{
text = _("Configure title bar"),
sub_item_table = setting_table,
},
self:_title_bar_arrange_items(sub_item_table_func),
}
_table[#_table].separator = true
for _, item in ipairs(config.order) do
table.insert(_table, self:_get_title_bar_item(item))
end
return _table
end
table.insert(self.tab_item_table[i], 1, {
text = _("Title bar"),
separator = true,
sub_item_table_func = sub_item_table_func,
})
break
end
end
end
@sebdelsol
Copy link
Author

sebdelsol commented May 16, 2025

I've to check that. It uses the default UI font. Should it uses its own font, whatever the UI one is ?
If you want to change all the UI fonts, which would be a more consistent solution UX wise, you can use this user patch.

@sebdelsol
Copy link
Author

@shuvashish76
Copy link

shuvashish76 commented May 26, 2025

Nice, thankyou for the repo. There is something similar for https://github.com/kiwidude68/calibre_plugins who compiled all his calibre plugins in one repo.
Maybe create your own thread at https://www.mobileread.com/forums/forumdisplay.php?f=276 for more user visibility/feedback?
with something like "My custom user patch collection" title?

@shuvashish76
Copy link

Also please do add any foss license file to the repo.

@sebdelsol
Copy link
Author

Oh I always forget about that. Tx for reminding me

@WIZARD012358
Copy link

After installing the patch, the kindle clock appears on the screensaver wallpaper. Can you fix that?

1000356632

@sebdelsol
Copy link
Author

I've tried to reproduce the bug (on a Kindle as well). In the file manager, I toggled on the title bar with the clock visible and enabled Auto refresh clock. However, I wasn't able to reproduce the bug. By the way, the clock placement doesn't align with the title bar: It should be centered.

@sebdelsol
Copy link
Author

sebdelsol commented May 28, 2025

I've moved all the user patches here.
So I've raised a bug here.

It feels like a native UI issue. Anyway, I've prevented any title bar updates while the device is suspended, better safe than sorry.
You'll find the fix here.

Tx to raise the issue.

@sebdelsol
Copy link
Author

Is there a way to change the font?

There this new user patch : 2-ui-font.lua that allows to change the UI font.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment