2
0
mirror of https://github.com/koreader/koreader synced 2024-11-10 01:10:34 +00:00
koreader/frontend/device/kindle/powerd.lua
Marek Veselý 4c4cfa2db0
Kindle: add wifi selector (#12056)
* Kindle: Implement a NetworkMgr backend loosely based on WpaClient in order to allow feature-parity with hasWifiManager platforms. This involves dealing with the native wifid over lipc (the native IPC system, based on DBus), through custom Lua bindings (https://github.com/notmarek/openlipclua), since the stock ones lack support for the needed hasharray data type.
* NetworkMgr: Clear up leftover hallucinations from #10669, making `enableWifi` much simpler (and much more similar to `turnOnWifiAndWaitForConnection`).
* NetworkMgr: Made it clearer that `turnOnWifi` implementations *must* deal with `complete_callback`, as part of the aforementioned changes mean that it's *always* wrapped in a connectivity check, and we need that for proper event signaling.
* Android, Emu: Run `complete_callback` properly in `turnOnWifi`.
* Kindle: Support `powerd:isCharged()` on the PW2 (yes, this is random, it just happened to be my test device :D).
* NetworkMgr:disableWifi: Properly tear down any potential ongoing connection attempt (e.g., connectivity check).
* NetworkMgr:promptWifi: Make the "wifi enabled but not connected" popup clearer if there's an ongoing connection attempt, and gray out the "Connect" button in this case (as it would only lead to another "connection already in progress" popup anyway).
* NetworkMgr:reconnectOrShowNetworkMenu: Make *total* scanning failures fatal (they will lead to an immediate wifi teardown).
* NetworkMgr:reconnectOrShowNetworkMenu: Clear up the long-press behavior (which *always* shows the network list popup) so that it doesn't weirdly break all the things (technical term!).
* NetworkMgr:reconnectOrShowNetworkMenu: When we manage to connect to a preferred network on our own *before* showing the network list, make sure it is flagged as "connected" in said list.
* NetworkMgr:reconnectOrShowNetworkMenu: Make connection failures fatal in non-interactive workflows (they'll lead to a wifi teardown).
* NetworkSetting (the aforementioned network list widget): Clear NetworkMgr's "connection pending" flag on dismiss when nothing else will (i.e., when there's no connectivity check ticking).
2024-06-25 21:17:36 +02:00

323 lines
12 KiB
Lua

local BasePowerD = require("device/generic/powerd")
local UIManager
local WakeupMgr = require("device/wakeupmgr")
local logger = require("logger")
local ffiUtil = require("ffi/util")
-- liblipclua, see require below
local KindlePowerD = BasePowerD:new{
fl_min = 0, fl_max = 24,
fl_warmth_min = 0, fl_warmth_max = 24,
lipc_handle = nil,
}
function KindlePowerD:init()
local haslipc, lipc = pcall(require, "liblipclua")
if haslipc then
self.lipc_handle = lipc.init("com.github.koreader.kindlepowerd")
end
-- On devices where lipc step 0 is *not* off, we add a synthetic fl level where 0 *is* off,
-- which allows us to keep being able to use said step 0 as the first "on" step.
if not self.device:canTurnFrontlightOff() then
self.fl_max = self.fl_max + 1
end
self:initWakeupMgr()
end
-- If we start with the light off (fl_intensity is fl_min), ensure a toggle will set it to the lowest "on" step,
-- and that we update fl_intensity (by using setIntensity and not setIntensityHW).
function KindlePowerD:turnOnFrontlightHW(done_callback)
self:setIntensity(self.fl_intensity == self.fl_min and self.fl_min + 1 or self.fl_intensity)
return false
end
-- Which means we need to get rid of the insane fl_intensity == fl_min shortcut in turnOnFrontlight, too...
-- That dates back to #2941, and I have no idea what it's supposed to help with.
function KindlePowerD:turnOnFrontlight(done_callback)
if not self.device:hasFrontlight() then return end
if self:isFrontlightOn() then return false end
local cb_handled = self:turnOnFrontlightHW(done_callback)
self.is_fl_on = true
self:stateChanged()
if not cb_handled and done_callback then
done_callback()
end
return true
end
function KindlePowerD:frontlightIntensityHW()
if not self.device:hasFrontlight() then return 0 end
-- Kindle stock software does not use intensity file directly, so go through lipc to keep us in sync.
if self.lipc_handle ~= nil then
-- Handle the step 0 switcheroo on ! canTurnFrontlightOff devices...
if self.device:canTurnFrontlightOff() then
return self.lipc_handle:get_int_property("com.lab126.powerd", "flIntensity")
else
local lipc_fl_intensity = self.lipc_handle:get_int_property("com.lab126.powerd", "flIntensity")
-- NOTE: If lipc returns 0, compare against what the kernel says,
-- to avoid breaking on/off detection on devices where lipc 0 doesn't actually turn it off (<= PW3),
-- c.f., #5986
if lipc_fl_intensity == self.fl_min then
local sysfs_fl_intensity = self:_readFLIntensity()
if sysfs_fl_intensity ~= self.fl_min then
-- Return something potentially slightly off (as we can't be sure of the sysfs -> lipc mapping),
-- but, more importantly, something that's not fl_min (0), so we properly detect the light as on,
-- and update fl_intensity accordingly.
-- That's only tripped if it was set to fl_min from the stock UI,
-- as we ourselves *do* really turn it off when we do that.
return self.fl_min + 1
else
return self.fl_min
end
else
-- We've added a synthetic step...
return lipc_fl_intensity + 1
end
end
else
-- NOTE: This fallback is of dubious use, as it will NOT match our expected [fl_min..fl_max] range,
-- each model has a specific curve.
return self:_readFLIntensity()
end
end
-- Make sure isFrontlightOn reflects the actual HW state,
-- as self.fl_intensity is kept as-is when toggling the light off,
-- in order to be able to toggle it back on at the right intensity.
function KindlePowerD:isFrontlightOnHW()
local hw_intensity = self:frontlightIntensityHW()
return hw_intensity > self.fl_min
end
function KindlePowerD:setIntensityHW(intensity)
-- Handle the synthetic step switcheroo on ! canTurnFrontlightOff devices...
local turn_it_off = false
if not self.device:canTurnFrontlightOff() then
if intensity > 0 then
intensity = intensity - 1
else
-- And if we *really* requested 0, turn it off manually.
turn_it_off = true
end
end
-- NOTE: This means we *require* a working lipc handle to set the FL:
-- it knows what the UI values should map to for the specific hardware much better than us.
if self.lipc_handle ~= nil then
-- NOTE: We want to bypass setIntensity's shenanigans and simply restore the light as-is
self.lipc_handle:set_int_property("com.lab126.powerd", "flIntensity", intensity)
end
if turn_it_off then
-- NOTE: when intensity is 0, we want to *really* kill the light, so do it manually
-- (asking lipc to set it to 0 would in fact set it to > 0 on ! canTurnFrontlightOff Kindles).
-- We do *both* to make the fl restore on resume less jarring on devices where lipc 0 != off.
ffiUtil.writeToSysfs(intensity, self.fl_intensity_file)
-- And in case there are two LED groups...
-- This should never happen as all warmth devices so far canTurnFrontlightOff
if self.warmth_intensity_file then
ffiUtil.writeToSysfs(intensity, self.warmth_intensity_file)
end
end
-- The state might have changed, make sure we don't break isFrontlightOn
self:_decideFrontlightState()
end
function KindlePowerD:frontlightWarmthHW()
if self.lipc_handle ~= nil then
local nat_warmth = self.lipc_handle:get_int_property("com.lab126.powerd", "currentAmberLevel")
if nat_warmth then
-- [0...24] -> [0...100]
return self:fromNativeWarmth(nat_warmth)
else
return 0
end
end
end
function KindlePowerD:setWarmthHW(warmth)
if self.lipc_handle ~= nil then
self.lipc_handle:set_int_property("com.lab126.powerd", "currentAmberLevel", warmth)
end
end
function KindlePowerD:getCapacityHW()
if self.lipc_handle ~= nil then
return self.lipc_handle:get_int_property("com.lab126.powerd", "battLevel")
elseif self.batt_capacity_file then
return self:read_int_file(self.batt_capacity_file)
else
local std_out = io.popen("gasgauge-info -c 2>/dev/null", "r")
if std_out then
local result = std_out:read("*number")
std_out:close()
return result or 0
else
return 0
end
end
end
function KindlePowerD:isChargingHW()
local is_charging
if self.lipc_handle ~= nil then
is_charging = self.lipc_handle:get_int_property("com.lab126.powerd", "isCharging")
else
is_charging = self:read_int_file(self.is_charging_file)
end
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:hasHallSensor()
return self.hall_file ~= nil
end
function KindlePowerD:isHallSensorEnabled()
local int = self:read_int_file(self.hall_file)
return int == 1
end
function KindlePowerD:onToggleHallSensor()
local stat = self:isHallSensorEnabled()
ffiUtil.writeToSysfs(stat and 0 or 1, self.hall_file)
end
function KindlePowerD:_readFLIntensity()
return self:read_int_file(self.fl_intensity_file)
end
function KindlePowerD:toggleSuspend()
if self.lipc_handle then
self.lipc_handle:set_int_property("com.lab126.powerd", "powerButton", 1)
else
os.execute("powerd_test -p")
end
end
-- Kindle only allows setting the RTC via lipc during the ReadyToSuspend state
function KindlePowerD:setRtcWakeup(seconds_from_now)
if self.lipc_handle then
self.lipc_handle:set_int_property("com.lab126.powerd", "rtcWakeup", seconds_from_now)
end
end
-- Check the powerd state: are we still in screensaver mode.
function KindlePowerD:getPowerdState()
if self.lipc_handle then
return self.lipc_handle:get_string_property("com.lab126.powerd", "state")
end
end
function KindlePowerD:checkUnexpectedWakeup()
local state = self:getPowerdState()
logger.dbg("Powerd resume state:", state)
-- If we moved on to the active state,
-- then we were woken by user input not our alarm.
if state ~= "screenSaver" and state ~= "suspended" then return end
if self.device.wakeup_mgr:isWakeupAlarmScheduled() and self.device.wakeup_mgr:wakeupAction(90) then
logger.info("Kindle scheduled wakeup")
else
logger.warn("Kindle unscheduled wakeup")
end
end
-- Dummy fuctions. They will be defined in initWakeupMgr
function KindlePowerD:wakeupFromSuspend() end
function KindlePowerD:readyToSuspend() end
-- Support WakeupMgr on Lipc & supportsScreensaver devices.
function KindlePowerD:initWakeupMgr()
if not self.device:supportsScreensaver() then return end
if self.lipc_handle == nil then return end
function KindlePowerD:wakeupFromSuspend(ts)
-- Give the device a few seconds to settle.
-- This filters out user input resumes -> device will resume to active
-- Also the Kindle stays in Ready to suspend for 10 seconds
-- so the alarm may fire 10 seconds early
UIManager:scheduleIn(15, self.checkUnexpectedWakeup, self)
end
function KindlePowerD:readyToSuspend(delay)
if self.device.wakeup_mgr:isWakeupAlarmScheduled() then
local now = os.time()
local alarm = self.device.wakeup_mgr:getWakeupAlarmEpoch()
if alarm > now then
-- Powerd / Lipc need seconds_from_now not epoch
self:setRtcWakeup(alarm - now)
else
-- wakeup time is in the past
self.device.wakeup_mgr:removeTasks(alarm)
end
end
end
self.device.wakeup_mgr = WakeupMgr:new{rtc = require("device/kindle/mockrtc")}
end
-- Ask powerd to reset the t1 timeout, so that AutoSuspend can do its thing properly
function KindlePowerD:resetT1Timeout()
-- NOTE: powerd will only send a t1TimerReset event every $(kdb get system/daemon/powerd/send_t1_reset_interval) (15s),
-- which is just fine, as we should only request it at most every 5 minutes ;).
-- NOTE: This will fail if the device is already showing the screensaver.
if self.lipc_handle then
-- AFAIK, the value is irrelevant
self.lipc_handle:set_int_property("com.lab126.powerd", "touchScreenSaverTimeout", 1)
else
os.execute("lipc-set-prop -i com.lab126.powerd touchScreenSaverTimeout 1")
end
end
function KindlePowerD:beforeSuspend()
-- Inhibit user input and emit the Suspend event.
self.device:_beforeSuspend()
end
function KindlePowerD:afterResume()
self:invalidateCapacityCache()
-- Restore user input and emit the Resume event.
self.device:_afterResume()
if not self.device:hasFrontlight() then
return
end
if self:isFrontlightOn() then
-- The Kindle framework should turn the front light back on automatically.
-- The following statement ensures consistency of intensity, but should basically always be redundant,
-- since we set intensity via lipc and not sysfs ;).
-- NOTE: This is race-y, and we want to *lose* the race, hence the use of the scheduler (c.f., #4392)
UIManager:tickAfterNext(function() self:turnOnFrontlightHW() end)
else
-- But in the off case, we *do* use sysfs, so this one actually matters.
UIManager:tickAfterNext(function() self:turnOffFrontlightHW() end)
end
end
function KindlePowerD:UIManagerReadyHW(uimgr)
UIManager = uimgr
end
--- @fixme: This won't ever fire on its own, as KindlePowerD is already a metatable on a plain table.
function KindlePowerD:__gc()
if self.lipc_handle then
self.lipc_handle:close()
self.lipc_handle = nil
end
end
return KindlePowerD