Last active
May 4, 2025 22:39
-
-
Save sebdelsol/4a274d43ec4439720ef2b89ed4c900e5 to your computer and use it in GitHub Desktop.
KOReader user patch: Screensaver message do not overlap the cover & clear all widgets when the screensaver has no background
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Add 4 new options @ the end of the "Sleep screen" menu : | |
-- Close widgets before showing the screensaver | |
-- Refresh before showing the screensaver | |
-- Message do not overlap image | |
-- Center image | |
-- By default it doesn't change the sleep screen behavior | |
local ReaderUI = require("apps/reader/readerui") | |
local Screensaver = require("ui/screensaver") | |
local Blitbuffer = require("ffi/blitbuffer") | |
local BookStatusWidget = require("ui/widget/bookstatuswidget") | |
local BottomContainer = require("ui/widget/container/bottomcontainer") | |
local Device = require("device") | |
local Font = require("ui/font") | |
local Geom = require("ui/geometry") | |
local InfoMessage = require("ui/widget/infomessage") | |
local ImageWidget = require("ui/widget/imagewidget") | |
local OverlapGroup = require("ui/widget/overlapgroup") | |
local RenderImage = require("ui/renderimage") | |
local ScreenSaverWidget = require("ui/widget/screensaverwidget") | |
local ScreenSaverLockWidget = require("ui/widget/screensaverlockwidget") | |
local TextBoxWidget = require("ui/widget/textboxwidget") | |
local TopContainer = require("ui/widget/container/topcontainer") | |
local UIManager = require("ui/uimanager") | |
local VerticalGroup = require("ui/widget/verticalgroup") | |
local VerticalSpan = require("ui/widget/verticalspan") | |
local ffiUtil = require("ffi/util") | |
local util = require("util") | |
local Screen = Device.screen | |
local _ = require("gettext") | |
local logger = require("logger") | |
-- default value, for new menu entries | |
if G_reader_settings:hasNot("screensaver_close_widgets_when_no_fill") then | |
G_reader_settings:saveSetting("screensaver_close_widgets_when_no_fill", false) | |
end | |
if G_reader_settings:hasNot("screensaver_center_image") then | |
G_reader_settings:saveSetting("screensaver_center_image", false) | |
end | |
if G_reader_settings:hasNot("screensaver_overlap_message") then | |
G_reader_settings:saveSetting("screensaver_overlap_message", true) | |
end | |
if G_reader_settings:hasNot("screensaver_refresh") then | |
G_reader_settings:saveSetting("screensaver_refresh", true) | |
end | |
Screensaver.show = function(self) | |
-- Notify Device methods that we're in screen saver mode, so they know whether to suspend or resume on Power events. | |
Device.screen_saver_mode = true | |
-- Check if we requested a lock gesture | |
local with_gesture_lock = Device:isTouchDevice() and G_reader_settings:readSetting("screensaver_delay") == "gesture" | |
-- In as-is mode with no message, no overlay and no lock, we've got nothing to show :) | |
if self.screensaver_type == "disable" and not self.show_message and not self.overlay_message and not with_gesture_lock then | |
return | |
end | |
local rotation_mode = Screen:getRotationMode() | |
-- We mostly always suspend in Portrait/Inverted Portrait mode... | |
-- ... except when we just show an InfoMessage or when the screensaver | |
-- is disabled, as it plays badly with Landscape mode (c.f., #4098 and #5920). | |
-- We also exclude full-screen widgets that work fine in Landscape mode, | |
-- like ReadingProgress and BookStatus (c.f., #5724) | |
if self:modeExpectsPortrait() then | |
Device.orig_rotation_mode = rotation_mode | |
-- Leave Portrait & Inverted Portrait alone, that works just fine. | |
if bit.band(Device.orig_rotation_mode, 1) == 1 then | |
-- i.e., only switch to Portrait if we're currently in *any* Landscape orientation (odd number) | |
Screen:setRotationMode(Screen.DEVICE_ROTATED_UPRIGHT) | |
else | |
Device.orig_rotation_mode = nil | |
end | |
-- On eInk, if we're using a screensaver mode that shows an image, | |
-- flash the screen to white first, to eliminate ghosting. | |
if G_reader_settings:readSetting("screensaver_refresh") then | |
if Device:hasEinkScreen() and self:modeIsImage() then | |
if self:withBackground() then | |
Screen:clear() | |
end | |
Screen:refreshFull(0, 0, Screen:getWidth(), Screen:getHeight()) | |
-- On Kobo, on sunxi SoCs with a recent kernel, wait a tiny bit more to avoid weird refresh glitches... | |
if Device:isKobo() and Device:isSunxi() then | |
ffiUtil.usleep(150 * 1000) | |
end | |
end | |
end | |
else | |
-- nil it, in case user switched ScreenSaver modes during our lifetime. | |
Device.orig_rotation_mode = nil | |
end | |
-- Assume that we'll be covering the full-screen by default (either because of a widget, or a background fill). | |
local covers_fullscreen = true | |
-- Speaking of, set that background fill up... | |
local background | |
local fgcolor, bgcolor = Blitbuffer.COLOR_BLACK, Blitbuffer.COLOR_WHITE | |
if self.screensaver_background == "black" then | |
background = Blitbuffer.COLOR_BLACK | |
bgcolor = background -- text follow the same color scheme | |
fgcolor = Blitbuffer.COLOR_WHITE | |
elseif self.screensaver_background == "white" then | |
background = Blitbuffer.COLOR_WHITE | |
elseif self.screensaver_background == "none" then | |
background = nil | |
end | |
local is_cover_or_image = self.screensaver_type == "cover" or self.screensaver_type == "random_image" | |
local message_height | |
local message_widget | |
local overlap_message = true | |
local is_message_top = false | |
if self.show_message then | |
-- Handle user settings & fallbacks, with that prefix mess on top... | |
local screensaver_message = self.default_screensaver_message | |
if G_reader_settings:has(self.prefix .. "screensaver_message") then | |
screensaver_message = G_reader_settings:readSetting(self.prefix .. "screensaver_message") | |
elseif G_reader_settings:has("screensaver_message") then | |
screensaver_message = G_reader_settings:readSetting("screensaver_message") | |
end | |
-- If the message is set to the defaults (which is also the case when it's unset), prefer the event message if there is one. | |
if screensaver_message == self.default_screensaver_message then | |
if self.event_message then | |
screensaver_message = self.event_message | |
-- The overlay is only ever populated with the event message, and we only want to show it once ;). | |
self.overlay_message = nil | |
end | |
end | |
-- NOTE: Only attempt to expand if there are special characters in the message. | |
if screensaver_message:find("%%") then | |
screensaver_message = self:expandSpecial(screensaver_message) or self.event_message or self.default_screensaver_message | |
end | |
if G_reader_settings:has(self.prefix .. "screensaver_message_position") then | |
message_pos = G_reader_settings:readSetting(self.prefix .. "screensaver_message_position") | |
else | |
message_pos = G_reader_settings:readSetting("screensaver_message_position") | |
end | |
if message_pos == "middle" then | |
message_widget = InfoMessage:new{ | |
text = screensaver_message, | |
readonly = true, | |
dismissable = false, | |
force_one_line = true, | |
} | |
else | |
local face = Font:getFace("infofont") | |
local screen_w = Screen:getWidth() | |
local container | |
local textbox = TextBoxWidget:new{ -- might need its height | |
text = screensaver_message, | |
face = face, | |
width = screen_w, | |
alignment = "center", | |
fgcolor = fgcolor, | |
bgcolor = bgcolor, | |
} | |
is_message_top = message_pos == "top" | |
container = is_message_top and TopContainer or BottomContainer | |
overlap_message = not is_cover_or_image or G_reader_settings:readSetting("screensaver_overlap_message") | |
message_widget = container:new{ | |
dimen = Geom:new{ | |
w = screen_w, | |
h = overlap_message and Screen:getHeight() or textbox:getSize().h, | |
}, | |
textbox, | |
} | |
-- Forward the height of the top message to the overlay widget | |
if is_message_top then | |
message_height = message_widget[1]:getSize().h | |
end | |
end | |
end | |
-- Build the main widget for the effective mode, all the sanity checks were handled in setup | |
local widget = nil | |
local center_image = false | |
if is_cover_or_image then | |
local image_height = Screen:getHeight() | |
if not overlap_message then | |
center_image = G_reader_settings:readSetting("screensaver_center_image") | |
image_height = image_height - message_widget[1]:getSize().h * (center_image and 2 or 1) | |
end | |
local widget_settings = { | |
width = Screen:getWidth(), | |
height = image_height, | |
scale_factor = G_reader_settings:isFalse("screensaver_stretch_images") and 0 or nil, | |
stretch_limit_percentage = G_reader_settings:readSetting("screensaver_stretch_limit_percentage"), | |
} | |
if self.image then | |
widget_settings.image = self.image | |
widget_settings.image_disposable = true | |
elseif self.image_file then | |
if G_reader_settings:isTrue("screensaver_rotate_auto_for_best_fit") then | |
-- We need to load the image here to determine whether to rotate | |
if util.getFileNameSuffix(self.image_file) == "svg" then | |
widget_settings.image = RenderImage:renderSVGImageFile(self.image_file, nil, nil, 1) | |
else | |
widget_settings.image = RenderImage:renderImageFile(self.image_file, false, nil, nil) | |
end | |
if not widget_settings.image then | |
widget_settings.image = RenderImage:renderCheckerboard(Screen:getWidth(), Screen:getHeight(), Screen.bb:getType()) | |
end | |
widget_settings.image_disposable = true | |
else | |
widget_settings.file = self.image_file | |
widget_settings.file_do_cache = false | |
end | |
widget_settings.alpha = true | |
end -- set cover or file | |
if G_reader_settings:isTrue("screensaver_rotate_auto_for_best_fit") then | |
local angle = rotation_mode == 3 and 180 or 0 -- match mode if possible | |
if (widget_settings.image:getWidth() < widget_settings.image:getHeight()) ~= (widget_settings.width < widget_settings.height) then | |
angle = angle + (G_reader_settings:isTrue("imageviewer_rotation_landscape_invert") and -90 or 90) | |
end | |
widget_settings.rotation_angle = angle | |
end | |
widget = ImageWidget:new(widget_settings) | |
elseif self.screensaver_type == "bookstatus" then | |
local ReaderUI = require("apps/reader/readerui") | |
widget = BookStatusWidget:new{ | |
ui = ReaderUI.instance, | |
readonly = true, | |
} | |
elseif self.screensaver_type == "readingprogress" then | |
widget = Screensaver.getReaderProgress() | |
end | |
if self.show_message then | |
-- The only case where we *won't* cover the full-screen is when we only display a message and no background. | |
if widget == nil and self.screensaver_background == "none" then | |
covers_fullscreen = false | |
end | |
-- Check if message_widget should be overlaid on another widget | |
if message_widget then | |
if widget then -- We have a Screensaver widget | |
-- Show message_widget depending on overlap_message and center_image | |
local group_settings | |
local group_type | |
if overlap_message then | |
group_type = OverlapGroup | |
group_settings = { widget, message_widget } | |
else | |
group_type = VerticalGroup | |
if center_image then | |
local verticalspan = VerticalSpan:new{width = message_widget[1]:getSize().h} | |
if is_message_top then | |
group_settings = { message_widget, widget, verticalspan } | |
else | |
group_settings = { verticalspan, widget, message_widget } | |
end | |
else | |
if is_message_top then | |
group_settings = { message_widget, widget } | |
else | |
group_settings = { widget, message_widget } | |
end | |
end | |
end | |
group_settings.dimen = { | |
w = Screen:getWidth(), | |
h = Screen:getHeight(), | |
} | |
widget = group_type:new(group_settings) | |
else | |
-- No previously created widget, so just show message widget | |
widget = message_widget | |
end | |
end | |
end | |
-- NOTE: Make sure InputContainer gestures are not disabled, to prevent stupid interactions with UIManager on close. | |
UIManager:setIgnoreTouchInput(false) | |
if self.screensaver_background == "none" and is_cover_or_image then -- is the background kept ? | |
if G_reader_settings:readSetting("screensaver_close_widgets_when_no_fill") then -- !!!!!!!!! | |
-- clear highlight | |
local readerui = ReaderUI.instance | |
if readerui and readerui.highlight then | |
readerui.highlight:clear(readerui.highlight:getClearId()) | |
end | |
local added = {} | |
local widgets = {} | |
for widget in UIManager:topdown_widgets_iter() do -- populate bottom up with unique widgets (eg. keyboard appears several times) | |
if not added[widget] then -- already added ? | |
table.insert(widgets, widget) | |
added[widget] = true | |
end | |
end | |
table.remove(widgets) -- remove the main widget @ the end of the stack, we don't want to close it | |
if #widgets >= 1 then -- close all the remaining ones and repaint | |
for _, widget in ipairs(widgets) do | |
UIManager:close(widget, "fast") | |
end | |
UIManager:forceRePaint() | |
end | |
end | |
end | |
if self.overlay_message then | |
widget = addOverlayMessage(widget, message_height, self.overlay_message) | |
end | |
if widget then | |
self.screensaver_widget = ScreenSaverWidget:new{ | |
widget = widget, | |
background = background, | |
covers_fullscreen = covers_fullscreen, | |
} | |
self.screensaver_widget.modal = true | |
self.screensaver_widget.dithered = true | |
UIManager:show(self.screensaver_widget, "full") | |
end | |
-- Setup the gesture lock through an additional invisible widget, so that it works regardless of the configuration. | |
if with_gesture_lock then | |
self.screensaver_lock_widget = ScreenSaverLockWidget:new{} | |
-- It's flagged as modal, so it'll stay on top | |
UIManager:show(self.screensaver_lock_widget) | |
end | |
end | |
local function find_item_from_path(menu, ...) | |
local function find_sub_item(sub_items, text) | |
-- logger.info("search item", text) | |
for _, item in ipairs(sub_items) do | |
local item_text = item.text or (item.text_func and item.text_func()) | |
if item_text and item_text == text then | |
-- logger.info("Found item", item_text) | |
return item | |
end | |
end | |
end | |
local sub_items, item | |
for _, text in ipairs({...}) do | |
sub_items = item and item.sub_item_table or menu | |
if not sub_items then return end | |
item = find_sub_item(sub_items, text) | |
if not item then return end | |
end | |
return item | |
end | |
local function add_options_in(menu) | |
local items = menu.sub_item_table | |
items[#items].separator = true | |
table.insert( | |
items, | |
{ | |
text = _("Close widgets before showing the screensaver"), | |
help_text = _("This option will only become available, if you have selected 'No fill'."), | |
enabled_func = function() | |
return G_reader_settings:readSetting("screensaver_img_background") == "none" | |
end, | |
checked_func = function() | |
return G_reader_settings:isTrue("screensaver_close_widgets_when_no_fill") | |
end, | |
callback = function(touchmenu_instance) | |
G_reader_settings:flipNilOrFalse("screensaver_close_widgets_when_no_fill") | |
touchmenu_instance:updateItems() | |
end, | |
} | |
) | |
table.insert( | |
items, | |
{ | |
text = _("Refresh before showing the screensaver"), | |
help_text = _("This option will only become available, if you have selected a cover or a random image."), | |
enabled_func = function() | |
local screensaver_type = G_reader_settings:readSetting("screensaver_type") | |
return Device:hasEinkScreen() and (screensaver_type=="cover" or screensaver_type=="random_image") | |
end, | |
checked_func = function() | |
return G_reader_settings:isTrue("screensaver_refresh") | |
end, | |
callback = function(touchmenu_instance) | |
G_reader_settings:toggle("screensaver_refresh") | |
touchmenu_instance:updateItems() | |
end, | |
} | |
) | |
items[#items].separator = true | |
table.insert( | |
items, | |
{ | |
text = _("Message do not overlap image"), | |
help_text = _("This option will only become available, if you have selected a cover or a random image and you have a message and the message position is 'top' or 'bottom'."), | |
enabled_func = function() | |
local screensaver_type = G_reader_settings:readSetting("screensaver_type") | |
local message_pos = G_reader_settings:readSetting("screensaver_message_position") | |
return G_reader_settings:readSetting("screensaver_show_message") | |
and (screensaver_type=="cover" or screensaver_type=="random_image") | |
and (message_pos=="top" or message_pos=="bottom") | |
end, | |
checked_func = function() | |
return G_reader_settings:nilOrFalse("screensaver_overlap_message") | |
end, | |
callback = function(touchmenu_instance) | |
G_reader_settings:toggle("screensaver_overlap_message") | |
touchmenu_instance:updateItems() | |
end, | |
} | |
) | |
table.insert( | |
items, | |
{ | |
text = _("Center image"), | |
help_text = _("This option will only become available, if you have selected 'Message do not overlap image'."), | |
enabled_func = function() | |
return G_reader_settings:nilOrFalse("screensaver_overlap_message") | |
end, | |
checked_func = function() | |
return G_reader_settings:isTrue("screensaver_center_image") | |
end, | |
callback = function(touchmenu_instance) | |
G_reader_settings:flipNilOrFalse("screensaver_center_image") | |
touchmenu_instance:updateItems() | |
end, | |
} | |
) | |
end | |
local function add_options_in_screensaver(order, menu, menu_name) | |
local buttons = order["KOMenu:menu_buttons"] | |
for i, button in ipairs(buttons) do | |
if button == "setting" then | |
local setting_menu = menu.tab_item_table[i] | |
-- logger.info(i, setting_menu) | |
if setting_menu then | |
local sub_menu = find_item_from_path( | |
setting_menu, | |
_("Screen"), | |
_("Sleep screen") | |
) | |
if sub_menu then | |
add_options_in(sub_menu) | |
logger.info("Add screensaver options in", menu_name, "menu") | |
end | |
end | |
end | |
end | |
end | |
local FileManagerMenuOrder = require("ui/elements/filemanager_menu_order") | |
local FileManagerMenu = require("apps/filemanager/filemanagermenu") | |
local orig_FileManagerMenu_setUpdateItemTable = FileManagerMenu.setUpdateItemTable | |
FileManagerMenu.setUpdateItemTable = function(self) | |
orig_FileManagerMenu_setUpdateItemTable(self) | |
add_options_in_screensaver(FileManagerMenuOrder, self, "file manager") | |
end | |
local ReaderMenuOrder = require("ui/elements/reader_menu_order") | |
local ReaderMenu = require("apps/reader/modules/readermenu") | |
local orig_ReaderMenu_setUpdateItemTable = ReaderMenu.setUpdateItemTable | |
ReaderMenu.setUpdateItemTable = function(self) | |
orig_ReaderMenu_setUpdateItemTable(self) | |
add_options_in_screensaver(ReaderMenuOrder, self, "reader") | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment