ScreenSaver: Refactor gesture lock to behave regardless of configuration

Go through a dedicated sticky invisible widget instead of piggybacking
on ScreenSaverWidget, so that we behave if there are other InputContainers
in ScreenSaverWidget, or if there isn't any ScreenSaverWidget at all.

Fix #9911, fix #9955
reviewable/pr9982/r1
NiLuJe 1 year ago
parent 6e1683e313
commit 9eac47e0df

@ -13,6 +13,7 @@ local ImageWidget = require("ui/widget/imagewidget")
local Math = require("optmath")
local OverlapGroup = require("ui/widget/overlapgroup")
local ScreenSaverWidget = require("ui/widget/screensaverwidget")
local ScreenSaverLockWidget = require("ui/widget/screensaverlockwidget")
local SpinWidget = require("ui/widget/spinwidget")
local TextBoxWidget = require("ui/widget/textboxwidget")
local TopContainer = require("ui/widget/container/topcontainer")
@ -605,16 +606,14 @@ function Screensaver:setup(event, event_message)
end
function Screensaver:show()
-- This should never happen...
if self.screensaver_widget then
UIManager:close(self.screensaver_widget)
self.screensaver_widget = nil
end
-- 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
-- In as-is mode with no message and no overlay, we've got nothing to show :)
if self.screensaver_type == "disable" and not self.show_message and not self.overlay_message then
-- 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
@ -820,6 +819,25 @@ function Screensaver:show()
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{}
-- If we don't have a ScreenSaverWidget handling cleanup for us, it'll fall to us...
if not self.screensaver_widget then
self.screensaver_lock_widget.onCloseWidget = function(this)
local super = getmetatable(this)
if super.onCloseWidget then
super.onCloseWidget(this)
end
self:cleanup()
end
end
-- It's flagged as modal, so it'll stay on top
UIManager:show(self.screensaver_lock_widget)
end
end
function Screensaver:close_widget()
@ -829,9 +847,9 @@ function Screensaver:close_widget()
end
function Screensaver:close()
if self.screensaver_widget == nil then
-- When we *do* have a widget, this is handled by ScreenSaverWidget:onCloseWidget ;).
Device.screen_saver_mode = false
if self.screensaver_widget == nil and self.screensaver_lock_widget == nil then
-- When we *do* have a widget, this is handled by ScreenSaver(Lock)Widget:onCloseWidget ;).
self:cleanup()
return
end
@ -846,8 +864,8 @@ function Screensaver:close()
-- that we've actually closed the widget *right now*.
return true
elseif screensaver_delay == "gesture" then
if self.screensaver_widget then
self.screensaver_widget:showWaitForGestureMessage()
if self.screensaver_lock_widget then
self.screensaver_lock_widget:showWaitForGestureMessage()
end
else
logger.dbg("tap to exit from screensaver")
@ -867,6 +885,12 @@ function Screensaver:cleanup()
self.delayed_close = nil
self.screensaver_widget = nil
self.screensaver_lock_widget = nil
-- We run *after* the screensaver has been dismissed, so reset the Device flags
Device.screen_saver_mode = false
Device.screen_saver_lock = false
end
return Screensaver

