mirror of
https://github.com/koreader/koreader.git
synced 2025-12-13 20:36:53 +01:00
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).
This commit is contained in:
@@ -130,7 +130,7 @@ function ReaderCoptListener:rescheduleHeaderRefreshIfNeeded()
|
||||
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
|
||||
-- configurable.status_line is set before this event is triggered
|
||||
ReaderCoptListener.onSetViewMode = ReaderCoptListener.rescheduleHeaderRefreshIfNeeded
|
||||
@@ -145,11 +145,12 @@ function ReaderCoptListener:onResume()
|
||||
end
|
||||
|
||||
self:headerRefresh()
|
||||
self:rescheduleHeaderRefreshIfNeeded()
|
||||
end
|
||||
|
||||
function ReaderCoptListener:onLeaveStandby()
|
||||
self:onResume()
|
||||
self:onOutOfScreenSaver()
|
||||
self:headerRefresh()
|
||||
self:rescheduleHeaderRefreshIfNeeded()
|
||||
end
|
||||
|
||||
function ReaderCoptListener:onOutOfScreenSaver()
|
||||
@@ -159,6 +160,7 @@ function ReaderCoptListener:onOutOfScreenSaver()
|
||||
|
||||
self._delayed_screensaver = nil
|
||||
self:headerRefresh()
|
||||
self:rescheduleHeaderRefreshIfNeeded()
|
||||
end
|
||||
|
||||
-- Unschedule on these events
|
||||
|
||||
@@ -191,13 +191,13 @@ local footerTextGeneratorMap = {
|
||||
batt_lvl = main_batt_lvl + aux_batt_lvl
|
||||
-- But average 'em to compute the icon...
|
||||
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
|
||||
else
|
||||
is_charging = powerd:isCharging()
|
||||
batt_lvl = main_batt_lvl
|
||||
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
|
||||
@@ -2448,8 +2448,8 @@ function ReaderFooter:onOutOfScreenSaver()
|
||||
end
|
||||
|
||||
function ReaderFooter:onLeaveStandby()
|
||||
self:onResume()
|
||||
self:onOutOfScreenSaver()
|
||||
self:maybeUpdateFooter()
|
||||
self:rescheduleFooterAutoRefreshIfNeeded()
|
||||
end
|
||||
|
||||
function ReaderFooter:onSuspend()
|
||||
|
||||
@@ -54,8 +54,12 @@ function BasePowerD:getAuxCapacityHW() return 0 end
|
||||
function BasePowerD:isAuxBatteryConnectedHW() return false end
|
||||
function BasePowerD:getDismissBatteryStatus() return self.battery_warning 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
|
||||
--- @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:isAuxChargedHW() return false end
|
||||
function BasePowerD:frontlightIntensityHW() return 0 end
|
||||
function BasePowerD:isFrontlightOnHW() return self.fl_intensity > self.fl_min end
|
||||
function BasePowerD:turnOffFrontlightHW() self:setIntensityHW(self.fl_min) end
|
||||
@@ -231,6 +235,10 @@ function BasePowerD:isCharging()
|
||||
return self:isChargingHW()
|
||||
end
|
||||
|
||||
function BasePowerD:isCharged()
|
||||
return self:isChargedHW()
|
||||
end
|
||||
|
||||
function BasePowerD:getAuxCapacity()
|
||||
local now_btv
|
||||
|
||||
@@ -261,6 +269,10 @@ function BasePowerD:isAuxCharging()
|
||||
return self:isAuxChargingHW()
|
||||
end
|
||||
|
||||
function BasePowerD:isAuxCharged()
|
||||
return self:isAuxChargedHW()
|
||||
end
|
||||
|
||||
function BasePowerD:isAuxBatteryConnected()
|
||||
return self:isAuxBatteryConnectedHW()
|
||||
end
|
||||
@@ -274,8 +286,10 @@ function BasePowerD:stateChanged()
|
||||
end
|
||||
|
||||
-- Silly helper to avoid code duplication ;).
|
||||
function BasePowerD:getBatterySymbol(is_charging, capacity)
|
||||
if is_charging then
|
||||
function BasePowerD:getBatterySymbol(is_charged, is_charging, capacity)
|
||||
if is_charged then
|
||||
return ""
|
||||
elseif is_charging then
|
||||
return ""
|
||||
else
|
||||
if capacity >= 100 then
|
||||
|
||||
@@ -578,6 +578,57 @@ function Input:handleKeyBoardEv(ev)
|
||||
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)
|
||||
-- should be handled by a misc event protocol plugin
|
||||
end
|
||||
@@ -892,13 +943,13 @@ function Input:toggleMiscEvNTX(toggle)
|
||||
elseif toggle == false then
|
||||
-- Ignore Gyro events
|
||||
if self.isNTXAccelHooked then
|
||||
self.handleMiscEv = function() end
|
||||
self.handleMiscEv = self.voidEv
|
||||
self.isNTXAccelHooked = false
|
||||
end
|
||||
else
|
||||
-- Toggle it
|
||||
if self.isNTXAccelHooked then
|
||||
self.handleMiscEv = function() end
|
||||
self.handleMiscEv = self.voidEv
|
||||
else
|
||||
self.handleMiscEv = self.handleMiscEvNTX
|
||||
end
|
||||
@@ -1204,7 +1255,10 @@ function Input:waitEvent(now, deadline)
|
||||
end
|
||||
else
|
||||
-- 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
|
||||
return handled
|
||||
@@ -1220,4 +1274,64 @@ function Input:waitEvent(now, deadline)
|
||||
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
|
||||
|
||||
@@ -757,6 +757,7 @@ function KindleOasis2:init()
|
||||
fl_intensity_file = "/sys/class/backlight/max77796-bl/brightness",
|
||||
batt_capacity_file = "/sys/class/power_supply/max77796-battery/capacity",
|
||||
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{
|
||||
@@ -832,6 +833,7 @@ function KindleOasis3:init()
|
||||
warmth_intensity_file = "/sys/class/backlight/lm3697-bl0/brightness",
|
||||
batt_capacity_file = "/sys/class/power_supply/max77796-battery/capacity",
|
||||
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{
|
||||
@@ -845,13 +847,7 @@ function KindleOasis3:init()
|
||||
}
|
||||
|
||||
|
||||
--- @fixme When starting KOReader with the device upside down ("D"), touch input is registered wrong
|
||||
-- (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.
|
||||
--- @fixme The same quirks as on the Oasis 2 apply ;).
|
||||
local haslipc, lipc = pcall(require, "liblipclua")
|
||||
if haslipc and lipc then
|
||||
local lipc_handle = lipc.init("com.github.koreader.screen")
|
||||
@@ -906,6 +902,7 @@ function KindleBasic2:init()
|
||||
device = self,
|
||||
batt_capacity_file = "/sys/class/power_supply/bd7181x_bat/capacity",
|
||||
is_charging_file = "/sys/class/power_supply/bd7181x_bat/charging",
|
||||
batt_status_file = "/sys/class/power_supply/bd7181x_bat/status",
|
||||
}
|
||||
|
||||
Kindle.init(self)
|
||||
@@ -921,6 +918,7 @@ function KindlePaperWhite4:init()
|
||||
fl_intensity_file = "/sys/class/backlight/bl/brightness",
|
||||
batt_capacity_file = "/sys/class/power_supply/bd71827_bat/capacity",
|
||||
is_charging_file = "/sys/class/power_supply/bd71827_bat/charging",
|
||||
batt_status_file = "/sys/class/power_supply/bd71827_bat/status",
|
||||
}
|
||||
|
||||
Kindle.init(self)
|
||||
@@ -946,6 +944,7 @@ function KindleBasic3:init()
|
||||
fl_intensity_file = "/sys/class/backlight/bl/brightness",
|
||||
batt_capacity_file = "/sys/class/power_supply/bd71827_bat/capacity",
|
||||
is_charging_file = "/sys/class/power_supply/bd71827_bat/charging",
|
||||
batt_status_file = "/sys/class/power_supply/bd71827_bat/status",
|
||||
}
|
||||
|
||||
Kindle.init(self)
|
||||
@@ -963,6 +962,7 @@ function KindlePaperWhite5:init()
|
||||
warmth_intensity_file = "/sys/class/backlight/fp9966-bl0/brightness",
|
||||
batt_capacity_file = "/sys/class/power_supply/bd71827_bat/capacity",
|
||||
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.
|
||||
|
||||
@@ -148,6 +148,15 @@ function KindlePowerD:isChargingHW()
|
||||
return is_charging == 1
|
||||
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()
|
||||
return self:read_int_file(self.fl_intensity_file)
|
||||
end
|
||||
|
||||
@@ -43,17 +43,22 @@ local function checkStandby()
|
||||
end
|
||||
|
||||
local function writeToSys(val, file)
|
||||
local f = io.open(file, "we")
|
||||
if not f then
|
||||
logger.err("Cannot open:", file)
|
||||
-- NOTE: We do things by hand via ffi, because io.write uses fwrite,
|
||||
-- which isn't a great fit for procfs/sysfs (e.g., we lose failure cases like EBUSY,
|
||||
-- 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
|
||||
end
|
||||
local re, err_msg, err_code = f:write(val, "\n")
|
||||
if not re then
|
||||
logger.err("Error writing value to file:", val, file, err_msg, err_code)
|
||||
local bytes = #val + 1 -- + LF
|
||||
local nw = C.write(fd, val .. "\n", bytes)
|
||||
if nw == -1 then
|
||||
logger.err("Cannot write `" .. val .. "` to file `" .. file .. "`:", ffi.string(C.strerror(ffi.errno())))
|
||||
end
|
||||
f:close()
|
||||
return re
|
||||
C.close(fd)
|
||||
-- NOTE: Allows the caller to possibly handle short writes (not that these should ever happen here).
|
||||
return nw == bytes
|
||||
end
|
||||
|
||||
local Kobo = Generic:new{
|
||||
@@ -424,9 +429,9 @@ local KoboIo = Kobo:new{
|
||||
function Kobo:setupChargingLED()
|
||||
if G_reader_settings:nilOrTrue("enable_charging_led") 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
|
||||
self:toggleChargingLED(self.powerd:isCharging())
|
||||
self:toggleChargingLED(self.powerd:isCharging() and not self.powerd:isCharged())
|
||||
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)
|
||||
end
|
||||
if os.execute(command) == 0 then
|
||||
os.execute('hwclock -u -w')
|
||||
os.execute("hwclock -u -w")
|
||||
return true
|
||||
else
|
||||
return false
|
||||
@@ -727,7 +732,7 @@ function Kobo:initEventAdjustHooks()
|
||||
end
|
||||
end
|
||||
|
||||
function Kobo:getCodeName()
|
||||
local function getCodeName()
|
||||
-- Try to get it from the env first
|
||||
local codename = os.getenv("PRODUCT")
|
||||
-- If that fails, run the script ourselves
|
||||
@@ -776,23 +781,21 @@ end
|
||||
|
||||
function Kobo:checkUnexpectedWakeup()
|
||||
local UIManager = require("ui/uimanager")
|
||||
-- just in case other events like SleepCoverClosed also scheduled a suspend
|
||||
UIManager:unschedule(Kobo.suspend)
|
||||
-- Just in case another event like SleepCoverClosed also scheduled a suspend
|
||||
UIManager:unschedule(self.suspend)
|
||||
|
||||
-- Do an initial validation to discriminate unscheduled wakeups happening *outside* of the alarm proximity window.
|
||||
if WakeupMgr:isWakeupAlarmScheduled() and WakeupMgr:validateWakeupAlarmByProximity() then
|
||||
logger.info("Kobo suspend: scheduled wakeup.")
|
||||
local res = WakeupMgr:wakeupAction()
|
||||
if not res then
|
||||
logger.err("Kobo suspend: wakeup action failed.")
|
||||
end
|
||||
logger.info("Kobo suspend: putting device back to sleep.")
|
||||
-- Most wakeup actions are linear, but we need some leeway for the
|
||||
-- poweroff action to send out close events to all requisite widgets.
|
||||
UIManager:scheduleIn(30, Kobo.suspend, self)
|
||||
-- The proximity window is rather large, because we're scheduled to run 15 seconds after resuming,
|
||||
-- so we're already guaranteed to be at least 15s away from the alarm ;).
|
||||
if self.wakeup_mgr:isWakeupAlarmScheduled() and self.wakeup_mgr:wakeupAction(30) then
|
||||
-- Assume we want to go back to sleep after running the scheduled action
|
||||
-- (Kobo:resume will unschedule this on an user-triggered resume).
|
||||
logger.info("Kobo suspend: scheduled wakeup; the device will go back to sleep in 30s.")
|
||||
-- We need significant leeway for the poweroff action to send out close events to all requisite widgets,
|
||||
-- since we don't actually want to suspend behind its back ;).
|
||||
UIManager:scheduleIn(30, self.suspend, self)
|
||||
else
|
||||
logger.dbg("Kobo suspend: checking unexpected wakeup:",
|
||||
self.unexpected_wakeup_count)
|
||||
-- We've hit an early resume, assume this is unexpected (as we only run if Kobo:resume hasn't already).
|
||||
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
|
||||
-- Don't put device back to sleep under the following two cases:
|
||||
-- 1. a resume event triggered Kobo:resume() function
|
||||
@@ -803,9 +806,8 @@ function Kobo:checkUnexpectedWakeup()
|
||||
return
|
||||
end
|
||||
|
||||
logger.err("Kobo suspend: putting device back to sleep. Unexpected wakeups:",
|
||||
self.unexpected_wakeup_count)
|
||||
Kobo:suspend()
|
||||
logger.err("Kobo suspend: putting device back to sleep after", self.unexpected_wakeup_count, "unexpected wakeups.")
|
||||
self:suspend()
|
||||
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.
|
||||
-- max_duration ... maximum time for the next standby, can wake earlier (e.g. Tap, Button ...)
|
||||
function Kobo:standby(max_duration)
|
||||
-- just for wake up, dummy function
|
||||
local function dummy() end
|
||||
-- We don't really have anything to schedule, we just need an alarm out of WakeupMgr ;).
|
||||
local function standby_alarm()
|
||||
end
|
||||
|
||||
if max_duration then
|
||||
self.wakeup_mgr:addTask(max_duration, dummy)
|
||||
self.wakeup_mgr:addTask(max_duration, standby_alarm)
|
||||
end
|
||||
|
||||
local TimeVal = require("ui/timeval")
|
||||
logger.info("Kobo standby: asking to enter standby . . .")
|
||||
local standby_time_tv = TimeVal:boottime_or_realtime_coarse()
|
||||
|
||||
logger.info("Kobo suspend: asking to enter standby . . .")
|
||||
local ret = writeToSys("standby", "/sys/power/state")
|
||||
|
||||
self.last_standby_tv = TimeVal:boottime_or_realtime_coarse() - standby_time_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
|
||||
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
|
||||
|
||||
@@ -841,7 +860,6 @@ function Kobo:suspend()
|
||||
logger.info("Kobo suspend: going to sleep . . .")
|
||||
local UIManager = require("ui/uimanager")
|
||||
UIManager:unschedule(self.checkUnexpectedWakeup)
|
||||
local f, re, err_msg, err_code
|
||||
-- NOTE: Sleep as little as possible here, sleeping has a tendency to make
|
||||
-- everything mysteriously hang...
|
||||
|
||||
@@ -854,6 +872,7 @@ function Kobo:suspend()
|
||||
-- So, unless that changes, unconditionally disable it.
|
||||
|
||||
--[[
|
||||
local f, re, err_msg, err_code
|
||||
local has_wakeup_count = false
|
||||
f = io.open("/sys/power/wakeup_count", "re")
|
||||
if f ~= nil then
|
||||
@@ -873,10 +892,14 @@ function Kobo:suspend()
|
||||
-]]
|
||||
|
||||
-- NOTE: Sets gSleep_Mode_Suspend to 1. Used as a flag throughout the
|
||||
-- kernel to suspend/resume various subsystems
|
||||
-- c.f., state_extended_store @ kernel/power/main.c
|
||||
-- kernel to suspend/resume various subsystems
|
||||
-- c.f., state_extended_store @ kernel/power/main.c
|
||||
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)
|
||||
logger.info("Kobo suspend: waited for 2s because of reasons...")
|
||||
@@ -902,90 +925,82 @@ function Kobo:suspend()
|
||||
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")
|
||||
logger.info("Kobo suspend: asking for a suspend to RAM . . .")
|
||||
local suspend_time_tv = TimeVal:boottime_or_realtime_coarse()
|
||||
|
||||
re, err_msg, err_code = f:write("mem\n")
|
||||
if not re then
|
||||
logger.err("write error: ", err_msg, err_code)
|
||||
end
|
||||
f:close()
|
||||
ret = writeToSys("mem", "/sys/power/state")
|
||||
|
||||
-- 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.total_suspend_tv = self.total_suspend_tv + self.last_suspend_tv
|
||||
|
||||
logger.info("Kobo suspend: ZzZ ZzZ ZzZ? Write syscall returned: ", re)
|
||||
-- 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 ret then
|
||||
logger.info("Kobo suspend: ZzZ ZzZ ZzZ... And woke up!")
|
||||
else
|
||||
logger.warn("Kobo suspend: the kernel refused to enter suspend!")
|
||||
-- Reset state-extended back to 0 since we are giving up.
|
||||
writeToSys("0", "/sys/power/state-extended")
|
||||
end
|
||||
|
||||
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
|
||||
logger.info("wakeup count: $(cat /sys/power/wakeup_count)")
|
||||
end
|
||||
|
||||
-- Print tke kernel log since our attempt to sleep...
|
||||
--dmesg -c
|
||||
|
||||
--]]
|
||||
|
||||
-- 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
|
||||
-- expected wakeup, which gets checked in checkUnexpectedWakeup().
|
||||
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")
|
||||
UIManager:scheduleIn(15, self.checkUnexpectedWakeup, self)
|
||||
end
|
||||
|
||||
function Kobo:resume()
|
||||
logger.info("Kobo resume: clean up after wakeup")
|
||||
-- reset unexpected_wakeup_count ASAP
|
||||
-- Reset unexpected_wakeup_count ASAP
|
||||
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...
|
||||
-- NOTE: Sets gSleep_Mode_Suspend to 0. Used as a flag throughout the
|
||||
-- kernel to suspend/resume various subsystems
|
||||
-- cf. kernel/power/main.c @ L#207
|
||||
|
||||
-- kernel to suspend/resume various subsystems
|
||||
-- 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")
|
||||
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
|
||||
util.usleep(100000)
|
||||
|
||||
if self.hasIRGrid then
|
||||
-- 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")
|
||||
end
|
||||
|
||||
@@ -1000,7 +1015,7 @@ end
|
||||
|
||||
function Kobo:powerOff()
|
||||
-- Much like Nickel itself, disable the RTC alarm before powering down.
|
||||
WakeupMgr:unsetWakeupAlarm()
|
||||
self.wakeup_mgr:unsetWakeupAlarm()
|
||||
|
||||
-- Then shut down without init's help
|
||||
os.execute("poweroff -f")
|
||||
@@ -1141,7 +1156,7 @@ end
|
||||
|
||||
-------------- device probe ------------
|
||||
|
||||
local codename = Kobo:getCodeName()
|
||||
local codename = getCodeName()
|
||||
local product_id = getProductId()
|
||||
|
||||
if codename == "dahlia" then
|
||||
|
||||
@@ -114,11 +114,14 @@ function KoboPowerD:init()
|
||||
end
|
||||
|
||||
self.isAuxChargingHW = function(this)
|
||||
-- 0 when not charging
|
||||
-- 0 when discharging
|
||||
-- 3 when full
|
||||
-- 2 when charging via DCP
|
||||
local charge_status = this:read_int_file(this.aux_batt_charging_file)
|
||||
return charge_status ~= 0 and charge_status ~= 3
|
||||
return this:read_int_file(this.aux_batt_charging_file) ~= 0
|
||||
end
|
||||
|
||||
self.isAuxChargedHW = function(this)
|
||||
return this:read_int_file(this.aux_batt_charging_file) == 3
|
||||
end
|
||||
end
|
||||
|
||||
@@ -274,8 +277,24 @@ function KoboPowerD:getCapacityHW()
|
||||
return self:read_int_file(self.batt_capacity_file)
|
||||
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()
|
||||
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
|
||||
|
||||
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.
|
||||
--]]
|
||||
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)
|
||||
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)
|
||||
|
||||
local new_upcoming_task = self._task_queue[1].epoch
|
||||
|
||||
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
|
||||
|
||||
--[[--
|
||||
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.
|
||||
Normally the preferred method for outside callers.
|
||||
@int callback A scheduled callback function. Store a reference for use
|
||||
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)
|
||||
if not type(idx) == "number"
|
||||
and not type(epoch) == "number"
|
||||
and not type(callback) == "function" then return end
|
||||
|
||||
function WakeupMgr:removeTasks(epoch, callback)
|
||||
if #self._task_queue < 1 then return end
|
||||
|
||||
for k, v in ipairs(self._task_queue) do
|
||||
if k == idx or epoch == v.epoch or callback == v.callback then
|
||||
local removed = false
|
||||
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)
|
||||
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
|
||||
|
||||
-- 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
|
||||
|
||||
--[[--
|
||||
@@ -106,25 +139,28 @@ Execute wakeup action.
|
||||
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},
|
||||
executes the task, and schedules the next wakeup if any.
|
||||
in which case the task is executed.
|
||||
|
||||
@treturn bool
|
||||
If necessary, the next upcoming task (if any) is scheduled on exit.
|
||||
|
||||
@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
|
||||
local task = self._task_queue[1]
|
||||
if self:validateWakeupAlarmByProximity(task.epoch) then
|
||||
if self:validateWakeupAlarmByProximity(task.epoch, proximity) then
|
||||
task.callback()
|
||||
-- NOTE: removeTask will take care of scheduling the next upcoming task, if necessary.
|
||||
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
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--[[--
|
||||
|
||||
@@ -84,6 +84,12 @@ end
|
||||
-- Everything below is to handle auto_disable_wifi ;).
|
||||
local default_network_timeout_seconds = 5*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.
|
||||
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
|
||||
-- resume. Done via the plugin's onSuspend/onResume handlers.
|
||||
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()
|
||||
Device:onPowerEvent("Suspend")
|
||||
end
|
||||
self.event_handlers["Resume"] = function()
|
||||
Device:onPowerEvent("Resume")
|
||||
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
|
||||
self.event_handlers["PowerPress"] = function()
|
||||
-- Always schedule power off.
|
||||
@@ -1179,14 +1171,24 @@ function UIManager:broadcastEvent(event)
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
function UIManager:getNextTaskTimes(count)
|
||||
count = count or 1
|
||||
local times = {}
|
||||
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
|
||||
return times
|
||||
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()
|
||||
self._now = TimeVal:now()
|
||||
@@ -1771,6 +1773,9 @@ 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()
|
||||
|
||||
@@ -1783,6 +1788,9 @@ function UIManager:_afterResume()
|
||||
-- Restore key repeat
|
||||
Device:restoreKeyRepeat()
|
||||
|
||||
-- Restore full input handling
|
||||
Input:inhibitInput(false)
|
||||
|
||||
self:broadcastEvent(Event:new("Resume"))
|
||||
end
|
||||
|
||||
|
||||
@@ -697,12 +697,12 @@ function TouchMenu:updateItems()
|
||||
local powerd = Device:getPowerDevice()
|
||||
if Device:hasBattery() then
|
||||
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 .. "%")
|
||||
|
||||
if Device:hasAuxBattery() and powerd:isAuxBatteryConnected() then
|
||||
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 .. "%")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,8 +53,8 @@ function AutoSuspend:_enabledShutdown()
|
||||
end
|
||||
|
||||
function AutoSuspend:_schedule(shutdown_only)
|
||||
if not self:_enabled() and Device:canPowerOff() and not self:_enabledShutdown() then
|
||||
logger.dbg("AutoSuspend:_schedule is disabled")
|
||||
if not self:_enabled() and not self:_enabledShutdown() then
|
||||
logger.dbg("AutoSuspend: suspend/shutdown timer is disabled")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -126,16 +126,15 @@ end
|
||||
|
||||
function 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",
|
||||
default_autoshutdown_timeout_seconds)
|
||||
self.auto_suspend_timeout_seconds = G_reader_settings:readSetting("auto_suspend_timeout_seconds",
|
||||
default_auto_suspend_timeout_seconds)
|
||||
|
||||
-- Disabled, until the user opts in.
|
||||
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)
|
||||
-- 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.
|
||||
@@ -159,6 +158,9 @@ function AutoSuspend:init()
|
||||
UIManager:broadcastEvent(Event:new("LeaveStandby"))
|
||||
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:_start()
|
||||
self:_start_standby()
|
||||
@@ -168,7 +170,7 @@ function AutoSuspend:init()
|
||||
self.ui.menu:registerToMainMenu(self)
|
||||
end
|
||||
|
||||
-- For event_hook automagic deregistration purposes
|
||||
-- NOTE: event_hook takes care of overloading this to unregister the hook, too.
|
||||
function AutoSuspend:onCloseWidget()
|
||||
logger.dbg("AutoSuspend: onCloseWidget")
|
||||
if Device:isPocketBook() and not Device:canSuspend() then return end
|
||||
@@ -205,13 +207,13 @@ end
|
||||
function AutoSuspend:_schedule_standby()
|
||||
-- Start the long list of conditions in which we do *NOT* want to go into standby ;).
|
||||
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
|
||||
end
|
||||
|
||||
-- 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")
|
||||
return
|
||||
end
|
||||
@@ -274,13 +276,6 @@ function AutoSuspend:allowStandby()
|
||||
-- Tell UIManager that we now allow standby.
|
||||
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.
|
||||
self.is_standby_scheduled = false
|
||||
end
|
||||
@@ -319,7 +314,7 @@ function AutoSuspend:onResume()
|
||||
self.going_to_suspend = false
|
||||
|
||||
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
|
||||
-- Unschedule in case we tripped onUnexpectedWakeupLimit first...
|
||||
self:_unschedule()
|
||||
@@ -408,6 +403,7 @@ function AutoSuspend:pickTimeoutValue(touchmenu_instance, title, info, setting,
|
||||
G_reader_settings:saveSetting(setting, self[setting])
|
||||
if is_standby then
|
||||
self:_unschedule_standby()
|
||||
self:toggleStandbyHandler(self:_enabledStandby())
|
||||
self:_start_standby()
|
||||
else
|
||||
self:_unschedule()
|
||||
@@ -449,6 +445,7 @@ function AutoSuspend:pickTimeoutValue(touchmenu_instance, title, info, setting,
|
||||
G_reader_settings:saveSetting(setting, -1)
|
||||
if is_standby then
|
||||
self:_unschedule_standby()
|
||||
self:toggleStandbyHandler(false)
|
||||
else
|
||||
self:_unschedule()
|
||||
end
|
||||
@@ -519,11 +516,14 @@ function AutoSuspend:addToMainMenu(menu_items)
|
||||
}
|
||||
end
|
||||
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.
|
||||
|
||||
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 = {
|
||||
sorting_hint = "device",
|
||||
@@ -557,19 +557,23 @@ end
|
||||
|
||||
-- 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.
|
||||
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")
|
||||
-- This piggy-backs minimally on the UI framework implemented for the PocketBook autostandby plugin,
|
||||
-- see its own AllowStandby handler for more details.
|
||||
|
||||
local wake_in = math.huge
|
||||
-- The next scheduled function should be our deadline_guard (c.f., `AutoSuspend:allowStandby`).
|
||||
-- Wake up before the second next scheduled function executes (e.g. footer update, suspend ...)
|
||||
local scheduler_times = UIManager:getNextTaskTimes(2)
|
||||
if #scheduler_times == 2 then
|
||||
local wake_in
|
||||
-- Wake up before the next scheduled function executes (e.g. footer update, suspend ...)
|
||||
local next_task_time = UIManager:getNextTaskTime()
|
||||
if next_task_time then
|
||||
-- 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).
|
||||
wake_in = math.floor(scheduler_times[2]:tonumber()) + 1
|
||||
wake_in = math.floor(next_task_time:tonumber()) + 1
|
||||
else
|
||||
wake_in = math.huge
|
||||
end
|
||||
|
||||
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
|
||||
-- (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).
|
||||
-- NOTE: UIManager consumes scheduled tasks before input events, so make sure we delay by a significant amount,
|
||||
-- especially given that this delay will likely be used as the next input polling loop timeout...
|
||||
UIManager:scheduleIn(1, self.leave_standby_task)
|
||||
-- NOTE: UIManager consumes scheduled tasks before input events, which is why we can't use nextTick.
|
||||
UIManager:tickAfterNext(self.leave_standby_task)
|
||||
end
|
||||
end
|
||||
|
||||
function AutoSuspend:toggleStandbyHandler(toggle)
|
||||
if toggle then
|
||||
self.onAllowStandby = self.AllowStandbyHandler
|
||||
else
|
||||
self.onAllowStandby = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ describe("WakeupMgr", function()
|
||||
assert.is_equal(epoch3, WakeupMgr._task_queue[2].epoch)
|
||||
end)
|
||||
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)
|
||||
it("should remove arbitrary task from stack", function()
|
||||
WakeupMgr:removeTask(2)
|
||||
@@ -50,6 +50,6 @@ describe("WakeupMgr", function()
|
||||
assert.is_true(WakeupMgr:wakeupAction())
|
||||
end)
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user