diff --git a/frontend/ui/screensaver.lua b/frontend/ui/screensaver.lua index ebc310d12..8775c45cd 100644 --- a/frontend/ui/screensaver.lua +++ b/frontend/ui/screensaver.lua @@ -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 diff --git a/frontend/ui/widget/screensaverlockwidget.lua b/frontend/ui/widget/screensaverlockwidget.lua new file mode 100644 index 000000000..3a5615a26 --- /dev/null +++ b/frontend/ui/widget/screensaverlockwidget.lua @@ -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 diff --git a/frontend/ui/widget/screensaverwidget.lua b/frontend/ui/widget/screensaverwidget.lua index 49586756c..cf89c717c 100644 --- a/frontend/ui/widget/screensaverwidget.lua +++ b/frontend/ui/widget/screensaverwidget.lua @@ -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)