From 29bbc84de2eb4145037198547585cf27d4895824 Mon Sep 17 00:00:00 2001 From: hius07 <62179190+hius07@users.noreply.github.com> Date: Tue, 1 Jul 2025 21:43:51 +0300 Subject: [PATCH 01/23] Touchmenu: fix menu refreshing on check (#13996) --- frontend/apps/reader/modules/readerbookmark.lua | 1 + frontend/apps/reader/modules/readertoc.lua | 1 + frontend/dispatcher.lua | 3 +-- frontend/ui/elements/screen_rotation_menu_table.lua | 1 + frontend/ui/widget/touchmenu.lua | 7 ++++--- plugins/SSH.koplugin/main.lua | 2 +- plugins/autoturn.koplugin/main.lua | 1 + plugins/autowarmth.koplugin/main.lua | 4 ++++ plugins/calibre.koplugin/main.lua | 1 + plugins/coverimage.koplugin/main.lua | 3 +++ plugins/gestures.koplugin/main.lua | 6 ++---- plugins/hotkeys.koplugin/main.lua | 8 +++----- plugins/profiles.koplugin/main.lua | 8 ++++---- 13 files changed, 27 insertions(+), 19 deletions(-) diff --git a/frontend/apps/reader/modules/readerbookmark.lua b/frontend/apps/reader/modules/readerbookmark.lua index f05a6d176..538e557c3 100644 --- a/frontend/apps/reader/modules/readerbookmark.lua +++ b/frontend/apps/reader/modules/readerbookmark.lua @@ -99,6 +99,7 @@ function ReaderBookmark:addToMainMenu(menu_items) checked_func = function() return self.ui.paging.bookmark_flipping_mode end, + check_callback_closes_menu = true, callback = function(touchmenu_instance) self.ui.paging:onToggleBookmarkFlipping() touchmenu_instance:closeMenu() diff --git a/frontend/apps/reader/modules/readertoc.lua b/frontend/apps/reader/modules/readertoc.lua index 77bc104ec..5f6881ad3 100644 --- a/frontend/apps/reader/modules/readertoc.lua +++ b/frontend/apps/reader/modules/readertoc.lua @@ -1122,6 +1122,7 @@ See Style tweaks → Miscellaneous → Alternative ToC hints.]]) checked_func = function() return self.ui.document:isTocAlternativeToc() end, + check_callback_closes_menu = true, callback = function(touchmenu_instance) if self.ui.document:isTocAlternativeToc() then UIManager:show(ConfirmBox:new{ diff --git a/frontend/dispatcher.lua b/frontend/dispatcher.lua index 760d09740..ab9ca5152 100644 --- a/frontend/dispatcher.lua +++ b/frontend/dispatcher.lua @@ -995,11 +995,10 @@ function Dispatcher:addSubMenu(caller, menu, location, settings) menu.ignored_by_menu_search = true -- all those would be duplicated table.insert(menu, { text = _("Nothing"), - keep_menu_open = true, - no_refresh_on_check = true, checked_func = function() return location[settings] ~= nil and Dispatcher:_itemsCount(location[settings]) == 0 end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) local function do_remove() local actions = location[settings] diff --git a/frontend/ui/elements/screen_rotation_menu_table.lua b/frontend/ui/elements/screen_rotation_menu_table.lua index e3b69ae46..678523729 100644 --- a/frontend/ui/elements/screen_rotation_menu_table.lua +++ b/frontend/ui/elements/screen_rotation_menu_table.lua @@ -14,6 +14,7 @@ local function genMenuItem(text, mode) return Screen:getRotationMode() == mode end, radio = true, + check_callback_closes_menu = true, callback = function(touchmenu_instance) UIManager:broadcastEvent(Event:new("SetRotationMode", mode)) touchmenu_instance:closeMenu() diff --git a/frontend/ui/widget/touchmenu.lua b/frontend/ui/widget/touchmenu.lua index ee20bad17..eb9f4ed2a 100644 --- a/frontend/ui/widget/touchmenu.lua +++ b/frontend/ui/widget/touchmenu.lua @@ -50,6 +50,8 @@ local TouchMenuItem = InputContainer:extend{ dimen = nil, face = Font:getFace("smallinfofont"), show_parent = nil, + check_callback_updates_menu = nil, -- set to true for item with checkmark if its callback updates menu + check_callback_closes_menu = nil, -- set to true for item with checkmark if its callback closes menu } function TouchMenuItem:init() @@ -206,8 +208,7 @@ function TouchMenuItem:onTapSelect(arg, ges) -- Unhighlight -- self.item_frame.invert = false - -- NOTE: If the menu is going to be closed, we can safely drop that. - if self.item.keep_menu_open then + if self.item.keep_menu_open or self.item.check_callback_updates_menu then UIManager:widgetInvert(self.item_frame, highlight_dimen.x, highlight_dimen.y, highlight_dimen.w) UIManager:setDirty(nil, "ui", highlight_dimen) end @@ -924,7 +925,7 @@ function TouchMenu:onMenuSelect(item, tap_on_checkmark) -- must set keep_menu_open=true if that is wished) callback(self) if refresh then - if not item.no_refresh_on_check then + if not (item.check_callback_updates_menu or item.check_callback_closes_menu) then self:updateItems() end elseif not item.keep_menu_open then diff --git a/plugins/SSH.koplugin/main.lua b/plugins/SSH.koplugin/main.lua index 26480298f..4d554eb19 100644 --- a/plugins/SSH.koplugin/main.lua +++ b/plugins/SSH.koplugin/main.lua @@ -170,8 +170,8 @@ function SSH:addToMainMenu(menu_items) sub_item_table = { { text = _("SSH server"), - keep_menu_open = true, checked_func = function() return self:isRunning() end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) self:onToggleSSHServer() -- sleeping might not be needed, but it gives the feeling diff --git a/plugins/autoturn.koplugin/main.lua b/plugins/autoturn.koplugin/main.lua index 4801aab33..10a96bf7f 100644 --- a/plugins/autoturn.koplugin/main.lua +++ b/plugins/autoturn.koplugin/main.lua @@ -131,6 +131,7 @@ function AutoTurn:addToMainMenu(menu_items) return self:_enabled() and T(_("Autoturn: %1"), time_string) or _("Autoturn") end, checked_func = function() return self:_enabled() end, + check_callback_updates_menu = true, callback = function(menu) local DateTimeWidget = require("ui/widget/datetimewidget") local autoturn_seconds = G_reader_settings:readSetting("autoturn_timeout_seconds", 30) diff --git a/plugins/autowarmth.koplugin/main.lua b/plugins/autowarmth.koplugin/main.lua index 4fa87fdb7..0a9534ff0 100644 --- a/plugins/autowarmth.koplugin/main.lua +++ b/plugins/autowarmth.koplugin/main.lua @@ -608,6 +608,7 @@ function AutoWarmth:getSubMenuItems() return not self.easy_mode end, help_text = _("In the expert mode, different types of twilight can be used in addition to civil twilight."), + check_callback_updates_menu = true, callback = function(touchmenu_instance) self.easy_mode = not self.easy_mode G_reader_settings:saveSetting("autowarmth_easy_mode", self.easy_mode) @@ -662,6 +663,7 @@ function AutoWarmth:getFlOffDuringDayMenu() return _("Frontlight off during day") end end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) if self.easy_mode then self.fl_off_during_day = not self.fl_off_during_day @@ -889,6 +891,7 @@ function AutoWarmth:getScheduleMenu() checked_func = function() return self.scheduler_times[num] ~= nil end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) local hh = 12 local mm = 0 @@ -1112,6 +1115,7 @@ function AutoWarmth:getWarmthMenu() }) end end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) if Device:hasNaturalLight() then if self.control_warmth and self.control_nightmode then diff --git a/plugins/calibre.koplugin/main.lua b/plugins/calibre.koplugin/main.lua index 25977fe9e..13588b056 100644 --- a/plugins/calibre.koplugin/main.lua +++ b/plugins/calibre.koplugin/main.lua @@ -303,6 +303,7 @@ function Calibre:getWirelessMenuTable() checked_func = function() return G_reader_settings:has("calibre_wireless_url") end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) local MultiInputDialog = require("ui/widget/multiinputdialog") local url_dialog diff --git a/plugins/coverimage.koplugin/main.lua b/plugins/coverimage.koplugin/main.lua index f77407f07..8709fc219 100644 --- a/plugins/coverimage.koplugin/main.lua +++ b/plugins/coverimage.koplugin/main.lua @@ -502,6 +502,7 @@ function CoverImage:menuEntryCache() checked_func = function() return self.cover_image_cache_maxfiles >= 0 end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) self:sizeSpinner(touchmenu_instance, "cover_image_cache_maxfiles", _("Number of covers"), -1, 100, 36, self.cleanCache) end, @@ -522,6 +523,7 @@ function CoverImage:menuEntryCache() checked_func = function() return self.cover_image_cache_maxsize >= 0 end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) self:sizeSpinner(touchmenu_instance, "cover_image_cache_maxsize", _("Cache size"), -1, 100, 5, self.cleanCache, C_("Data storage size", "MB")) end, @@ -574,6 +576,7 @@ function CoverImage:menuEntrySetPath(key, title, help, info, default, folder_onl checked_func = function() return isFileOk(self[key]) or (isPathAllowed(self[key]) and folder_only) end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) UIManager:show(ConfirmBox:new{ text = info, diff --git a/plugins/gestures.koplugin/main.lua b/plugins/gestures.koplugin/main.lua index 199b36b2c..5886559e8 100644 --- a/plugins/gestures.koplugin/main.lua +++ b/plugins/gestures.koplugin/main.lua @@ -295,11 +295,10 @@ function Gestures:genMenu(ges) if gestures_list[ges] ~= nil then table.insert(sub_items, { text = T(_("%1 (default)"), Dispatcher:menuTextFunc(self.defaults[ges])), - keep_menu_open = true, - no_refresh_on_check = true, checked_func = function() return util.tableEquals(self.gestures[ges], self.defaults[ges]) end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) local function do_remove() self.gestures[ges] = util.tableDeepCopy(self.defaults[ges]) @@ -313,11 +312,10 @@ function Gestures:genMenu(ges) end table.insert(sub_items, { text = _("Pass through"), - keep_menu_open = true, - no_refresh_on_check = true, checked_func = function() return self.gestures[ges] == nil end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) local function do_remove() self.gestures[ges] = nil diff --git a/plugins/hotkeys.koplugin/main.lua b/plugins/hotkeys.koplugin/main.lua index cbd84b620..4ea748f3e 100644 --- a/plugins/hotkeys.koplugin/main.lua +++ b/plugins/hotkeys.koplugin/main.lua @@ -192,11 +192,10 @@ function HotKeys:genMenu(hotkey) local default_text = default_action and Dispatcher:menuTextFunc(default_action) or _("No action") table.insert(sub_items, { text = T(_("%1 (default)"), default_text), - keep_menu_open = true, - no_refresh_on_check = true, checked_func = function() return util.tableEquals(self.hotkeys[hotkey], self.defaults[hotkey]) end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) local function do_remove() self.hotkeys[hotkey] = util.tableDeepCopy(self.defaults[hotkey]) @@ -209,12 +208,10 @@ function HotKeys:genMenu(hotkey) end table.insert(sub_items, { text = _("No action"), - keep_menu_open = true, - no_refresh_on_check = true, - separator = true, checked_func = function() return self.hotkeys[hotkey] == nil or next(self.hotkeys[hotkey]) == nil end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) local function do_remove() self.hotkeys[hotkey] = nil @@ -223,6 +220,7 @@ function HotKeys:genMenu(hotkey) end Dispatcher.removeActions(self.hotkeys[hotkey], do_remove) end, + separator = true, }) Dispatcher:addSubMenu(self, sub_items, self.hotkeys, hotkey) -- Since we are already handling potential conflicts via overrideConflictingKeyEvents(), both "No action" and "Nothing", diff --git a/plugins/profiles.koplugin/main.lua b/plugins/profiles.koplugin/main.lua index af61e7dcc..ba5d52fc0 100644 --- a/plugins/profiles.koplugin/main.lua +++ b/plugins/profiles.koplugin/main.lua @@ -629,10 +629,10 @@ function Profiles:genAutoExecPathChangedMenuItem(text, event, profile_name, sepa local value = util.tableGetValue(self.autoexec, event, profile_name, condition) return value and txt .. ": " .. value or txt end, - no_refresh_on_check = true, checked_func = function() return util.tableGetValue(self.autoexec, event, profile_name, condition) end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) local dialog local buttons = {{ @@ -760,10 +760,10 @@ function Profiles:genAutoExecDocConditionalMenuItem(text, event, profile_name, s local txt = util.tableGetValue(self.autoexec, event, profile_name, condition, prop) return txt and title .. " " .. txt or title:sub(1, -2) end, - no_refresh_on_check = true, checked_func = function() return util.tableGetValue(self.autoexec, event, profile_name, condition, prop) and true end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) local dialog local buttons = self.document == nil and {} or {{ @@ -830,10 +830,10 @@ function Profiles:genAutoExecDocConditionalMenuItem(text, event, profile_name, s enabled_func = function() return not util.tableGetValue(self.autoexec, event_always, profile_name) end, - no_refresh_on_check = true, checked_func = function() return util.tableGetValue(self.autoexec, event, profile_name, conditions[3][2]) and true end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) local condition = conditions[3][2] local dialog @@ -895,10 +895,10 @@ function Profiles:genAutoExecDocConditionalMenuItem(text, event, profile_name, s enabled_func = function() return not util.tableGetValue(self.autoexec, event_always, profile_name) end, - no_refresh_on_check = true, checked_func = function() return util.tableGetValue(self.autoexec, event, profile_name, conditions[4][2]) and true end, + check_callback_updates_menu = true, callback = function(touchmenu_instance) local condition = conditions[4][2] local collections = util.tableGetValue(self.autoexec, event, profile_name, condition) From ad3dddbdd24b5cfd7b514e71b63890be69f2eabb Mon Sep 17 00:00:00 2001 From: hius07 <62179190+hius07@users.noreply.github.com> Date: Tue, 1 Jul 2025 21:44:39 +0300 Subject: [PATCH 02/23] readerhighlight: fix accidental tap while long-pressing (#14004) --- .../apps/reader/modules/readerhighlight.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/apps/reader/modules/readerhighlight.lua b/frontend/apps/reader/modules/readerhighlight.lua index ea4059870..3befd58e3 100644 --- a/frontend/apps/reader/modules/readerhighlight.lua +++ b/frontend/apps/reader/modules/readerhighlight.lua @@ -1015,13 +1015,10 @@ function ReaderHighlight:onTapSelectModeIcon() end function ReaderHighlight:onTap(_, ges) - -- We only actually need to clear if we have something to clear in the first place. - -- (We mainly want to avoid CRe's clearSelection, - -- which may incur a redraw as it invalidates the cache, c.f., #6854) - -- ReaderHighlight:clear can only return true if self.hold_pos was set anyway. - local cleared = self.hold_pos and self:clear() - -- We only care about potential taps on existing highlights, not on taps that closed a highlight menu. - if not cleared and ges and #self.view.highlight.visible_boxes > 0 then + if self.hold_pos then -- accidental tap while long-pressing + return self:onHoldRelease() + end + if ges and #self.view.highlight.visible_boxes > 0 then local pos = self.view:screenToPageTransform(ges.pos) local highlights_tapped = {} for _, box in ipairs(self.view.highlight.visible_boxes) do @@ -1487,7 +1484,6 @@ function ReaderHighlight:showHighlightDialog(index) end, } UIManager:show(edit_highlight_dialog) - return true end function ReaderHighlight:addToHighlightDialog(idx, fn_button) @@ -1526,7 +1522,11 @@ function ReaderHighlight:onShowHighlightMenu(index) anchor = function() return self:_getDialogAnchor(self.highlight_dialog, index) end, - tap_close_callback = function() self:handleEvent(Event:new("Tap")) end, + tap_close_callback = function() + if self.hold_pos then + self:clear() + end + end, } -- NOTE: Disable merging for this update, -- or the buggy Sage kernel may alpha-blend it into the page (with a bogus alpha value, to boot)... From 095b0d7421e16abb6fe73d1e2a8d7f004bdb649c Mon Sep 17 00:00:00 2001 From: Volterxien <101931401+Volterxien@users.noreply.github.com> Date: Fri, 4 Jul 2025 12:02:28 -0400 Subject: [PATCH 03/23] Add OPDS sync feature (#13946) Closes #8494. --- plugins/opds.koplugin/main.lua | 14 +- plugins/opds.koplugin/opdsbrowser.lua | 602 +++++++++++++++++++++++--- 2 files changed, 556 insertions(+), 60 deletions(-) diff --git a/plugins/opds.koplugin/main.lua b/plugins/opds.koplugin/main.lua index feaab388d..f45f4b7db 100644 --- a/plugins/opds.koplugin/main.lua +++ b/plugins/opds.koplugin/main.lua @@ -45,12 +45,14 @@ local OPDS = WidgetContainer:extend{ } function OPDS:init() - self.settings = LuaSettings:open(self.opds_settings_file) - if next(self.settings.data) == nil then + self.opds_settings = LuaSettings:open(self.opds_settings_file) + if next(self.opds_settings.data) == nil then self.updated = true -- first run, force flush end - self.servers = self.settings:readSetting("servers", self.default_servers) - self.downloads = self.settings:readSetting("downloads", {}) + self.servers = self.opds_settings:readSetting("servers", self.default_servers) + self.downloads = self.opds_settings:readSetting("downloads", {}) + self.settings = self.opds_settings:readSetting("settings", {}) + self.pending_syncs = self.opds_settings:readSetting("pending_syncs", {}) self:onDispatcherRegisterActions() self.ui.menu:registerToMainMenu(self) end @@ -76,6 +78,8 @@ function OPDS:onShowOPDSCatalog() self.opds_browser = OPDSBrowser:new{ servers = self.servers, downloads = self.downloads, + settings = self.settings, + pending_syncs = self.pending_syncs, title = _("OPDS catalog"), is_popout = false, is_borderless = true, @@ -121,7 +125,7 @@ end function OPDS:onFlushSettings() if self.updated then - self.settings:flush() + self.opds_settings:flush() self.updated = nil end end diff --git a/plugins/opds.koplugin/opdsbrowser.lua b/plugins/opds.koplugin/opdsbrowser.lua index a307c6091..768f2e7c5 100644 --- a/plugins/opds.koplugin/opdsbrowser.lua +++ b/plugins/opds.koplugin/opdsbrowser.lua @@ -3,6 +3,7 @@ local ButtonDialog = require("ui/widget/buttondialog") local Cache = require("cache") local CheckButton = require("ui/widget/checkbutton") local ConfirmBox = require("ui/widget/confirmbox") +local Device = require("device") local DocumentRegistry = require("document/documentregistry") local InfoMessage = require("ui/widget/infomessage") local InputDialog = require("ui/widget/inputdialog") @@ -12,6 +13,9 @@ local NetworkMgr = require("ui/network/manager") local Notification = require("ui/widget/notification") local OPDSParser = require("opdsparser") local OPDSPSE = require("opdspse") +local SpinWidget = require("ui/widget/spinwidget") +local TextViewer = require("ui/widget/textviewer") +local Trapper = require("ui/trapper") local UIManager = require("ui/uimanager") local http = require("socket.http") local lfs = require("libs/libkoreader-lfs") @@ -61,20 +65,94 @@ function OPDSBrowser:init() self.catalog_title = nil self.title_bar_left_icon = "plus" self.onLeftButtonTap = function() - self:addEditCatalog() + self:showOPDSMenu() end Menu.init(self) -- call parent's init() end +function OPDSBrowser:showOPDSMenu() + local dialog + dialog = ButtonDialog:new{ + buttons = { + {{ + text = _("Add catalog"), + callback = function() + UIManager:close(dialog) + self:addEditCatalog() + end, + align = "left", + }}, + {}, + {{ + text = _("Sync all catalogs"), + callback = function() + UIManager:close(dialog) + NetworkMgr:runWhenConnected(function() + self.sync_force = false + self:checkSyncDownload() + end) + end, + align = "left", + }}, + {{ + text = _("Force sync all catalogs"), + callback = function() + UIManager:close(dialog) + NetworkMgr:runWhenConnected(function() + self.sync_force = true + self:checkSyncDownload() + end) + end, + align = "left", + }}, + {{ + text = _("Set max number of files to sync"), + callback = function() + self:setMaxSyncDownload() + end, + align = "left", + }}, + {{ + text = _("Set sync folder"), + callback = function() + self:setSyncDir() + end, + align = "left", + }}, + {{ + text = _("Set file types to sync"), + callback = function() + self:setSyncFiletypes() + end, + align = "left", + }}, + }, + shrink_unneeded_width = true, + anchor = function() + return self.title_bar.left_button.image.dimen + end, + } + UIManager:show(dialog) +end + + local function buildRootEntry(server) + local icons = "" + if server.username then + icons = "\u{f2c0}" + end + if server.sync then + icons = "\u{f46a} " .. icons + end return { text = server.title, - mandatory = server.username and "\u{f2c0}", + mandatory = icons, url = server.url, username = server.username, password = server.password, raw_names = server.raw_names, -- use server raw filenames for download searchable = server.url and server.url:match("%%s") and true or false, + sync = server.sync, } end @@ -120,7 +198,7 @@ function OPDSBrowser:addEditCatalog(item) title = _("Add OPDS catalog") end - local dialog, check_button_raw_names + local dialog, check_button_raw_names, check_button_sync_catalog dialog = MultiInputDialog:new{ title = title, fields = fields, @@ -138,6 +216,7 @@ function OPDSBrowser:addEditCatalog(item) callback = function() local new_fields = dialog:getFields() new_fields[5] = check_button_raw_names.checked or nil + new_fields[6] = check_button_sync_catalog.checked or nil self:editCatalogFromInput(new_fields, item) UIManager:close(dialog) end, @@ -150,7 +229,13 @@ function OPDSBrowser:addEditCatalog(item) checked = item and item.raw_names, parent = dialog, } + check_button_sync_catalog = CheckButton:new{ + text = _("Sync catalog"), + checked = item and item.sync, + parent = dialog, + } dialog:addWidget(check_button_raw_names) + dialog:addWidget(check_button_sync_catalog) UIManager:show(dialog) dialog:onShowKeyboard() end @@ -198,6 +283,7 @@ function OPDSBrowser:editCatalogFromInput(fields, item, no_refresh) username = fields[3] ~= "" and fields[3] or nil, password = fields[4] ~= "" and fields[4] or nil, raw_names = fields[5], + sync = fields[6], } local new_item = buildRootEntry(new_server) local new_idx, itemnumber @@ -208,7 +294,7 @@ function OPDSBrowser:editCatalogFromInput(fields, item, no_refresh) new_idx = #self.servers + 2 itemnumber = new_idx end - self.servers[new_idx - 1] = new_server + self.servers[new_idx - 1] = new_server -- first item is "Downloads" self.item_table[new_idx] = new_item if not no_refresh then self:switchItemTable(nil, self.item_table, itemnumber) @@ -366,25 +452,27 @@ function OPDSBrowser:genItemTableFromCatalog(catalog, item_url) hrefs[link.rel] = build_href(link.href) end end - -- OpenSearch - if link.type:find(self.search_type) then - if link.href then - table.insert(item_table, { -- the first item in each subcatalog - text = "\u{f002} " .. _("Search"), -- append SEARCH icon - url = build_href(self:getSearchTemplate(build_href(link.href))), - searchable = true, - }) - has_opensearch = true + if not self.sync then + -- OpenSearch + if link.type:find(self.search_type) then + if link.href then + table.insert(item_table, { -- the first item in each subcatalog + text = "\u{f002} " .. _("Search"), -- append SEARCH icon + url = build_href(self:getSearchTemplate(build_href(link.href))), + searchable = true, + }) + has_opensearch = true + end end - end - -- Calibre search (also matches the actual template for OpenSearch!) - if link.type:find(self.search_template_type) and link.rel and link.rel:find("search") then - if link.href and not has_opensearch then - table.insert(item_table, { - text = "\u{f002} " .. _("Search"), - url = build_href(link.href:gsub("{searchTerms}", "%%s")), - searchable = true, - }) + -- Calibre search (also matches the actual template for OpenSearch!) + if link.type:find(self.search_template_type) and link.rel and link.rel:find("search") then + if link.href and not has_opensearch then + table.insert(item_table, { + text = "\u{f002} " .. _("Search"), + url = build_href(link.href:gsub("{searchTerms}", "%%s")), + searchable = true, + }) + end end end end @@ -460,13 +548,16 @@ function OPDSBrowser:genItemTableFromCatalog(catalog, item_url) -- Check for the presence of the pdf suffix and add it -- if it's missing. local href = link.href - if util.getFileNameSuffix(href) ~= "pdf" then - href = href .. ".pdf" + -- Calibre web OPDS download links end with "//" + if not util.stringEndsWith(href, "/pdf/") then + if util.getFileNameSuffix(href) ~= "pdf" then + href = href .. ".pdf" + end + table.insert(item.acquisitions, { + type = link.title, + href = build_href(href), + }) end - table.insert(item.acquisitions, { - type = link.title, - href = build_href(href), - }) end end end @@ -577,14 +668,7 @@ end -- Shows dialog to download / stream a book function OPDSBrowser:showDownloads(item) local acquisitions = item.acquisitions - local filename = item.title - if item.author then - filename = item.author .. " - " .. filename - end - local filename_orig = filename - if self.root_catalog_raw_names then - filename = nil - end + local filename, filename_orig = self:getFileName(item) local function createTitle(path, file) -- title for ButtonDialog return T(_("Download folder:\n%1\n\nDownload filename:\n%2\n\nDownload file type:"), @@ -635,14 +719,7 @@ function OPDSBrowser:showDownloads(item) enabled = false, }) else - local filetype = util.getFileNameSuffix(acquisition.href) - logger.dbg("Filetype for download is", filetype) - if not DocumentRegistry:hasProvider("dummy." .. filetype) then - filetype = nil - end - if not filetype and DocumentRegistry:hasProvider(nil, acquisition.type) then - filetype = DocumentRegistry:mimeToExt(acquisition.type) - end + local filetype = self.getFiletype(acquisition) if filetype then -- supported file type local text = url.unescape(acquisition.title or string.upper(filetype)) table.insert(download_buttons, { @@ -663,7 +740,7 @@ function OPDSBrowser:showDownloads(item) password = self.root_catalog_password, }) self._manager.updated = true - Notification:notify(_("Book added to download list")) + Notification:notify(_("Book added to download list"), Notification.SOURCE_OTHER) end, }) end @@ -700,7 +777,7 @@ function OPDSBrowser:showDownloads(item) G_reader_settings:saveSetting("download_dir", path) self.download_dialog:setTitle(createTitle(path, filename)) end, - }:chooseDir(self.getCurrentDownloadDir()) + }:chooseDir(self:getCurrentDownloadDir()) end, }, { @@ -729,7 +806,7 @@ function OPDSBrowser:showDownloads(item) filename = filename_orig end UIManager:close(dialog) - self.download_dialog:setTitle(createTitle(self.getCurrentDownloadDir(), filename)) + self.download_dialog:setTitle(createTitle(self:getCurrentDownloadDir(), filename)) end, }, } @@ -753,7 +830,6 @@ function OPDSBrowser:showDownloads(item) text = _("Book information"), enabled = type(item.content) == "string", callback = function() - local TextViewer = require("ui/widget/textviewer") UIManager:show(TextViewer:new{ title = item.text, title_multilines = true, @@ -765,19 +841,35 @@ function OPDSBrowser:showDownloads(item) }) self.download_dialog = ButtonDialog:new{ - title = createTitle(self.getCurrentDownloadDir(), filename), + title = createTitle(self:getCurrentDownloadDir(), filename), buttons = buttons, } UIManager:show(self.download_dialog) end +-- Helper function to get the filetype from an acquisitions table +function OPDSBrowser.getFiletype(link) + local filetype = util.getFileNameSuffix(link.href) + if not DocumentRegistry:hasProvider("dummy." .. filetype) then + filetype = nil + end + if not filetype and DocumentRegistry:hasProvider(nil, link.type) then + filetype = DocumentRegistry:mimeToExt(link.type) + end + return filetype +end + -- Returns user selected or last opened folder -function OPDSBrowser.getCurrentDownloadDir() - return G_reader_settings:readSetting("download_dir") or G_reader_settings:readSetting("lastdir") +function OPDSBrowser:getCurrentDownloadDir() + if self.sync then + return self.settings.sync_dir + else + return G_reader_settings:readSetting("download_dir") or G_reader_settings:readSetting("lastdir") + end end function OPDSBrowser:getLocalDownloadPath(filename, filetype, remote_url) - local download_dir = OPDSBrowser.getCurrentDownloadDir() + local download_dir = self:getCurrentDownloadDir() filename = filename and filename .. "." .. filetype:lower() or self:getServerFileName(remote_url) filename = util.getSafeFilename(filename, download_dir) filename = (download_dir ~= "/" and download_dir or "") .. '/' .. filename @@ -895,6 +987,29 @@ function OPDSBrowser:onMenuHold(item) title = item.text, title_align = "center", buttons = { + { + { + text = _("Force sync"), + callback = function() + UIManager:close(dialog) + NetworkMgr:runWhenConnected(function() + self.sync_force = true + self:checkSyncDownload(item.idx) + end) + end, + }, + { + text = _("Sync"), + callback = function() + UIManager:close(dialog) + NetworkMgr:runWhenConnected(function() + self.sync_force = false + self:checkSyncDownload(item.idx) + end) + end, + }, + }, + {}, { { text = _("Delete"), @@ -1063,7 +1178,6 @@ function OPDSBrowser:showDownloadListItemDialog(item) ok_text = _("Download"), ok_callback = function() NetworkMgr:runWhenConnected(function() - local Trapper = require("ui/trapper") Trapper:wrap(function() self._manager:downloadDownloadList() end) @@ -1084,7 +1198,6 @@ function OPDSBrowser:showDownloadListItemDialog(item) TextBoxWidget.PTF_BOLD_START, _("Description"), TextBoxWidget.PTF_BOLD_END, "\n", dl_item.info, }) - local TextViewer = require("ui/widget/textviewer") textviewer = TextViewer:new{ title = dl_item.catalog, text = text, @@ -1095,11 +1208,11 @@ function OPDSBrowser:showDownloadListItemDialog(item) return true end +-- Download whole download list function OPDSBrowser:downloadDownloadList() local info = InfoMessage:new{ text = _("Downloading… (tap to cancel)") } UIManager:show(info) UIManager:forceRePaint() - local Trapper = require("ui/trapper") local completed, downloaded = Trapper:dismissableRunInSubprocess(function() local dl = {} for _, item in ipairs(self.downloads) do @@ -1137,4 +1250,383 @@ function OPDSBrowser:downloadDownloadList() end end +function OPDSBrowser:setMaxSyncDownload() + local current_max_dl = self.settings.sync_max_dl or 50 + local spin = SpinWidget:new{ + title_text = "Set maximum sync size", + info_text = "Set the max number of books to download at a time", + value = current_max_dl, + value_min = 0, + value_max = 1000, + value_step = 10, + value_hold_step = 50, + default_value = 50, + wrap = true, + ok_text = "Save", + callback = function(spin) + self.settings.sync_max_dl = spin.value + self._manager.updated = true + end, + } + UIManager:show(spin) +end + +function OPDSBrowser:setSyncDir() + local force_chooser_dir + if Device:isAndroid() then + force_chooser_dir = Device.home_dir + end + + require("ui/downloadmgr"):new{ + onConfirm = function(inbox) + logger.info("set opds sync folder", inbox) + self.settings.sync_dir = inbox + self._manager.updated = true + end, + }:chooseDir(force_chooser_dir) +end + +-- Set string for desired filetypes +function OPDSBrowser:setSyncFiletypes(filetype_list) + local input = self.settings.filetypes + local dialog + dialog = InputDialog:new{ + title = _("File types to sync"), + description = _("A comma separated list of desired filetypes"), + input_hint = _("epub, mobi"), + input = input, + buttons = { + { + { + text = _("Cancel"), + id = "close", + callback = function() + UIManager:close(dialog) + end, + }, + { + text = _("Save"), + is_enter_default = true, + callback = function() + local str = dialog:getInputText() + self.settings.filetypes = str ~= "" and str or nil + self._manager.updated = true + UIManager:close(dialog) + end, + }, + }, + }, + } + UIManager:show(dialog) + dialog:onShowKeyboard() +end + +-- Helper function to get filename and set nil if using raw names +function OPDSBrowser:getFileName(item) + local filename = item.title + if item.author then + filename = item.author .. " - " .. filename + end + local filename_orig = filename + if self.root_catalog_raw_names then + filename = nil + end + return filename, filename_orig +end + +function OPDSBrowser:updateFieldInCatalog(item, name, value) + item[name] = value + self._manager.updated = true +end + +function OPDSBrowser:checkSyncDownload(idx) + if self.settings.sync_dir then + self.sync = true + local info = InfoMessage:new{ + text = _("Synchronizing lists…"), + } + UIManager:show(info) + UIManager:forceRePaint() + if idx then + self:fillPendingSyncs(self.servers[idx-1]) -- First item is "Downloads" + else + for _, item in ipairs(self.servers) do + if item.sync then + self:fillPendingSyncs(item) + end + end + end + UIManager:close(info) + if #self.pending_syncs > 0 then + Trapper:wrap(function() + self:downloadPendingSyncs() + end) + else + UIManager:show(InfoMessage:new{ + text = _("Up to date!"), + }) + end + self.sync = false + else + UIManager:show(InfoMessage:new{ + text = _("Please choose a folder for sync downloads first"), + }) + end +end + +-- Add entries to self.pending_syncs +function OPDSBrowser:fillPendingSyncs(server) + self.root_catalog_password = server.password + self.root_catalog_raw_names = server.raw_names + self.root_catalog_username = server.username + self.root_catalog_title = server.title + self.sync_server = server + self.sync_server_list = self.sync_server_list or {} + self.sync_max_dl = self.settings.sync_max_dl or 50 + + local file_list + local file_str = self.settings.filetypes + local new_last_download = nil + local dl_count = 1 + if file_str then + file_list = {} + for filetype in util.gsplit(file_str, ",") do + file_list[util.trim(filetype)] = true + end + end + local sync_list = self:getSyncDownloadList() + if sync_list then + for i, entry in ipairs(sync_list) do + -- for project gutenberg + local sub_table = {} + local item + if entry.url then + sub_table = self:getSyncDownloadList(entry.url) + end + if #sub_table > 0 then + -- The first element seems to be most compatible. Second element has most options + item = sub_table[2] + else + item = entry + end + for j, link in ipairs(item.acquisitions) do + -- Only save first link in case of several file types + if i == 1 and j == 1 then + new_last_download = link.href + end + local filetype = self.getFiletype(link) + if filetype then + if not file_str or file_list and file_list[filetype] then + local filename = self:getFileName(entry) + local download_path = self:getLocalDownloadPath(filename, filetype, link.href) + if dl_count <= self.sync_max_dl then -- Append only max_dl entries... may still have sync backlog + table.insert(self.pending_syncs, { + file = download_path, + url = link.href, + username = self.root_catalog_username, + password = self.root_catalog_password, + catalog = server.url, + }) + dl_count = dl_count + 1 + end + break + end + end + end + end + end + self.sync_server_list[server.url] = true + if new_last_download then + logger.dbg("Updating opds last download for server", server.title, "to", new_last_download) + self:updateFieldInCatalog(server, "last_download", new_last_download) + end + +end + +-- Get list of books to download bigger than sync_max_dl +function OPDSBrowser:getSyncDownloadList(url_arg) + local sync_table = {} + local fetch_url = url_arg or self.sync_server.url + local sub_table + local up_to_date = false + while #sync_table < self.sync_max_dl and not up_to_date do + sub_table = self:genItemTableFromURL(fetch_url) + -- timeout + if #sub_table == 0 then + return sync_table + end + local count = 1 + local acquisitions_empty = false + -- For project gutenberg + while #sub_table[count].acquisitions == 0 do + if util.stringEndsWith(sub_table[count].url, ".opds") then + acquisitions_empty = true + break + end + if count == #sub_table then + return sync_table + end + count = count + 1 + end + -- First entry in table is the newest + -- If already downloaded, return + local first_href + if acquisitions_empty then + first_href = sub_table[count].url + else + first_href = sub_table[1].acquisitions[1].href + end + if first_href == self.sync_server.last_download and not self.sync_force then + return nil + end + local href + for i, entry in ipairs(sub_table) do + if acquisitions_empty then + if i >= count then + href = entry.url + else + href = nil + end + else + href = entry.acquisitions[1].href + end + if href then + if href == self.sync_server.last_download and not self.sync_force then + up_to_date = true + break + else + table.insert(sync_table, entry) + end + end + end + if not sub_table.hrefs.next then + break + end + fetch_url = sub_table.hrefs.next + end + return sync_table +end + +-- Download pending syncs list +function OPDSBrowser:downloadPendingSyncs() + local dl_list = self.pending_syncs + local function dismissable_download() + local info = InfoMessage:new{ text = _("Downloading… (tap to cancel)") } + UIManager:show(info) + UIManager:forceRePaint() + local completed, downloaded, duplicate_list = Trapper:dismissableRunInSubprocess(function() + local dl = {} + local dupe_list = {} + for _, item in ipairs(dl_list) do + if self.sync_server_list[item.catalog] then + if lfs.attributes(item.file) and not self.sync_force then + table.insert(dupe_list, item) + else + if self:downloadFile(item.file, item.url, item.username, item.password) then + dl[item.file] = true + end + end + end + end + return dl, dupe_list + end, info) + + if completed then + UIManager:close(info) + end + local dl_count = 0 + local dl_size = #dl_list + for i = dl_size, 1, -1 do + local item = dl_list[i] + if downloaded and downloaded[item.file] then + dl_count = dl_count + 1 + table.remove(dl_list, i) + else -- if subprocess has been interrupted, check for the downloaded file + local attr = lfs.attributes(item.file) + if attr then + if attr.size > 0 then + table.remove(dl_list, i) + if attr.modification > os.time() - 300 then -- Only count files touched in the last 5 mins + dl_count = dl_count + 1 + end + else -- incomplete download + os.remove(item.file) + end + end + end + end + local duplicate_count = duplicate_list and #duplicate_list or 0 + dl_count = dl_count - duplicate_count + -- Make downloaded count timeout if there's a duplicate file prompt + local timeout = nil + if duplicate_count > 0 then + timeout = 3 + end + if dl_count > 0 then + UIManager:show(InfoMessage:new{ text = T(N_("1 book downloaded", "%1 books downloaded", dl_count), dl_count), timeout = timeout,}) + end + self._manager.updated = true + return duplicate_list + end + + local duplicate_list = dismissable_download() + + if duplicate_list and #duplicate_list > 0 then + local textviewer + local duplicate_files = { _("These files are already on the device:") } + for _, entry in ipairs(duplicate_list) do + table.insert(duplicate_files, entry.file) + end + local text = table.concat(duplicate_files, "\n") + textviewer = TextViewer:new{ + title = _("Duplicate files"), + text = text, + buttons_table = { + { + { + text = _("Do nothing"), + callback = function() + textviewer:onClose() + end + }, + { + text = _("Overwrite"), + callback = function() + self.sync_force = true + textviewer:onClose() + for _, entry in ipairs(duplicate_list) do + table.insert(dl_list, entry) + end + Trapper:wrap(function() + dismissable_download() + end) + end + }, + { + text = _("Download copies"), + callback = function() + self.sync_force = true + textviewer:onClose() + local copy_download_dir, original_dir, copies_dir, copy_download_path + copies_dir = "copies" + original_dir = util.splitFilePathName(duplicate_list[1].file) + copy_download_dir = original_dir .. copies_dir .. "/" + util.makePath(copy_download_dir) + for _, entry in ipairs(duplicate_list) do + local _, file_name = util.splitFilePathName(entry.file) + copy_download_path = copy_download_dir .. file_name + entry.file = copy_download_path + table.insert(dl_list, entry) + end + Trapper:wrap(function() + dismissable_download() + end) + end + }, + }, + }, + } + UIManager:show(textviewer) + end +end return OPDSBrowser From 5d3c71724f0adeaeec7c8886b94e82147c0a2e2d Mon Sep 17 00:00:00 2001 From: TnS-hun Date: Sun, 6 Jul 2025 23:39:51 +0200 Subject: [PATCH 04/23] Dictionary: fix invalid highlight when switching dictionary (#14028) When the contents of the HtmlBoxWidget changed the page boxes were not invalidated, this resulted the highlight to show up at a wrong coordinate. Fixes #14013 --- frontend/ui/widget/htmlboxwidget.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/ui/widget/htmlboxwidget.lua b/frontend/ui/widget/htmlboxwidget.lua index fb66077fd..6b08a01b3 100644 --- a/frontend/ui/widget/htmlboxwidget.lua +++ b/frontend/ui/widget/htmlboxwidget.lua @@ -264,6 +264,8 @@ function HtmlBoxWidget:setContent(body, css, default_font_size, is_xhtml, no_css self.document:layoutDocument(self.dimen.w, self.dimen.h, default_font_size) self.page_count = self.document:getPages() + self.page_boxes = nil + self:clearHighlight() end function HtmlBoxWidget:_render() From 18621018fd808d44acc07a426db4c11c50e16615 Mon Sep 17 00:00:00 2001 From: hius07 <62179190+hius07@users.noreply.github.com> Date: Mon, 7 Jul 2025 09:17:41 +0300 Subject: [PATCH 05/23] File browser Plus: fix add/remove folder shortcut (#14032) --- frontend/apps/filemanager/filemanager.lua | 44 ++++++++++------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/frontend/apps/filemanager/filemanager.lua b/frontend/apps/filemanager/filemanager.lua index 9b4ce2c52..ed88a071a 100644 --- a/frontend/apps/filemanager/filemanager.lua +++ b/frontend/apps/filemanager/filemanager.lua @@ -129,7 +129,7 @@ function FileManager:setupLayout() button_padding = Screen:scaleBySize(5), left_icon = "home", left_icon_size_ratio = 1, - left_icon_tap_callback = function() self:goHome() end, + left_icon_tap_callback = function() self:onHome() end, left_icon_hold_callback = function() self:onShowFolderMenu() end, right_icon = self.selected_files and "check" or "plus", right_icon_size_ratio = 1, @@ -163,7 +163,7 @@ function FileManager:setupLayout() if file_manager.selected_files then -- toggle selection item.dim = not item.dim and true or nil file_manager.selected_files[item.path] = item.dim - self:updateItems() + self:updateItems(1, true) else file_manager:openFile(item.path) end @@ -212,7 +212,7 @@ function FileManager:setupLayout() if is_file then file_manager.selected_files[file] = true item.dim = true - self:updateItems() + self:updateItems(1, true) end end, }, @@ -649,10 +649,11 @@ function FileManager:tapPlus() end else -- no selected files + local folder = self.file_chooser.path local function refresh_titlebar_callback() - self:updateTitleBarPath() + self:updateTitleBarPath(folder) end - title = BD.dirpath(filemanagerutil.abbreviate(self.file_chooser.path)) + title = BD.dirpath(filemanagerutil.abbreviate(folder)) buttons = { { { @@ -696,7 +697,7 @@ function FileManager:tapPlus() text = _("Go to HOME folder"), callback = function() UIManager:close(plus_dialog) - self:goHome() + self:onHome() end }, }, @@ -706,12 +707,12 @@ function FileManager:tapPlus() callback = function() UIManager:close(plus_dialog) -- any random document - self:openRandomFile(self.file_chooser.path, false) + self:openRandomFile(folder, false) end, hold_callback = function() UIManager:close(plus_dialog) -- only previously unopened - self:openRandomFile(self.file_chooser.path, true) + self:openRandomFile(folder, true) end }, }, @@ -719,7 +720,7 @@ function FileManager:tapPlus() self.folder_shortcuts:genShowFolderShortcutsButton(close_dialog_callback), }, { - self.folder_shortcuts:genAddRemoveShortcutButton(self.file_chooser.path, close_dialog_callback, refresh_titlebar_callback), + self.folder_shortcuts:genAddRemoveShortcutButton(folder, close_dialog_callback, refresh_titlebar_callback), }, } @@ -727,12 +728,12 @@ function FileManager:tapPlus() table.insert(buttons, 4, { -- after "Paste" or "Import files here" button { text_func = function() - return Device:isValidPath(self.file_chooser.path) + return Device:isValidPath(folder) and _("Switch to SDCard") or _("Switch to internal storage") end, callback = function() UIManager:close(plus_dialog) - if Device:isValidPath(self.file_chooser.path) then + if Device:isValidPath(folder) then local ok, sd_path = Device:hasExternalSD() if ok then self.file_chooser:changeToPath(sd_path) @@ -749,10 +750,10 @@ function FileManager:tapPlus() table.insert(buttons, 4, { -- always after "Paste" button { text = _("Import files here"), - enabled = Device:isValidPath(self.file_chooser.path), + enabled = Device:isValidPath(folder), callback = function() UIManager:close(plus_dialog) - Device.importFile(self.file_chooser.path) + Device.importFile(folder) end, }, }) @@ -847,7 +848,10 @@ function FileManager:onRefresh() return true end -function FileManager:goHome() +FileManager.onRefreshContent = FileManager.onRefresh +FileManager.onBookMetadataChanged = FileManager.onRefresh + +function FileManager:onHome() if not self.file_chooser:goHome() then self:setHome() end @@ -1307,18 +1311,6 @@ function FileManager:copyRecursive(from, to) return ffiUtil.execute(self.cp_bin, "-r", from, to ) == 0 end -function FileManager:onHome() - return self:goHome() -end - -function FileManager:onRefreshContent() - self:onRefresh() -end - -function FileManager:onBookMetadataChanged() - self:onRefresh() -end - function FileManager:onShowFolderMenu() local button_dialog local function genButton(button_text, button_path) From 8a7e077cdd1223b91837d807a6750e7b4d2230b6 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Mon, 7 Jul 2025 08:24:12 +0200 Subject: [PATCH 06/23] doc: fix macOS build instructions (#14030) Add missing flock dependency. Close #14029. --- doc/Building.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Building.md b/doc/Building.md index bfd47937b..ced06b8e0 100644 --- a/doc/Building.md +++ b/doc/Building.md @@ -115,7 +115,7 @@ Install the prerequisites using [Homebrew](https://brew.sh/): ``` brew install autoconf automake bash binutils cmake coreutils findutils \ - gnu-getopt libtool make meson nasm ninja pkg-config sdl2 util-linux + flock gnu-getopt libtool make meson nasm ninja pkg-config sdl2 util-linux ``` You will also have to ensure Homebrew's findutils, gnu-getopt, make & util-linux are in your path, e.g., via From 07845f29a3ce1f4dd094b57a4aadc0416c9cf90b Mon Sep 17 00:00:00 2001 From: Frans de Jonge Date: Mon, 7 Jul 2025 19:40:27 +0200 Subject: [PATCH 07/23] Revert "doc: fix macOS build instructions (#14030)" (#14033) This reverts commit 8a7e077cdd1223b91837d807a6750e7b4d2230b6. --- doc/Building.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Building.md b/doc/Building.md index ced06b8e0..bfd47937b 100644 --- a/doc/Building.md +++ b/doc/Building.md @@ -115,7 +115,7 @@ Install the prerequisites using [Homebrew](https://brew.sh/): ``` brew install autoconf automake bash binutils cmake coreutils findutils \ - flock gnu-getopt libtool make meson nasm ninja pkg-config sdl2 util-linux + gnu-getopt libtool make meson nasm ninja pkg-config sdl2 util-linux ``` You will also have to ensure Homebrew's findutils, gnu-getopt, make & util-linux are in your path, e.g., via From 6a567406e2faf65e9c59ba6b22f4b99a229f5bd9 Mon Sep 17 00:00:00 2001 From: hius07 <62179190+hius07@users.noreply.github.com> Date: Tue, 8 Jul 2025 09:50:59 +0300 Subject: [PATCH 08/23] touchmenu: optimize (#14027) --- frontend/ui/widget/touchmenu.lua | 174 +++++++++++-------------------- 1 file changed, 60 insertions(+), 114 deletions(-) diff --git a/frontend/ui/widget/touchmenu.lua b/frontend/ui/widget/touchmenu.lua index eb9f4ed2a..0d7ddc7b2 100644 --- a/frontend/ui/widget/touchmenu.lua +++ b/frontend/ui/widget/touchmenu.lua @@ -471,7 +471,6 @@ local TouchMenu = FocusManager:extend{ fface = Font:getFace("ffont"), width = nil, height = nil, - page = 1, max_per_page_default = 10, -- for UIManager:setDirty show_parent = nil, @@ -481,6 +480,7 @@ local TouchMenu = FocusManager:extend{ } function TouchMenu:init() + self.screen_size = Screen:getSize() -- We won't include self.bordersize in our width calculations, so that -- borders are pushed off-(screen-)width and so not visible. -- We'll then be similar to bottom menu ConfigDialog (where this @@ -500,8 +500,8 @@ function TouchMenu:init() ges = "tap", range = Geom:new{ x = 0, y = 0, - w = Screen:getWidth(), - h = Screen:getHeight(), + w = self.screen_size.w, + h = self.screen_size.h, } } } @@ -624,7 +624,7 @@ function TouchMenu:init() -- This CenterContainer will make the left and right borders drawn -- off-screen self[1] = CenterContainer:new{ - dimen = Screen:getSize(), + dimen = self.screen_size, ignore = "height", self.menu_frame } @@ -643,51 +643,30 @@ function TouchMenu:init() HorizontalSpan:new{width = Size.span.horizontal_default}, } self.footer_top_margin = VerticalSpan:new{width = Size.span.vertical_default} + + local menu_height = self.height and math.min(self.height, self.screen_size.h) or self.screen_size.h + local items_height = menu_height - self.bar:getSize().h - self.footer_top_margin:getSize().h - self.footer:getSize().h + self.max_per_page = math.floor(items_height / (self.item_height + self.split_line:getSize().h)) + self.bar:switchToTab(self.last_index or 1) end -function TouchMenu:onCloseWidget() - -- NOTE: We don't pass a region in order to ensure a full-screen flash to avoid ghosting, - -- but we only need to do that if we actually have a FM or RD below us. - -- Don't do anything when we're switching between the two, or if we don't actually have a live instance of 'em... - local FileManager = require("apps/filemanager/filemanager") - local ReaderUI = require("apps/reader/readerui") - if (FileManager.instance and not FileManager.instance.tearing_down) - or (ReaderUI.instance and not ReaderUI.instance.tearing_down) then - UIManager:setDirty(nil, "flashui") - end -end - -function TouchMenu:_recalculatePageLayout() - local content_height -- content == item_list + footer - - local bar_height = self.bar:getSize().h - local footer_height = self.footer:getSize().h - if self.height then - content_height = self.height - bar_height - else - content_height = #self.item_table * self.item_height + footer_height - -- split line height - content_height = content_height + (#self.item_table - 1) - content_height = content_height + self.footer_top_margin:getSize().h - end - if content_height + bar_height > Screen:getHeight() then - content_height = Screen:getHeight() - bar_height - end - - local item_list_content_height = content_height - footer_height - self.perpage = math.floor(item_list_content_height / self.item_height) - local max_per_page = self.item_table.max_per_page or self.max_per_page_default - if self.perpage > max_per_page then - self.perpage = max_per_page - end - +function TouchMenu:updateItems(target_page, target_item_id) + self.perpage = math.min(self.max_per_page, self.item_table.max_per_page or self.max_per_page_default) self.page_num = math.ceil(#self.item_table / self.perpage) -end + if target_item_id ~= nil then -- show menu page with target item + for i, v in ipairs(self.item_table) do + if v.menu_item_id == target_item_id then + target_page = math.floor( (i - 1) / self.perpage ) + 1 + break + end + end + end + self.page = target_page or self.page + if self.page > self.page_num then + self.page = self.page_num + end -function TouchMenu:updateItems() - local old_dimen = self.dimen and self.dimen:copy() - self:_recalculatePageLayout() self.item_group:clear() self.layout = {} table.insert(self.item_group, self.bar) @@ -708,14 +687,12 @@ function TouchMenu:updateItems() h = self.item_height, }, show_parent = self.show_parent, - item_visible_index = c, } table.insert(self.item_group, item_tmp) if item_tmp:isEnabled() then table.insert(self.layout, {[self.cur_tab] = item_tmp}) -- for the focusmanager end if item.separator and c ~= self.perpage and i ~= #self.item_table then - -- insert split line table.insert(self.item_group, self.split_line) end else @@ -743,7 +720,6 @@ function TouchMenu:updateItems() local batt_lvl = powerd:getCapacity() local batt_symbol = powerd:getBatterySymbol(powerd:isCharged(), powerd:isCharging(), batt_lvl) time_info_txt = BD.wrap(time_info_txt) .. " " .. BD.wrap("⌁") .. 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) @@ -753,6 +729,7 @@ function TouchMenu:updateItems() self.time_info:setText(time_info_txt) -- recalculate dimen based on new layout + local old_dimen = self.dimen:copy() self.dimen.w = self.width self.dimen.h = self.item_group:getSize().h + self.bordersize*2 + self.padding -- (no padding at top) self:moveFocusTo(self.cur_tab, 1, FocusManager.NOT_FOCUS) -- reset the position of the focusmanager @@ -791,13 +768,11 @@ function TouchMenu:switchMenuTab(tab_num) -- It's like getting a new menu every time we switch tab! -- Also, switching to the _same_ tab resets the stack and takes us back to -- the top of the menu tree - self.page = 1 - -- clear item table stack self.item_table_stack = {} self.parent_id = nil self.cur_tab = tab_num self.item_table = self.tab_item_table[tab_num] - self:updateItems() + self:updateItems(1) end function TouchMenu:backToUpperMenu(no_close) @@ -808,68 +783,40 @@ function TouchMenu:backToUpperMenu(no_close) if self.item_table.needs_refresh and self.item_table.refresh_func then self.item_table = self.item_table.refresh_func() end - self.page = 1 - if self.parent_id then - self:_recalculatePageLayout() -- we need an accurate self.perpage - for i = 1, #self.item_table do - if self.item_table[i].menu_item_id == self.parent_id then - self.page = math.floor( (i - 1) / self.perpage ) + 1 - break - end - end - self.parent_id = nil - end - self:updateItems() + self:updateItems(1, self.parent_id) + self.parent_id = nil elseif not no_close then self:closeMenu() end end -function TouchMenu:closeMenu() - self.close_callback() +function TouchMenu:onBack() + self:backToUpperMenu() end function TouchMenu:onNextPage() - if self.page < self.page_num then - self.page = self.page + 1 - elseif self.page == self.page_num then - self.page = 1 - end - self:updateItems() - return true + return self:onGotoPage(self.page + 1) end function TouchMenu:onPrevPage() - if self.page > 1 then - self.page = self.page - 1 - elseif self.page == 1 then - self.page = self.page_num - end - self:updateItems() - return true + return self:onGotoPage(self.page - 1) end function TouchMenu:onFirstPage() - self.page = 1 - self:updateItems() - return true + return self:onGotoPage(1) end function TouchMenu:onLastPage() - self.page = self.page_num - self:updateItems() - return true + return self:onGotoPage(self.page_num) end function TouchMenu:onGotoPage(nb) - if nb > self.page_num then - self.page = self.page_num + if nb > self.page_num then -- cycle by swipes only + nb = 1 elseif nb < 1 then - self.page = 1 - else - self.page = nb + nb = self.page_num end - self:updateItems() + self:updateItems(nb) return true end @@ -937,18 +884,7 @@ function TouchMenu:onMenuSelect(item, tap_on_checkmark) item.menu_item_id = item.menu_item_id or tostring(item) -- unique id self.parent_id = item.menu_item_id self.item_table = sub_item_table - self.page = 1 - if self.item_table.open_on_menu_item_id_func then - self:_recalculatePageLayout() -- we need an accurate self.perpage - local open_id = self.item_table.open_on_menu_item_id_func() - for i = 1, #self.item_table do - if self.item_table[i].menu_item_id == open_id then - self.page = math.floor( (i - 1) / self.perpage ) + 1 - break - end - end - end - self:updateItems() + self:updateItems(1, self.item_table.open_on_menu_item_id_func and self.item_table.open_on_menu_item_id_func()) end end return true @@ -1000,6 +936,10 @@ function TouchMenu:onMenuHold(item, text_truncated) return true end +function TouchMenu:closeMenu() + self.close_callback() +end + function TouchMenu:onTapCloseAllMenus(arg, ges_ev) if ges_ev.pos:notIntersectWith(self.dimen) then self:closeMenu() @@ -1010,8 +950,16 @@ function TouchMenu:onClose() self:closeMenu() end -function TouchMenu:onBack() - self:backToUpperMenu() +function TouchMenu:onCloseWidget() + -- NOTE: We don't pass a region in order to ensure a full-screen flash to avoid ghosting, + -- but we only need to do that if we actually have a FM or RD below us. + -- Don't do anything when we're switching between the two, or if we don't actually have a live instance of 'em... + local FileManager = require("apps/filemanager/filemanager") + local ReaderUI = require("apps/reader/readerui") + if (FileManager.instance and not FileManager.instance.tearing_down) + or (ReaderUI.instance and not ReaderUI.instance.tearing_down) then + UIManager:setDirty(nil, "flashui") + end end -- Menu search feature @@ -1019,7 +967,7 @@ function TouchMenu:search(search_for) local found_menu_items = {} local MAX_MENU_DEPTH = 10 -- our menu max depth is currently 6 - local function recurse(item_table, path, text, icon, depth, is_disabled) + local function recurse(item_table, path, text, icon, depth) if item_table.ignored_by_menu_search then return end @@ -1031,8 +979,7 @@ function TouchMenu:search(search_for) if type(v) == "table" and not v.ignored_by_menu_search then local entry_text = v.text_func and v.text_func() or v.text local entry_displayed_text = entry_text - is_disabled = is_disabled or v.enabled == false or (v.enabled_func and v.enabled_func() == false) - if is_disabled then + if v.enabled == false or (v.enabled_func and v.enabled_func() == false) then entry_displayed_text = "\u{2592}\u{200A}" .. entry_displayed_text -- Medium Shade (▒) + Hair Space end local indent = "\u{2192}\u{200A}" -- Rightwards Arrow (→) + Hair Space @@ -1049,7 +996,7 @@ function TouchMenu:search(search_for) sub_item_table = v.sub_item_table_func() end if sub_item_table and not sub_item_table.ignored_by_menu_search then - recurse(sub_item_table, walk_path, walk_text, icon, depth, is_disabled) + recurse(sub_item_table, walk_path, walk_text, icon, depth) end end end @@ -1171,10 +1118,9 @@ function TouchMenu:openMenu(path, with_animation) end end elseif step == STEPS.MENU_ITEM_HIGHLIGHT then - local item_visible_index = (item_nb - 1) % self.perpage + 1 local item_widget - for i, w in ipairs(self.item_group) do - if w.item_visible_index == item_visible_index then + for _, w in ipairs(self.item_group) do + if w.item and w.item.idx == item_nb then item_widget = w break end @@ -1312,8 +1258,8 @@ function TouchMenu:onShowMenuSearch() title = _("Search results"), subtitle = T(_("Query: %1"), search_string), item_table = get_current_search_results(), - width = math.floor(Screen:getWidth() * 0.9), - height = math.floor(Screen:getHeight() * 0.9), + width = math.floor(self.screen_size.w * 0.9), + height = math.floor(self.screen_size.h * 0.9), single_line = true, items_per_page = 10, items_font_size = Menu.getItemFontSize(10), @@ -1330,7 +1276,7 @@ function TouchMenu:onShowMenuSearch() -- build container self.results_menu_container = CenterContainer:new{ - dimen = Screen:getSize(), + dimen = self.screen_size, results_menu, } From e0c85306513eedb00eaa197d4c1f91dbcd78f6ec Mon Sep 17 00:00:00 2001 From: hius07 <62179190+hius07@users.noreply.github.com> Date: Wed, 9 Jul 2025 13:46:48 +0300 Subject: [PATCH 09/23] touchmenu: no empty menus (#14040) --- frontend/ui/widget/touchmenu.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/ui/widget/touchmenu.lua b/frontend/ui/widget/touchmenu.lua index 0d7ddc7b2..577e3bff4 100644 --- a/frontend/ui/widget/touchmenu.lua +++ b/frontend/ui/widget/touchmenu.lua @@ -652,6 +652,7 @@ function TouchMenu:init() end function TouchMenu:updateItems(target_page, target_item_id) + if #self.item_table == 0 then return end self.perpage = math.min(self.max_per_page, self.item_table.max_per_page or self.max_per_page_default) self.page_num = math.ceil(#self.item_table / self.perpage) if target_item_id ~= nil then -- show menu page with target item From 655ba58ae53908b5a48f7e8762c1523de24e6528 Mon Sep 17 00:00:00 2001 From: Volterxien <101931401+Volterxien@users.noreply.github.com> Date: Sat, 12 Jul 2025 02:40:47 -0400 Subject: [PATCH 10/23] Moving download all and remove all buttons in OPDS download menu (#14024) --- plugins/opds.koplugin/opdsbrowser.lua | 88 +++++++++++++++++++-------- 1 file changed, 64 insertions(+), 24 deletions(-) diff --git a/plugins/opds.koplugin/opdsbrowser.lua b/plugins/opds.koplugin/opdsbrowser.lua index 768f2e7c5..d5b4c348b 100644 --- a/plugins/opds.koplugin/opdsbrowser.lua +++ b/plugins/opds.koplugin/opdsbrowser.lua @@ -63,7 +63,7 @@ local OPDSBrowser = Menu:extend{ function OPDSBrowser:init() self.item_table = self:genItemTableFromRoot() self.catalog_title = nil - self.title_bar_left_icon = "plus" + self.title_bar_left_icon = "appbar.menu" self.onLeftButtonTap = function() self:showOPDSMenu() end @@ -605,6 +605,7 @@ function OPDSBrowser:updateCatalog(item_url, paths_updated) }) end self:switchItemTable(self.catalog_title, menu_table) + self:setTitleBarLeftIcon("plus") self.onLeftButtonTap = function() self:addSubCatalog(item_url) end @@ -1089,6 +1090,8 @@ function OPDSBrowser:showDownloadList() title_bar_fm_style = true, onMenuSelect = self.showDownloadListItemDialog, _manager = self, + title_bar_left_icon = "appbar.menu", + onLeftButtonTap = self.showDownloadListMenu } self.download_list.close_callback = function() UIManager:close(self.download_list) @@ -1103,6 +1106,35 @@ function OPDSBrowser:showDownloadList() UIManager:show(self.download_list) end +function OPDSBrowser:showDownloadListMenu() + local dialog + dialog = ButtonDialog:new{ + buttons = { + {{ + text = _("Download all"), + callback = function() + UIManager:close(dialog) + self._manager:confirmDownloadDownloadList() + end, + align = "left", + }}, + {{ + text = _("Remove all"), + callback = function() + UIManager:close(dialog) + self._manager:confirmClearDownloadList() + end, + align = "left", + }}, + }, + shrink_unneeded_width = true, + anchor = function() + return self.title_bar.left_button.image.dimen + end, + } + UIManager:show(dialog) +end + function OPDSBrowser:updateDownloadListItemTable(item_table) if item_table == nil then item_table = {} @@ -1117,6 +1149,35 @@ function OPDSBrowser:updateDownloadListItemTable(item_table) self.download_list:switchItemTable(title, item_table) end +function OPDSBrowser:confirmDownloadDownloadList() + UIManager:show(ConfirmBox:new{ + text = _("Download all books?\nExisting files will be overwritten."), + ok_text = _("Download"), + ok_callback = function() + NetworkMgr:runWhenConnected(function() + Trapper:wrap(function() + self:downloadDownloadList() + end) + end) + end, + }) +end + +function OPDSBrowser:confirmClearDownloadList() + UIManager:show(ConfirmBox:new{ + text = _("Remove all downloads?"), + ok_text = _("Remove"), + ok_callback = function() + for i in ipairs(self.downloads) do + self.downloads[i] = nil + end + self.download_list_updated = true + self._manager.updated = true + self.download_list:close_callback() + end, + }) +end + function OPDSBrowser:showDownloadListItemDialog(item) local dl_item = self._manager.downloads[item.idx] local textviewer @@ -1155,35 +1216,14 @@ function OPDSBrowser:showDownloadListItemDialog(item) text = _("Remove all"), callback = function() textviewer:onClose() - UIManager:show(ConfirmBox:new{ - text = _("Remove all downloads?"), - ok_text = _("Remove"), - ok_callback = function() - for i in ipairs(self._manager.downloads) do - self._manager.downloads[i] = nil - end - self._manager.download_list_updated = true - self._manager._manager.updated = true - self:close_callback() - end, - }) + self._manager:confirmClearDownloadList() end, }, { text = _("Download all"), callback = function() textviewer:onClose() - UIManager:show(ConfirmBox:new{ - text = _("Download all books?\nExisting files will be overwritten."), - ok_text = _("Download"), - ok_callback = function() - NetworkMgr:runWhenConnected(function() - Trapper:wrap(function() - self._manager:downloadDownloadList() - end) - end) - end, - }) + self._manager:confirmDownloadDownloadList() end, }, }, From c4c95a0e906e3938c9603360d7ef1f0556e54463 Mon Sep 17 00:00:00 2001 From: hius07 <62179190+hius07@users.noreply.github.com> Date: Sat, 12 Jul 2025 20:29:41 +0300 Subject: [PATCH 11/23] ReaderHighlight: fix long-pressing on image (#14048) --- frontend/apps/reader/modules/readerhighlight.lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/frontend/apps/reader/modules/readerhighlight.lua b/frontend/apps/reader/modules/readerhighlight.lua index 3befd58e3..70281d643 100644 --- a/frontend/apps/reader/modules/readerhighlight.lua +++ b/frontend/apps/reader/modules/readerhighlight.lua @@ -1678,15 +1678,13 @@ function ReaderHighlight:onHold(arg, ges) local image = self.ui.document:getImageFromPosition(self.hold_pos, true, true) if image then logger.dbg("hold on image") + self.hold_pos = nil local ImageViewer = require("ui/widget/imageviewer") - local imgviewer = ImageViewer:new{ + UIManager:show(ImageViewer:new{ image = image, - -- title_text = _("Document embedded image"), - -- No title, more room for image - with_title_bar = false, + with_title_bar = false, -- more room for image fullscreen = true, - } - UIManager:show(imgviewer) + }) self:onStopHighlightIndicator() return true end From 9b07f943f5ff42dcb4ee2bf6d44d4c3304975d5a Mon Sep 17 00:00:00 2001 From: fkaduk <35834593+fkaduk@users.noreply.github.com> Date: Sun, 13 Jul 2025 13:15:22 +0200 Subject: [PATCH 12/23] Wallabag: allow filtering download to starred articles (#14051) --- plugins/wallabag.koplugin/main.lua | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plugins/wallabag.koplugin/main.lua b/plugins/wallabag.koplugin/main.lua index 5a2758276..b1e001e9a 100644 --- a/plugins/wallabag.koplugin/main.lua +++ b/plugins/wallabag.koplugin/main.lua @@ -88,6 +88,7 @@ function Wallabag:init() -- These settings do have defaults self.filter_tag = self.wb_settings.data.wallabag.filter_tag or "" + self.filter_starred = self.wb_settings.data.wallabag.filter_starred or false self.ignore_tags = self.wb_settings.data.wallabag.ignore_tags or "" self.auto_tags = self.wb_settings.data.wallabag.auto_tags or "" self.archive_finished = self.wb_settings.data.wallabag.archive_finished or true @@ -260,6 +261,17 @@ function Wallabag:addToMainMenu(menu_items) ) end, }, + { + text = _("Only download starred articles"), + keep_menu_open = true, + checked_func = function() + return self.filter_starred or false + end, + callback = function() + self.filter_starred = not self.filter_starred + self:saveSettings() + end, + }, { text = _("Prefer original non-HTML document"), keep_menu_open = true, @@ -616,6 +628,10 @@ function Wallabag:getArticleList() filtering = "&tags=" .. self.filter_tag end + if self.filter_starred then + filtering = filtering .. "&starred=1" + end + local article_list = {} local page = 1 @@ -1593,6 +1609,7 @@ function Wallabag:saveSettings() password = self.password, directory = self.directory, filter_tag = self.filter_tag, + filter_starred = self.filter_starred, ignore_tags = self.ignore_tags, auto_tags = self.auto_tags, archive_finished = self.archive_finished, From 8db0dee5e07343bfb9af845d69a73734366dc5dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Mnich?= Date: Sun, 13 Jul 2025 22:34:17 +0200 Subject: [PATCH 13/23] [fix] Improve handling of illegal vFAT filenames. (#14043) This change fixes an issue where a file/directory is not created when its name contains trailing spaces or dots. The issue has been observed on a Kobo device, but probably also applies to other systems using vFAT. We also might want to consider handling other illegal vFAT filenames like `NUL` or `AUX`. Please see the [official Microsoft documentation](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions) for more details on the naming conventions of vFAT (this change addresses the last bullet point). --- frontend/util.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/util.lua b/frontend/util.lua index 7e22edddf..65e306009 100644 --- a/frontend/util.lua +++ b/frontend/util.lua @@ -948,14 +948,17 @@ end --- Replaces characters that are invalid filenames. -- --- Replaces the characters \/:*?"<>| with an _. +-- Replaces the characters \/:*?"<>| with an _ +-- and removes trailing dots and spaces, in line with . -- These characters are problematic on Windows filesystems. On Linux only -- / poses a problem. ---- @string str filename ---- @treturn string sanitized filename local function replaceAllInvalidChars(str) if str then - return str:gsub('[\\/:*?"<>|]', '_') + str = str:gsub('[\\/:*?"<>|]', '_') + str = str:gsub("[.%s]+$", "") + return str end end From 9ad9d52cefb32a766938b223da0bd72814a64022 Mon Sep 17 00:00:00 2001 From: Peter Snajczuk Date: Mon, 14 Jul 2025 00:44:45 -0600 Subject: [PATCH 14/23] Support Kindle Oasis (KOA) charging cover "soda" (#13133) Much of this code was taken from the Kobo implementation of an auxiliary battery. The biggest difference between the Kobo and Kindle implementation is that the status file for the charging cover is not present when the cover is detached, so the check for status there will result in the file failing to read. --- frontend/device/kindle/device.lua | 5 ++++- frontend/device/kindle/powerd.lua | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/frontend/device/kindle/device.lua b/frontend/device/kindle/device.lua index 76e1a99ad..ac365a484 100644 --- a/frontend/device/kindle/device.lua +++ b/frontend/device/kindle/device.lua @@ -953,6 +953,7 @@ local KindleOasis = Kindle:extend{ hasKeys = yes, hasGSensor = yes, display_dpi = 300, + hasAuxBattery = yes, --[[ -- NOTE: Points to event3 on Wi-Fi devices, event4 on 3G devices... -- 3G devices apparently have an extra SX9500 Proximity/Capacitive controller for mysterious purposes... @@ -1351,9 +1352,11 @@ function KindleOasis:init() self.powerd = require("device/kindle/powerd"):new{ device = self, fl_intensity_file = "/sys/class/backlight/max77696-bl/brightness", - -- NOTE: Points to the embedded battery. The one in the cover is codenamed "soda". + -- NOTE: Points to the embedded battery. The one in the cover is codenamed "soda", see aux_batt_capacity_file below. batt_capacity_file = "/sys/devices/system/wario_battery/wario_battery0/battery_capacity", is_charging_file = "/sys/devices/system/wario_charger/wario_charger0/charging", + aux_batt_capacity_file = "/sys/devices/platform/soda/power_supply/soda_fg/capacity", + aux_batt_status_file = "/sys/devices/platform/soda/power_supply/soda_fg/status", hall_file = "/sys/devices/system/wario_hall/wario_hall0/hall_enable", } diff --git a/frontend/device/kindle/powerd.lua b/frontend/device/kindle/powerd.lua index c58a3a27a..7e27735df 100644 --- a/frontend/device/kindle/powerd.lua +++ b/frontend/device/kindle/powerd.lua @@ -24,6 +24,33 @@ function KindlePowerD:init() self.fl_max = self.fl_max + 1 end + if self.device:hasAuxBattery() then + self.getAuxCapacityHW = function(this) + return this:unchecked_read_int_file(self.aux_batt_capacity_file) + end + + self.isAuxBatteryConnectedHW = function(this) + local status = this:read_str_file(self.aux_batt_status_file) + if status == nil then + -- File could not be read, assume not connected + return false + end + -- File was read, assume aux battery is connected + return true + end + + self.isAuxChargingHW = function(this) + -- "Discharging" when discharging + -- "Full" when full + -- "Charging" when charging via DCP + return this:read_str_file(this.aux_batt_status_file) ~= "Discharging" + end + + self.isAuxChargedHW = function(this) + return this:read_str_file(this.aux_batt_status_file) == "Full" + end + end + self:initWakeupMgr() end From 105abfa159fb784d6ec97bd309712892ec74b7b1 Mon Sep 17 00:00:00 2001 From: David <97603719+Commodore64user@users.noreply.github.com> Date: Mon, 14 Jul 2025 21:48:01 +0100 Subject: [PATCH 15/23] [ReaderDictionary] Clean interpuncts, pipes and up arrows from text selection (#14031) --- frontend/apps/reader/modules/readerdictionary.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/apps/reader/modules/readerdictionary.lua b/frontend/apps/reader/modules/readerdictionary.lua index e7fd048a6..67c11b0cc 100644 --- a/frontend/apps/reader/modules/readerdictionary.lua +++ b/frontend/apps/reader/modules/readerdictionary.lua @@ -852,6 +852,9 @@ function ReaderDictionary:cleanSelection(text, is_sane) text = text:gsub("\u{2019}", "'") -- Right single quotation mark -- Strip punctuation characters around selection text = util.stripPunctuation(text) + -- In some dictionaries, both interpuncts (·) and pipes (|) are used to delimiter syllables. + -- Up arrows (↑), are used in some dictionaries to indicate related words. + text = text:gsub("[·|↑]", "") -- Strip some common english grammatical construct text = text:gsub("'s$", '') -- english possessive -- Strip some common french grammatical constructs From 70039c9f04f788c9a08996b9b5097e1de24f0c39 Mon Sep 17 00:00:00 2001 From: Frans de Jonge Date: Tue, 15 Jul 2025 15:40:48 +0200 Subject: [PATCH 16/23] [plugin] Terminal Emulator: implement delWord for terminal (#14059) Fixes #14049. --- plugins/terminal.koplugin/main.lua | 3 +++ plugins/terminal.koplugin/terminputtext.lua | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/plugins/terminal.koplugin/main.lua b/plugins/terminal.koplugin/main.lua index 2245510ab..6a42d774a 100644 --- a/plugins/terminal.koplugin/main.lua +++ b/plugins/terminal.koplugin/main.lua @@ -477,6 +477,9 @@ function Terminal:generateInputDialog() end, }, }}, + del_word_callback = function() + self:transmit("\023") -- Ctrl+U + end, enter_callback = function() self:transmit("\r") end, diff --git a/plugins/terminal.koplugin/terminputtext.lua b/plugins/terminal.koplugin/terminputtext.lua index 68a9c5cd3..8c7a067c3 100644 --- a/plugins/terminal.koplugin/terminputtext.lua +++ b/plugins/terminal.koplugin/terminputtext.lua @@ -779,6 +779,12 @@ function TermInputText:delChar() InputText.delChar(self) end +function TermInputText:delWord(left_to_cursor) + if self.parent and self.parent.del_word_callback then + self.parent.del_word_callback(left_to_cursor) + end +end + function TermInputText:delToStartOfLine() return end From de789615ac573e08470029c093cbfae062d8ab83 Mon Sep 17 00:00:00 2001 From: Volterxien <101931401+Volterxien@users.noreply.github.com> Date: Fri, 18 Jul 2025 06:12:41 -0400 Subject: [PATCH 17/23] Add the ability to launch read timer with a gesture (#14061) Closes #14014. --- plugins/calibre.koplugin/main.lua | 2 +- plugins/readtimer.koplugin/main.lua | 194 +++++++++++++++++----------- 2 files changed, 119 insertions(+), 77 deletions(-) diff --git a/plugins/calibre.koplugin/main.lua b/plugins/calibre.koplugin/main.lua index 13588b056..66995d771 100644 --- a/plugins/calibre.koplugin/main.lua +++ b/plugins/calibre.koplugin/main.lua @@ -67,7 +67,7 @@ function Calibre:onDispatcherRegisterActions() Dispatcher:registerAction("calibre_browse_authors", { category="none", event="CalibreBrowseBy", arg="authors", title=_("Browse all calibre authors"), general=true,}) Dispatcher:registerAction("calibre_browse_titles", { category="none", event="CalibreBrowseBy", arg="title", title=_("Browse all calibre titles"), general=true, separator=true,}) Dispatcher:registerAction("calibre_start_connection", { category="none", event="StartWirelessConnection", title=_("Calibre wireless connect"), general=true,}) - Dispatcher:registerAction("calibre_close_connection", { category="none", event="CloseWirelessConnection", title=_("Calibre wireless disconnect"), general=true,}) + Dispatcher:registerAction("calibre_close_connection", { category="none", event="CloseWirelessConnection", title=_("Calibre wireless disconnect"), general=true, separator=true,}) end function Calibre:init() diff --git a/plugins/readtimer.koplugin/main.lua b/plugins/readtimer.koplugin/main.lua index ae992943d..073dfbabb 100644 --- a/plugins/readtimer.koplugin/main.lua +++ b/plugins/readtimer.koplugin/main.lua @@ -1,6 +1,7 @@ local CheckButton = require("ui/widget/checkbutton") local ConfirmBox = require("ui/widget/confirmbox") local DateTimeWidget = require("ui/widget/datetimewidget") +local Dispatcher = require("dispatcher") local Event = require("ui/event") local InfoMessage = require("ui/widget/infomessage") local UIManager = require("ui/uimanager") @@ -17,6 +18,15 @@ local ReadTimer = WidgetContainer:extend{ last_interval_time = 0, } +function ReadTimer:onDispatcherRegisterActions() + Dispatcher:registerAction("show_alarm", + {category="none", event="ShowAlarm", title=_("Set reader alarm"), general=true}) + Dispatcher:registerAction("show_timer", + {category="none", event="ShowTimer", title=_("Set reader timer"), general=true}) + Dispatcher:registerAction("stop_timer", + {category="none", event="StopTimer", title=_("Stop reader timer"), general=true, separator=true}) +end + function ReadTimer:init() self.timer_symbol = "\u{23F2}" -- ⏲ timer symbol self.timer_letter = "T" @@ -91,6 +101,7 @@ function ReadTimer:init() end self.ui.menu:registerToMainMenu(self) + self:onDispatcherRegisterActions() end function ReadTimer:update_status_bars(seconds) @@ -248,85 +259,14 @@ function ReadTimer:addToMainMenu(menu_items) text = _("Set time"), keep_menu_open = true, callback = function(touchmenu_instance) - local now_t = os.date("*t") - local curr_hour = now_t.hour - local curr_min = now_t.min - local time_widget = DateTimeWidget:new{ - hour = curr_hour, - min = curr_min, - ok_text = _("Set alarm"), - title_text = _("New alarm"), - info_text = _("Enter a time in hours and minutes."), - callback = function(alarm_time) - self.last_interval_time = 0 - self:unschedule() - local then_t = now_t - then_t.hour = alarm_time.hour - then_t.min = alarm_time.min - then_t.sec = 0 - local seconds = os.difftime(os.time(then_t), os.time()) - if seconds <= 0 then - then_t.day = then_t.day + 1 - seconds = os.difftime(os.time(then_t), os.time()) - end - self:rescheduleIn(seconds) - local user_duration_format = G_reader_settings:readSetting("duration_format") - UIManager:show(InfoMessage:new{ - -- @translators %1:%2 is a clock time (HH:MM), %3 is a duration - text = T(_("Timer set for %1:%2.\n\nThat's %3 from now."), - string.format("%02d", alarm_time.hour), string.format("%02d", alarm_time.min), - datetime.secondsToClockDuration(user_duration_format, seconds, false)), - timeout = 5, - }) - if touchmenu_instance then touchmenu_instance:updateItems() end - end - } - self:addCheckboxes(time_widget) - UIManager:show(time_widget) + self:onShowAlarm(touchmenu_instance) end, }, { text = _("Set interval"), keep_menu_open = true, callback = function(touchmenu_instance) - local remain_time = {} - local remain_hours, remain_minutes = self:remainingTime() - if not remain_hours and not remain_minutes then - remain_time = G_reader_settings:readSetting("reader_timer_remain_time") - if remain_time then - remain_hours = remain_time[1] - remain_minutes = remain_time[2] - end - end - local time_widget = DateTimeWidget:new{ - hour = remain_hours or 0, - min = remain_minutes or 0, - hour_max = 17, - ok_text = _("Set timer"), - title_text = _("Set reader timer"), - info_text = _("Enter a time in hours and minutes."), - callback = function(timer_time) - self:unschedule() - local seconds = timer_time.hour * 3600 + timer_time.min * 60 - if seconds > 0 then - self.last_interval_time = seconds - self:rescheduleIn(seconds) - local user_duration_format = G_reader_settings:readSetting("duration_format") - UIManager:show(InfoMessage:new{ - -- @translators This is a duration - text = T(_("Timer will expire in %1."), - datetime.secondsToClockDuration(user_duration_format, seconds, true)), - timeout = 5, - }) - remain_time = {timer_time.hour, timer_time.min} - G_reader_settings:saveSetting("reader_timer_remain_time", remain_time) - if touchmenu_instance then touchmenu_instance:updateItems() end - end - end - } - - self:addCheckboxes(time_widget) - UIManager:show(time_widget) + self:onShowTimer(touchmenu_instance) end, }, { @@ -336,9 +276,7 @@ function ReadTimer:addToMainMenu(menu_items) return self:scheduled() end, callback = function(touchmenu_instance) - self.last_interval_time = 0 - self:unschedule() - touchmenu_instance:updateItems() + self:onStopTimer(touchmenu_instance) end, }, }, @@ -365,4 +303,108 @@ function ReadTimer:onResume() end end +function ReadTimer:onShowAlarm(touchmenu_instance) + local now_t = os.date("*t") + local curr_hour = now_t.hour + local curr_min = now_t.min + local time_widget = DateTimeWidget:new{ + hour = curr_hour, + min = curr_min, + ok_text = _("Set alarm"), + title_text = _("New alarm"), + info_text = _("Enter a time in hours and minutes."), + callback = function(alarm_time) + self:setAlarm(alarm_time, now_t, touchmenu_instance) + end + } + self:addCheckboxes(time_widget) + UIManager:show(time_widget) + return true +end + +function ReadTimer:setAlarm(alarm_time, then_t, touchmenu_instance) + then_t.hour = alarm_time.hour + then_t.min = alarm_time.min + then_t.sec = 0 + local seconds = os.difftime(os.time(then_t), os.time()) + if seconds <= 0 then + then_t.day = then_t.day + 1 + seconds = os.difftime(os.time(then_t), os.time()) + end + self.last_interval_time = 0 + self:unschedule() + self:rescheduleIn(seconds) + + local user_duration_format = G_reader_settings:readSetting("duration_format") + UIManager:show(InfoMessage:new{ + -- @translators %1:%2 is a clock time (HH:MM), %3 is a duration + text = T(_("Timer set for %1:%2.\n\nThat's %3 from now."), + string.format("%02d", alarm_time.hour), string.format("%02d", alarm_time.min), + datetime.secondsToClockDuration(user_duration_format, seconds, false)), + timeout = 5, + }) + if touchmenu_instance then touchmenu_instance:updateItems() end +end + +function ReadTimer:onShowTimer(touchmenu_instance) + local remain_hours, remain_minutes = self:remainingTime() + if not remain_hours and not remain_minutes then + local remain_time = G_reader_settings:readSetting("reader_timer_remain_time") + if remain_time then + remain_hours = remain_time[1] + remain_minutes = remain_time[2] + end + end + local time_widget = DateTimeWidget:new{ + hour = remain_hours or 0, + min = remain_minutes or 0, + hour_max = 17, + ok_text = _("Set timer"), + title_text = _("Set reader timer"), + info_text = _("Enter a time in hours and minutes."), + callback = function(timer_time) + self:setInterval(timer_time, touchmenu_instance) + end + } + + self:addCheckboxes(time_widget) + UIManager:show(time_widget) + return true +end + +function ReadTimer:setInterval(timer_time, touchmenu_instance) + local seconds = timer_time.hour * 3600 + timer_time.min * 60 + if seconds > 0 then + self:unschedule() + self.last_interval_time = seconds + self:rescheduleIn(seconds) + + local user_duration_format = G_reader_settings:readSetting("duration_format") + UIManager:show(InfoMessage:new{ + -- @translators This is a duration + text = T(_("Timer will expire in %1."), + datetime.secondsToClockDuration(user_duration_format, seconds, true)), + timeout = 5, + }) + if touchmenu_instance then touchmenu_instance:updateItems() end + + -- Save settings + local remain_time = {timer_time.hour, timer_time.min} + G_reader_settings:saveSetting("reader_timer_remain_time", remain_time) + end +end + +function ReadTimer:onStopTimer(touchmenu_instance) + if self:scheduled() then + self.last_interval_time = 0 + self:unschedule() + if touchmenu_instance then + touchmenu_instance:updateItems() + else + UIManager:show(InfoMessage:new{text=_("Timer stopped")}) + end + end + return true +end + return ReadTimer From bc83fc6c05234661524c7da25d9aa3756d4782da Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Sat, 19 Jul 2025 11:44:45 +0200 Subject: [PATCH 18/23] ci: update build configurations (#14069) Explicitly set `KODEBUG` in preparation for bumping base with https://github.com/koreader/koreader-base/pull/2118. --- .circleci/config.yml | 1 + .github/workflows/build.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index be0ef607d..d8cdc07fa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -38,6 +38,7 @@ jobs: CCACHE_MAXSIZE: "128M" CLICOLOR_FORCE: "1" EMULATE_READER: "1" + KODEBUG: "" MAKEFLAGS: "PARALLEL_JOBS=3 OUTPUT_DIR=build INSTALL_DIR=install" steps: # Checkout / fetch. {{{ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8bdd110ab..a8094073b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,6 +35,7 @@ jobs: # Bump first number to reset all caches. CACHE_KEY: "1-macOS-${{ matrix.image }}-${{ matrix.platform }}-XC${{ matrix.xcode_version }}-DT${{ matrix.deployment_target }}" CLICOLOR_FORCE: '1' + KODEBUG: "" MACOSX_DEPLOYMENT_TARGET: ${{ matrix.deployment_target }} MAKEFLAGS: 'OUTPUT_DIR=build INSTALL_DIR=install TARGET=macos' From 9e6492f650f4c2ecf15d1ac9c9c7de7f3a019d67 Mon Sep 17 00:00:00 2001 From: poire-z Date: Sat, 19 Jul 2025 08:54:42 +0200 Subject: [PATCH 19/23] bump base: update mupdf and others Includes: - sqlite: enable FTS5 extension - ci/macos: fix "Dump build timings" step - sdl2: always compile on macOS - kindle & kobo-usbms: move setting `DEBUG` where it belongs - make: align `KODEBUG` default with kodev - sqlite: update to 3.50.2 - mupdf: fix possible disparity when generating FFI cdecls - mupdf: update to 1.26.3 - xz: always build a static library - Patch lua-spore to properly set filename in form-data - libpng: update to 1.6.50 - libwebp: update to 1.6.0 - luasystem: update to 0.6.2 - xz: fix android non-monolibtic build - ffi/libarchive: fix cdecls --- base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base b/base index 63f95f0f2..d33404485 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit 63f95f0f2a8d87ce2b0c1a32e9f9774e2d80b556 +Subproject commit d33404485b0105f226446b7198918d0b070707fe From c348861f6d92f995ec31c2e5467f3c973bfcaef6 Mon Sep 17 00:00:00 2001 From: poire-z Date: Sat, 19 Jul 2025 08:57:25 +0200 Subject: [PATCH 20/23] bump crengine: improve md rendering, fix some stylesheet issues Includes: - mdfmt: improve rendering - ci: lint `mdfmt.cpp` too - DrawBackgroundImage: handle %-encoded urls - lvstsheet: parsing: ignore parens inside quotes - lvstsheet: fix crash on "color/font-family: initial" - ldomElementWriter: fix minor parsing issue --- base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base b/base index d33404485..e64da8654 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit d33404485b0105f226446b7198918d0b070707fe +Subproject commit e64da86542f4af5adc8baed05fde048f2bae149c From fd63e03b3086e57db66482f8e4606d65dc43abb4 Mon Sep 17 00:00:00 2001 From: Offlinemaker <5461150+Offlinemaker@users.noreply.github.com> Date: Sun, 20 Jul 2025 12:44:06 +0200 Subject: [PATCH 21/23] Allow toggling Kosync auto sync from gestures (#14037) --- plugins/kosync.koplugin/main.lua | 54 ++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/plugins/kosync.koplugin/main.lua b/plugins/kosync.koplugin/main.lua index cb90551b0..7c66e3a3c 100644 --- a/plugins/kosync.koplugin/main.lua +++ b/plugins/kosync.koplugin/main.lua @@ -6,6 +6,7 @@ local InfoMessage = require("ui/widget/infomessage") local Math = require("optmath") local MultiInputDialog = require("ui/widget/multiinputdialog") local NetworkMgr = require("ui/network/manager") +local Notification = require("ui/widget/notification") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") local logger = require("logger") @@ -164,6 +165,10 @@ local function validateUser(user, pass) end function KOSync:onDispatcherRegisterActions() + Dispatcher:registerAction("kosync_set_autosync", + { category="string", event="KOSyncToggleAutoSync", title=_("Set auto progress sync"), reader=true, + args={true, false}, toggle={_("on"), _("off")},}) + Dispatcher:registerAction("kosync_toggle_autosync", { category="none", event="KOSyncToggleAutoSync", title=_("Toggle auto progress sync"), reader=true,}) Dispatcher:registerAction("kosync_push_progress", { category="none", event="KOSyncPushProgress", title=_("Push progress from this device"), reader=true,}) Dispatcher:registerAction("kosync_pull_progress", { category="none", event="KOSyncPullProgress", title=_("Pull progress from other devices"), reader=true, separator=true,}) end @@ -225,23 +230,7 @@ function KOSync:addToMainMenu(menu_items) checked_func = function() return self.settings.auto_sync end, help_text = _([[This may lead to nagging about toggling WiFi on document close and suspend/resume, depending on the device's connectivity.]]), callback = function() - -- Actively recommend switching the before wifi action to "turn_on" instead of prompt, as prompt will just not be practical (or even plain usable) here. - if Device:hasSeamlessWifiToggle() and G_reader_settings:readSetting("wifi_enable_action") ~= "turn_on" and not self.settings.auto_sync then - UIManager:show(InfoMessage:new{ text = _("You will have to switch the 'Action when Wi-Fi is off' Network setting to 'turn on' to be able to enable this feature!") }) - return - end - - self.settings.auto_sync = not self.settings.auto_sync - self:registerEvents() - if self.settings.auto_sync then - -- Since we will update the progress when closing the document, - -- pull the current progress now so as not to silently overwrite it. - self:getProgress(true, true) - else - -- Since we won't update the progress when closing the document, - -- push the current progress now so as not to lose it. - self:updateProgress(true, true) - end + self:onKOSyncToggleAutoSync(nil, true) end, }, { @@ -919,6 +908,37 @@ function KOSync:onKOSyncPullProgress() self:getProgress(true, true) end +function KOSync:onKOSyncToggleAutoSync(toggle, from_menu) + if toggle == self.settings.auto_sync then + return true + end + -- Actively recommend switching the before wifi action to "turn_on" instead of prompt, + -- as prompt will just not be practical (or even plain usable) here. + if not self.settings.auto_sync + and Device:hasSeamlessWifiToggle() + and G_reader_settings:readSetting("wifi_enable_action") ~= "turn_on" then + UIManager:show(InfoMessage:new{ text = _("You will have to switch the 'Action when Wi-Fi is off' Network setting to 'turn on' to be able to enable this feature!") }) + return true + end + self.settings.auto_sync = not self.settings.auto_sync + self:registerEvents() + + if self.settings.auto_sync then + -- Since we will update the progress when closing the document, + -- pull the current progress now so as not to silently overwrite it. + self:getProgress(true, true) + elseif from_menu then + -- Since we won't update the progress when closing the document, + -- push the current progress now so as not to lose it. + self:updateProgress(true, true) + end + + if not from_menu then + Notification:notify(self.settings.auto_sync and _("Auto progress sync: on") or _("Auto progress sync: off")) + end + return true +end + function KOSync:registerEvents() if self.settings.auto_sync then self.onCloseDocument = self._onCloseDocument From ac03e9cf064f2241872c577455dc9d3c9b4ef3fb Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Sun, 20 Jul 2025 16:09:24 +0200 Subject: [PATCH 22/23] kodev: use LLDB for debugging on macOS (#14072) --- kodev | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/kodev b/kodev index d492c1f0a..bf6856fcb 100755 --- a/kodev +++ b/kodev @@ -475,12 +475,18 @@ ANDROID TARGET: if [[ -n "${VALUE}" ]]; then declare -a "wrap=(${VALUE})" else - # Try to use friendly defaults for GDB: - # - DDD is a slightly less nice GUI - # - cgdb is a nice curses-based GDB front - # - GDB standard CLI has a fallback - if ! wrap=("$(command -v ddd cgdb gdb | head -n1)"); then - die 1 "Couldn't find GDB." + if is_system macOS; then + if ! wrap=("$(command -v lldb | head -n1)"); then + die 1 "Couldn't find LLDB." + fi + else + # Try to use friendly defaults for GDB: + # - DDD is a slightly less nice GUI + # - cgdb is a nice curses-based GDB front + # - GDB standard CLI has a fallback + if ! wrap=("$(command -v ddd cgdb gdb | head -n1)"); then + die 1 "Couldn't find GDB." + fi fi fi if [[ ${#wrap[@]} -eq 1 ]]; then From 6fd133519666203f5b5fd05d021aef2ecc123270 Mon Sep 17 00:00:00 2001 From: zwim <36999612+zwim@users.noreply.github.com> Date: Mon, 21 Jul 2025 21:18:13 +0200 Subject: [PATCH 23/23] Fix some typos (harmless) (#14079) --- frontend/apps/filemanager/filemanagercollection.lua | 2 +- frontend/document/document.lua | 2 +- frontend/document/koptinterface.lua | 4 ++-- frontend/ui/widget/bookmapwidget.lua | 4 ++-- frontend/ui/widget/inputdialog.lua | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/apps/filemanager/filemanagercollection.lua b/frontend/apps/filemanager/filemanagercollection.lua index 7072578d2..c08870af0 100644 --- a/frontend/apps/filemanager/filemanagercollection.lua +++ b/frontend/apps/filemanager/filemanagercollection.lua @@ -1388,7 +1388,7 @@ function FileManagerCollection:searchCollections(coll_name) -- Fortunately, this is run in a subprocess, so we won't be affecting the -- main process's crengine state or any document opened in the main -- process (we furthermore prevent this feature when one is opened). - -- To avoid creating half-rendered/invalide cache files, it's best to disable + -- To avoid creating half-rendered/invalid cache files, it's best to disable -- crengine saving of such cache files. if not self.is_cre_cache_disabled then local cre = require("document/credocument"):engineInit() diff --git a/frontend/document/document.lua b/frontend/document/document.lua index 1308e0630..30f4ede2c 100644 --- a/frontend/document/document.lua +++ b/frontend/document/document.lua @@ -486,7 +486,7 @@ function Document:renderPage(pageno, rect, zoom, rotation, gamma, hinting) -- Make the context match the rotation, -- by pointing at the rotated origin via coordinates offsets. -- NOTE: We rotate our *Screen* bb on rotation (SetRotationMode), not the document, - -- so we hardly ever exercize this codepath... + -- so we hardly ever exercise this codepath... -- AFAICT, the only thing that *ever* (attempted to) rotate the document was ReaderRotation's key bindings (RotationUpdate). --- @note: It was broken as all hell (it had likely never worked outside of its original implementation in KPV), and has been removed in #12658 if rotation == 90 then diff --git a/frontend/document/koptinterface.lua b/frontend/document/koptinterface.lua index e97d7b058..30b7cad6a 100644 --- a/frontend/document/koptinterface.lua +++ b/frontend/document/koptinterface.lua @@ -297,7 +297,7 @@ function KoptInterface:reflowPage(doc, pageno, bbox, background) kc:setPreCache() self.bg_thread = true end - -- Caculate zoom. + -- Calculate zoom. kc.zoom = (1.5 * kc.zoom * kc.quality * kc.dev_width) / bbox.x1 -- Generate pixmap. local page = doc._document:openPage(pageno) @@ -1433,7 +1433,7 @@ end local function get_pattern_list(pattern, case_insensitive) -- pattern list of single words local plist = {} - -- (as in util.splitToWords(), but only splitting on spaces, keeping punctuations) + -- (as in util.splitToWords(), but only splitting on spaces, keeping punctuation marks) for word in util.gsplit(pattern, "%s+") do if util.hasCJKChar(word) then for char in util.gsplit(word, "[\192-\255][\128-\191]+", true) do diff --git a/frontend/ui/widget/bookmapwidget.lua b/frontend/ui/widget/bookmapwidget.lua index 8e8c3ee7c..69bdfced9 100644 --- a/frontend/ui/widget/bookmapwidget.lua +++ b/frontend/ui/widget/bookmapwidget.lua @@ -618,7 +618,7 @@ function BookMapRow:paintTo(bb, x, y) alt_bb = glyph.bb:rotatedCopy(indicator.rotation) end -- Glyph's bb fit the blackbox of the glyph, so there's no cropping - -- or complicated positionning to do + -- or complicated positioning to do -- By default, just center the glyph at x local d_x_pct = indicator.shift_x_pct or 0.5 local d_x = math.floor(glyph.bb:getWidth() * d_x_pct) @@ -759,7 +759,7 @@ function BookMapWidget:init() } end - -- No real need for any explicite edge and inter-row padding: + -- No real need for any explicit edge and inter-row padding: -- we use the scrollbar width on both sides for balance (we may put a start -- page number on the left space), and each BookMapRow will have itself some -- blank space at bottom below page slots (where we may put hanging markers diff --git a/frontend/ui/widget/inputdialog.lua b/frontend/ui/widget/inputdialog.lua index 8ab3340af..0887e725e 100644 --- a/frontend/ui/widget/inputdialog.lua +++ b/frontend/ui/widget/inputdialog.lua @@ -643,7 +643,7 @@ function InputDialog:toggleKeyboard(force_toggle) -- Remember the *current* visibility, as the following close will reset it local visible = self:isKeyboardVisible() - -- When we forcibly close the keyboard, remember its current visiblity state, so that we can properly restore it later. + -- When we forcibly close the keyboard, remember its current visibility state, so that we can properly restore it later. -- (This is used by some buttons in fullscreen mode, where we might want to keep the original keyboard hidden when popping up a new one for another InputDialog). if force_toggle == false then -- NOTE: visible will be nil between our own init and a show of the keyboard, which is precisely what happens when we *hide* the keyboard.