@ -0,0 +1,135 @@
local Device = require("device")
local Geom = require("ui/geometry")
local GestureRange = require("ui/gesturerange")
local InfoMessage = require("ui/widget/infomessage")
local InputContainer = require("ui/widget/container/inputcontainer")
local UIManager = require("ui/uimanager")
local util = require("util")
local _ = require("gettext")
local Screen = Device.screen
local ScreenSaverLockWidget = InputContainer:extend{
name = "ScreenSaverLock",
modal = true, -- So it's on top the window stack
invisible = true, -- So UIManager ignores it refresh-wise
}
function ScreenSaverLockWidget:init()
if Device:isTouchDevice() then
if G_reader_settings:readSetting("screensaver_delay") == "gesture" then
self:setupGestureEvents()
end
if not self.has_exit_screensaver_gesture then
-- Exit with gesture not enabled, or no configured gesture found: allow exiting with tap
local range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
}
self.ges_events.Tap = { GestureRange:new{ ges = "tap", range = range } }
end
end
end
function ScreenSaverLockWidget:setupGestureEvents()
-- The configured gesture(s) won't trigger, because this widget is at top
-- of the UI stack and will prevent ReaderUI/Filemanager from getting
-- and handling any configured gesture event.
-- We need to find all the ones configured for the "exit_screensaver" action,
-- and clone them so they are handled by this widget.
local ReaderUI = require("apps/reader/readerui")
local ui = ReaderUI.instance
if not ui then
local FileManager = require("apps/filemanager/filemanager")
ui = FileManager.instance
end
if ui and ui.gestures and ui.gestures.gestures then
local multiswipe_already_met = false
for gesture, actions in pairs(ui.gestures.gestures) do
if util.stringStartsWith(gesture, "multiswipe") then
-- All multiswipes are handled by the single handler for "multiswipe"
-- We only need to clone one of them
gesture = "multiswipe"
end
if actions["exit_screensaver"] and (gesture ~= "multiswipe" or not multiswipe_already_met) then
if gesture == "multiswipe" then
multiswipe_already_met = true
end
-- Clone the gesture found in our self.ges_events
local ui_gesture = ui._zones[gesture]
if ui_gesture and ui_gesture.handler then
-- We can reuse its GestureRange object
self.ges_events[gesture] = { ui_gesture.gs_range }
-- For each of them, we need a distinct event and its handler.
-- This handler will call the original handler (created by gestures.koplugin)
-- which, after some checks (like swipe distance and direction, and multiswipe
-- directions), will emit normally the configured real ExitScreensaver event,
-- that this widget (being at top of the UI stack) will get and that
-- onExitScreensaver() will handle.
local event_name = "TriggerExitScreensaver_" .. gesture
self.ges_events[gesture].event = event_name
self["on"..event_name] = function(this, args, ev)
ui_gesture.handler(ev)
return true
end
end
end
end
end
if next(self.ges_events) then -- we found a gesture configured
self.has_exit_screensaver_gesture = true
-- Override handleEvent(), so we can stop any event from propagating to widgets
-- below this one (we may get some from other multiswipe as the handler does
-- not filter the one we are interested with, but also when multiple actions
-- are assigned to a single gesture).
self.handleEvent = function(this, event)
InputContainer.handleEvent(this, event)
return true
end
end
end
function ScreenSaverLockWidget:showWaitForGestureMessage()
-- We just paint an InfoMessage on screen directly: we don't want
-- another widget that we would need to prevent catching events
local infomsg = InfoMessage:new{
text = self.has_exit_screensaver_gesture
and _("Waiting for specific gesture to exit screensaver.")
or _("No exit screensaver gesture configured. Tap to exit.")
}
infomsg:paintTo(Screen.bb, 0, 0)
infomsg:onShow() -- get the screen refreshed
infomsg:free()
end
function ScreenSaverLockWidget:onClose()
UIManager:close(self)
-- Close the actual Screensaver, if any
local Screensaver = require("ui/screensaver")
if Screensaver.screensaver_widget then
Screensaver.screensaver_widget:onClose()
end
return true
end
-- That's the Event Dispatcher will send us ;)
ScreenSaverLockWidget.onExitScreensaver = ScreenSaverLockWidget.onClose
ScreenSaverLockWidget.onTap = ScreenSaverLockWidget.onClose
function ScreenSaverLockWidget:onCloseWidget()
-- If we don't have a ScreenSaverWidget, request a full repaint to dismiss our InfoMessage.
local Screensaver = require("ui/screensaver")
if not Screensaver.screensaver_widget then
UIManager:setDirty("all", "full")
end
end
-- NOTE: We duplicate this bit of logic from ScreenSaverWidget, because not every Screensaver config will spawn one...
function ScreenSaverLockWidget:onResume()
Device.screen_saver_lock = true
end
function ScreenSaverLockWidget:onSuspend()
Device.screen_saver_lock = false
end
return ScreenSaverLockWidget

