A host of low power states related tweaks (#9036)

* Disable all non power management related input during suspend. (This prevents wonky touch events from being tripped when closing a sleep cover on an already-in-suspend device, among other things).
* Kobo: Use our WakeupMgr instance, not the class.
* WakupMgr: split `removeTask` in two: 
* `removeTask`, which *only* takes a queue index as input, and only removes a single task. Greatly simplifies the function (i.e., it's just a `table.remove`).
* `removeTasks`, which takes an epoch or a cb ref, and removes *every* task that matches.
* Both of these will also *always* re-schedule the next task (if any) on exit, since we can have multiple WakeupMgr tasks queued, but we can only have a single RTC wake alarm set ;).
* `wakeupAction` now takes a `proximity` argument, which it passes on to its `validateWakeupAlarmByProximity` call, allowing call sites to avoir having to duplicate that call themselves when they want to use a custom proximity window.
* `wakeupAction` now re-schedules the next task (if any) on exit.
* Simplify `Kobo:checkUnexpectedWakeup`, by removing the duplicate `WakerupMgr:validateWakeupAlarmByProximity` call, now that we can pass a proximity window to `WakeuoMgr:wakeupAction`.
* The various network activity timeouts are now halved when autostandby is enabled.
* Autostandby: get rid of the dummy deadline_guard task, as it's no longer necessary since #9009.
* UIManager: The previous change allows us to simplify `getNextTaskTimes` into a simpler `getNextTaskTime` variant, getting rid of a table & a loop.
* ReaderFooter & ReaderHeader: Make sure we only perform a single refresh when exiting standby.
* Kobo: Rewrite sysfs writes to use ANSI C via FFI instead of stdio via Lua, as it obscured some common error cases (e.g., EBUSY on /sys/power/state).
* Kobo: Simplify `suspend`, now that we have sane error handling in sysfs writes.
* Kobo.powerd: Change `isCharging` & `isAuxCharging` behavior to match the behavior of the NTX ioctl (i.e., Charging == Plugged-in). This has the added benefit of making the AutoSuspend checks behave sensibly in the "fully-charged but still plugged in" scenario (because being plugged in is enough to break PM on `!canPowerSaveWhileCharging` devices).
* AutoSuspend: Disable our `AllowStandby` handler when auto standby is disabled, so as to not interfere with other modules using `UIManager:allowStandby` (fix #9038).
* PowerD: Allow platforms to implement `isCharged`, indicating that the battery is full while still plugged in to a power source (battery icon becomes a power plug icon).
* Kobo.powerd: Implement `isCharged`, and kill charging LEDs once battery is full.
* Kindle.powerd: Implement `isCharged` on post-Wario devices. (`isCharging` is still true in that state, as it ought to).
reviewable/pr9058/r1
NiLuJe 2 years ago committed by GitHub
parent 7cac083db4
commit 86c35ad066
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -130,7 +130,7 @@ function ReaderCoptListener:rescheduleHeaderRefreshIfNeeded()
end end
end end
-- Schedule or stop scheluding on these events, as they may change what is shown: -- Schedule or stop scheduling on these events, as they may change what is shown:
ReaderCoptListener.onSetStatusLine = ReaderCoptListener.rescheduleHeaderRefreshIfNeeded ReaderCoptListener.onSetStatusLine = ReaderCoptListener.rescheduleHeaderRefreshIfNeeded
-- configurable.status_line is set before this event is triggered -- configurable.status_line is set before this event is triggered
ReaderCoptListener.onSetViewMode = ReaderCoptListener.rescheduleHeaderRefreshIfNeeded ReaderCoptListener.onSetViewMode = ReaderCoptListener.rescheduleHeaderRefreshIfNeeded
@ -145,11 +145,12 @@ function ReaderCoptListener:onResume()
end end
self:headerRefresh() self:headerRefresh()
self:rescheduleHeaderRefreshIfNeeded()
end end
function ReaderCoptListener:onLeaveStandby() function ReaderCoptListener:onLeaveStandby()
self:onResume() self:headerRefresh()
self:onOutOfScreenSaver() self:rescheduleHeaderRefreshIfNeeded()
end end
function ReaderCoptListener:onOutOfScreenSaver() function ReaderCoptListener:onOutOfScreenSaver()
@ -159,6 +160,7 @@ function ReaderCoptListener:onOutOfScreenSaver()
self._delayed_screensaver = nil self._delayed_screensaver = nil
self:headerRefresh() self:headerRefresh()
self:rescheduleHeaderRefreshIfNeeded()
end end
-- Unschedule on these events -- Unschedule on these events

@ -191,13 +191,13 @@ local footerTextGeneratorMap = {
batt_lvl = main_batt_lvl + aux_batt_lvl batt_lvl = main_batt_lvl + aux_batt_lvl
-- But average 'em to compute the icon... -- But average 'em to compute the icon...
if symbol_type == "icons" or symbol_type == "compact_items" then if symbol_type == "icons" or symbol_type == "compact_items" then
prefix = powerd:getBatterySymbol(is_charging, batt_lvl / 2) prefix = powerd:getBatterySymbol(powerd:isAuxCharged(), is_charging, batt_lvl / 2)
end end
else else
is_charging = powerd:isCharging() is_charging = powerd:isCharging()
batt_lvl = main_batt_lvl batt_lvl = main_batt_lvl
if symbol_type == "icons" or symbol_type == "compact_items" then if symbol_type == "icons" or symbol_type == "compact_items" then
prefix = powerd:getBatterySymbol(is_charging, main_batt_lvl) prefix = powerd:getBatterySymbol(powerd:isCharged(), is_charging, main_batt_lvl)
end end
end end
end end
@ -2448,8 +2448,8 @@ function ReaderFooter:onOutOfScreenSaver()
end end
function ReaderFooter:onLeaveStandby() function ReaderFooter:onLeaveStandby()
self:onResume() self:maybeUpdateFooter()
self:onOutOfScreenSaver() self:rescheduleFooterAutoRefreshIfNeeded()
end end
function ReaderFooter:onSuspend() function ReaderFooter:onSuspend()

@ -54,8 +54,12 @@ function BasePowerD:getAuxCapacityHW() return 0 end
function BasePowerD:isAuxBatteryConnectedHW() return false end function BasePowerD:isAuxBatteryConnectedHW() return false end
function BasePowerD:getDismissBatteryStatus() return self.battery_warning end function BasePowerD:getDismissBatteryStatus() return self.battery_warning end
function BasePowerD:setDismissBatteryStatus(status) self.battery_warning = status end function BasePowerD:setDismissBatteryStatus(status) self.battery_warning = status end
--- @note: Should ideally return true as long as the device is plugged in, even once the battery is full...
function BasePowerD:isChargingHW() return false end function BasePowerD:isChargingHW() return false end
--- @note: ...at which point this should start returning true (i.e., plugged in & fully charged).
function BasePowerD:isChargedHW() return false end
function BasePowerD:isAuxChargingHW() return false end function BasePowerD:isAuxChargingHW() return false end
function BasePowerD:isAuxChargedHW() return false end
function BasePowerD:frontlightIntensityHW() return 0 end function BasePowerD:frontlightIntensityHW() return 0 end
function BasePowerD:isFrontlightOnHW() return self.fl_intensity > self.fl_min end function BasePowerD:isFrontlightOnHW() return self.fl_intensity > self.fl_min end
function BasePowerD:turnOffFrontlightHW() self:setIntensityHW(self.fl_min) end function BasePowerD:turnOffFrontlightHW() self:setIntensityHW(self.fl_min) end
@ -231,6 +235,10 @@ function BasePowerD:isCharging()
return self:isChargingHW() return self:isChargingHW()
end end
function BasePowerD:isCharged()
return self:isChargedHW()
end
function BasePowerD:getAuxCapacity() function BasePowerD:getAuxCapacity()
local now_btv local now_btv
@ -261,6 +269,10 @@ function BasePowerD:isAuxCharging()
return self:isAuxChargingHW() return self:isAuxChargingHW()
end end
function BasePowerD:isAuxCharged()
return self:isAuxChargedHW()
end
function BasePowerD:isAuxBatteryConnected() function BasePowerD:isAuxBatteryConnected()
return self:isAuxBatteryConnectedHW() return self:isAuxBatteryConnectedHW()
end end
@ -274,8 +286,10 @@ function BasePowerD:stateChanged()
end end
-- Silly helper to avoid code duplication ;). -- Silly helper to avoid code duplication ;).
function BasePowerD:getBatterySymbol(is_charging, capacity) function BasePowerD:getBatterySymbol(is_charged, is_charging, capacity)
if is_charging then if is_charged then
return ""
elseif is_charging then
return "" return ""
else else
if capacity >= 100 then if capacity >= 100 then

@ -578,6 +578,57 @@ function Input:handleKeyBoardEv(ev)
end end
end end
-- Mangled variant of handleKeyBoardEv that will only handle power management related keys.
-- (Used when blocking input during suspend via sleep cover).
function Input:handlePowerManagementOnlyEv(ev)
local keycode = self.event_map[ev.code]
if not keycode then
-- Do not handle keypress for keys we don't know
return
end
-- We'll need to parse the synthetic event map, because SleepCover* events are synthetic.
if self.event_map_adapter[keycode] then
keycode = self.event_map_adapter[keycode](ev)
end
-- Power management synthetic events
if keycode == "SleepCoverClosed" or keycode == "SleepCoverOpened"
or keycode == "Suspend" or keycode == "Resume" then
return keycode
end
-- Fake events
if keycode == "IntoSS" or keycode == "OutOfSS"
or keycode == "UsbPlugIn" or keycode == "UsbPlugOut"
or keycode == "Charging" or keycode == "NotCharging" then
return keycode
end
if keycode == "Power" then
-- Kobo generates Power keycode only, we need to decide whether it's
-- power-on or power-off ourselves.
if ev.value == EVENT_VALUE_KEY_PRESS then
return "PowerPress"
elseif ev.value == EVENT_VALUE_KEY_RELEASE then
return "PowerRelease"
end
end
-- Nothing to see, move along!
return
end
-- Empty event handler used to send input to the void
function Input:voidEv(ev)
return
end
-- Generic event handler for unhandled input events
function Input:handleGenericEv(ev)
return Event:new("GenericInput", ev)
end
function Input:handleMiscEv(ev) function Input:handleMiscEv(ev)
-- should be handled by a misc event protocol plugin -- should be handled by a misc event protocol plugin
end end
@ -892,13 +943,13 @@ function Input:toggleMiscEvNTX(toggle)
elseif toggle == false then elseif toggle == false then
-- Ignore Gyro events -- Ignore Gyro events
if self.isNTXAccelHooked then if self.isNTXAccelHooked then
self.handleMiscEv = function() end self.handleMiscEv = self.voidEv
self.isNTXAccelHooked = false self.isNTXAccelHooked = false
end end
else else
-- Toggle it -- Toggle it
if self.isNTXAccelHooked then if self.isNTXAccelHooked then
self.handleMiscEv = function() end self.handleMiscEv = self.voidEv
else else
self.handleMiscEv = self.handleMiscEvNTX self.handleMiscEv = self.handleMiscEvNTX
end end
@ -1204,7 +1255,10 @@ function Input:waitEvent(now, deadline)
end end
else else
-- Received some other kind of event that we do not know how to specifically handle yet -- Received some other kind of event that we do not know how to specifically handle yet
table.insert(handled, Event:new("GenericInput", event)) local handled_ev = self:handleGenericEv(event)
if handled_ev then
table.insert(handled, handled_ev)
end
end end
end end
return handled return handled
@ -1220,4 +1274,64 @@ function Input:waitEvent(now, deadline)
end end
end end
-- Allow toggling the handling of most every kind of input, except for power management related events.
function Input:inhibitInput(toggle)
if toggle then
-- Only handle power management events
if not self._key_ev_handler then
logger.info("Inhibiting user input")
self._key_ev_handler = self.handleKeyBoardEv
self.handleKeyBoardEv = self.handlePowerManagementOnlyEv
end
-- And send everything else to the void
if not self._oasis_ev_handler then
self._oasis_ev_handler = self.handleOasisOrientationEv
self.handleOasisOrientationEv = self.voidEv
end
if not self._abs_ev_handler then
self._abs_ev_handler = self.handleTouchEv
self.handleTouchEv = self.voidEv
end
if not self._msc_ev_handler then
self._msc_ev_handler = self.handleMiscEv
self.handleMiscEv = self.voidEv
end
if not self._sdl_ev_handler then
self._sdl_ev_handler = self.handleSdlEv
self.handleSdlEv = self.voidEv
end
if not self._generic_ev_handler then
self._generic_ev_handler = self.handleGenericEv
self.handleGenericEv = self.voidEv
end
else
-- Restore event handlers, if any
if self._key_ev_handler then
logger.info("Restoring user input handling")
self.handleKeyBoardEv = self._key_ev_handler
self._key_ev_handler = nil
end
if self._oasis_ev_handler then
self.handleOasisOrientationEv = self._oasis_ev_handler
self._oasis_ev_handler = nil
end
if self._abs_ev_handler then
self.handleTouchEv = self._abs_ev_handler
self._abs_ev_handler = nil
end
if self._msc_ev_handler then
self.handleMiscEv = self._msc_ev_handler
self._msc_ev_handler = nil
end
if self._sdl_ev_handler then
self.handleSdlEv = self._sdl_ev_handler
self._sdl_ev_handler = nil
end
if self._generic_ev_handler then
self.handleGenericEv = self._generic_ev_handler
self._generic_ev_handler = nil
end
end
end
return Input return Input

@ -757,6 +757,7 @@ function KindleOasis2:init()
fl_intensity_file = "/sys/class/backlight/max77796-bl/brightness", fl_intensity_file = "/sys/class/backlight/max77796-bl/brightness",
batt_capacity_file = "/sys/class/power_supply/max77796-battery/capacity", batt_capacity_file = "/sys/class/power_supply/max77796-battery/capacity",
is_charging_file = "/sys/class/power_supply/max77796-charger/charging", is_charging_file = "/sys/class/power_supply/max77796-charger/charging",
batt_status_file = "/sys/class/power_supply/max77796-charger/status",
} }
self.input = require("device/input"):new{ self.input = require("device/input"):new{
@ -832,6 +833,7 @@ function KindleOasis3:init()
warmth_intensity_file = "/sys/class/backlight/lm3697-bl0/brightness", warmth_intensity_file = "/sys/class/backlight/lm3697-bl0/brightness",
batt_capacity_file = "/sys/class/power_supply/max77796-battery/capacity", batt_capacity_file = "/sys/class/power_supply/max77796-battery/capacity",
is_charging_file = "/sys/class/power_supply/max77796-charger/charging", is_charging_file = "/sys/class/power_supply/max77796-charger/charging",
batt_status_file = "/sys/class/power_supply/max77796-charger/status",
} }
self.input = require("device/input"):new{ self.input = require("device/input"):new{
@ -845,13 +847,7 @@ function KindleOasis3:init()
} }
--- @fixme When starting KOReader with the device upside down ("D"), touch input is registered wrong --- @fixme The same quirks as on the Oasis 2 apply ;).
-- (i.e., probably upside down).
-- If it's started upright ("U"), everything's okay, and turning it upside down after that works just fine.
-- See #2206 & #2209 for the original KOA implementation, which obviously doesn't quite cut it here...
-- See also <https://www.mobileread.com/forums/showthread.php?t=298302&page=5>
-- NOTE: It'd take some effort to actually start KOReader while in a LANDSCAPE orientation,
-- since they're only exposed inside the stock reader, and not the Home/KUAL Booklets.
local haslipc, lipc = pcall(require, "liblipclua") local haslipc, lipc = pcall(require, "liblipclua")
if haslipc and lipc then if haslipc and lipc then
local lipc_handle = lipc.init("com.github.koreader.screen") local lipc_handle = lipc.init("com.github.koreader.screen")
@ -906,6 +902,7 @@ function KindleBasic2:init()
device = self, device = self,
batt_capacity_file = "/sys/class/power_supply/bd7181x_bat/capacity", batt_capacity_file = "/sys/class/power_supply/bd7181x_bat/capacity",
is_charging_file = "/sys/class/power_supply/bd7181x_bat/charging", is_charging_file = "/sys/class/power_supply/bd7181x_bat/charging",
batt_status_file = "/sys/class/power_supply/bd7181x_bat/status",
} }
Kindle.init(self) Kindle.init(self)
@ -921,6 +918,7 @@ function KindlePaperWhite4:init()
fl_intensity_file = "/sys/class/backlight/bl/brightness", fl_intensity_file = "/sys/class/backlight/bl/brightness",
batt_capacity_file = "/sys/class/power_supply/bd71827_bat/capacity", batt_capacity_file = "/sys/class/power_supply/bd71827_bat/capacity",
is_charging_file = "/sys/class/power_supply/bd71827_bat/charging", is_charging_file = "/sys/class/power_supply/bd71827_bat/charging",
batt_status_file = "/sys/class/power_supply/bd71827_bat/status",
} }
Kindle.init(self) Kindle.init(self)
@ -946,6 +944,7 @@ function KindleBasic3:init()
fl_intensity_file = "/sys/class/backlight/bl/brightness", fl_intensity_file = "/sys/class/backlight/bl/brightness",
batt_capacity_file = "/sys/class/power_supply/bd71827_bat/capacity", batt_capacity_file = "/sys/class/power_supply/bd71827_bat/capacity",
is_charging_file = "/sys/class/power_supply/bd71827_bat/charging", is_charging_file = "/sys/class/power_supply/bd71827_bat/charging",
batt_status_file = "/sys/class/power_supply/bd71827_bat/status",
} }
Kindle.init(self) Kindle.init(self)
@ -963,6 +962,7 @@ function KindlePaperWhite5:init()
warmth_intensity_file = "/sys/class/backlight/fp9966-bl0/brightness", warmth_intensity_file = "/sys/class/backlight/fp9966-bl0/brightness",
batt_capacity_file = "/sys/class/power_supply/bd71827_bat/capacity", batt_capacity_file = "/sys/class/power_supply/bd71827_bat/capacity",
is_charging_file = "/sys/class/power_supply/bd71827_bat/charging", is_charging_file = "/sys/class/power_supply/bd71827_bat/charging",
batt_status_file = "/sys/class/power_supply/bd71827_bat/status",
} }
-- Enable the so-called "fast" mode, so as to prevent the driver from silently promoting refreshes to REAGL. -- Enable the so-called "fast" mode, so as to prevent the driver from silently promoting refreshes to REAGL.

