diff --git a/base b/base index 9b8e1fc44..d5b2e3798 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit 9b8e1fc44dc4c4b08891d55e48fbf28db1f77799 +Subproject commit d5b2e3798043630906251b99f9b79e1a59ab6099 diff --git a/frontend/device/input.lua b/frontend/device/input.lua index c31c0cb19..67e758737 100644 --- a/frontend/device/input.lua +++ b/frontend/device/input.lua @@ -243,6 +243,8 @@ function Input:init() self.event_map[10011] = "UsbPlugOut" self.event_map[10020] = "Charging" self.event_map[10021] = "NotCharging" + self.event_map[10030] = "WakeupFromSuspend" + self.event_map[10031] = "ReadyToSuspend" -- user custom event map local custom_event_map_location = string.format( @@ -496,7 +498,8 @@ function Input:handleKeyBoardEv(ev) -- fake events if keycode == "IntoSS" or keycode == "OutOfSS" or keycode == "UsbPlugIn" or keycode == "UsbPlugOut" - or keycode == "Charging" or keycode == "NotCharging" then + or keycode == "Charging" or keycode == "NotCharging" + or keycode == "WakeupFromSuspend" or keycode == "ReadyToSuspend" then return keycode end @@ -611,7 +614,8 @@ function Input:handlePowerManagementOnlyEv(ev) -- Fake events if keycode == "IntoSS" or keycode == "OutOfSS" or keycode == "UsbPlugIn" or keycode == "UsbPlugOut" - or keycode == "Charging" or keycode == "NotCharging" then + or keycode == "Charging" or keycode == "NotCharging" + or keycode == "WakeupFromSuspend" or keycode == "ReadyToSuspend" then return keycode end diff --git a/frontend/device/kindle/device.lua b/frontend/device/kindle/device.lua index 956803abe..b4462c8fc 100644 --- a/frontend/device/kindle/device.lua +++ b/frontend/device/kindle/device.lua @@ -275,6 +275,14 @@ function Kindle:usbPlugOut() self.charging_mode = false end +function Kindle:wakeupFromSuspend() + self.powerd:wakeupFromSuspend() +end + +function Kindle:readyToSuspend() + self.powerd:readyToSuspend() +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/kindle/mockrtc.lua b/frontend/device/kindle/mockrtc.lua new file mode 100644 index 000000000..6d6639e72 --- /dev/null +++ b/frontend/device/kindle/mockrtc.lua @@ -0,0 +1,62 @@ +-- Mock RTC implementation backed by kindle's system powerd via lipc +local MockRTC = { + _wakeup_scheduled = false, + _wakeup_scheduled_epoch = nil, +} + +-- This call always succeeds, errors will only happen at suspend time in +-- powerd:setRtcWakeup() +function MockRTC:setWakeupAlarm(epoch, enabled) + enabled = (enabled ~= nil) and enabled or true + if enabled then + self._wakeup_scheduled = true + self._wakeup_scheduled_epoch = epoch + else + self:unsetWakeupAlarm() + end + return true +end + +function MockRTC:unsetWakeupAlarm() + self._wakeup_scheduled = false + self._wakeup_scheduled_epoch = nil +end + +function MockRTC:getWakeupAlarmEpoch() + return self._wakeup_scheduled_epoch +end + +--[[-- +Checks if the alarm we set matches the current time. +--]] +function MockRTC:validateWakeupAlarmByProximity(task_alarm, proximity) + -- In principle alarm time and current time should match within a second, + -- but let's be absurdly generous and assume anything within 30 is a match. + -- In practice, suspend() schedules check_unexpected_wakeup 15s *after* + -- the actual wakeup, so we need to account for at least that much ;). + proximity = proximity or 30 + + -- We want everything in UTC time_t (i.e. a Posix epoch). + local now = os.time() + local alarm = self:getWakeupAlarmEpoch() + if not (alarm and task_alarm) then return end + + -- Everything's in UTC, ask Lua to convert that to a human-readable format in the local timezone + print("validateWakeupAlarmByProximity:", + "\ntask @ " .. task_alarm .. os.date(" (%F %T %z)", task_alarm), + "\nlast set alarm @ " .. alarm .. os.date(" (%F %T %z)", alarm), + "\ncurrent time is " .. now .. os.date(" (%F %T %z)", now)) + + -- If our stored alarm and the provided task alarm don't match, + -- we're not talking about the same task. + if task_alarm and alarm ~= task_alarm then return end + + local diff = now - alarm + if diff >= 0 and diff < proximity then return true end +end + +function MockRTC:isWakeupAlarmScheduled() + return self._wakeup_scheduled +end + +return MockRTC diff --git a/frontend/device/kindle/powerd.lua b/frontend/device/kindle/powerd.lua index 6ed329f01..df0eaac36 100644 --- a/frontend/device/kindle/powerd.lua +++ b/frontend/device/kindle/powerd.lua @@ -1,4 +1,6 @@ local BasePowerD = require("device/generic/powerd") +local WakeupMgr = require("device/wakeupmgr") +local logger = require("logger") -- liblipclua, see require below local KindlePowerD = BasePowerD:new{ @@ -19,6 +21,8 @@ function KindlePowerD:init() if not self.device:canTurnFrontlightOff() then self.fl_max = self.fl_max + 1 end + + self:initWakeupMgr() end -- If we start with the light off (fl_intensity is fl_min), ensure a toggle will set it to the lowest "on" step, @@ -186,6 +190,71 @@ function KindlePowerD:toggleSuspend() end end +-- Kindle only allows setting the RTC via lipc during the ReadyToSuspend state +function KindlePowerD:setRtcWakeup(seconds_from_now) + if self.lipc_handle then + self.lipc_handle:set_int_property("com.lab126.powerd", "rtcWakeup", seconds_from_now) + end +end + +-- Check the powerd state: are we still in screensaver mode. +function KindlePowerD:getPowerdState() + if self.lipc_handle then + return self.lipc_handle:get_string_property("com.lab126.powerd", "state") + end +end + +function KindlePowerD:checkUnexpectedWakeup() + local state = self:getPowerdState() + logger.info("Powerd resume state:", state) + -- If we moved on to the active state, + -- then we were woken by user input not our alarm. + if state ~= "screenSaver" and state ~= "suspended" then return end + + if self.device.wakeup_mgr:isWakeupAlarmScheduled() and self.device.wakeup_mgr:wakeupAction() then + logger.info("Kindle scheduled wakeup") + else + logger.info("Kindle unscheduled wakeup") + end +end + +-- Dummy fuctions. They will be defined in initWakeupMgr +function KindlePowerD:wakeupFromSuspend() end +function KindlePowerD:readyToSuspend() end + +-- Support WakeupMgr on Lipc & supportsScreensaver devices. +function KindlePowerD:initWakeupMgr() + if not self.device:supportsScreensaver() then return end + if self.lipc_handle == nil then return end + + function KindlePowerD:wakeupFromSuspend() + logger.info("Kindle wakeupFromSuspend") + -- Give the device a few seconds to settle. + -- This filters out user input resumes -> device will resume to active + -- Also the Kindle stays in Ready to suspend for 10 seconds + -- so the alarm may fire 10 seconds early + local UIManager = require("ui/uimanager") + UIManager:scheduleIn(15, self.checkUnexpectedWakeup, self) + end + + function KindlePowerD:readyToSuspend() + logger.info("Kindle readyToSuspend") + if self.device.wakeup_mgr:isWakeupAlarmScheduled() then + local now = os.time() + local alarm = self.device.wakeup_mgr:getWakeupAlarmEpoch() + if alarm > now then + -- Powerd / Lipc need seconds_from_now not epoch + self:setRtcWakeup(alarm - now) + else + -- wakeup time is in the past + self.device.wakeup_mgr:removeTasks(alarm) + end + end + end + + self.device.wakeup_mgr = WakeupMgr:new{rtc = require("device/kindle/mockrtc")} +end + --- @fixme: This won't ever fire, as KindlePowerD is already a metatable on a plain table. function KindlePowerD:__gc() if self.lipc_handle then diff --git a/frontend/device/wakeupmgr.lua b/frontend/device/wakeupmgr.lua index 724864cdb..19d831ec5 100644 --- a/frontend/device/wakeupmgr.lua +++ b/frontend/device/wakeupmgr.lua @@ -22,6 +22,7 @@ WakeupMgr base class. local WakeupMgr = { dev_rtc = "/dev/rtc0", -- RTC device _task_queue = {}, -- Table with epoch at which to schedule the task and the function to be scheduled. + rtc = RTC, -- The RTC implementation to use, defaults to the RTC module. } --[[-- @@ -169,7 +170,7 @@ Set wakeup alarm. Simple wrapper for @{ffi.rtc.setWakeupAlarm}. --]] function WakeupMgr:setWakeupAlarm(epoch, enabled) - return RTC:setWakeupAlarm(epoch, enabled) + return self.rtc:setWakeupAlarm(epoch, enabled) end --[[-- @@ -178,7 +179,7 @@ Unset wakeup alarm. Simple wrapper for @{ffi.rtc.unsetWakeupAlarm}. --]] function WakeupMgr:unsetWakeupAlarm() - return RTC:unsetWakeupAlarm() + return self.rtc:unsetWakeupAlarm() end --[[-- @@ -187,13 +188,22 @@ Get wakealarm as set by us. Simple wrapper for @{ffi.rtc.getWakeupAlarm}. --]] function WakeupMgr:getWakeupAlarm() - return RTC:getWakeupAlarm() + return self.rtc:getWakeupAlarm() +end + +--[[-- +Get wakealarm epoch as set by us. + +Simple wrapper for @{ffi.rtc.getWakeupAlarmEpoch}. +--]] +function WakeupMgr:getWakeupAlarmEpoch() + return self.rtc:getWakeupAlarmEpoch() end --[[-- Get RTC wakealarm from system. -Simple wrapper for @{ffi.rtc.getWakeupAlarm}. +Simple wrapper for @{ffi.rtc.getWakeupAlarmSys}. --]] function WakeupMgr:getWakeupAlarmSys() return RTC:getWakeupAlarmSys() @@ -207,7 +217,7 @@ Checks if we set the alarm. Simple wrapper for @{ffi.rtc.validateWakeupAlarmByProximity}. --]] function WakeupMgr:validateWakeupAlarmByProximity(task_alarm_epoch, proximity) - return RTC:validateWakeupAlarmByProximity(task_alarm_epoch, proximity) + return self.rtc:validateWakeupAlarmByProximity(task_alarm_epoch, proximity) end --[[-- @@ -216,10 +226,10 @@ Check if a wakeup is scheduled. Simple wrapper for @{ffi.rtc.isWakeupAlarmScheduled}. --]] function WakeupMgr:isWakeupAlarmScheduled() - local wakeup_scheduled = RTC:isWakeupAlarmScheduled() + local wakeup_scheduled = self.rtc:isWakeupAlarmScheduled() if wakeup_scheduled then -- NOTE: This can't return nil given that we're behind an isWakeupAlarmScheduled check. - local alarm = RTC:getWakeupAlarmEpoch() + local alarm = self.rtc:getWakeupAlarmEpoch() logger.dbg("WakeupMgr:isWakeupAlarmScheduled: An alarm is scheduled for " .. alarm .. os.date(" (%F %T %z)", alarm)) else logger.dbg("WakeupMgr:isWakeupAlarmScheduled: No alarm is currently scheduled.") diff --git a/frontend/ui/uimanager.lua b/frontend/ui/uimanager.lua index 5a7e53410..2a5994202 100644 --- a/frontend/ui/uimanager.lua +++ b/frontend/ui/uimanager.lua @@ -239,6 +239,12 @@ function UIManager:init() 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()