@ -3,10 +3,8 @@ local Event = require("ui/event")
local Geom = require("ui/geometry")
local GestureRange = require("ui/gesturerange")
local FrameContainer = require("ui/widget/container/framecontainer")
local InfoMessage = require("ui/widget/infomessage")
local InputContainer = require("ui/widget/container/inputcontainer")
local UIManager = require("ui/uimanager")
local util = require("util")
local _ = require("gettext")
local Screen = Device.screen
@ -21,94 +19,16 @@ function ScreenSaverWidget:init()
self.key_events.AnyKeyPressed = { { Device.input.group.Any } }
end
if Device:isTouchDevice() then
if G_reader_settings:readSetting("screensaver_delay") == "gesture" then
self:setupGestureEvents()
end
if not self.has_exit_screensaver_gesture then
-- Exit with gesture not enabled, or no configured gesture found: allow exiting with tap
local range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
}
self.ges_events.Tap = { GestureRange:new{ ges = "tap", range = range } }
end
local range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
}
self.ges_events.Tap = { GestureRange:new{ ges = "tap", range = range } }
end
self:update()
end
function ScreenSaverWidget:setupGestureEvents()
-- The configured gesture(s) won't trigger, because this widget is at top
-- of the UI stack and will prevent ReaderUI/Filemanager from getting
-- and handling any configured gesture event.
-- We need to find all the ones configured for the "exit_screensaver" action,
-- and clone them so they are handled by this widget.
local ReaderUI = require("apps/reader/readerui")
local ui = ReaderUI:_getRunningInstance()
if not ui then
local FileManager = require("apps/filemanager/filemanager")
ui = FileManager.instance
end
if ui and ui.gestures and ui.gestures.gestures then
local multiswipe_already_met = false
for gesture, actions in pairs(ui.gestures.gestures) do
if util.stringStartsWith(gesture, "multiswipe") then
-- All multiswipes are handled by the single handler for "multiswipe"
-- We only need to clone one of them
gesture = "multiswipe"
end
if actions["exit_screensaver"] and (gesture ~= "multiswipe" or not multiswipe_already_met) then
if gesture == "multiswipe" then
multiswipe_already_met = true
end
-- Clone the gesture found in our self.ges_events
local ui_gesture = ui._zones[gesture]
if ui_gesture and ui_gesture.handler then
-- We can reuse its GestureRange object
self.ges_events[gesture] = { ui_gesture.gs_range }
-- For each of them, we need a distinct event and its handler.
-- This handler will call the original handler (created by gestures.koplugin)
-- which, after some checks (like swipe distance and direction, and multiswipe
-- directions), will emit normally the configured real ExitScreensaver event,
-- that this widget (being at top of the UI stack) will get and that
-- onExitScreensaver() will handle.
local event_name = "TriggerExitScreensaver_" .. gesture
self.ges_events[gesture].event = event_name
self["on"..event_name] = function(this, args, ev)
ui_gesture.handler(ev)
return true
end
end
end
end
end
if next(self.ges_events) then -- we found a gesture configured
self.has_exit_screensaver_gesture = true
-- Override handleEvent(), so we can stop any event from propagating to widgets
-- below this one (we may get some from other multiswipe as the handler does
-- not filter the one we are insterested with, but also when multiple actions
-- are assigned to a single gesture).
self.handleEvent = function(this, event)
InputContainer.handleEvent(this, event)
return true
end
self.key_events = {} -- also disable exit with keys
end
end
function ScreenSaverWidget:showWaitForGestureMessage(msg)
-- We just paint an InfoMessage on screen directly: we don't want
-- another widget that we would need to prevent catching events
local infomsg = InfoMessage:new{
text = self.has_exit_screensaver_gesture
and _("Waiting for specific gesture to exit screensaver.")
or _("No exit screensaver gesture configured. Tap to exit.")
}
infomsg:paintTo(Screen.bb, 0, 0)
infomsg:onShow() -- get the screen refreshed
infomsg:free()
end
function ScreenSaverWidget:update()
self.height = Screen:getHeight()
self.width = Screen:getWidth()
@ -163,9 +83,6 @@ ScreenSaverWidget.onAnyKeyPressed = ScreenSaverWidget.onClose
ScreenSaverWidget.onExitScreensaver = ScreenSaverWidget.onClose
function ScreenSaverWidget:onCloseWidget()
Device.screen_saver_mode = false
Device.screen_saver_lock = false
-- Restore to previous rotation mode, if need be.
if Device.orig_rotation_mode then
Screen:setRotationMode(Device.orig_rotation_mode)

Loading…
Cancel
Save