@ -148,6 +148,15 @@ function KindlePowerD:isChargingHW()
return is_charging == 1 return is_charging == 1
end end
function KindlePowerD:isChargedHW()
-- Older kernels don't necessarily have this...
if self.batt_status_file then
return self:read_str_file(self.batt_status_file) == "Full"
end
return false
end
function KindlePowerD:_readFLIntensity() function KindlePowerD:_readFLIntensity()
return self:read_int_file(self.fl_intensity_file) return self:read_int_file(self.fl_intensity_file)
end end

@ -43,17 +43,22 @@ local function checkStandby()
end end
local function writeToSys(val, file) local function writeToSys(val, file)
local f = io.open(file, "we") -- NOTE: We do things by hand via ffi, because io.write uses fwrite,
if not f then -- which isn't a great fit for procfs/sysfs (e.g., we lose failure cases like EBUSY,
logger.err("Cannot open:", file) -- as it only reports failures to write to the *stream*, not to the disk/file!).
local fd = C.open(file, bit.bor(C.O_WRONLY, C.O_CLOEXEC)) -- procfs/sysfs, we shouldn't need O_TRUNC
if fd == -1 then
logger.err("Cannot open file `" .. file .. "`:", ffi.string(C.strerror(ffi.errno())))
return return
end end
local re, err_msg, err_code = f:write(val, "\n") local bytes = #val + 1 -- + LF
if not re then local nw = C.write(fd, val .. "\n", bytes)
logger.err("Error writing value to file:", val, file, err_msg, err_code) if nw == -1 then
logger.err("Cannot write `" .. val .. "` to file `" .. file .. "`:", ffi.string(C.strerror(ffi.errno())))
end end
f:close() C.close(fd)
return re -- NOTE: Allows the caller to possibly handle short writes (not that these should ever happen here).
return nw == bytes
end end
local Kobo = Generic:new{ local Kobo = Generic:new{
@ -424,9 +429,9 @@ local KoboIo = Kobo:new{
function Kobo:setupChargingLED() function Kobo:setupChargingLED()
if G_reader_settings:nilOrTrue("enable_charging_led") then if G_reader_settings:nilOrTrue("enable_charging_led") then
if self:hasAuxBattery() and self.powerd:isAuxBatteryConnected() then if self:hasAuxBattery() and self.powerd:isAuxBatteryConnected() then
self:toggleChargingLED(self.powerd:isAuxCharging()) self:toggleChargingLED(self.powerd:isAuxCharging() and not self.powerd:isAuxCharged())
else else
self:toggleChargingLED(self.powerd:isCharging()) self:toggleChargingLED(self.powerd:isCharging() and not self.powerd:isCharged())
end end
end end
end end
@ -618,7 +623,7 @@ function Kobo:setDateTime(year, month, day, hour, min, sec)
command = string.format("date -s '%d:%d'",hour, min) command = string.format("date -s '%d:%d'",hour, min)
end end
if os.execute(command) == 0 then if os.execute(command) == 0 then
os.execute('hwclock -u -w') os.execute("hwclock -u -w")
return true return true
else else
return false return false
@ -727,7 +732,7 @@ function Kobo:initEventAdjustHooks()
end end
end end
function Kobo:getCodeName() local function getCodeName()
-- Try to get it from the env first -- Try to get it from the env first
local codename = os.getenv("PRODUCT") local codename = os.getenv("PRODUCT")
-- If that fails, run the script ourselves -- If that fails, run the script ourselves
@ -776,23 +781,21 @@ end
function Kobo:checkUnexpectedWakeup() function Kobo:checkUnexpectedWakeup()
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
-- just in case other events like SleepCoverClosed also scheduled a suspend -- Just in case another event like SleepCoverClosed also scheduled a suspend
UIManager:unschedule(Kobo.suspend) UIManager:unschedule(self.suspend)
-- Do an initial validation to discriminate unscheduled wakeups happening *outside* of the alarm proximity window. -- The proximity window is rather large, because we're scheduled to run 15 seconds after resuming,
if WakeupMgr:isWakeupAlarmScheduled() and WakeupMgr:validateWakeupAlarmByProximity() then -- so we're already guaranteed to be at least 15s away from the alarm ;).
logger.info("Kobo suspend: scheduled wakeup.") if self.wakeup_mgr:isWakeupAlarmScheduled() and self.wakeup_mgr:wakeupAction(30) then
local res = WakeupMgr:wakeupAction() -- Assume we want to go back to sleep after running the scheduled action
if not res then -- (Kobo:resume will unschedule this on an user-triggered resume).
logger.err("Kobo suspend: wakeup action failed.") logger.info("Kobo suspend: scheduled wakeup; the device will go back to sleep in 30s.")
end -- We need significant leeway for the poweroff action to send out close events to all requisite widgets,
logger.info("Kobo suspend: putting device back to sleep.") -- since we don't actually want to suspend behind its back ;).
-- Most wakeup actions are linear, but we need some leeway for the UIManager:scheduleIn(30, self.suspend, self)
-- poweroff action to send out close events to all requisite widgets.
UIManager:scheduleIn(30, Kobo.suspend, self)
else else
logger.dbg("Kobo suspend: checking unexpected wakeup:", -- We've hit an early resume, assume this is unexpected (as we only run if Kobo:resume hasn't already).
self.unexpected_wakeup_count) logger.dbg("Kobo suspend: checking unexpected wakeup number", self.unexpected_wakeup_count)
if self.unexpected_wakeup_count == 0 or self.unexpected_wakeup_count > 20 then if self.unexpected_wakeup_count == 0 or self.unexpected_wakeup_count > 20 then
-- Don't put device back to sleep under the following two cases: -- Don't put device back to sleep under the following two cases:
-- 1. a resume event triggered Kobo:resume() function -- 1. a resume event triggered Kobo:resume() function
@ -803,9 +806,8 @@ function Kobo:checkUnexpectedWakeup()
return return
end end
logger.err("Kobo suspend: putting device back to sleep. Unexpected wakeups:", logger.err("Kobo suspend: putting device back to sleep after", self.unexpected_wakeup_count, "unexpected wakeups.")
self.unexpected_wakeup_count) self:suspend()
Kobo:suspend()
end end
end end
@ -814,26 +816,43 @@ function Kobo:getUnexpectedWakeup() return self.unexpected_wakeup_count end
--- The function to put the device into standby, with enabled touchscreen. --- The function to put the device into standby, with enabled touchscreen.
-- max_duration ... maximum time for the next standby, can wake earlier (e.g. Tap, Button ...) -- max_duration ... maximum time for the next standby, can wake earlier (e.g. Tap, Button ...)
function Kobo:standby(max_duration) function Kobo:standby(max_duration)
-- just for wake up, dummy function -- We don't really have anything to schedule, we just need an alarm out of WakeupMgr ;).
local function dummy() end local function standby_alarm()
end
if max_duration then if max_duration then
self.wakeup_mgr:addTask(max_duration, dummy) self.wakeup_mgr:addTask(max_duration, standby_alarm)
end end
local TimeVal = require("ui/timeval") local TimeVal = require("ui/timeval")
logger.info("Kobo standby: asking to enter standby . . .")
local standby_time_tv = TimeVal:boottime_or_realtime_coarse() local standby_time_tv = TimeVal:boottime_or_realtime_coarse()
logger.info("Kobo suspend: asking to enter standby . . .")
local ret = writeToSys("standby", "/sys/power/state") local ret = writeToSys("standby", "/sys/power/state")
self.last_standby_tv = TimeVal:boottime_or_realtime_coarse() - standby_time_tv self.last_standby_tv = TimeVal:boottime_or_realtime_coarse() - standby_time_tv
self.total_standby_tv = self.total_standby_tv + self.last_standby_tv self.total_standby_tv = self.total_standby_tv + self.last_standby_tv
logger.info("Kobo suspend: zZz zZz zZz zZz? Write syscall returned: ", ret) if ret then
logger.info("Kobo standby: zZz zZz zZz zZz... And woke up!")
else
logger.warn("Kobo standby: the kernel refused to enter standby!")
end
if max_duration then if max_duration then
self.wakeup_mgr:removeTask(nil, nil, dummy) -- NOTE: We don't actually care about discriminating exactly *why* we woke up,
-- and our scheduled wakeup action is a NOP anyway,
-- so we can just drop the task instead of doing things the right way like suspend ;).
-- This saves us some pointless RTC shenanigans, so, everybody wins.
--[[
-- There's no scheduling shenanigans like in suspend, so the proximity window can be much tighter...
if self.wakeup_mgr:isWakeupAlarmScheduled() and self.wakeup_mgr:wakeupAction(5) then
-- We tripped the standby alarm, UIManager will be able to run whatever was actually scheduled,
-- and AutoSuspend will handle going back to standby if necessary.
logger.dbg("Kobo standby: tripped rtc wake alarm")
end
--]]
self.wakeup_mgr:removeTasks(nil, standby_alarm)
end end
end end
@ -841,7 +860,6 @@ function Kobo:suspend()
logger.info("Kobo suspend: going to sleep . . .") logger.info("Kobo suspend: going to sleep . . .")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
UIManager:unschedule(self.checkUnexpectedWakeup) UIManager:unschedule(self.checkUnexpectedWakeup)
local f, re, err_msg, err_code
-- NOTE: Sleep as little as possible here, sleeping has a tendency to make -- NOTE: Sleep as little as possible here, sleeping has a tendency to make
-- everything mysteriously hang... -- everything mysteriously hang...
@ -854,6 +872,7 @@ function Kobo:suspend()
-- So, unless that changes, unconditionally disable it. -- So, unless that changes, unconditionally disable it.
--[[ --[[
local f, re, err_msg, err_code
local has_wakeup_count = false local has_wakeup_count = false
f = io.open("/sys/power/wakeup_count", "re") f = io.open("/sys/power/wakeup_count", "re")
if f ~= nil then if f ~= nil then
@ -873,10 +892,14 @@ function Kobo:suspend()
-]] -]]
-- NOTE: Sets gSleep_Mode_Suspend to 1. Used as a flag throughout the -- NOTE: Sets gSleep_Mode_Suspend to 1. Used as a flag throughout the
-- kernel to suspend/resume various subsystems -- kernel to suspend/resume various subsystems
-- c.f., state_extended_store @ kernel/power/main.c -- c.f., state_extended_store @ kernel/power/main.c
local ret = writeToSys("1", "/sys/power/state-extended") local ret = writeToSys("1", "/sys/power/state-extended")
logger.info("Kobo suspend: asked the kernel to put subsystems to sleep, ret:", ret) if ret then
logger.info("Kobo suspend: successfully asked the kernel to put subsystems to sleep")
else
logger.warn("Kobo suspend: the kernel refused to flag subsystems for suspend!")
end
util.sleep(2) util.sleep(2)
logger.info("Kobo suspend: waited for 2s because of reasons...") logger.info("Kobo suspend: waited for 2s because of reasons...")
@ -902,90 +925,82 @@ function Kobo:suspend()
end end
--]] --]]
logger.info("Kobo suspend: asking for a suspend to RAM . . .")
f = io.open("/sys/power/state", "we")
if not f then
-- Reset state-extended back to 0 since we are giving up.
local ext_fd = io.open("/sys/power/state-extended", "we")
if not ext_fd then
logger.err("cannot open /sys/power/state-extended for writing!")
else
ext_fd:write("0\n")
ext_fd:close()
end
return false
end
local TimeVal = require("ui/timeval") local TimeVal = require("ui/timeval")
logger.info("Kobo suspend: asking for a suspend to RAM . . .")
local suspend_time_tv = TimeVal:boottime_or_realtime_coarse() local suspend_time_tv = TimeVal:boottime_or_realtime_coarse()
re, err_msg, err_code = f:write("mem\n") ret = writeToSys("mem", "/sys/power/state")
if not re then
logger.err("write error: ", err_msg, err_code)
end
f:close()
-- NOTE: At this point, we *should* be in suspend to RAM, as such, -- NOTE: At this point, we *should* be in suspend to RAM, as such,
-- execution should only resume on wakeup... -- execution should only resume on wakeup...
self.last_suspend_tv = TimeVal:boottime_or_realtime_coarse() - suspend_time_tv self.last_suspend_tv = TimeVal:boottime_or_realtime_coarse() - suspend_time_tv
self.total_suspend_tv = self.total_suspend_tv + self.last_suspend_tv self.total_suspend_tv = self.total_suspend_tv + self.last_suspend_tv
logger.info("Kobo suspend: ZzZ ZzZ ZzZ? Write syscall returned: ", re) if ret then
-- NOTE: Ideally, we'd need a way to warn the user that suspending logger.info("Kobo suspend: ZzZ ZzZ ZzZ... And woke up!")
-- gloriously failed at this point... else
-- We can safely assume that just from a non-zero return code, without logger.warn("Kobo suspend: the kernel refused to enter suspend!")
-- looking at the detailed stderr message -- Reset state-extended back to 0 since we are giving up.
-- (most of the failures we'll see are -EBUSY anyway) writeToSys("0", "/sys/power/state-extended")
-- For reference, when that happens to nickel, it appears to keep retrying end
-- to wakeup & sleep ad nauseam,
-- which is where the non-sensical 1 -> mem -> 0 loop idea comes from...
-- cf. nickel_suspend_strace.txt for more details.
logger.info("Kobo suspend: woke up!") -- NOTE: Ideally, we'd need a way to warn the user that suspending
-- gloriously failed at this point...
-- We can safely assume that just from a non-zero return code, without
-- looking at the detailed stderr message
-- (most of the failures we'll see are -EBUSY anyway)
-- For reference, when that happens to nickel, it appears to keep retrying
-- to wakeup & sleep ad nauseam,
-- which is where the non-sensical 1 -> mem -> 0 loop idea comes from...
-- cf. nickel_suspend_strace.txt for more details.
--[[ --[[
if has_wakeup_count then if has_wakeup_count then
logger.info("wakeup count: $(cat /sys/power/wakeup_count)") logger.info("wakeup count: $(cat /sys/power/wakeup_count)")
end end
-- Print tke kernel log since our attempt to sleep... -- Print tke kernel log since our attempt to sleep...
--dmesg -c --dmesg -c
--]] --]]
-- NOTE: We unflag /sys/power/state-extended in Kobo:resume() to keep -- NOTE: We unflag /sys/power/state-extended in Kobo:resume() to keep
-- things tidy and easier to follow -- things tidy and easier to follow
-- Kobo:resume() will reset unexpected_wakeup_count = 0 to signal an -- Kobo:resume() will reset unexpected_wakeup_count = 0 to signal an
-- expected wakeup, which gets checked in checkUnexpectedWakeup(). -- expected wakeup, which gets checked in checkUnexpectedWakeup().
self.unexpected_wakeup_count = self.unexpected_wakeup_count + 1 self.unexpected_wakeup_count = self.unexpected_wakeup_count + 1
-- assuming Kobo:resume() will be called in 15 seconds -- We're assuming Kobo:resume() will be called in the next 15 seconds in ordrer to cancel that check.
logger.dbg("Kobo suspend: scheduling unexpected wakeup guard") logger.dbg("Kobo suspend: scheduling unexpected wakeup guard")
UIManager:scheduleIn(15, self.checkUnexpectedWakeup, self) UIManager:scheduleIn(15, self.checkUnexpectedWakeup, self)
end end
function Kobo:resume() function Kobo:resume()
logger.info("Kobo resume: clean up after wakeup") logger.info("Kobo resume: clean up after wakeup")
-- reset unexpected_wakeup_count ASAP -- Reset unexpected_wakeup_count ASAP
self.unexpected_wakeup_count = 0 self.unexpected_wakeup_count = 0
require("ui/uimanager"):unschedule(self.checkUnexpectedWakeup) -- Unschedule the checkUnexpectedWakeup shenanigans.
local UIManager = require("ui/uimanager")
UIManager:unschedule(self.checkUnexpectedWakeup)
UIManager:unschedule(self.suspend)
-- Now that we're up, unflag subsystems for suspend... -- Now that we're up, unflag subsystems for suspend...
-- NOTE: Sets gSleep_Mode_Suspend to 0. Used as a flag throughout the -- NOTE: Sets gSleep_Mode_Suspend to 0. Used as a flag throughout the
-- kernel to suspend/resume various subsystems -- kernel to suspend/resume various subsystems
-- cf. kernel/power/main.c @ L#207 -- cf. kernel/power/main.c @ L#207
-- Among other things, this sets up the wakeup pins (e.g., resume on input).
local ret = writeToSys("0", "/sys/power/state-extended") local ret = writeToSys("0", "/sys/power/state-extended")
logger.info("Kobo resume: unflagged kernel subsystems for resume, ret:", ret) if ret then
logger.info("Kobo resume: successfully asked the kernel to resume subsystems")
else
logger.warn("Kobo resume: the kernel refused to flag subsystems for resume!")
end
-- HACK: wait a bit (0.1 sec) for the kernel to catch up -- HACK: wait a bit (0.1 sec) for the kernel to catch up
util.usleep(100000) util.usleep(100000)
if self.hasIRGrid then if self.hasIRGrid then
-- cf. #1862, I can reliably break IR touch input on resume... -- cf. #1862, I can reliably break IR touch input on resume...
-- cf. also #1943 for the rationale behind applying this workaorund in every case... -- cf. also #1943 for the rationale behind applying this workaround in every case...
writeToSys("a", "/sys/devices/virtual/input/input1/neocmd") writeToSys("a", "/sys/devices/virtual/input/input1/neocmd")
end end
@ -1000,7 +1015,7 @@ end
function Kobo:powerOff() function Kobo:powerOff()
-- Much like Nickel itself, disable the RTC alarm before powering down. -- Much like Nickel itself, disable the RTC alarm before powering down.
WakeupMgr:unsetWakeupAlarm() self.wakeup_mgr:unsetWakeupAlarm()
-- Then shut down without init's help -- Then shut down without init's help
os.execute("poweroff -f") os.execute("poweroff -f")
@ -1141,7 +1156,7 @@ end
-------------- device probe ------------ -------------- device probe ------------
local codename = Kobo:getCodeName() local codename = getCodeName()
local product_id = getProductId() local product_id = getProductId()
if codename == "dahlia" then if codename == "dahlia" then

@ -114,11 +114,14 @@ function KoboPowerD:init()
end end
self.isAuxChargingHW = function(this) self.isAuxChargingHW = function(this)
-- 0 when not charging -- 0 when discharging
-- 3 when full -- 3 when full
-- 2 when charging via DCP -- 2 when charging via DCP
local charge_status = this:read_int_file(this.aux_batt_charging_file) return this:read_int_file(this.aux_batt_charging_file) ~= 0
return charge_status ~= 0 and charge_status ~= 3 end
self.isAuxChargedHW = function(this)
return this:read_int_file(this.aux_batt_charging_file) == 3
end end
end end
@ -274,8 +277,24 @@ function KoboPowerD:getCapacityHW()
return self:read_int_file(self.batt_capacity_file) return self:read_int_file(self.batt_capacity_file)
end end
-- NOTE: Match the behavior of the NXP ntx_io _Is_USB_plugged ioctl!
-- (Otherwise, a device that is fully charged, but still plugged in will no longer be flagged as charging).
function KoboPowerD:isChargingHW() function KoboPowerD:isChargingHW()
return self:read_str_file(self.is_charging_file) == "Charging" return self:read_str_file(self.is_charging_file) ~= "Discharging"
end
function KoboPowerD:isChargedHW()
-- On sunxi, the proper "Full" status is reported, while older kernels (even Mk. 9) report "Not charging"
-- c.f., POWER_SUPPLY_PROP_STATUS in ricoh61x_batt_get_prop @ drivers/power/ricoh619-battery.c
-- (or drivers/power/supply/ricoh619-battery.c on newer kernels).
local status = self:read_str_file(self.is_charging_file)
if status == "Full" then
return true
elseif status == "Not charging" and self:getCapacity() == 100 then
return true
end
return false
end end
function KoboPowerD:turnOffFrontlightHW() function KoboPowerD:turnOffFrontlightHW()

@ -52,7 +52,9 @@ I'm not sure if the distinction between maintenance and sync makes sense
but it's wifi on vs. off. but it's wifi on vs. off.
--]] --]]
function WakeupMgr:addTask(seconds_from_now, callback) function WakeupMgr:addTask(seconds_from_now, callback)
if not type(seconds_from_now) == "number" and not type(callback) == "function" then return end -- Make sure we passed valid input, so that stuff doesn't break in fun and interesting ways (especially in removeTasks).
assert(type(seconds_from_now) == "number", "delay is not a number")
assert(type(callback) == "function", "callback is not a function")
local epoch = RTC:secondsFromNowToEpoch(seconds_from_now) local epoch = RTC:secondsFromNowToEpoch(seconds_from_now)
logger.info("WakeupMgr: scheduling wakeup in", seconds_from_now) logger.info("WakeupMgr: scheduling wakeup in", seconds_from_now)
@ -68,36 +70,67 @@ function WakeupMgr:addTask(seconds_from_now, callback)
table.sort(self._task_queue, function(a, b) return a.epoch < b.epoch end) table.sort(self._task_queue, function(a, b) return a.epoch < b.epoch end)
local new_upcoming_task = self._task_queue[1].epoch local new_upcoming_task = self._task_queue[1].epoch
if not old_upcoming_task or (new_upcoming_task < old_upcoming_task) then if not old_upcoming_task or (new_upcoming_task < old_upcoming_task) then
self:setWakeupAlarm(self._task_queue[1].epoch) self:setWakeupAlarm(new_upcoming_task)
end end
end end
--[[-- --[[--
Remove task from queue. Remove task(s) from queue.
This method removes a task by either index, scheduled time or callback. This method removes one or more tasks by either scheduled time or callback.
If any tasks are left on exit, the upcoming one will automatically be scheduled (if necessary).
@int idx Task queue index. Mainly useful within this module.
@int epoch The epoch for when this task is scheduled to wake up. @int epoch The epoch for when this task is scheduled to wake up.
Normally the preferred method for outside callers. Normally the preferred method for outside callers.
@int callback A scheduled callback function. Store a reference for use @int callback A scheduled callback function. Store a reference for use
with anonymous functions. with anonymous functions.
@treturn bool (true if one or more tasks were removed; false otherwise; nil if the task queue is empty).
--]] --]]
function WakeupMgr:removeTask(idx, epoch, callback) function WakeupMgr:removeTasks(epoch, callback)
if not type(idx) == "number"
and not type(epoch) == "number"
and not type(callback) == "function" then return end
if #self._task_queue < 1 then return end if #self._task_queue < 1 then return end
for k, v in ipairs(self._task_queue) do local removed = false
if k == idx or epoch == v.epoch or callback == v.callback then local reschedule = false
for k = #self._task_queue, 1, -1 do
local v = self._task_queue[k]
if epoch == v.epoch or callback == v.callback then
table.remove(self._task_queue, k) table.remove(self._task_queue, k)
return true removed = true
-- If we've successfuly pop'ed the upcoming task, we need to schedule the next one (if any) on exit.
if k == 1 then
reschedule = true
end
end end
end end
-- Schedule the next wakeup action, if any (and if necessary).
if reschedule and self._task_queue[1] then
self:setWakeupAlarm(self._task_queue[1].epoch)
end
return removed
end
--[[--
Variant of @{removeTasks} that will only remove a single task, identified by its task queue index.
@int idx Task queue index. Mainly useful within this module.
@treturn bool (true if a task was removed; false otherwise).
--]]
function WakeupMgr:removeTask(idx)
local removed = false
-- We don't want to keep the pop'ed entry around, we just want to know if we pop'ed something.
if table.remove(self._task_queue, idx) then
removed = true
end
-- Schedule the next wakeup action, if any (and if necessary).
if removed and idx == 1 and self._task_queue[1] then
self:setWakeupAlarm(self._task_queue[1].epoch)
end
return removed
end end
--[[-- --[[--
@ -106,25 +139,28 @@ Execute wakeup action.
This method should be called by the device resume logic in case of a scheduled wakeup. This method should be called by the device resume logic in case of a scheduled wakeup.
It checks if the wakeup was scheduled by us using @{validateWakeupAlarmByProximity}, It checks if the wakeup was scheduled by us using @{validateWakeupAlarmByProximity},
executes the task, and schedules the next wakeup if any. in which case the task is executed.
If necessary, the next upcoming task (if any) is scheduled on exit.
@treturn bool @int proximity Proximity window to the scheduled wakeup (passed to @{validateWakeupAlarmByProximity}).
@treturn bool (true if we were truly woken up by the scheduled wakeup; false otherwise; nil if there weren't any tasks scheduled).
--]] --]]
function WakeupMgr:wakeupAction() function WakeupMgr:wakeupAction(proximity)
if #self._task_queue > 0 then if #self._task_queue > 0 then
local task = self._task_queue[1] local task = self._task_queue[1]
if self:validateWakeupAlarmByProximity(task.epoch) then if self:validateWakeupAlarmByProximity(task.epoch, proximity) then
task.callback() task.callback()
-- NOTE: removeTask will take care of scheduling the next upcoming task, if necessary.
self:removeTask(1) self:removeTask(1)
if self._task_queue[1] then
-- Set next scheduled wakeup, if any.
self:setWakeupAlarm(self._task_queue[1].epoch)
end
return true return true
else
return false
end end
return false
end end
return nil
end end
--[[-- --[[--

@ -84,6 +84,12 @@ end
-- Everything below is to handle auto_disable_wifi ;). -- Everything below is to handle auto_disable_wifi ;).
local default_network_timeout_seconds = 5*60 local default_network_timeout_seconds = 5*60
local max_network_timeout_seconds = 30*60 local max_network_timeout_seconds = 30*60
-- If autostandby is enabled, shorten the timeouts
local auto_standby = G_reader_settings:readSetting("auto_standby_timeout_seconds", -1)
if auto_standby > 0 then
default_network_timeout_seconds = default_network_timeout_seconds / 2
max_network_timeout_seconds = max_network_timeout_seconds / 2
end
-- This should be more than enough to catch actual activity vs. noise spread over 5 minutes. -- This should be more than enough to catch actual activity vs. noise spread over 5 minutes.
local network_activity_noise_margin = 12 -- unscaled_size_check: ignore local network_activity_noise_margin = 12 -- unscaled_size_check: ignore

@ -123,20 +123,12 @@ function UIManager:init()
-- suspend. So let's unschedule it when suspending, and restart it after -- suspend. So let's unschedule it when suspending, and restart it after
-- resume. Done via the plugin's onSuspend/onResume handlers. -- resume. Done via the plugin's onSuspend/onResume handlers.
self.event_handlers["Suspend"] = function() self.event_handlers["Suspend"] = function()
-- Ignore the accelerometer (if that's not already the case) while we're alseep
if G_reader_settings:nilOrFalse("input_ignore_gsensor") then
Device:toggleGSensor(false)
end
self:_beforeSuspend() self:_beforeSuspend()
Device:onPowerEvent("Suspend") Device:onPowerEvent("Suspend")
end end
self.event_handlers["Resume"] = function() self.event_handlers["Resume"] = function()
Device:onPowerEvent("Resume") Device:onPowerEvent("Resume")
self:_afterResume() self:_afterResume()
-- Stop ignoring the accelerometer (unless requested) when we wakeup
if G_reader_settings:nilOrFalse("input_ignore_gsensor") then
Device:toggleGSensor(true)
end
end end
self.event_handlers["PowerPress"] = function() self.event_handlers["PowerPress"] = function()
-- Always schedule power off. -- Always schedule power off.
@ -1179,14 +1171,24 @@ function UIManager:broadcastEvent(event)
end end
end end
--[[
function UIManager:getNextTaskTimes(count) function UIManager:getNextTaskTimes(count)
count = count or 1 count = count or 1
local times = {} local times = {}
for i = 1, math.min(count, #self._task_queue) do for i = 1, math.min(count, #self._task_queue) do
times[i] = UIManager._task_queue[i].time - TimeVal:now() times[i] = self._task_queue[i].time - TimeVal:now()
end end
return times return times
end end
--]]
function UIManager:getNextTaskTime()
if #self._task_queue > 0 then
return self._task_queue[1].time - TimeVal:now()
else
return nil
end
end
function UIManager:_checkTasks() function UIManager:_checkTasks()
self._now = TimeVal:now() self._now = TimeVal:now()
@ -1771,6 +1773,9 @@ function UIManager:_beforeSuspend()
self:flushSettings() self:flushSettings()
self:broadcastEvent(Event:new("Suspend")) 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...) -- Disable key repeat to avoid useless chatter (especially where Sleep Covers are concerned...)
Device:disableKeyRepeat() Device:disableKeyRepeat()
@ -1783,6 +1788,9 @@ function UIManager:_afterResume()
-- Restore key repeat -- Restore key repeat
Device:restoreKeyRepeat() Device:restoreKeyRepeat()
-- Restore full input handling
Input:inhibitInput(false)
self:broadcastEvent(Event:new("Resume")) self:broadcastEvent(Event:new("Resume"))
end end

@ -697,12 +697,12 @@ function TouchMenu:updateItems()
local powerd = Device:getPowerDevice() local powerd = Device:getPowerDevice()
if Device:hasBattery() then if Device:hasBattery() then
local batt_lvl = powerd:getCapacity() local batt_lvl = powerd:getCapacity()
local batt_symbol = powerd:getBatterySymbol(powerd:isCharging(), batt_lvl) local batt_symbol = powerd:getBatterySymbol(powerd:isCharged(), powerd:isCharging(), batt_lvl)
time_info_txt = BD.wrap(time_info_txt) .. " " .. BD.wrap("") .. BD.wrap(batt_symbol) .. BD.wrap(batt_lvl .. "%") time_info_txt = BD.wrap(time_info_txt) .. " " .. BD.wrap("") .. BD.wrap(batt_symbol) .. BD.wrap(batt_lvl .. "%")
if Device:hasAuxBattery() and powerd:isAuxBatteryConnected() then if Device:hasAuxBattery() and powerd:isAuxBatteryConnected() then
local aux_batt_lvl = powerd:getAuxCapacity() local aux_batt_lvl = powerd:getAuxCapacity()
local aux_batt_symbol = powerd:getBatterySymbol(powerd:isAuxCharging(), aux_batt_lvl) local aux_batt_symbol = powerd:getBatterySymbol(powerd:isAuxCharged(), powerd:isAuxCharging(), aux_batt_lvl)
time_info_txt = time_info_txt .. " " .. BD.wrap("+") .. BD.wrap(aux_batt_symbol) .. BD.wrap(aux_batt_lvl .. "%") time_info_txt = time_info_txt .. " " .. BD.wrap("+") .. BD.wrap(aux_batt_symbol) .. BD.wrap(aux_batt_lvl .. "%")
end end
end end

@ -53,8 +53,8 @@ function AutoSuspend:_enabledShutdown()
end end
function AutoSuspend:_schedule(shutdown_only) function AutoSuspend:_schedule(shutdown_only)
if not self:_enabled() and Device:canPowerOff() and not self:_enabledShutdown() then if not self:_enabled() and not self:_enabledShutdown() then
logger.dbg("AutoSuspend:_schedule is disabled") logger.dbg("AutoSuspend: suspend/shutdown timer is disabled")
return return
end end
@ -126,16 +126,15 @@ end
function AutoSuspend:init() function AutoSuspend:init()
logger.dbg("AutoSuspend: init") logger.dbg("AutoSuspend: init")
if Device:isPocketBook() and not Device:canSuspend() then return end
self.autoshutdown_timeout_seconds = G_reader_settings:readSetting("autoshutdown_timeout_seconds", self.autoshutdown_timeout_seconds = G_reader_settings:readSetting("autoshutdown_timeout_seconds",
default_autoshutdown_timeout_seconds) default_autoshutdown_timeout_seconds)
self.auto_suspend_timeout_seconds = G_reader_settings:readSetting("auto_suspend_timeout_seconds", self.auto_suspend_timeout_seconds = G_reader_settings:readSetting("auto_suspend_timeout_seconds",
default_auto_suspend_timeout_seconds) default_auto_suspend_timeout_seconds)
-- Disabled, until the user opts in. -- Disabled, until the user opts in.
self.auto_standby_timeout_seconds = G_reader_settings:readSetting("auto_standby_timeout_seconds", -1) self.auto_standby_timeout_seconds = G_reader_settings:readSetting("auto_standby_timeout_seconds", -1)
if Device:isPocketBook() and not Device:canSuspend() then return end
UIManager.event_hook:registerWidget("InputEvent", self) UIManager.event_hook:registerWidget("InputEvent", self)
-- We need an instance-specific function reference to schedule, because in some rare cases, -- We need an instance-specific function reference to schedule, because in some rare cases,
-- we may instantiate a new plugin instance *before* tearing down the old one. -- we may instantiate a new plugin instance *before* tearing down the old one.
@ -159,6 +158,9 @@ function AutoSuspend:init()
UIManager:broadcastEvent(Event:new("LeaveStandby")) UIManager:broadcastEvent(Event:new("LeaveStandby"))
end end
-- Make sure we only have an AllowStandby handler when we actually want one...
self:toggleStandbyHandler(self:_enabledStandby())
self.last_action_tv = UIManager:getElapsedTimeSinceBoot() self.last_action_tv = UIManager:getElapsedTimeSinceBoot()
self:_start() self:_start()
self:_start_standby() self:_start_standby()
@ -168,7 +170,7 @@ function AutoSuspend:init()
self.ui.menu:registerToMainMenu(self) self.ui.menu:registerToMainMenu(self)
end end
-- For event_hook automagic deregistration purposes -- NOTE: event_hook takes care of overloading this to unregister the hook, too.
function AutoSuspend:onCloseWidget() function AutoSuspend:onCloseWidget()
logger.dbg("AutoSuspend: onCloseWidget") logger.dbg("AutoSuspend: onCloseWidget")
if Device:isPocketBook() and not Device:canSuspend() then return end if Device:isPocketBook() and not Device:canSuspend() then return end
@ -205,13 +207,13 @@ end
function AutoSuspend:_schedule_standby() function AutoSuspend:_schedule_standby()
-- Start the long list of conditions in which we do *NOT* want to go into standby ;). -- Start the long list of conditions in which we do *NOT* want to go into standby ;).
if not Device:canStandby() then if not Device:canStandby() then
-- NOTE: This partly duplicates what `_enabledStandby` does,
-- but it's here to avoid logging noise on devices that can't even standby ;).
return return
end end
-- Don't even schedule standby if we haven't set a proper timeout yet. -- Don't even schedule standby if we haven't set a proper timeout yet.
if not self:_enabledStandby() then -- NOTE: We've essentially split the _enabledStandby check in two branches,
-- simply to avoid logging noise on devices that can't even standby ;).
if self.auto_standby_timeout_seconds <= 0 then
logger.dbg("AutoSuspend: No timeout set, no standby") logger.dbg("AutoSuspend: No timeout set, no standby")
return return
end end
@ -274,13 +276,6 @@ function AutoSuspend:allowStandby()
-- Tell UIManager that we now allow standby. -- Tell UIManager that we now allow standby.
UIManager:allowStandby() UIManager:allowStandby()
-- This is necessary for wakeup from standby, as the deadline for receiving input events
-- is calculated from the time to the next scheduled function.
-- Make sure this function comes soon, as the time for going to standby after a scheduled wakeup
-- is prolonged by the given time. Any time between 0.500 and 0.001 seconds should do.
-- Let's call it deadline_guard.
UIManager:scheduleIn(0.100, function() end)
-- We've just run our course. -- We've just run our course.
self.is_standby_scheduled = false self.is_standby_scheduled = false
end end
@ -319,7 +314,7 @@ function AutoSuspend:onResume()
self.going_to_suspend = false self.going_to_suspend = false
if self:_enabledShutdown() and Device.wakeup_mgr then if self:_enabledShutdown() and Device.wakeup_mgr then
Device.wakeup_mgr:removeTask(nil, nil, UIManager.poweroff_action) Device.wakeup_mgr:removeTasks(nil, UIManager.poweroff_action)
end end
-- Unschedule in case we tripped onUnexpectedWakeupLimit first... -- Unschedule in case we tripped onUnexpectedWakeupLimit first...
self:_unschedule() self:_unschedule()
@ -408,6 +403,7 @@ function AutoSuspend:pickTimeoutValue(touchmenu_instance, title, info, setting,
G_reader_settings:saveSetting(setting, self[setting]) G_reader_settings:saveSetting(setting, self[setting])
if is_standby then if is_standby then
self:_unschedule_standby() self:_unschedule_standby()
self:toggleStandbyHandler(self:_enabledStandby())
self:_start_standby() self:_start_standby()
else else
self:_unschedule() self:_unschedule()
@ -449,6 +445,7 @@ function AutoSuspend:pickTimeoutValue(touchmenu_instance, title, info, setting,
G_reader_settings:saveSetting(setting, -1) G_reader_settings:saveSetting(setting, -1)
if is_standby then if is_standby then
self:_unschedule_standby() self:_unschedule_standby()
self:toggleStandbyHandler(false)
else else
self:_unschedule() self:_unschedule()
end end
@ -519,11 +516,14 @@ function AutoSuspend:addToMainMenu(menu_items)
} }
end end
if Device:canStandby() then if Device:canStandby() then
--- @fixme: Reword the final warning when we have more data on the hangs on some Kobo kernels (e.g., #9038).
local standby_help = _([[Standby puts the device into a power-saving state in which the screen is on and user input can be performed. local standby_help = _([[Standby puts the device into a power-saving state in which the screen is on and user input can be performed.
Standby can not be entered if Wi-Fi is on. Standby can not be entered if Wi-Fi is on.
Upon user input, the device needs a certain amount of time to wake up. Generally, the newer the device, the less noticeable this delay will be, but it can be fairly aggravating on slower devices.]]) Upon user input, the device needs a certain amount of time to wake up. Generally, the newer the device, the less noticeable this delay will be, but it can be fairly aggravating on slower devices.
This is experimental on most devices, except those running on a sunxi SoC (Kobo Elipsa & Sage), so much so that it might in fact hang some of the more broken kernels out there (Kobo Libra 2).]])
menu_items.autostandby = { menu_items.autostandby = {
sorting_hint = "device", sorting_hint = "device",
@ -557,19 +557,23 @@ end
-- KOReader is merely waiting for user input right now. -- KOReader is merely waiting for user input right now.
-- UI signals us that standby is allowed at this very moment because nothing else goes on in the background. -- UI signals us that standby is allowed at this very moment because nothing else goes on in the background.
function AutoSuspend:onAllowStandby() -- NOTE: To make sure this will not even run when autostandby is disabled,
-- this is only aliased as `onAllowStandby` when necessary.
-- (Because the Event is generated regardless of us, as many things can call UIManager:allowStandby).
function AutoSuspend:AllowStandbyHandler()
logger.dbg("AutoSuspend: onAllowStandby") logger.dbg("AutoSuspend: onAllowStandby")
-- This piggy-backs minimally on the UI framework implemented for the PocketBook autostandby plugin, -- This piggy-backs minimally on the UI framework implemented for the PocketBook autostandby plugin,
-- see its own AllowStandby handler for more details. -- see its own AllowStandby handler for more details.
local wake_in = math.huge local wake_in
-- The next scheduled function should be our deadline_guard (c.f., `AutoSuspend:allowStandby`). -- Wake up before the next scheduled function executes (e.g. footer update, suspend ...)
-- Wake up before the second next scheduled function executes (e.g. footer update, suspend ...) local next_task_time = UIManager:getNextTaskTime()
local scheduler_times = UIManager:getNextTaskTimes(2) if next_task_time then
if #scheduler_times == 2 then
-- Wake up slightly after the formerly scheduled event, -- Wake up slightly after the formerly scheduled event,
-- to avoid resheduling the same function after a fraction of a second again (e.g. don't draw footer twice). -- to avoid resheduling the same function after a fraction of a second again (e.g. don't draw footer twice).
wake_in = math.floor(scheduler_times[2]:tonumber()) + 1 wake_in = math.floor(next_task_time:tonumber()) + 1
else
wake_in = math.huge
end end
if wake_in >= 3 then -- don't go into standby, if scheduled wakeup is in less than 3 secs if wake_in >= 3 then -- don't go into standby, if scheduled wakeup is in less than 3 secs
@ -585,9 +589,16 @@ function AutoSuspend:onAllowStandby()
-- to make sure UIManager will consume the input events that woke us up first -- to make sure UIManager will consume the input events that woke us up first
-- (in case we were woken up by user input, as opposed to an rtc wake alarm)! -- (in case we were woken up by user input, as opposed to an rtc wake alarm)!
-- (This ensures we'll use an up to date last_action_tv, and that it only ever gets updated from *user* input). -- (This ensures we'll use an up to date last_action_tv, and that it only ever gets updated from *user* input).
-- NOTE: UIManager consumes scheduled tasks before input events, so make sure we delay by a significant amount, -- NOTE: UIManager consumes scheduled tasks before input events, which is why we can't use nextTick.
-- especially given that this delay will likely be used as the next input polling loop timeout... UIManager:tickAfterNext(self.leave_standby_task)
UIManager:scheduleIn(1, self.leave_standby_task) end
end
function AutoSuspend:toggleStandbyHandler(toggle)
if toggle then
self.onAllowStandby = self.AllowStandbyHandler
else
self.onAllowStandby = nil
end end
end end

@ -39,7 +39,7 @@ describe("WakeupMgr", function()
assert.is_equal(epoch3, WakeupMgr._task_queue[2].epoch) assert.is_equal(epoch3, WakeupMgr._task_queue[2].epoch)
end) end)
it("should have scheduled next task after execution", function() it("should have scheduled next task after execution", function()
assert.stub(WakeupMgr.setWakeupAlarm).was.called(3) assert.stub(WakeupMgr.setWakeupAlarm).was.called(3) -- 2 from addTask (the second addTask doesn't replace the upcoming task), 1 from wakeupAction (via removeTask).
end) end)
it("should remove arbitrary task from stack", function() it("should remove arbitrary task from stack", function()
WakeupMgr:removeTask(2) WakeupMgr:removeTask(2)
@ -50,6 +50,6 @@ describe("WakeupMgr", function()
assert.is_true(WakeupMgr:wakeupAction()) assert.is_true(WakeupMgr:wakeupAction())
end) end)
it("should not have scheduled a wakeup without a task", function() it("should not have scheduled a wakeup without a task", function()
assert.stub(WakeupMgr.setWakeupAlarm).was.called(3) assert.stub(WakeupMgr.setWakeupAlarm).was.called(3) -- 2 from addTask, 1 from wakeupAction, 0 from removeTask (because it wasn't the upcoming task that was removed)
end) end)
end) end)

Loading…
Cancel
Save