diff --git a/frontend/device/android/device.lua b/frontend/device/android/device.lua index 719ea36e7..9fa6f269a 100644 --- a/frontend/device/android/device.lua +++ b/frontend/device/android/device.lua @@ -209,7 +209,7 @@ function Device:init() end elseif ev.code == C.APP_CMD_PAUSE then if not android.prop.brokenLifecycle then - UIManager:broadcastEvent(Event:new("Suspend")) + UIManager:broadcastEvent(Event:new("RequestSuspend")) end elseif ev.code == C.AEVENT_POWER_CONNECTED then UIManager:broadcastEvent(Event:new("Charging")) diff --git a/frontend/device/cervantes/device.lua b/frontend/device/cervantes/device.lua index 82b4c1e88..862d61160 100644 --- a/frontend/device/cervantes/device.lua +++ b/frontend/device/cervantes/device.lua @@ -236,6 +236,84 @@ function Cervantes:powerOff() os.execute("halt") end +-- This method is the same as the one in kobo/device.lua except the sleep cover part. +function Cervantes:setEventHandlers(UIManager) + -- We do not want auto suspend procedure to waste battery during + -- suspend. So let's unschedule it when suspending, and restart it after + -- resume. Done via the plugin's onSuspend/onResume handlers. + UIManager.event_handlers["Suspend"] = function() + self:_beforeSuspend() + self:onPowerEvent("Suspend") + end + UIManager.event_handlers["Resume"] = function() + -- MONOTONIC doesn't tick during suspend, + -- invalidate the last battery capacity pull time so that we get up to date data immediately. + self:getPowerDevice():invalidateCapacityCache() + + self:onPowerEvent("Resume") + self:_afterResume() + end + UIManager.event_handlers["PowerPress"] = function() + -- Always schedule power off. + -- Press the power button for 2+ seconds to shutdown directly from suspend. + UIManager:scheduleIn(2, UIManager.poweroff_action) + end + UIManager.event_handlers["PowerRelease"] = function() + if not self._entered_poweroff_stage then + UIManager:unschedule(UIManager.poweroff_action) + -- resume if we were suspended + if self.screen_saver_mode then + UIManager.event_handlers["Resume"]() + else + UIManager.event_handlers["Suspend"]() + end + end + end + UIManager.event_handlers["Light"] = function() + self:getPowerDevice():toggleFrontlight() + end + -- USB plug events with a power-only charger + UIManager.event_handlers["Charging"] = function() + self:_beforeCharging() + -- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep. + if self.screen_saver_mode then + UIManager.event_handlers["Suspend"]() + end + end + UIManager.event_handlers["NotCharging"] = function() + -- We need to put the device into suspension, other things need to be done before it. + self:usbPlugOut() + self:_afterNotCharging() + if self.screen_saver_mode then + UIManager.event_handlers["Suspend"]() + end + end + -- USB plug events with a data-aware host + UIManager.event_handlers["UsbPlugIn"] = function() + self:_beforeCharging() + -- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep. + if self.screen_saver_mode then + UIManager.event_handlers["Suspend"]() + else + -- Potentially start an USBMS session + local MassStorage = require("ui/elements/mass_storage") + MassStorage:start() + end + end + UIManager.event_handlers["UsbPlugOut"] = function() + -- We need to put the device into suspension, other things need to be done before it. + self:usbPlugOut() + self:_afterNotCharging() + if self.screen_saver_mode then + UIManager.event_handlers["Suspend"]() + else + -- Potentially dismiss the USBMS ConfirmBox + local MassStorage = require("ui/elements/mass_storage") + MassStorage:dismiss() + end + end +end + -------------- device probe ------------ local product_id = getProductId() diff --git a/frontend/device/devicelistener.lua b/frontend/device/devicelistener.lua index 22c5942a8..d479fc161 100644 --- a/frontend/device/devicelistener.lua +++ b/frontend/device/devicelistener.lua @@ -1,4 +1,3 @@ -local ConfirmBox = require("ui/widget/confirmbox") local Device = require("device") local Event = require("ui/event") local InputContainer = require("ui/widget/container/inputcontainer") @@ -318,40 +317,24 @@ function DeviceListener:onSwapPageTurnButtons() Device:invertButtons() end -if Device:canReboot() then - function DeviceListener:onReboot() - UIManager:show(ConfirmBox:new{ - text = _("Are you sure you want to reboot the device?"), - ok_text = _("Reboot"), - ok_callback = function() - UIManager:nextTick(UIManager.reboot_action) - end, - }) - end +function DeviceListener:onRestart() + self.ui.menu:exitOrRestart(function() UIManager:restartKOReader() end) end -if Device:canPowerOff() then - function DeviceListener:onPowerOff() - UIManager:show(ConfirmBox:new{ - text = _("Are you sure you want to power off the device?"), - ok_text = _("Power off"), - ok_callback = function() - UIManager:nextTick(UIManager.poweroff_action) - end, - }) - end +function DeviceListener:onRequestSuspend() + UIManager:suspend() end -function DeviceListener:onSuspendEvent() - UIManager:suspend() +function DeviceListener:onRequestReboot() + UIManager:reboot() end -function DeviceListener:onExit(callback) - self.ui.menu:exitOrRestart(callback) +function DeviceListener:onRequestPowerOff() + UIManager:powerOff() end -function DeviceListener:onRestart() - self.ui.menu:exitOrRestart(function() UIManager:restartKOReader() end) +function DeviceListener:onExit(callback) + self.ui.menu:exitOrRestart(callback) end function DeviceListener:onFullRefresh() diff --git a/frontend/device/generic/device.lua b/frontend/device/generic/device.lua index cdb250976..df7553e4a 100644 --- a/frontend/device/generic/device.lua +++ b/frontend/device/generic/device.lua @@ -573,4 +573,103 @@ function Device:untar(archive, extract_to) return os.execute(("./tar xf %q -C %q"):format(archive, extract_to)) end +-- Set device event handlers common to all devices +function Device:_setEventHandlers(UIManager) + if self:canReboot() then + UIManager.event_handlers["Reboot"] = function() + local ConfirmBox = require("ui/widget/confirmbox") + UIManager:show(ConfirmBox:new{ + text = _("Are you sure you want to reboot the device?"), + ok_text = _("Reboot"), + ok_callback = function() + local Event = require("ui/event") + UIManager:broadcastEvent(Event:new("Reboot")) + UIManager:nextTick(UIManager.reboot_action) + end, + }) + end + else + UIManager.event_handlers["Reboot"] = function() end + end + + if self:canPowerOff() then + UIManager.event_handlers["PowerOff"] = function() + local ConfirmBox = require("ui/widget/confirmbox") + UIManager:show(ConfirmBox:new{ + text = _("Are you sure you want to power off the device?"), + ok_text = _("Power off"), + ok_callback = function() + local Event = require("ui/event") + UIManager:broadcastEvent(Event:new("PowerOff")) + UIManager:nextTick(UIManager.poweroff_action) + end, + }) + end + else + UIManager.event_handlers["PowerOff"] = function() end + end + + self:setEventHandlers(UIManager) +end + +-- Devices can add additional event handlers by overwriting this method. +function Device:setEventHandlers(UIManager) + -- These will be most probably overwritten in the device specific `setEventHandlers` + UIManager.event_handlers["Suspend"] = function() + self:_beforeSuspend(false) + end + UIManager.event_handlers["Resume"] = function() + self:_afterResume(false) + end +end + +-- The common operations that should be performed before suspending the device. +function Device:_beforeSuspend(inhibit) + local Event = require("ui/event") + local UIManager = require("ui/uimanager") + UIManager:flushSettings() + UIManager:broadcastEvent(Event:new("Suspend")) + + if inhibit ~= false then + -- Block input events unrelated to power management + self.input:inhibitInput(true) + + -- Disable key repeat to avoid useless chatter (especially where Sleep Covers are concerned...) + self:disableKeyRepeat() + end +end + +-- The common operations that should be performed after resuming the device. +function Device:_afterResume(inhibit) + if inhibit ~= false then + -- Restore key repeat + self:restoreKeyRepeat() + + -- Restore full input handling + self.input:inhibitInput(false) + end + + local Event = require("ui/event") + local UIManager = require("ui/uimanager") + UIManager:broadcastEvent(Event:new("Resume")) +end + +-- The common operations that should be performed when the device is plugged to a power source. +function Device:_beforeCharging() + -- Leave the kernel some time to figure it out ;o). + local Event = require("ui/event") + local UIManager = require("ui/uimanager") + UIManager:scheduleIn(1, function() self:setupChargingLED() end) + UIManager:broadcastEvent(Event:new("Charging")) +end + +-- The common operations that should be performed when the device is unplugged from a power source. +function Device:_afterNotCharging() + -- Leave the kernel some time to figure it out ;o). + local Event = require("ui/event") + local UIManager = require("ui/uimanager") + UIManager:scheduleIn(1, function() self:setupChargingLED() end) + UIManager:broadcastEvent(Event:new("NotCharging")) +end + return Device diff --git a/frontend/device/kindle/device.lua b/frontend/device/kindle/device.lua index ec9d071b4..bac1ffe0f 100644 --- a/frontend/device/kindle/device.lua +++ b/frontend/device/kindle/device.lua @@ -368,6 +368,34 @@ function Kindle:readyToSuspend() self.suspend_time = time.boottime_or_realtime_coarse() end +function Kindle:setEventHandlers(UIManager) + UIManager.event_handlers["Suspend"] = function() + self.powerd:toggleSuspend() + end + UIManager.event_handlers["IntoSS"] = function() + self:_beforeSuspend() + self:intoScreenSaver() + end + UIManager.event_handlers["OutOfSS"] = function() + self:outofScreenSaver() + self:_afterResume() + end + UIManager.event_handlers["Charging"] = function() + self:_beforeCharging() + self:usbPlugIn() + end + UIManager.event_handlers["NotCharging"] = function() + self:usbPlugOut() + self:_afterNotCharging() + end + UIManager.event_handlers["WakeupFromSuspend"] = function() + self:wakeupFromSuspend() + end + UIManager.event_handlers["ReadyToSuspend"] = function() + self:readyToSuspend() + end +end + function Kindle:ambientBrightnessLevel() local haslipc, lipc = pcall(require, "liblipclua") if not haslipc or lipc == nil then return 0 end diff --git a/frontend/device/kobo/device.lua b/frontend/device/kobo/device.lua index 081cee19f..5909ae0ab 100644 --- a/frontend/device/kobo/device.lua +++ b/frontend/device/kobo/device.lua @@ -1235,6 +1235,108 @@ function Kobo:isStartupScriptUpToDate() return md5.sumFile(current_script) == md5.sumFile(new_script) end +function Kobo:setEventHandlers(UIManager) + -- We do not want auto suspend procedure to waste battery during + -- suspend. So let's unschedule it when suspending, and restart it after + -- resume. Done via the plugin's onSuspend/onResume handlers. + UIManager.event_handlers["Suspend"] = function() + self:_beforeSuspend() + self:onPowerEvent("Suspend") + end + UIManager.event_handlers["Resume"] = function() + -- MONOTONIC doesn't tick during suspend, + -- invalidate the last battery capacity pull time so that we get up to date data immediately. + self:getPowerDevice():invalidateCapacityCache() + + self:onPowerEvent("Resume") + self:_afterResume() + end + UIManager.event_handlers["PowerPress"] = function() + -- Always schedule power off. + -- Press the power button for 2+ seconds to shutdown directly from suspend. + UIManager:scheduleIn(2, UIManager.poweroff_action) + end + UIManager.event_handlers["PowerRelease"] = function() + if not self._entered_poweroff_stage then + UIManager:unschedule(UIManager.poweroff_action) + -- resume if we were suspended + if self.screen_saver_mode then + UIManager.event_handlers["Resume"]() + else + UIManager.event_handlers["Suspend"]() + end + end + end + UIManager.event_handlers["Light"] = function() + self:getPowerDevice():toggleFrontlight() + end + -- USB plug events with a power-only charger + UIManager.event_handlers["Charging"] = function() + self:_beforeCharging() + -- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep. + if self.screen_saver_mode then + UIManager.event_handlers["Suspend"]() + end + end + UIManager.event_handlers["NotCharging"] = function() + -- We need to put the device into suspension, other things need to be done before it. + self:usbPlugOut() + self:_afterNotCharging() + if self.screen_saver_mode then + UIManager.event_handlers["Suspend"]() + end + end + -- USB plug events with a data-aware host + UIManager.event_handlers["UsbPlugIn"] = function() + self:_beforeCharging() + -- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep. + if self.screen_saver_mode then + UIManager.event_handlers["Suspend"]() + else + -- Potentially start an USBMS session + local MassStorage = require("ui/elements/mass_storage") + MassStorage:start() + end + end + UIManager.event_handlers["UsbPlugOut"] = function() + -- We need to put the device into suspension, other things need to be done before it. + self:usbPlugOut() + self:_afterNotCharging() + if self.screen_saver_mode then + UIManager.event_handlers["Suspend"]() + else + -- Potentially dismiss the USBMS ConfirmBox + local MassStorage = require("ui/elements/mass_storage") + MassStorage:dismiss() + end + end + -- Sleep Cover handling + if G_reader_settings:isTrue("ignore_power_sleepcover") then + -- NOTE: The hardware event itself will wake the kernel up if it's in suspend (:/). + -- Let the unexpected wakeup guard handle that. + UIManager.event_handlers["SleepCoverClosed"] = nil + UIManager.event_handlers["SleepCoverOpened"] = nil + elseif G_reader_settings:isTrue("ignore_open_sleepcover") then + -- Just ignore wakeup events, and do NOT set is_cover_closed, + -- so device/generic/device will let us use the power button to wake ;). + UIManager.event_handlers["SleepCoverClosed"] = function() + UIManager.event_handlers["Suspend"]() + end + UIManager.event_handlers["SleepCoverOpened"] = function() + self.is_cover_closed = false + end + else + UIManager.event_handlers["SleepCoverClosed"] = function() + self.is_cover_closed = true + UIManager.event_handlers["Suspend"]() + end + UIManager.event_handlers["SleepCoverOpened"] = function() + self.is_cover_closed = false + UIManager.event_handlers["Resume"]() + end + end +end + -------------- device probe ------------ local codename = getCodeName() diff --git a/frontend/device/pocketbook/device.lua b/frontend/device/pocketbook/device.lua index c9aeb1a68..52ba32eb1 100644 --- a/frontend/device/pocketbook/device.lua +++ b/frontend/device/pocketbook/device.lua @@ -388,6 +388,16 @@ function PocketBook:getDefaultCoverPath() return "/mnt/ext1/system/logo/offlogo/cover.bmp" end +function PocketBook:setEventHandlers(UIManager) + -- Only fg/bg state plugin notifiers, not real power event. + UIManager.event_handlers["Suspend"] = function() + self:_beforeSuspend() + end + UIManager.event_handlers["Resume"] = function() + self:_afterResume() + end +end + -- Pocketbook HW rotation modes start from landsape, CCW local function landscape_ccw() return { 1, 0, 3, 2, -- PORTRAIT, LANDSCAPE, PORTRAIT_180, LANDSCAPE_180 @@ -619,7 +629,6 @@ local PocketBook741 = PocketBook:new{ usingForcedRotation = landscape_ccw, } - function PocketBook741._fb_init(fb, finfo, vinfo) -- Pocketbook Color Lux reports bits_per_pixel = 8, but actually uses an RGB24 framebuffer vinfo.bits_per_pixel = 24 diff --git a/frontend/device/remarkable/device.lua b/frontend/device/remarkable/device.lua index 32e252d25..93f4b0623 100644 --- a/frontend/device/remarkable/device.lua +++ b/frontend/device/remarkable/device.lua @@ -232,6 +232,31 @@ function Remarkable:getDefaultCoverPath() return "/usr/share/remarkable/poweroff.png" end +function Remarkable:setEventHandlers(UIManager) + UIManager.event_handlers["Suspend"] = function() + self:_beforeSuspend() + self:onPowerEvent("Suspend") + end + UIManager.event_handlers["Resume"] = function() + self:onPowerEvent("Resume") + self:_afterResume() + end + UIManager.event_handlers["PowerPress"] = function() + UIManager:scheduleIn(2, UIManager.poweroff_action) + end + UIManager.event_handlers["PowerRelease"] = function() + if not UIManager._entered_poweroff_stage then + UIManager:unschedule(UIManager.poweroff_action) + -- resume if we were suspended + if self.screen_saver_mode then + UIManager.event_handlers["Resume"]() + else + UIManager.event_handlers["Suspend"]() + end + end + end +end + if isRm2 then if not os.getenv("RM2FB_SHIM") then error("reMarkable2 requires RM2FB to work (https://github.com/ddvk/remarkable2-framebuffer)") diff --git a/frontend/device/sdl/device.lua b/frontend/device/sdl/device.lua index 89b197e4d..4c287d51b 100644 --- a/frontend/device/sdl/device.lua +++ b/frontend/device/sdl/device.lua @@ -343,6 +343,25 @@ function Device:toggleFullscreen() end end +function Device:setEventHandlers(UIManager) + UIManager.event_handlers["Suspend"] = function() + self:_beforeSuspend() + self:simulateSuspend() + end + UIManager.event_handlers["Resume"] = function() + self:simulateResume() + self:_afterResume() + end + UIManager.event_handlers["PowerRelease"] = function() + -- Resume if we were suspended + if self.screen_saver_mode then + UIManager.event_handlers["Resume"]() + else + UIManager.event_handlers["Suspend"]() + end + end +end + function Emulator:supportsScreensaver() return true end function Emulator:simulateSuspend() diff --git a/frontend/device/sony-prstux/device.lua b/frontend/device/sony-prstux/device.lua index 2f442210e..0da49ff63 100644 --- a/frontend/device/sony-prstux/device.lua +++ b/frontend/device/sony-prstux/device.lua @@ -188,6 +188,56 @@ function SonyPRSTUX:getDeviceModel() return ffi.string("PRS-T2") end +function SonyPRSTUX:setEventHandlers(UIManager) + UIManager.event_handlers["Suspend"] = function() + self:_beforeSuspend() + self:intoScreenSaver() + self:suspend() + end + UIManager.event_handlers["Resume"] = function() + self:resume() + self:outofScreenSaver() + self:_afterResume() + end + UIManager.event_handlers["PowerPress"] = function() + UIManager:scheduleIn(2, UIManager.poweroff_action) + end + UIManager.event_handlers["PowerRelease"] = function() + if not UIManager._entered_poweroff_stage then + UIManager:unschedule(UIManager.poweroff_action) + -- resume if we were suspended + if self.screen_saver_mode then + UIManager.event_handlers["Resume"]() + else + UIManager.event_handlers["Suspend"]() + end + end + end + UIManager.event_handlers["Charging"] = function() + self:_beforeCharging() + end + UIManager.event_handlers["NotCharging"] = function() + self:_afterNotCharging() + end + UIManager.event_handlers["UsbPlugIn"] = function() + if self.screen_saver_mode then + self:resume() + self:outofScreenSaver() + self:_afterResume() + end + self:usbPlugIn() + end + UIManager.event_handlers["UsbPlugOut"] = function() + self:usbPlugOut() + end + UIManager.event_handlers["__default__"] = function(input_event) + -- Same as in Kobo: we want to ignore keys during suspension + if not self.screen_saver_mode then + UIManager:sendEvent(input_event) + end + end +end + -- For Sony PRS-T2 local SonyPRSTUX_T2 = SonyPRSTUX:new{ isTouchDevice = yes, diff --git a/frontend/dispatcher.lua b/frontend/dispatcher.lua index 53e000ffc..31f39e2f0 100644 --- a/frontend/dispatcher.lua +++ b/frontend/dispatcher.lua @@ -74,11 +74,11 @@ local settingsList = { toggle_fullscreen = {category="none", event="ToggleFullscreen", title=_("Toggle Fullscreen"), device=true, condition=not Device:isAlwaysFullscreen()}, show_network_info = {category="none", event="ShowNetworkInfo", title=_("Show network info"), device=true, separator=true}, exit_screensaver = {category="none", event="ExitScreensaver", title=_("Exit screensaver"), device=true}, - suspend = {category="none", event="SuspendEvent", title=_("Suspend"), device=true}, - exit = {category="none", event="Exit", title=_("Exit KOReader"), device=true}, restart = {category="none", event="Restart", title=_("Restart KOReader"), device=true, condition=Device:canRestart()}, - reboot = {category="none", event="Reboot", title=_("Reboot the device"), device=true, condition=Device:canReboot()}, - poweroff = {category="none", event="PowerOff", title=_("Power off"), device=true, condition=Device:canPowerOff(), separator=true}, + suspend = {category="none", event="RequestSuspend", title=_("Suspend"), device=true}, + reboot = {category="none", event="RequestReboot", title=_("Reboot the device"), device=true, condition=Device:canReboot()}, + poweroff = {category="none", event="RequestPowerOff", title=_("Power off"), device=true, condition=Device:canPowerOff(), separator=true}, + exit = {category="none", event="Exit", title=_("Exit KOReader"), device=true}, toggle_hold_corners = {category="none", event="IgnoreHoldCorners", title=_("Toggle hold corners"), device=true, separator=true}, toggle_rotation = {category="none", event="SwapRotation", title=_("Toggle orientation"), device=true}, invert_rotation = {category="none", event="InvertRotation", title=_("Invert rotation"), device=true}, diff --git a/frontend/ui/elements/common_exit_menu_table.lua b/frontend/ui/elements/common_exit_menu_table.lua index 91e0c8ac2..f92b4a48d 100644 --- a/frontend/ui/elements/common_exit_menu_table.lua +++ b/frontend/ui/elements/common_exit_menu_table.lua @@ -39,7 +39,7 @@ if Device:canReboot() then text = _("Reboot the device"), keep_menu_open = true, callback = function() - UIManager:broadcastEvent(Event:new("Reboot")) + UIManager:reboot() end } end @@ -48,7 +48,7 @@ if Device:canPowerOff() then text = _("Power off"), keep_menu_open = true, callback = function() - UIManager:broadcastEvent(Event:new("PowerOff")) + UIManager:powerOff() end } end diff --git a/frontend/ui/uimanager.lua b/frontend/ui/uimanager.lua index ea71ae2e0..d8f7fc2ea 100644 --- a/frontend/ui/uimanager.lua +++ b/frontend/ui/uimanager.lua @@ -108,299 +108,8 @@ function UIManager:init() Device:reboot() end) end - if Device:isPocketBook() then - -- Only fg/bg state plugin notifiers, not real power event. - self.event_handlers["Suspend"] = function() - self:_beforeSuspend() - end - self.event_handlers["Resume"] = function() - self:_afterResume() - end - end - if Device:isKobo() then - -- We do not want auto suspend procedure to waste battery during - -- suspend. So let's unschedule it when suspending, and restart it after - -- resume. Done via the plugin's onSuspend/onResume handlers. - self.event_handlers["Suspend"] = function() - self:_beforeSuspend() - Device:onPowerEvent("Suspend") - end - self.event_handlers["Resume"] = function() - Device:onPowerEvent("Resume") - self:_afterResume() - end - self.event_handlers["PowerPress"] = function() - -- Always schedule power off. - -- Press the power button for 2+ seconds to shutdown directly from suspend. - UIManager:scheduleIn(2, self.poweroff_action) - end - self.event_handlers["PowerRelease"] = function() - if not self._entered_poweroff_stage then - UIManager:unschedule(self.poweroff_action) - -- resume if we were suspended - if Device.screen_saver_mode then - self:resume() - else - self:suspend() - end - end - end - -- Sleep Cover handling - if G_reader_settings:isTrue("ignore_power_sleepcover") then - -- NOTE: The hardware event itself will wake the kernel up if it's in suspend (:/). - -- Let the unexpected wakeup guard handle that. - self.event_handlers["SleepCoverClosed"] = nil - self.event_handlers["SleepCoverOpened"] = nil - elseif G_reader_settings:isTrue("ignore_open_sleepcover") then - -- Just ignore wakeup events, and do NOT set is_cover_closed, - -- so device/generic/device will let us use the power button to wake ;). - self.event_handlers["SleepCoverClosed"] = function() - self:suspend() - end - self.event_handlers["SleepCoverOpened"] = function() - Device.is_cover_closed = false - end - else - self.event_handlers["SleepCoverClosed"] = function() - Device.is_cover_closed = true - self:suspend() - end - self.event_handlers["SleepCoverOpened"] = function() - Device.is_cover_closed = false - self:resume() - end - end - self.event_handlers["Light"] = function() - Device:getPowerDevice():toggleFrontlight() - end - -- USB plug events with a power-only charger - self.event_handlers["Charging"] = function() - self:_beforeCharging() - -- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep. - if Device.screen_saver_mode then - self:suspend() - end - end - self.event_handlers["NotCharging"] = function() - -- We need to put the device into suspension, other things need to be done before it. - Device:usbPlugOut() - self:_afterNotCharging() - if Device.screen_saver_mode then - self:suspend() - end - end - -- USB plug events with a data-aware host - self.event_handlers["UsbPlugIn"] = function() - self:_beforeCharging() - -- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep. - if Device.screen_saver_mode then - self:suspend() - else - -- Potentially start an USBMS session - local MassStorage = require("ui/elements/mass_storage") - MassStorage:start() - end - end - self.event_handlers["UsbPlugOut"] = function() - -- We need to put the device into suspension, other things need to be done before it. - Device:usbPlugOut() - self:_afterNotCharging() - if Device.screen_saver_mode then - self:suspend() - else - -- Potentially dismiss the USBMS ConfirmBox - local MassStorage = require("ui/elements/mass_storage") - MassStorage:dismiss() - end - end - self.event_handlers["__default__"] = function(input_event) - -- Suspension in Kobo can be interrupted by screen updates. We ignore user touch input - -- in screen_saver_mode so screen updates won't be triggered in suspend mode. - -- We should not call self:suspend() in screen_saver_mode lest we stay on forever - -- trying to reschedule suspend. Other systems take care of unintended wake-up. - if not Device.screen_saver_mode then - self:sendEvent(input_event) - end - end - elseif Device:isKindle() then - self.event_handlers["IntoSS"] = function() - self:_beforeSuspend() - Device:intoScreenSaver() - end - self.event_handlers["OutOfSS"] = function() - Device:outofScreenSaver() - self:_afterResume(); - end - self.event_handlers["Charging"] = function() - self:_beforeCharging() - Device:usbPlugIn() - end - self.event_handlers["NotCharging"] = function() - Device:usbPlugOut() - self:_afterNotCharging() - end - self.event_handlers["WakeupFromSuspend"] = function() - Device:wakeupFromSuspend() - end - self.event_handlers["ReadyToSuspend"] = function() - Device:readyToSuspend() - end - elseif Device:isRemarkable() then - self.event_handlers["Suspend"] = function() - self:_beforeSuspend() - Device:onPowerEvent("Suspend") - end - self.event_handlers["Resume"] = function() - Device:onPowerEvent("Resume") - self:_afterResume() - end - self.event_handlers["PowerPress"] = function() - UIManager:scheduleIn(2, self.poweroff_action) - end - self.event_handlers["PowerRelease"] = function() - if not self._entered_poweroff_stage then - UIManager:unschedule(self.poweroff_action) - -- resume if we were suspended - if Device.screen_saver_mode then - self:resume() - else - self:suspend() - end - end - end - self.event_handlers["__default__"] = function(input_event) - -- Same as in Kobo: we want to ignore keys during suspension - if not Device.screen_saver_mode then - self:sendEvent(input_event) - end - end - elseif Device:isSonyPRSTUX() then - self.event_handlers["PowerPress"] = function() - UIManager:scheduleIn(2, self.poweroff_action) - end - self.event_handlers["PowerRelease"] = function() - if not self._entered_poweroff_stage then - UIManager:unschedule(self.poweroff_action) - -- resume if we were suspended - if Device.screen_saver_mode then - self:resume() - else - self:suspend() - end - end - end - self.event_handlers["Suspend"] = function() - self:_beforeSuspend() - Device:intoScreenSaver() - Device:suspend() - end - self.event_handlers["Resume"] = function() - Device:resume() - Device:outofScreenSaver() - self:_afterResume() - end - self.event_handlers["Charging"] = function() - self:_beforeCharging() - end - self.event_handlers["NotCharging"] = function() - self:_afterNotCharging() - end - self.event_handlers["UsbPlugIn"] = function() - if Device.screen_saver_mode then - Device:resume() - Device:outofScreenSaver() - self:_afterResume() - end - Device:usbPlugIn() - end - self.event_handlers["UsbPlugOut"] = function() - Device:usbPlugOut() - end - self.event_handlers["__default__"] = function(input_event) - -- Same as in Kobo: we want to ignore keys during suspension - if not Device.screen_saver_mode then - self:sendEvent(input_event) - end - end - elseif Device:isCervantes() then - self.event_handlers["Suspend"] = function() - self:_beforeSuspend() - Device:onPowerEvent("Suspend") - end - self.event_handlers["Resume"] = function() - Device:onPowerEvent("Resume") - self:_afterResume() - end - self.event_handlers["PowerPress"] = function() - UIManager:scheduleIn(2, self.poweroff_action) - end - self.event_handlers["PowerRelease"] = function() - if not self._entered_poweroff_stage then - UIManager:unschedule(self.poweroff_action) - -- resume if we were suspended - if Device.screen_saver_mode then - self:resume() - else - self:suspend() - end - end - end - self.event_handlers["Charging"] = function() - self:_beforeCharging() - if Device.screen_saver_mode then - self:suspend() - end - end - self.event_handlers["NotCharging"] = function() - self:_afterNotCharging() - if Device.screen_saver_mode then - self:suspend() - end - end - self.event_handlers["UsbPlugIn"] = function() - self:_beforeCharging() - if Device.screen_saver_mode then - self:suspend() - else - -- Potentially start an USBMS session - local MassStorage = require("ui/elements/mass_storage") - MassStorage:start() - end - end - self.event_handlers["UsbPlugOut"] = function() - self:_afterNotCharging() - if Device.screen_saver_mode then - self:suspend() - else - -- Potentially dismiss the USBMS ConfirmBox - local MassStorage = require("ui/elements/mass_storage") - MassStorage:dismiss() - end - end - self.event_handlers["__default__"] = function(input_event) - -- Same as in Kobo: we want to ignore keys during suspension - if not Device.screen_saver_mode then - self:sendEvent(input_event) - end - end - elseif Device:isSDL() then - self.event_handlers["Suspend"] = function() - self:_beforeSuspend() - Device:simulateSuspend() - end - self.event_handlers["Resume"] = function() - Device:simulateResume() - self:_afterResume() - end - self.event_handlers["PowerRelease"] = function() - -- Resume if we were suspended - if Device.screen_saver_mode then - self:resume() - else - self:suspend() - end - end - end + + Device:_setEventHandlers(self) end --[[-- @@ -1764,72 +1473,35 @@ function UIManager:runForever() return self:run() end --- The common operations that should be performed before suspending the device. -function UIManager:_beforeSuspend() - self:flushSettings() - self:broadcastEvent(Event:new("Suspend")) - - -- Block input events unrelated to power management - Input:inhibitInput(true) - - -- Disable key repeat to avoid useless chatter (especially where Sleep Covers are concerned...) - Device:disableKeyRepeat() -end - --- The common operations that should be performed after resuming the device. -function UIManager:_afterResume() - -- Restore key repeat - Device:restoreKeyRepeat() - - -- Restore full input handling - Input:inhibitInput(false) - - self:broadcastEvent(Event:new("Resume")) -end - --- The common operations that should be performed when the device is plugged to a power source. -function UIManager:_beforeCharging() - -- Leave the kernel some time to figure it out ;o). - self:scheduleIn(1, function() Device:setupChargingLED() end) - self:broadcastEvent(Event:new("Charging")) -end - --- The common operations that should be performed when the device is unplugged from a power source. -function UIManager:_afterNotCharging() - -- Leave the kernel some time to figure it out ;o). - self:scheduleIn(1, function() Device:setupChargingLED() end) - self:broadcastEvent(Event:new("NotCharging")) -end - --[[-- Executes all the operations of a suspension (i.e., sleep) request. This function usually puts the device into suspension. ]] function UIManager:suspend() + -- Should always exist, as defined in `generic/device` or overwritten with `setEventHandlers` if self.event_handlers["Suspend"] then - self.event_handlers["Suspend"]() - elseif Device:isKindle() then - Device.powerd:toggleSuspend() - elseif Device:canSuspend() then - Device:suspend() + -- Give the other event handlers a chance to be executed. + -- `Suspend` and `Resume` events will be sent by the handler + UIManager:nextTick(self.event_handlers["Suspend"]) end end ---[[-- -Executes all the operations of a resume (i.e., wakeup) request. +function UIManager:reboot() + -- Should always exist, as defined in `generic/device` or overwritten with `setEventHandlers` + if self.event_handlers["Reboot"] then + -- Give the other event handlers a chance to be executed. + -- 'Reboot' event will be sent by the handler + UIManager:nextTick(self.event_handlers["Reboot"]) + end +end -This function usually wakes up the device. -]] -function UIManager:resume() - -- MONOTONIC doesn't tick during suspend, - -- invalidate the last battery capacity pull time so that we get up to date data immediately. - Device:getPowerDevice():invalidateCapacityCache() - - if self.event_handlers["Resume"] then - self.event_handlers["Resume"]() - elseif Device:isKindle() then - self.event_handlers["OutOfSS"]() +function UIManager:powerOff() + -- Should always exist, as defined in `generic/device` or overwritten with `setEventHandlers` + if self.event_handlers["PowerOff"] then + -- Give the other event handlers a chance to be executed. + -- 'PowerOff' event will be sent by the handler + UIManager:nextTick(self.event_handlers["PowerOff"]) end end diff --git a/spec/unit/device_spec.lua b/spec/unit/device_spec.lua index a3a4c1946..f94dec70f 100644 --- a/spec/unit/device_spec.lua +++ b/spec/unit/device_spec.lua @@ -2,6 +2,7 @@ describe("device module", function() -- luacheck: push ignore local mock_fb, mock_input local iopen = io.open + local ipopen = io.popen local osgetenv = os.getenv local ffi, C @@ -43,6 +44,7 @@ describe("device module", function() os.getenv = osgetenv io.open = iopen + io.popen = ipopen end) describe("kobo", function() @@ -163,36 +165,6 @@ describe("device module", function() -- reset eventAdjustHook kobo_dev.input.eventAdjustHook = function() end end) - - it("should flush book settings before suspend", function() - local sample_pdf = "spec/front/unit/data/tall.pdf" - local ReaderUI = require("apps/reader/readerui") - local Device = require("device") - - NickelConf.frontLightLevel.get.returns(1) - NickelConf.frontLightState.get.returns(0) - - local UIManager = require("ui/uimanager") - stub(Device, "suspend") - stub(Device.powerd, "beforeSuspend") - stub(Device, "isKobo") - - Device.isKobo.returns(true) - UIManager:init() - - ReaderUI:doShowReader(sample_pdf) - local readerui = ReaderUI._getRunningInstance() - stub(readerui, "onFlushSettings") - UIManager.event_handlers["PowerPress"]() - UIManager.event_handlers["PowerRelease"]() - assert.stub(readerui.onFlushSettings).was_called() - - Device.suspend:revert() - Device.powerd.beforeSuspend:revert() - Device.isKobo:revert() - readerui.onFlushSettings:revert() - readerui:onClose() - end) end) describe("kindle", function() @@ -300,5 +272,169 @@ describe("device module", function() UIManager.onRotation:revert() end) end) + + describe("Flush book Settings for", function() + it("Kobo", function() + os.getenv.invokes(function(key) + if key == "PRODUCT" then + return "trilogy" + else + return osgetenv(key) + end + end) + local sample_pdf = "spec/front/unit/data/tall.pdf" + local ReaderUI = require("apps/reader/readerui") + local device_to_test = require("device/kobo/device") + local Device = require("device") + Device.setEventHandlers = device_to_test.setEventHandlers + + local UIManager = require("ui/uimanager") + stub(Device, "suspend") + stub(Device.powerd, "beforeSuspend") + stub(Device, "isKobo") + + Device.isKobo.returns(true) + UIManager:init() + + ReaderUI:doShowReader(sample_pdf) + local readerui = ReaderUI._getRunningInstance() + stub(readerui, "onFlushSettings") + UIManager.event_handlers["PowerPress"]() + UIManager.event_handlers["PowerRelease"]() + assert.stub(readerui.onFlushSettings).was_called() + + Device.suspend:revert() + Device.powerd.beforeSuspend:revert() + Device.isKobo:revert() + readerui.onFlushSettings:revert() + Device.screen_saver_mode = false + readerui:onClose() + end) + + it("Cervantes", function() + io.popen = function(filename, mode) + if filename:find("/usr/bin/ntxinfo") then + return { + read = function() + return 68 -- Cervantes4 + end, + close = function() end + } + else + return ipopen(filename, mode) + end + end + + local sample_pdf = "spec/front/unit/data/tall.pdf" + local ReaderUI = require("apps/reader/readerui") + local Device = require("device") + local device_to_test = require("device/cervantes/device") + Device.setEventHandlers = device_to_test.setEventHandlers + + local UIManager = require("ui/uimanager") + + stub(Device, "suspend") + stub(Device.powerd, "beforeSuspend") + stub(Device, "isCervantes") + + Device.isCervantes.returns(true) + UIManager:init() + + ReaderUI:doShowReader(sample_pdf) + local readerui = ReaderUI._getRunningInstance() + stub(readerui, "onFlushSettings") + UIManager.event_handlers["PowerPress"]() + UIManager.event_handlers["PowerRelease"]() + assert.stub(readerui.onFlushSettings).was_called() + + Device.suspend:revert() + Device.powerd.beforeSuspend:revert() + Device.isCervantes:revert() + Device.screen_saver_mode = false + readerui.onFlushSettings:revert() + readerui:onClose() + end) + + it("SDL", function() + local sample_pdf = "spec/front/unit/data/tall.pdf" + local ReaderUI = require("apps/reader/readerui") + local Device = require("device") + local device_to_test = require("device/sdl/device") + Device.setEventHandlers = device_to_test.setEventHandlers + + local UIManager = require("ui/uimanager") + + stub(Device, "suspend") + stub(Device.powerd, "beforeSuspend") + stub(Device, "isSDL") + + Device.isSDL.returns(true) + UIManager:init() + + ReaderUI:doShowReader(sample_pdf) + local readerui = ReaderUI._getRunningInstance() + stub(readerui, "onFlushSettings") + UIManager.event_handlers["PowerPress"]() + UIManager.event_handlers["PowerRelease"]() + assert.stub(readerui.onFlushSettings).was_called() + + Device.suspend:revert() + Device.powerd.beforeSuspend:revert() + Device.isSDL:revert() + Device.screen_saver_mode = false + readerui.onFlushSettings:revert() + readerui:onClose() + end) + + it("Remarkable", function() + io.open = function(filename, mode) + if filename == "/usr/bin/xochitl" then + return { + read = function() + return true + end, + close = function() end + } + elseif filename == "/sys/devices/soc0/machine" then + return { + read = function() + return "reMarkable", "generic" + end, + close = function() end + } + else + return iopen(filename, mode) + end + end + local sample_pdf = "spec/front/unit/data/tall.pdf" + local ReaderUI = require("apps/reader/readerui") + local Device = require("device") + local device_to_test = require("device/remarkable/device") + Device.setEventHandlers = device_to_test.setEventHandlers + + local UIManager = require("ui/uimanager") + + stub(Device, "suspend") + stub(Device.powerd, "beforeSuspend") + stub(Device, "isRemarkable") + + Device.isRemarkable.returns(true) + UIManager:init() + + ReaderUI:doShowReader(sample_pdf) + local readerui = ReaderUI._getRunningInstance() + stub(readerui, "onFlushSettings") + UIManager.event_handlers["PowerPress"]() + UIManager.event_handlers["PowerRelease"]() + assert.stub(readerui.onFlushSettings).was_called() + + Device.suspend:revert() + Device.powerd.beforeSuspend:revert() + Device.isRemarkable:revert() + Device.screen_saver_mode = false + readerui.onFlushSettings:revert() + readerui:onClose() + end) + end) -- luacheck: pop end)