Assorted bag'o tweaks & fixes (#9569)

* UIManager: Support more specialized update modes for corner-cases:
  * A2, which we'll use for the VirtualKeyboards keys (they'd... inadvertently switched to UI with the highlight refactor).
  * NO_MERGE variants of ui & partial (for sunxi). Use `[ui]` in ReaderHighlight's popup, because of a Sage kernel bug that could otherwise make it translucent, sometimes completely so (*sigh*).
* UIManager: Assorted code cleanups & simplifications.
* Logger & dbg: Unify logging style, and code cleanups.
* SDL: Unbreak suspend/resume outside of the emulator (fix #9567).
* NetworkMgr: Cache the network status, and allow it to be queried. (Used by AutoSuspend to avoid repeatedly poking the system when computing the standby schedule delay).
* OneTimeMigration: Don't forget about `NETWORK_PROXY` & `STARDICT_DATA_DIR` when migrating `defaults.persistent.lua` (fix #9573)
* WakeupMgr: Workaround an apparent limitation of the RTC found on i.MX5 Kobo devices, where setting a wakealarm further than UINT16_MAX seconds in the future would apparently overflow and wraparound... (fix #8039, many thanks to @yfede for the extensive deep-dive and for actually accurately pinpointing the issue!).
* Kobo: Handle standby transitions at full CPU clock speeds, in order to limit the latency hit.
* UIManager: Properly quit on reboot & exit. This ensures our exit code is preserved, as we exit on our own terms (instead of being killed by the init system). This is important on platforms where exit codes are semantically meaningful (e.g., Kobo).
* UIManager: Speaking of reboot & exit, make sure the Screensaver shows in all circumstances (e.g., autoshutdown, re: #9542)), and that there aren't any extraneous refreshes triggered. (Additionally, fix a minor regression since #9448 about tracking this very transient state on Kobo & Cervantes).
* Kindle: ID the upcoming Scribe.
* Bump base (https://github.com/koreader/koreader-base/pull/1524)
reviewable/pr9582/r1
NiLuJe 2 years ago committed by GitHub
parent 5d9f036331
commit 9bf19d1bb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1 +1 @@
Subproject commit 9e4e7e899e06853305d0a2e4b1f103d2a0840068 Subproject commit 99143a2a11f40fb9ea635bfe876ec3dd8ae7b2d4

@ -941,7 +941,9 @@ function ReaderHighlight:onShowHighlightMenu(page, index)
buttons = highlight_buttons, buttons = highlight_buttons,
tap_close_callback = function() self:handleEvent(Event:new("Tap")) end, tap_close_callback = function() self:handleEvent(Event:new("Tap")) end,
} }
UIManager:show(self.highlight_dialog) -- NOTE: Disable merging for this update,
-- or the buggy Sage kernel may alpha-blend it into the page (with a bogus alpha value, to boot)...
UIManager:show(self.highlight_dialog, "[ui]")
end end
dbg:guard(ReaderHighlight, "onShowHighlightMenu", dbg:guard(ReaderHighlight, "onShowHighlightMenu",
function(self) function(self)

@ -787,7 +787,7 @@ function ReaderRolling:onRestoreBookLocation(saved_location)
end end
function ReaderRolling:onGotoViewRel(diff) function ReaderRolling:onGotoViewRel(diff)
logger.dbg("goto relative screen:", diff, ", in mode: ", self.view.view_mode) logger.dbg("goto relative screen:", diff, ", in mode:", self.view.view_mode)
if self.view.view_mode == "scroll" then if self.view.view_mode == "scroll" then
local footer_height = ((self.view.footer_visible and not self.view.footer.settings.reclaim_height) and 1 or 0) * self.view.footer:getHeight() local footer_height = ((self.view.footer_visible and not self.view.footer.settings.reclaim_height) and 1 or 0) * self.view.footer:getHeight()
local page_visible_height = self.ui.dimen.h - footer_height local page_visible_height = self.ui.dimen.h - footer_height

@ -26,28 +26,45 @@ local Dbg = {
-- set to nil so first debug:turnOff call won't be skipped -- set to nil so first debug:turnOff call won't be skipped
is_on = nil, is_on = nil,
is_verbose = nil, is_verbose = nil,
ev_log = nil,
} }
local Dbg_mt = {} local Dbg_mt = {}
local function LvDEBUG(lv, ...) local LvDEBUG
local line = "" if isAndroid then
for i,v in ipairs({...}) do LvDEBUG = function(...)
if type(v) == "table" then local line = {}
line = line .. " " .. dump(v, lv) for _, v in ipairs({...}) do
else if type(v) == "table" then
line = line .. " " .. tostring(v) table.insert(line, dump(v, math.huge))
else
table.insert(line, tostring(v))
end
end end
return android.LOGV(table.concat(line, " "))
end end
if isAndroid then else
android.LOGV(line) LvDEBUG = function(...)
else local line = {
io.stdout:write(string.format("# %s %s\n", os.date("%x-%X"), line)) os.date("%x-%X DEBUG"),
io.stdout:flush() }
for _, v in ipairs({...}) do
if type(v) == "table" then
table.insert(line, dump(v, math.huge))
else
table.insert(line, tostring(v))
end
end
table.insert(line, "\n")
return io.write(table.concat(line, " "))
end end
end end
--- Helper function to help dealing with nils in Dbg:guard...
local function pack_values(...)
return select("#", ...), {...}
end
--- Turn on debug mode. --- Turn on debug mode.
-- This should only be used in tests and at the user's request. -- This should only be used in tests and at the user's request.
function Dbg:turnOn() function Dbg:turnOn()
@ -55,7 +72,7 @@ function Dbg:turnOn()
self.is_on = true self.is_on = true
logger:setLevel(logger.levels.dbg) logger:setLevel(logger.levels.dbg)
Dbg_mt.__call = function(dbg, ...) LvDEBUG(math.huge, ...) end Dbg_mt.__call = function(_, ...) return LvDEBUG(...) end
--- Pass a guard function to detect bad input values. --- Pass a guard function to detect bad input values.
Dbg.guard = function(_, mod, method, pre_guard, post_guard) Dbg.guard = function(_, mod, method, pre_guard, post_guard)
local old_method = mod[method] local old_method = mod[method]
@ -63,11 +80,11 @@ function Dbg:turnOn()
if pre_guard then if pre_guard then
pre_guard(...) pre_guard(...)
end end
local values = {old_method(...)} local n, values = pack_values(old_method(...))
if post_guard then if post_guard then
post_guard(...) post_guard(...)
end end
return unpack(values) return unpack(values, 1, n)
end end
end end
--- Use this instead of a regular Lua @{assert}(). --- Use this instead of a regular Lua @{assert}().
@ -75,19 +92,6 @@ function Dbg:turnOn()
assert(check, msg) assert(check, msg)
return check return check
end end
-- create or clear ev log file
--- @note: On Linux, use CLOEXEC to avoid polluting the fd table of our child processes.
--- Otherwise, it can be problematic w/ wpa_supplicant & USBMS...
--- Note that this is entirely undocumented, but at least LuaJIT passes the mode as-is to fopen, so, we're good.
local open_flags = "w"
if jit.os == "Linux" then
-- Oldest Kindle devices are too old to support O_CLOEXEC...
if os.getenv("KINDLE_LEGACY") ~= "yes" then
open_flags = "we"
end
end
self.ev_log = io.open("ev.log", open_flags)
end end
--- Turn off debug mode. --- Turn off debug mode.
@ -96,15 +100,12 @@ function Dbg:turnOff()
if self.is_on == false then return end if self.is_on == false then return end
self.is_on = false self.is_on = false
logger:setLevel(logger.levels.info) logger:setLevel(logger.levels.info)
function Dbg_mt.__call() end Dbg_mt.__call = function() end
function Dbg.guard() end -- NOTE: This doesn't actually disengage previously wrapped methods!
Dbg.guard = function() end
Dbg.dassert = function(check) Dbg.dassert = function(check)
return check return check
end end
if self.ev_log then
self.ev_log:close()
self.ev_log = nil
end
end end
--- Turn on verbose mode. --- Turn on verbose mode.
@ -116,24 +117,13 @@ end
--- Simple table dump. --- Simple table dump.
function Dbg:v(...) function Dbg:v(...)
if self.is_verbose then if self.is_verbose then
LvDEBUG(math.huge, ...) return LvDEBUG(...)
end
end
--- Log @{ui.event|Event} to dedicated log file.
function Dbg:logEv(ev)
local ev_value = tostring(ev.value)
local log = ev.type.."|"..ev.code.."|"
..ev_value.."|"..ev.time.sec.."|"..ev.time.usec.."\n"
if self.ev_log then
self.ev_log:write(log)
self.ev_log:flush()
end end
end end
--- Simple traceback. --- Simple traceback.
function Dbg:traceback() function Dbg:traceback()
LvDEBUG(math.huge, debug.traceback()) return LvDEBUG(debug.traceback())
end end
setmetatable(Dbg, Dbg_mt) setmetatable(Dbg, Dbg_mt)

@ -230,10 +230,10 @@ function Cervantes:resume()
os.execute("./resume.sh") os.execute("./resume.sh")
end end
function Cervantes:reboot() function Cervantes:reboot()
os.execute("reboot") os.execute("sleep 1 && reboot &")
end end
function Cervantes:powerOff() function Cervantes:powerOff()
os.execute("halt") os.execute("sleep 1 && halt &")
end end
-- This method is the same as the one in kobo/device.lua except the sleep cover part. -- This method is the same as the one in kobo/device.lua except the sleep cover part.
@ -241,11 +241,11 @@ function Cervantes:setEventHandlers(UIManager)
-- We do not want auto suspend procedure to waste battery during -- We do not want auto suspend procedure to waste battery during
-- 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.
UIManager.event_handlers["Suspend"] = function() UIManager.event_handlers.Suspend = function()
self:_beforeSuspend() self:_beforeSuspend()
self:onPowerEvent("Suspend") self:onPowerEvent("Suspend")
end end
UIManager.event_handlers["Resume"] = function() UIManager.event_handlers.Resume = function()
-- MONOTONIC doesn't tick during suspend, -- MONOTONIC doesn't tick during suspend,
-- invalidate the last battery capacity pull time so that we get up to date data immediately. -- invalidate the last battery capacity pull time so that we get up to date data immediately.
self:getPowerDevice():invalidateCapacityCache() self:getPowerDevice():invalidateCapacityCache()
@ -253,59 +253,59 @@ function Cervantes:setEventHandlers(UIManager)
self:onPowerEvent("Resume") self:onPowerEvent("Resume")
self:_afterResume() self:_afterResume()
end end
UIManager.event_handlers["PowerPress"] = function() UIManager.event_handlers.PowerPress = function()
-- Always schedule power off. -- Always schedule power off.
-- Press the power button for 2+ seconds to shutdown directly from suspend. -- Press the power button for 2+ seconds to shutdown directly from suspend.
UIManager:scheduleIn(2, UIManager.poweroff_action) UIManager:scheduleIn(2, UIManager.poweroff_action)
end end
UIManager.event_handlers["PowerRelease"] = function() UIManager.event_handlers.PowerRelease = function()
if not self._entered_poweroff_stage then if not UIManager._entered_poweroff_stage then
UIManager:unschedule(UIManager.poweroff_action) UIManager:unschedule(UIManager.poweroff_action)
-- resume if we were suspended -- resume if we were suspended
if self.screen_saver_mode then if self.screen_saver_mode then
UIManager.event_handlers["Resume"]() UIManager.event_handlers.Resume()
else else
UIManager.event_handlers["Suspend"]() UIManager.event_handlers.Suspend()
end end
end end
end end
UIManager.event_handlers["Light"] = function() UIManager.event_handlers.Light = function()
self:getPowerDevice():toggleFrontlight() self:getPowerDevice():toggleFrontlight()
end end
-- USB plug events with a power-only charger -- USB plug events with a power-only charger
UIManager.event_handlers["Charging"] = function() UIManager.event_handlers.Charging = function()
self:_beforeCharging() self:_beforeCharging()
-- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep. -- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep.
if self.screen_saver_mode then if self.screen_saver_mode then
UIManager.event_handlers["Suspend"]() UIManager.event_handlers.Suspend()
end end
end end
UIManager.event_handlers["NotCharging"] = function() UIManager.event_handlers.NotCharging = function()
-- We need to put the device into suspension, other things need to be done before it. -- We need to put the device into suspension, other things need to be done before it.
self:usbPlugOut() self:usbPlugOut()
self:_afterNotCharging() self:_afterNotCharging()
if self.screen_saver_mode then if self.screen_saver_mode then
UIManager.event_handlers["Suspend"]() UIManager.event_handlers.Suspend()
end end
end end
-- USB plug events with a data-aware host -- USB plug events with a data-aware host
UIManager.event_handlers["UsbPlugIn"] = function() UIManager.event_handlers.UsbPlugIn = function()
self:_beforeCharging() self:_beforeCharging()
-- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep. -- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep.
if self.screen_saver_mode then if self.screen_saver_mode then
UIManager.event_handlers["Suspend"]() UIManager.event_handlers.Suspend()
else else
-- Potentially start an USBMS session -- Potentially start an USBMS session
local MassStorage = require("ui/elements/mass_storage") local MassStorage = require("ui/elements/mass_storage")
MassStorage:start() MassStorage:start()
end end
end end
UIManager.event_handlers["UsbPlugOut"] = function() UIManager.event_handlers.UsbPlugOut = function()
-- We need to put the device into suspension, other things need to be done before it. -- We need to put the device into suspension, other things need to be done before it.
self:usbPlugOut() self:usbPlugOut()
self:_afterNotCharging() self:_afterNotCharging()
if self.screen_saver_mode then if self.screen_saver_mode then
UIManager.event_handlers["Suspend"]() UIManager.event_handlers.Suspend()
else else
-- Potentially dismiss the USBMS ConfirmBox -- Potentially dismiss the USBMS ConfirmBox
local MassStorage = require("ui/elements/mass_storage") local MassStorage = require("ui/elements/mass_storage")

@ -350,8 +350,7 @@ function Device:install()
ok_callback = function() ok_callback = function()
local save_quit = function() local save_quit = function()
self:saveSettings() self:saveSettings()
UIManager:quit() UIManager:quit(85)
UIManager._exit_code = 85
end end
UIManager:broadcastEvent(Event:new("Exit", save_quit)) UIManager:broadcastEvent(Event:new("Exit", save_quit))
end, end,
@ -384,9 +383,11 @@ function Device:suspend() end
-- Hardware specific method to resume the device -- Hardware specific method to resume the device
function Device:resume() end function Device:resume() end
-- NOTE: These two should ideally run in the background, and only trip the action after a small delay,
-- to give us time to quit first.
-- e.g., os.execute("sleep 1 && shutdown -r now &")
-- Hardware specific method to power off the device -- Hardware specific method to power off the device
function Device:powerOff() end function Device:powerOff() end
-- Hardware specific method to reboot the device -- Hardware specific method to reboot the device
function Device:reboot() end function Device:reboot() end
@ -576,7 +577,7 @@ end
-- Set device event handlers common to all devices -- Set device event handlers common to all devices
function Device:_setEventHandlers(UIManager) function Device:_setEventHandlers(UIManager)
if self:canReboot() then if self:canReboot() then
UIManager.event_handlers["Reboot"] = function() UIManager.event_handlers.Reboot = function()
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = _("Are you sure you want to reboot the device?"), text = _("Are you sure you want to reboot the device?"),
@ -589,11 +590,11 @@ function Device:_setEventHandlers(UIManager)
}) })
end end
else else
UIManager.event_handlers["Reboot"] = function() end UIManager.event_handlers.Reboot = function() end
end end
if self:canPowerOff() then if self:canPowerOff() then
UIManager.event_handlers["PowerOff"] = function() UIManager.event_handlers.PowerOff = function()
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = _("Are you sure you want to power off the device?"), text = _("Are you sure you want to power off the device?"),
@ -606,7 +607,7 @@ function Device:_setEventHandlers(UIManager)
}) })
end end
else else
UIManager.event_handlers["PowerOff"] = function() end UIManager.event_handlers.PowerOff = function() end
end end
self:setEventHandlers(UIManager) self:setEventHandlers(UIManager)
@ -615,10 +616,10 @@ end
-- Devices can add additional event handlers by overwriting this method. -- Devices can add additional event handlers by overwriting this method.
function Device:setEventHandlers(UIManager) function Device:setEventHandlers(UIManager)
-- These will be most probably overwritten in the device specific `setEventHandlers` -- These will be most probably overwritten in the device specific `setEventHandlers`
UIManager.event_handlers["Suspend"] = function() UIManager.event_handlers.Suspend = function()
self:_beforeSuspend(false) self:_beforeSuspend(false)
end end
UIManager.event_handlers["Resume"] = function() UIManager.event_handlers.Resume = function()
self:_afterResume(false) self:_afterResume(false)
end end
end end

@ -155,7 +155,7 @@ function BasePowerD:unchecked_read_int_file(file)
fd:close() fd:close()
return int return int
else else
return return nil
end end
end end

@ -1287,7 +1287,6 @@ function Input:waitEvent(now, deadline)
-- NOTE: This is rather spammy and computationally intensive, -- NOTE: This is rather spammy and computationally intensive,
-- and we can't conditionally prevent evalutation of function arguments, -- and we can't conditionally prevent evalutation of function arguments,
-- so, just hide the whole thing behind a branch ;). -- so, just hide the whole thing behind a branch ;).
DEBUG:logEv(event)
if event.type == C.EV_KEY then if event.type == C.EV_KEY then
logger.dbg(string.format( logger.dbg(string.format(
"key event => code: %d (%s), value: %s, time: %d.%06d", "key event => code: %d (%s), value: %s, time: %d.%06d",

@ -369,29 +369,29 @@ function Kindle:readyToSuspend()
end end
function Kindle:setEventHandlers(UIManager) function Kindle:setEventHandlers(UIManager)
UIManager.event_handlers["Suspend"] = function() UIManager.event_handlers.Suspend = function()
self.powerd:toggleSuspend() self.powerd:toggleSuspend()
end end
UIManager.event_handlers["IntoSS"] = function() UIManager.event_handlers.IntoSS = function()
self:_beforeSuspend() self:_beforeSuspend()
self:intoScreenSaver() self:intoScreenSaver()
end end
UIManager.event_handlers["OutOfSS"] = function() UIManager.event_handlers.OutOfSS = function()
self:outofScreenSaver() self:outofScreenSaver()
self:_afterResume() self:_afterResume()
end end
UIManager.event_handlers["Charging"] = function() UIManager.event_handlers.Charging = function()
self:_beforeCharging() self:_beforeCharging()
self:usbPlugIn() self:usbPlugIn()
end end
UIManager.event_handlers["NotCharging"] = function() UIManager.event_handlers.NotCharging = function()
self:usbPlugOut() self:usbPlugOut()
self:_afterNotCharging() self:_afterNotCharging()
end end
UIManager.event_handlers["WakeupFromSuspend"] = function() UIManager.event_handlers.WakeupFromSuspend = function()
self:wakeupFromSuspend() self:wakeupFromSuspend()
end end
UIManager.event_handlers["ReadyToSuspend"] = function() UIManager.event_handlers.ReadyToSuspend = function()
self:readyToSuspend() self:readyToSuspend()
end end
end end
@ -599,6 +599,20 @@ local KindlePaperWhite5 = Kindle:new{
canDoSwipeAnimation = yes, canDoSwipeAnimation = yes,
} }
local KindleScribe = Kindle:new{
model = "KindleScribe",
isMTK = yes,
isTouchDevice = yes,
hasFrontlight = yes,
hasNaturalLight = yes,
hasNaturalLightMixer = yes,
display_dpi = 300,
-- TBD
touch_dev = "/dev/input/by-path/platform-1001e000.i2c-event",
canHWDither = no,
canDoSwipeAnimation = yes,
}
function Kindle2:init() function Kindle2:init()
self.screen = require("ffi/framebuffer_einkfb"):new{device = self, debug = logger.dbg} self.screen = require("ffi/framebuffer_einkfb"):new{device = self, debug = logger.dbg}
self.powerd = require("device/kindle/powerd"):new{ self.powerd = require("device/kindle/powerd"):new{
@ -1096,6 +1110,27 @@ function KindlePaperWhite5:init()
self.input.open("fake_events") self.input.open("fake_events")
end end
function KindleScribe:init()
self.screen = require("ffi/framebuffer_mxcfb"):new{device = self, debug = logger.dbg}
-- TBD, assume PW5 for now
self.powerd = require("device/kindle/powerd"):new{
device = self,
fl_intensity_file = "/sys/class/backlight/fp9966-bl1/brightness",
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.
self.screen:_MTK_ToggleFastMode(true)
Kindle.init(self)
self.input.open(self.touch_dev)
self.input.open("fake_events")
end
function KindleTouch:exit() function KindleTouch:exit()
if self:isMTK() then if self:isMTK() then
-- Disable the so-called "fast" mode -- Disable the so-called "fast" mode
@ -1135,6 +1170,7 @@ KindlePaperWhite4.exit = KindleTouch.exit
KindleBasic3.exit = KindleTouch.exit KindleBasic3.exit = KindleTouch.exit
KindleOasis3.exit = KindleTouch.exit KindleOasis3.exit = KindleTouch.exit
KindlePaperWhite5.exit = KindleTouch.exit KindlePaperWhite5.exit = KindleTouch.exit
KindleScribe.exit = KindleTouch.exit
function Kindle3:exit() function Kindle3:exit()
-- send double menu key press events to trigger screen refresh -- send double menu key press events to trigger screen refresh
@ -1188,7 +1224,8 @@ local pw4_set = Set { "0PP", "0T1", "0T2", "0T3", "0T4", "0T5", "0T6",
"16Q", "16R", "16S", "16T", "16U", "16V" } "16Q", "16R", "16S", "16T", "16U", "16V" }
local kt4_set = Set { "10L", "0WF", "0WG", "0WH", "0WJ", "0VB" } local kt4_set = Set { "10L", "0WF", "0WG", "0WH", "0WJ", "0VB" }
local koa3_set = Set { "11L", "0WQ", "0WP", "0WN", "0WM", "0WL" } local koa3_set = Set { "11L", "0WQ", "0WP", "0WN", "0WM", "0WL" }
local pw5_set = Set { "1LG", "1Q0", "1PX", "1VD", "219", "21A", "2BH", "2BJ" } local pw5_set = Set { "1LG", "1Q0", "1PX", "1VD", "219", "21A", "2BH", "2BJ", "2DK" }
local scribe_set = Set { "22D", "25T", "23A", "2AQ", "2AP", "1XH", "22C" }
if kindle_sn_lead == "B" or kindle_sn_lead == "9" then if kindle_sn_lead == "B" or kindle_sn_lead == "9" then
local kindle_devcode = string.sub(kindle_sn, 3, 4) local kindle_devcode = string.sub(kindle_sn, 3, 4)
@ -1233,6 +1270,8 @@ else
return KindleOasis3 return KindleOasis3
elseif pw5_set[kindle_devcode_v2] then elseif pw5_set[kindle_devcode_v2] then
return KindlePaperWhite5 return KindlePaperWhite5
elseif scribe_set[kindle_devcode_v2] then
return KindleScribe
end end
end end

@ -1,6 +1,8 @@
local Generic = require("device/generic/device") local Generic = require("device/generic/device")
local Geom = require("ui/geometry") local Geom = require("ui/geometry")
local UIManager -- Updated on UIManager init
local WakeupMgr = require("device/wakeupmgr") local WakeupMgr = require("device/wakeupmgr")
local time = require("ui/time")
local ffiUtil = require("ffi/util") local ffiUtil = require("ffi/util")
local lfs = require("libs/libkoreader-lfs") local lfs = require("libs/libkoreader-lfs")
local logger = require("logger") local logger = require("logger")
@ -27,6 +29,31 @@ local function koboEnableWifi(toggle)
end end
end end
-- NOTE: Cheap-ass way of checking if Wi-Fi seems to be enabled...
-- Since the crux of the issues lies in race-y module unloading, this is perfectly fine for our usage.
local function koboIsWifiOn()
local needle = os.getenv("WIFI_MODULE") or "sdio_wifi_pwr"
local nlen = #needle
-- /proc/modules is usually empty, unless Wi-Fi or USB is enabled
-- We could alternatively check if lfs.attributes("/proc/sys/net/ipv4/conf/" .. os.getenv("INTERFACE"), "mode") == "directory"
-- c.f., also what Cervantes does via /sys/class/net/eth0/carrier to check if the interface is up.
-- That said, since we only care about whether *modules* are loaded, this does the job nicely.
local f = io.open("/proc/modules", "re")
if not f then
return false
end
local found = false
for haystack in f:lines() do
if haystack:sub(1, nlen) == needle then
found = true
break
end
end
f:close()
return found
end
-- checks if standby is available on the device -- checks if standby is available on the device
local function checkStandby() local function checkStandby()
logger.dbg("Kobo: checking if standby is possible ...") logger.dbg("Kobo: checking if standby is possible ...")
@ -63,6 +90,47 @@ local function writeToSys(val, file)
return nw == bytes return nw == bytes
end end
-- Return the highest core number
local function getCPUCount()
local fd = io.open("/sys/devices/system/cpu/possible", "re")
if fd then
local str = fd:read("*line")
fd:close()
-- Format is n-N, where n is the first core, and N the last (e.g., 0-3)
return tonumber(str:match("%d+$")) or 1
else
return 1
end
end
local function getCPUGovernor(knob)
local fd = io.open(knob, "re")
if fd then
local str = fd:read("*line")
fd:close()
-- If we're currently using the userspace governor, fudge that to conservative, as we won't ever standby with Wi-Fi on.
-- (userspace is only used on i.MX5 for DVFS shenanigans when Wi-Fi is enabled)
if str == "userspace" then
str = "conservative"
end
return str
else
return nil
end
end
local function getRTCName()
local fd = io.open("/sys/class/rtc/rtc0/name", "re")
if fd then
local str = fd:read("*line")
fd:close()
return str
else
return nil
end
end
local Kobo = Generic:new{ local Kobo = Generic:new{
model = "Kobo", model = "Kobo",
isKobo = yes, isKobo = yes,
@ -74,6 +142,7 @@ local Kobo = Generic:new{
canReboot = yes, canReboot = yes,
canPowerOff = yes, canPowerOff = yes,
canSuspend = yes, canSuspend = yes,
supportsScreensaver = yes,
-- most Kobos have X/Y switched for the touch screen -- most Kobos have X/Y switched for the touch screen
touch_switch_xy = true, touch_switch_xy = true,
-- most Kobos have also mirrored X coordinates -- most Kobos have also mirrored X coordinates
@ -477,7 +546,6 @@ end
function Kobo:getKeyRepeat() function Kobo:getKeyRepeat()
-- Sanity check (mostly for the testsuite's benefit...) -- Sanity check (mostly for the testsuite's benefit...)
if not self.ntx_fd then if not self.ntx_fd then
self.hasKeyRepeat = false
return false return false
end end
@ -485,20 +553,14 @@ function Kobo:getKeyRepeat()
if C.ioctl(self.ntx_fd, C.EVIOCGREP, self.key_repeat) < 0 then if C.ioctl(self.ntx_fd, C.EVIOCGREP, self.key_repeat) < 0 then
local err = ffi.errno() local err = ffi.errno()
logger.warn("Device:getKeyRepeat: EVIOCGREP ioctl failed:", ffi.string(C.strerror(err))) logger.warn("Device:getKeyRepeat: EVIOCGREP ioctl failed:", ffi.string(C.strerror(err)))
self.hasKeyRepeat = false return false
else else
self.hasKeyRepeat = true
logger.dbg("Key repeat is set up to repeat every", self.key_repeat[C.REP_PERIOD], "ms after a delay of", self.key_repeat[C.REP_DELAY], "ms") logger.dbg("Key repeat is set up to repeat every", self.key_repeat[C.REP_PERIOD], "ms after a delay of", self.key_repeat[C.REP_DELAY], "ms")
return true
end end
return self.hasKeyRepeat
end end
function Kobo:disableKeyRepeat() function Kobo:disableKeyRepeat()
if not self.hasKeyRepeat then
return
end
-- NOTE: LuaJIT zero inits, and PERIOD == 0 with DELAY == 0 disables repeats ;). -- NOTE: LuaJIT zero inits, and PERIOD == 0 with DELAY == 0 disables repeats ;).
local key_repeat = ffi.new("unsigned int[?]", C.REP_CNT) local key_repeat = ffi.new("unsigned int[?]", C.REP_CNT)
if C.ioctl(self.ntx_fd, C.EVIOCSREP, key_repeat) < 0 then if C.ioctl(self.ntx_fd, C.EVIOCSREP, key_repeat) < 0 then
@ -508,10 +570,6 @@ function Kobo:disableKeyRepeat()
end end
function Kobo:restoreKeyRepeat() function Kobo:restoreKeyRepeat()
if not self.hasKeyRepeat then
return
end
if C.ioctl(self.ntx_fd, C.EVIOCSREP, self.key_repeat) < 0 then if C.ioctl(self.ntx_fd, C.EVIOCSREP, self.key_repeat) < 0 then
local err = ffi.errno() local err = ffi.errno()
logger.warn("Device:restoreKeyRepeat: EVIOCSREP ioctl failed:", ffi.string(C.strerror(err))) logger.warn("Device:restoreKeyRepeat: EVIOCSREP ioctl failed:", ffi.string(C.strerror(err)))
@ -606,6 +664,35 @@ function Kobo:init()
end end
end end
-- NOTE: i.MX5 devices have a wonky RTC that doesn't like alarms set further away that UINT16_MAX seconds from now...
-- (c.f., WakeupMgr for more details).
-- NOTE: getRTCName is currently hardcoded to rtc0 (which is also WakeupMgr's default).
local dodgy_rtc = false
if getRTCName() == "pmic_rtc" then
-- This *should* match the 'RTC' (46) NTX HWConfig field being set to 'MSP430' (0).
dodgy_rtc = true
end
-- Detect the various CPU governor sysfs knobs...
if util.pathExists("/sys/devices/system/cpu/cpufreq/policy0") then
self.cpu_governor_knob = "/sys/devices/system/cpu/cpufreq/policy0/scaling_governor"
else
self.cpu_governor_knob = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"
end
self.default_cpu_governor = getCPUGovernor(self.cpu_governor_knob)
-- NOP unsupported methods
if not self.default_cpu_governor then
self.performanceCPUGovernor = function() end
self.defaultCPUGovernor = function() end
end
-- And while we're on CPU-related endeavors...
self.cpu_count = self:isSMP() and getCPUCount() or 1
-- NOP unsupported methods
if self.cpu_count == 1 then
self.enableCPUCores = function() end
end
-- Automagically set this so we never have to remember to do it manually ;p -- Automagically set this so we never have to remember to do it manually ;p
if self:hasNaturalLight() and self.frontlight_settings and self.frontlight_settings.frontlight_mixer then if self:hasNaturalLight() and self.frontlight_settings and self.frontlight_settings.frontlight_mixer then
self.hasNaturalLightMixer = yes self.hasNaturalLightMixer = yes
@ -651,7 +738,9 @@ function Kobo:init()
main_finger_slot = self.main_finger_slot or 0, main_finger_slot = self.main_finger_slot or 0,
pressure_event = self.pressure_event, pressure_event = self.pressure_event,
} }
self.wakeup_mgr = WakeupMgr:new() self.wakeup_mgr = WakeupMgr:new{
dodgy_rtc = dodgy_rtc,
}
Generic.init(self) Generic.init(self)
@ -677,7 +766,17 @@ function Kobo:init()
self:initEventAdjustHooks() self:initEventAdjustHooks()
-- See if the device supports key repeat -- See if the device supports key repeat
self:getKeyRepeat() if not self:getKeyRepeat() then
-- NOP unsupported methods
self.disableKeyRepeat = function() end
self.restoreKeyRepeat = function() end
end
-- NOP unsupported methods
if not self:canToggleChargingLED() then
self.toggleChargingLED = function() end
self.setupChargingLED = function() end
end
-- We have no way of querying the current state of the charging LED, so, start from scratch. -- We have no way of querying the current state of the charging LED, so, start from scratch.
-- Much like Nickel, start by turning it off. -- Much like Nickel, start by turning it off.
@ -754,34 +853,9 @@ function Kobo:initNetworkManager(NetworkMgr)
os.execute("./restore-wifi-async.sh") os.execute("./restore-wifi-async.sh")
end end
-- NOTE: Cheap-ass way of checking if Wi-Fi seems to be enabled... NetworkMgr.isWifiOn = koboIsWifiOn
-- Since the crux of the issues lies in race-y module unloading, this is perfectly fine for our usage.
function NetworkMgr:isWifiOn()
local needle = os.getenv("WIFI_MODULE") or "sdio_wifi_pwr"
local nlen = #needle
-- /proc/modules is usually empty, unless Wi-Fi or USB is enabled
-- We could alternatively check if lfs.attributes("/proc/sys/net/ipv4/conf/" .. os.getenv("INTERFACE"), "mode") == "directory"
-- c.f., also what Cervantes does via /sys/class/net/eth0/carrier to check if the interface is up.
-- That said, since we only care about whether *modules* are loaded, this does the job nicely.
local f = io.open("/proc/modules", "re")
if not f then
return false
end
local found = false
for haystack in f:lines() do
if haystack:sub(1, nlen) == needle then
found = true
break
end
end
f:close()
return found
end
end end
function Kobo:supportsScreensaver() return true end
function Kobo:setTouchEventHandler() function Kobo:setTouchEventHandler()
if self.touch_snow_protocol then if self.touch_snow_protocol then
self.input.snow_protocol = true self.input.snow_protocol = true
@ -872,14 +946,12 @@ end
-- NOTE: We overload this to make sure checkUnexpectedWakeup doesn't trip *before* the newly scheduled suspend -- NOTE: We overload this to make sure checkUnexpectedWakeup doesn't trip *before* the newly scheduled suspend
function Kobo:rescheduleSuspend() function Kobo:rescheduleSuspend()
local UIManager = require("ui/uimanager")
UIManager:unschedule(self.suspend) UIManager:unschedule(self.suspend)
UIManager:unschedule(self.checkUnexpectedWakeup) UIManager:unschedule(self.checkUnexpectedWakeup)
UIManager:scheduleIn(self.suspend_wait_timeout, self.suspend, self) UIManager:scheduleIn(self.suspend_wait_timeout, self.suspend, self)
end end
function Kobo:checkUnexpectedWakeup() function Kobo:checkUnexpectedWakeup()
local UIManager = require("ui/uimanager")
-- Just in case another event like SleepCoverClosed also scheduled a suspend -- Just in case another event like SleepCoverClosed also scheduled a suspend
UIManager:unschedule(self.suspend) UIManager:unschedule(self.suspend)
@ -911,6 +983,20 @@ 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)
-- NOTE: Switch to the performance CPU governor, in order to speed up the resume process so as to lower its latency cost...
-- (It won't have any impact on power efficiency *during* suspend, so there's not really any drawback).
self:performanceCPUGovernor()
--[[
-- On most devices, attempting to PM with a Wi-Fi module loaded will horribly crash the kernel, so, don't?
-- NOTE: Much like suspend, our caller should ensure this never happens, hence this being commented out ;).
if koboIsWifiOn() then
-- AutoSuspend relies on NetworkMgr:getWifiState to prevent this, so, if we ever trip this, it's a bug ;).
logger.err("Kobo standby: cannot standby with Wi-Fi modules loaded! (NetworkMgr is confused: this is a bug)")
return
end
--]]
-- We don't really have anything to schedule, we just need an alarm out of WakeupMgr ;). -- We don't really have anything to schedule, we just need an alarm out of WakeupMgr ;).
local function standby_alarm() local function standby_alarm()
end end
@ -919,7 +1005,6 @@ function Kobo:standby(max_duration)
self.wakeup_mgr:addTask(max_duration, standby_alarm) self.wakeup_mgr:addTask(max_duration, standby_alarm)
end end
local time = require("ui/time")
logger.info("Kobo standby: asking to enter standby . . .") logger.info("Kobo standby: asking to enter standby . . .")
local standby_time = time.boottime_or_realtime_coarse() local standby_time = time.boottime_or_realtime_coarse()
@ -949,11 +1034,13 @@ function Kobo:standby(max_duration)
--]] --]]
self.wakeup_mgr:removeTasks(nil, standby_alarm) self.wakeup_mgr:removeTasks(nil, standby_alarm)
end end
-- And restore the standard CPU scheduler once we're done dealing with the wakeup event.
UIManager:tickAfterNext(self.defaultCPUGovernor, self)
end end
function Kobo:suspend() function Kobo:suspend()
logger.info("Kobo suspend: going to sleep . . .") logger.info("Kobo suspend: going to sleep . . .")
local UIManager = require("ui/uimanager")
UIManager:unschedule(self.checkUnexpectedWakeup) UIManager:unschedule(self.checkUnexpectedWakeup)
-- 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...
@ -1020,7 +1107,6 @@ function Kobo:suspend()
end end
--]] --]]
local time = require("ui/time")
logger.info("Kobo suspend: asking for a suspend to RAM . . .") logger.info("Kobo suspend: asking for a suspend to RAM . . .")
local suspend_time = time.boottime_or_realtime_coarse() local suspend_time = time.boottime_or_realtime_coarse()
@ -1072,7 +1158,6 @@ function Kobo:resume()
-- Reset unexpected_wakeup_count ASAP -- Reset unexpected_wakeup_count ASAP
self.unexpected_wakeup_count = 0 self.unexpected_wakeup_count = 0
-- Unschedule the checkUnexpectedWakeup shenanigans. -- Unschedule the checkUnexpectedWakeup shenanigans.
local UIManager = require("ui/uimanager")
UIManager:unschedule(self.checkUnexpectedWakeup) UIManager:unschedule(self.checkUnexpectedWakeup)
UIManager:unschedule(self.suspend) UIManager:unschedule(self.suspend)
@ -1122,11 +1207,11 @@ function Kobo:powerOff()
self.wakeup_mgr: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("sleep 1 && poweroff -f &")
end end
function Kobo:reboot() function Kobo:reboot()
os.execute("reboot") os.execute("sleep 1 && reboot &")
end end
function Kobo:toggleGSensor(toggle) function Kobo:toggleGSensor(toggle)
@ -1138,10 +1223,6 @@ function Kobo:toggleGSensor(toggle)
end end
function Kobo:toggleChargingLED(toggle) function Kobo:toggleChargingLED(toggle)
if not self:canToggleChargingLED() then
return
end
-- We have no way of querying the current state from the HW! -- We have no way of querying the current state from the HW!
if toggle == nil then if toggle == nil then
return return
@ -1204,33 +1285,20 @@ function Kobo:toggleChargingLED(toggle)
end end
-- Return the highest core number -- Return the highest core number
local function getCPUCount() function Kobo:getCPUCount()
local fd = io.open("/sys/devices/system/cpu/possible", "re") local fd = io.open("/sys/devices/system/cpu/possible", "re")
if fd then if fd then
local str = fd:read("*line") local str = fd:read("*line")
fd:close() fd:close()
-- Format is n-N, where n is the first core, and N the last (e.g., 0-3) -- Format is n-N, where n is the first core, and N the last (e.g., 0-3)
return tonumber(str:match("%d+$")) or 0 return tonumber(str:match("%d+$")) or 1
else else
return 0 return 1
end end
end end
function Kobo:enableCPUCores(amount) function Kobo:enableCPUCores(amount)
if not self:isSMP() then
return
end
if not self.cpu_count then
self.cpu_count = getCPUCount()
end
-- Not actually SMP or getCPUCount failed...
if self.cpu_count == 0 then
return
end
-- CPU0 is *always* online ;). -- CPU0 is *always* online ;).
for n=1, self.cpu_count do for n=1, self.cpu_count do
local path = "/sys/devices/system/cpu/cpu" .. n .. "/online" local path = "/sys/devices/system/cpu/cpu" .. n .. "/online"
@ -1249,6 +1317,14 @@ function Kobo:enableCPUCores(amount)
end end
end end
function Kobo:performanceCPUGovernor()
writeToSys("performance", self.cpu_governor_knob)
end
function Kobo:defaultCPUGovernor()
writeToSys(self.default_cpu_governor, self.cpu_governor_knob)
end
function Kobo:isStartupScriptUpToDate() function Kobo:isStartupScriptUpToDate()
-- Compare the hash of the *active* script (i.e., the one in /tmp) to the *potential* one (i.e., the one in KOREADER_DIR) -- Compare the hash of the *active* script (i.e., the one in /tmp) to the *potential* one (i.e., the one in KOREADER_DIR)
local current_script = "/tmp/koreader.sh" local current_script = "/tmp/koreader.sh"
@ -1258,15 +1334,18 @@ function Kobo:isStartupScriptUpToDate()
return md5.sumFile(current_script) == md5.sumFile(new_script) return md5.sumFile(current_script) == md5.sumFile(new_script)
end end
function Kobo:setEventHandlers(UIManager) function Kobo:setEventHandlers(uimgr)
-- Update our module-local
UIManager = uimgr
-- We do not want auto suspend procedure to waste battery during -- We do not want auto suspend procedure to waste battery during
-- 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.
UIManager.event_handlers["Suspend"] = function() UIManager.event_handlers.Suspend = function()
self:_beforeSuspend() self:_beforeSuspend()
self:onPowerEvent("Suspend") self:onPowerEvent("Suspend")
end end
UIManager.event_handlers["Resume"] = function() UIManager.event_handlers.Resume = function()
-- MONOTONIC doesn't tick during suspend, -- MONOTONIC doesn't tick during suspend,
-- invalidate the last battery capacity pull time so that we get up to date data immediately. -- invalidate the last battery capacity pull time so that we get up to date data immediately.
self:getPowerDevice():invalidateCapacityCache() self:getPowerDevice():invalidateCapacityCache()
@ -1274,59 +1353,59 @@ function Kobo:setEventHandlers(UIManager)
self:onPowerEvent("Resume") self:onPowerEvent("Resume")
self:_afterResume() self:_afterResume()
end end
UIManager.event_handlers["PowerPress"] = function() UIManager.event_handlers.PowerPress = function()
-- Always schedule power off. -- Always schedule power off.
-- Press the power button for 2+ seconds to shutdown directly from suspend. -- Press the power button for 2+ seconds to shutdown directly from suspend.
UIManager:scheduleIn(2, UIManager.poweroff_action) UIManager:scheduleIn(2, UIManager.poweroff_action)
end end
UIManager.event_handlers["PowerRelease"] = function() UIManager.event_handlers.PowerRelease = function()
if not self._entered_poweroff_stage then if not UIManager._entered_poweroff_stage then
UIManager:unschedule(UIManager.poweroff_action) UIManager:unschedule(UIManager.poweroff_action)
-- resume if we were suspended -- resume if we were suspended
if self.screen_saver_mode then if self.screen_saver_mode then
UIManager.event_handlers["Resume"]() UIManager.event_handlers.Resume()
else else
UIManager.event_handlers["Suspend"]() UIManager.event_handlers.Suspend()
end end
end end
end end
UIManager.event_handlers["Light"] = function() UIManager.event_handlers.Light = function()
self:getPowerDevice():toggleFrontlight() self:getPowerDevice():toggleFrontlight()
end end
-- USB plug events with a power-only charger -- USB plug events with a power-only charger
UIManager.event_handlers["Charging"] = function() UIManager.event_handlers.Charging = function()
self:_beforeCharging() self:_beforeCharging()
-- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep. -- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep.
if self.screen_saver_mode then if self.screen_saver_mode then
UIManager.event_handlers["Suspend"]() UIManager.event_handlers.Suspend()
end end
end end
UIManager.event_handlers["NotCharging"] = function() UIManager.event_handlers.NotCharging = function()
-- We need to put the device into suspension, other things need to be done before it. -- We need to put the device into suspension, other things need to be done before it.
self:usbPlugOut() self:usbPlugOut()
self:_afterNotCharging() self:_afterNotCharging()
if self.screen_saver_mode then if self.screen_saver_mode then
UIManager.event_handlers["Suspend"]() UIManager.event_handlers.Suspend()
end end
end end
-- USB plug events with a data-aware host -- USB plug events with a data-aware host
UIManager.event_handlers["UsbPlugIn"] = function() UIManager.event_handlers.UsbPlugIn = function()
self:_beforeCharging() self:_beforeCharging()
-- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep. -- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep.
if self.screen_saver_mode then if self.screen_saver_mode then
UIManager.event_handlers["Suspend"]() UIManager.event_handlers.Suspend()
else else
-- Potentially start an USBMS session -- Potentially start an USBMS session
local MassStorage = require("ui/elements/mass_storage") local MassStorage = require("ui/elements/mass_storage")
MassStorage:start() MassStorage:start()
end end
end end
UIManager.event_handlers["UsbPlugOut"] = function() UIManager.event_handlers.UsbPlugOut = function()
-- We need to put the device into suspension, other things need to be done before it. -- We need to put the device into suspension, other things need to be done before it.
self:usbPlugOut() self:usbPlugOut()
self:_afterNotCharging() self:_afterNotCharging()
if self.screen_saver_mode then if self.screen_saver_mode then
UIManager.event_handlers["Suspend"]() UIManager.event_handlers.Suspend()
else else
-- Potentially dismiss the USBMS ConfirmBox -- Potentially dismiss the USBMS ConfirmBox
local MassStorage = require("ui/elements/mass_storage") local MassStorage = require("ui/elements/mass_storage")
@ -1337,25 +1416,25 @@ function Kobo:setEventHandlers(UIManager)
if G_reader_settings:isTrue("ignore_power_sleepcover") then if G_reader_settings:isTrue("ignore_power_sleepcover") then
-- NOTE: The hardware event itself will wake the kernel up if it's in suspend (:/). -- NOTE: The hardware event itself will wake the kernel up if it's in suspend (:/).
-- Let the unexpected wakeup guard handle that. -- Let the unexpected wakeup guard handle that.
UIManager.event_handlers["SleepCoverClosed"] = nil UIManager.event_handlers.SleepCoverClosed = nil
UIManager.event_handlers["SleepCoverOpened"] = nil UIManager.event_handlers.SleepCoverOpened = nil
elseif G_reader_settings:isTrue("ignore_open_sleepcover") then elseif G_reader_settings:isTrue("ignore_open_sleepcover") then
-- Just ignore wakeup events, and do NOT set is_cover_closed, -- Just ignore wakeup events, and do NOT set is_cover_closed,
-- so device/generic/device will let us use the power button to wake ;). -- so device/generic/device will let us use the power button to wake ;).
UIManager.event_handlers["SleepCoverClosed"] = function() UIManager.event_handlers.SleepCoverClosed = function()
UIManager.event_handlers["Suspend"]() UIManager.event_handlers.Suspend()
end end
UIManager.event_handlers["SleepCoverOpened"] = function() UIManager.event_handlers.SleepCoverOpened = function()
self.is_cover_closed = false self.is_cover_closed = false
end end
else else
UIManager.event_handlers["SleepCoverClosed"] = function() UIManager.event_handlers.SleepCoverClosed = function()
self.is_cover_closed = true self.is_cover_closed = true
UIManager.event_handlers["Suspend"]() UIManager.event_handlers.Suspend()
end end
UIManager.event_handlers["SleepCoverOpened"] = function() UIManager.event_handlers.SleepCoverOpened = function()
self.is_cover_closed = false self.is_cover_closed = false
UIManager.event_handlers["Resume"]() UIManager.event_handlers.Resume()
end end
end end
end end

@ -390,10 +390,10 @@ end
function PocketBook:setEventHandlers(UIManager) function PocketBook:setEventHandlers(UIManager)
-- Only fg/bg state plugin notifiers, not real power event. -- Only fg/bg state plugin notifiers, not real power event.
UIManager.event_handlers["Suspend"] = function() UIManager.event_handlers.Suspend = function()
self:_beforeSuspend() self:_beforeSuspend()
end end
UIManager.event_handlers["Resume"] = function() UIManager.event_handlers.Resume = function()
self:_afterResume() self:_afterResume()
end end
end end

@ -233,25 +233,25 @@ function Remarkable:getDefaultCoverPath()
end end
function Remarkable:setEventHandlers(UIManager) function Remarkable:setEventHandlers(UIManager)
UIManager.event_handlers["Suspend"] = function() UIManager.event_handlers.Suspend = function()
self:_beforeSuspend() self:_beforeSuspend()
self:onPowerEvent("Suspend") self:onPowerEvent("Suspend")
end end
UIManager.event_handlers["Resume"] = function() UIManager.event_handlers.Resume = function()
self:onPowerEvent("Resume") self:onPowerEvent("Resume")
self:_afterResume() self:_afterResume()
end end
UIManager.event_handlers["PowerPress"] = function() UIManager.event_handlers.PowerPress = function()
UIManager:scheduleIn(2, UIManager.poweroff_action) UIManager:scheduleIn(2, UIManager.poweroff_action)
end end
UIManager.event_handlers["PowerRelease"] = function() UIManager.event_handlers.PowerRelease = function()
if not UIManager._entered_poweroff_stage then if not UIManager._entered_poweroff_stage then
UIManager:unschedule(UIManager.poweroff_action) UIManager:unschedule(UIManager.poweroff_action)
-- resume if we were suspended -- resume if we were suspended
if self.screen_saver_mode then if self.screen_saver_mode then
UIManager.event_handlers["Resume"]() UIManager.event_handlers.Resume()
else else
UIManager.event_handlers["Suspend"]() UIManager.event_handlers.Suspend()
end end
end end
end end

@ -344,20 +344,27 @@ function Device:toggleFullscreen()
end end
function Device:setEventHandlers(UIManager) function Device:setEventHandlers(UIManager)
UIManager.event_handlers["Suspend"] = function() if not self:canSuspend() then
-- If we can't suspend, we have no business even trying to, as we may not have overloaded `Device:simulateResume`,
-- and since the empty Generic prototype doesn't flip `Device.screen_saver_mode`, we'd be stuck if we tried...
-- Instead, rely on the Generic Suspend/Resume handlers, which are sane ;).
return
end
UIManager.event_handlers.Suspend = function()
self:_beforeSuspend() self:_beforeSuspend()
self:simulateSuspend() self:simulateSuspend()
end end
UIManager.event_handlers["Resume"] = function() UIManager.event_handlers.Resume = function()
self:simulateResume() self:simulateResume()
self:_afterResume() self:_afterResume()
end end
UIManager.event_handlers["PowerRelease"] = function() UIManager.event_handlers.PowerRelease = function()
-- Resume if we were suspended -- Resume if we were suspended
if self.screen_saver_mode then if self.screen_saver_mode then
UIManager.event_handlers["Resume"]() UIManager.event_handlers.Resume()
else else
UIManager.event_handlers["Suspend"]() UIManager.event_handlers.Suspend()
end end
end end
end end

@ -119,11 +119,11 @@ function SonyPRSTUX:resume()
end end
function SonyPRSTUX:powerOff() function SonyPRSTUX:powerOff()
os.execute("poweroff") os.execute("sleep 1 && poweroff &")
end end
function SonyPRSTUX:reboot() function SonyPRSTUX:reboot()
os.execute("reboot") os.execute("sleep 1 && reboot &")
end end
function SonyPRSTUX:usbPlugIn() function SonyPRSTUX:usbPlugIn()
@ -189,37 +189,37 @@ function SonyPRSTUX:getDeviceModel()
end end
function SonyPRSTUX:setEventHandlers(UIManager) function SonyPRSTUX:setEventHandlers(UIManager)
UIManager.event_handlers["Suspend"] = function() UIManager.event_handlers.Suspend = function()
self:_beforeSuspend() self:_beforeSuspend()
self:intoScreenSaver() self:intoScreenSaver()
self:suspend() self:suspend()
end end
UIManager.event_handlers["Resume"] = function() UIManager.event_handlers.Resume = function()
self:resume() self:resume()
self:outofScreenSaver() self:outofScreenSaver()
self:_afterResume() self:_afterResume()
end end
UIManager.event_handlers["PowerPress"] = function() UIManager.event_handlers.PowerPress = function()
UIManager:scheduleIn(2, UIManager.poweroff_action) UIManager:scheduleIn(2, UIManager.poweroff_action)
end end
UIManager.event_handlers["PowerRelease"] = function() UIManager.event_handlers.PowerRelease = function()
if not UIManager._entered_poweroff_stage then if not UIManager._entered_poweroff_stage then
UIManager:unschedule(UIManager.poweroff_action) UIManager:unschedule(UIManager.poweroff_action)
-- resume if we were suspended -- resume if we were suspended
if self.screen_saver_mode then if self.screen_saver_mode then
UIManager.event_handlers["Resume"]() UIManager.event_handlers.Resume()
else else
UIManager.event_handlers["Suspend"]() UIManager.event_handlers.Suspend()
end end
end end
end end
UIManager.event_handlers["Charging"] = function() UIManager.event_handlers.Charging = function()
self:_beforeCharging() self:_beforeCharging()
end end
UIManager.event_handlers["NotCharging"] = function() UIManager.event_handlers.NotCharging = function()
self:_afterNotCharging() self:_afterNotCharging()
end end
UIManager.event_handlers["UsbPlugIn"] = function() UIManager.event_handlers.UsbPlugIn = function()
if self.screen_saver_mode then if self.screen_saver_mode then
self:resume() self:resume()
self:outofScreenSaver() self:outofScreenSaver()
@ -227,10 +227,10 @@ function SonyPRSTUX:setEventHandlers(UIManager)
end end
self:usbPlugIn() self:usbPlugIn()
end end
UIManager.event_handlers["UsbPlugOut"] = function() UIManager.event_handlers.UsbPlugOut = function()
self:usbPlugOut() self:usbPlugOut()
end end
UIManager.event_handlers["__default__"] = function(input_event) UIManager.event_handlers.__default__ = function(input_event)
-- Same as in Kobo: we want to ignore keys during suspension -- Same as in Kobo: we want to ignore keys during suspension
if not self.screen_saver_mode then if not self.screen_saver_mode then
UIManager:sendEvent(input_event) UIManager:sendEvent(input_event)

@ -23,6 +23,7 @@ local WakeupMgr = {
dev_rtc = "/dev/rtc0", -- RTC device dev_rtc = "/dev/rtc0", -- RTC device
_task_queue = {}, -- Table with epoch at which to schedule the task and the function to be scheduled. _task_queue = {}, -- Table with epoch at which to schedule the task and the function to be scheduled.
rtc = RTC, -- The RTC implementation to use, defaults to the RTC module. rtc = RTC, -- The RTC implementation to use, defaults to the RTC module.
dodgy_rtc = false, -- If the RTC has trouble with timers further away than UINT16_MAX (e.g., on i.MX5).
} }
--[[-- --[[--
@ -44,6 +45,11 @@ function WakeupMgr:new(o)
return o return o
end end
-- This is a dummy task we use when working around i.MX5 RTC issues.
-- We need to be able to recognize it so that we can deal with it in removeTasks...
function WakeupMgr.DummyTaskCallback()
end
--[[-- --[[--
Add a task to the queue. Add a task to the queue.
@ -57,17 +63,37 @@ function WakeupMgr:addTask(seconds_from_now, callback)
assert(type(seconds_from_now) == "number", "delay is not a number") assert(type(seconds_from_now) == "number", "delay is not a number")
assert(type(callback) == "function", "callback is not a function") 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)
local old_upcoming_task = (self._task_queue[1] or {}).epoch local old_upcoming_task = (self._task_queue[1] or {}).epoch
table.insert(self._task_queue, { -- NOTE: Apparently, some RTCs have trouble with timers further away than UINT16_MAX, so,
epoch = epoch, -- if necessary, setup an alarm chain to work it around...
callback = callback, -- c.f., https://github.com/koreader/koreader/issues/8039#issuecomment-1263547625
}) if self.dodgy_rtc and seconds_from_now > 0xFFFF then
--- @todo Binary insert? This table should be so small that performance doesn't matter. logger.info("WakeupMgr: scheduling a chain of alarms for a wakeup in", seconds_from_now)
-- It might be useful to have that available as a utility function regardless.
local seconds_left = seconds_from_now
while seconds_left > 0 do
local epoch = RTC:secondsFromNowToEpoch(seconds_left)
logger.info("WakeupMgr: scheduling wakeup in", seconds_left, "->", epoch)
-- We only need a callback for the final wakeup, we take care of not breaking the chain when an action is pop'ed.
table.insert(self._task_queue, {
epoch = epoch,
callback = seconds_left == seconds_from_now and callback or self.DummyTaskCallback,
})
seconds_left = seconds_left - 0xFFFF
end
else
local epoch = RTC:secondsFromNowToEpoch(seconds_from_now)
logger.info("WakeupMgr: scheduling wakeup in", seconds_from_now, "->", epoch)
table.insert(self._task_queue, {
epoch = epoch,
callback = callback,
})
end
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
@ -93,9 +119,15 @@ function WakeupMgr:removeTasks(epoch, callback)
local removed = false local removed = false
local reschedule = false local reschedule = false
local match_epoch = epoch
for k = #self._task_queue, 1, -1 do for k = #self._task_queue, 1, -1 do
local v = self._task_queue[k] local v = self._task_queue[k]
if epoch == v.epoch or callback == v.callback then -- NOTE: For the DummyTaskCallback shenanigans, we at least try to only remove those that come earlier than our match...
if (epoch == v.epoch or callback == v.callback) or
(self.dodgy_rtc and match_epoch and self.DummyTaskCallback == v.callback and v.epoch < match_epoch) then
if not match_epoch then
match_epoch = v.epoch
end
table.remove(self._task_queue, k) table.remove(self._task_queue, k)
removed = true removed = true
-- If we've successfuly pop'ed the upcoming task, we need to schedule the next one (if any) on exit. -- If we've successfuly pop'ed the upcoming task, we need to schedule the next one (if any) on exit.
@ -170,6 +202,7 @@ Set wakeup alarm.
Simple wrapper for @{ffi.rtc.setWakeupAlarm}. Simple wrapper for @{ffi.rtc.setWakeupAlarm}.
--]] --]]
function WakeupMgr:setWakeupAlarm(epoch, enabled) function WakeupMgr:setWakeupAlarm(epoch, enabled)
logger.dbg("WakeupMgr:setWakeupAlarm for", epoch, os.date("(%F %T %z)", epoch))
return self.rtc:setWakeupAlarm(epoch, enabled) return self.rtc:setWakeupAlarm(epoch, enabled)
end end

@ -21,17 +21,17 @@ local DEFAULT_DUMP_LVL = 10
-- @field warn warning -- @field warn warning
-- @field err error -- @field err error
local LOG_LVL = { local LOG_LVL = {
dbg = 1, dbg = 1,
info = 2, info = 2,
warn = 3, warn = 3,
err = 4, err = 4,
} }
local LOG_PREFIX = { local LOG_PREFIX = {
dbg = 'DEBUG', dbg = "DEBUG",
info = 'INFO ', info = "INFO ",
warn = 'WARN ', warn = "WARN ",
err = 'ERROR', err = "ERROR",
} }
local noop = function() end local noop = function() end
@ -40,39 +40,55 @@ local Logger = {
levels = LOG_LVL, levels = LOG_LVL,
} }
local function log(log_lvl, dump_lvl, ...) local log
local line = "" if isAndroid then
for i,v in ipairs({...}) do local ANDROID_LOG_FNS = {
if type(v) == "table" then dbg = android.LOGV,
line = line .. " " .. dump(v, dump_lvl) info = android.LOGI,
else warn = android.LOGW,
line = line .. " " .. tostring(v) err = android.LOGE,
}
log = function(log_lvl, ...)
local line = {}
for _, v in ipairs({...}) do
if type(v) == "table" then
table.insert(line, dump(v, DEFAULT_DUMP_LVL))
else
table.insert(line, tostring(v))
end
end end
return ANDROID_LOG_FNS[log_lvl](table.concat(line, " "))
end end
if isAndroid then else
if log_lvl == "dbg" then log = function(log_lvl, ...)
android.LOGV(line) local line = {
elseif log_lvl == "info" then os.date("%x-%X"),
android.LOGI(line) LOG_PREFIX[log_lvl],
elseif log_lvl == "warn" then }
android.LOGW(line) for _, v in ipairs({...}) do
elseif log_lvl == "err" then if type(v) == "table" then
android.LOGE(line) table.insert(line, dump(v, DEFAULT_DUMP_LVL))
else
table.insert(line, tostring(v))
end
end end
else
io.stdout:write(os.date("%x-%X"), " ", LOG_PREFIX[log_lvl], line, "\n") -- NOTE: Either we add the LF to the table and we get an extra space before it because of table.concat,
io.stdout:flush() -- or we pass it to write after a comma, and it generates an extra write syscall...
-- That, or just rewrite every logger call to handle spacing themselves ;).
table.insert(line, "\n")
return io.write(table.concat(line, " "))
end end
end end
local LVL_FUNCTIONS = { local LVL_FUNCTIONS = {
dbg = function(...) log('dbg', DEFAULT_DUMP_LVL, ...) end, dbg = function(...) return log("dbg", ...) end,
info = function(...) log('info', DEFAULT_DUMP_LVL, ...) end, info = function(...) return log("info", ...) end,
warn = function(...) log('warn', DEFAULT_DUMP_LVL, ...) end, warn = function(...) return log("warn", ...) end,
err = function(...) log('err', DEFAULT_DUMP_LVL, ...) end, err = function(...) return log("err", ...) end,
} }
--[[-- --[[--
Set logging level. By default, level is set to info. Set logging level. By default, level is set to info.

@ -7,7 +7,7 @@ local lfs = require("libs/libkoreader-lfs")
local logger = require("logger") local logger = require("logger")
-- Date at which the last migration snippet was added -- Date at which the last migration snippet was added
local CURRENT_MIGRATION_DATE = 20220922 local CURRENT_MIGRATION_DATE = 20220930
-- Retrieve the date of the previous migration, if any -- Retrieve the date of the previous migration, if any
local last_migration_date = G_reader_settings:readSetting("last_migration_date", 0) local last_migration_date = G_reader_settings:readSetting("last_migration_date", 0)
@ -446,9 +446,9 @@ if last_migration_date < 20220914 then
end end
end end
-- The great defaults.persistent.lua migration to LuaDefaults -- The great defaults.persistent.lua migration to LuaDefaults (#9546)
if last_migration_date < 20220922 then if last_migration_date < 20220930 then
logger.info("Performing one-time migration for 20220922") logger.info("Performing one-time migration for 20220930")
local defaults_path = DataStorage:getDataDir() .. "/defaults.persistent.lua" local defaults_path = DataStorage:getDataDir() .. "/defaults.persistent.lua"
local defaults = {} local defaults = {}
@ -465,6 +465,10 @@ if last_migration_date < 20220922 then
G_defaults:saveSetting(k, v) G_defaults:saveSetting(k, v)
end end
end end
-- Handle NETWORK_PROXY & STARDICT_DATA_DIR, which default to nil (and as such don't actually exist in G_defaults).
G_defaults:saveSetting("NETWORK_PROXY", defaults.NETWORK_PROXY)
G_defaults:saveSetting("STARDICT_DATA_DIR", defaults.STARDICT_DATA_DIR)
G_defaults:flush() G_defaults:flush()
local archived_path = DataStorage:getDataDir() .. "/defaults.legacy.lua" local archived_path = DataStorage:getDataDir() .. "/defaults.legacy.lua"

@ -18,8 +18,7 @@ if Device:isCervantes() then
common_settings.start_bq = { common_settings.start_bq = {
text = T(_("Start %1 reader app"), "BQ"), text = T(_("Start %1 reader app"), "BQ"),
callback = function() callback = function()
UIManager:quit() UIManager:quit(87)
UIManager._exit_code = 87
end, end,
} }
end end

@ -61,9 +61,8 @@ function MassStorage:start(never_ask)
ok_callback = function() ok_callback = function()
-- save settings before activating USBMS: -- save settings before activating USBMS:
UIManager:flushSettings() UIManager:flushSettings()
UIManager._exit_code = 86
UIManager:broadcastEvent(Event:new("Close")) UIManager:broadcastEvent(Event:new("Close"))
UIManager:quit() UIManager:quit(86)
end, end,
cancel_callback = function() cancel_callback = function()
self:dismiss() self:dismiss()
@ -74,9 +73,8 @@ function MassStorage:start(never_ask)
else else
-- save settings before activating USBMS: -- save settings before activating USBMS:
UIManager:flushSettings() UIManager:flushSettings()
UIManager._exit_code = 86
UIManager:broadcastEvent(Event:new("Close")) UIManager:broadcastEvent(Event:new("Close"))
UIManager:quit() UIManager:quit(86)
end end
end end

@ -32,7 +32,7 @@ function Event:new(name, ...)
handler = "on"..name, handler = "on"..name,
-- Minor trickery to handle nils, c.f., http://lua-users.org/wiki/VarargTheSecondClassCitizen -- Minor trickery to handle nils, c.f., http://lua-users.org/wiki/VarargTheSecondClassCitizen
--- @fixme: Move to table.pack() (which stores the count in the field `n`) here & table.unpack() in @{ui.widget.eventlistener|EventListener} once we build LuaJIT w/ 5.2 compat. --- @fixme: Move to table.pack() (which stores the count in the field `n`) here & table.unpack() in @{ui.widget.eventlistener|EventListener} once we build LuaJIT w/ 5.2 compat.
argc = select('#', ...), argc = select("#", ...),
args = {...} args = {...}
} }
setmetatable(o, self) setmetatable(o, self)

@ -12,7 +12,10 @@ local logger = require("logger")
local _ = require("gettext") local _ = require("gettext")
local T = ffiutil.template local T = ffiutil.template
local NetworkMgr = {} local NetworkMgr = {
is_wifi_on = false,
is_connected = false,
}
function NetworkMgr:readNWSettings() function NetworkMgr:readNWSettings()
self.nw_settings = LuaSettings:open(DataStorage:getSettingsDir().."/network.lua") self.nw_settings = LuaSettings:open(DataStorage:getSettingsDir().."/network.lua")
@ -30,7 +33,7 @@ function NetworkMgr:connectivityCheck(iter, callback, widget)
if Device:hasWifiManager() and not Device:isEmulator() then if Device:hasWifiManager() and not Device:isEmulator() then
os.execute("pkill -TERM restore-wifi-async.sh 2>/dev/null") os.execute("pkill -TERM restore-wifi-async.sh 2>/dev/null")
end end
NetworkMgr:turnOffWifi() self:turnOffWifi()
-- Handle the UI warning if it's from a beforeWifiAction... -- Handle the UI warning if it's from a beforeWifiAction...
if widget then if widget then
@ -40,7 +43,8 @@ function NetworkMgr:connectivityCheck(iter, callback, widget)
return return
end end
if NetworkMgr:isWifiOn() and NetworkMgr:isConnected() then self:queryNetworkState()
if self.is_wifi_on and self.is_connected then
self.wifi_was_on = true self.wifi_was_on = true
G_reader_settings:makeTrue("wifi_was_on") G_reader_settings:makeTrue("wifi_was_on")
UIManager:broadcastEvent(Event:new("NetworkConnected")) UIManager:broadcastEvent(Event:new("NetworkConnected"))
@ -63,12 +67,12 @@ function NetworkMgr:connectivityCheck(iter, callback, widget)
end end
end end
else else
UIManager:scheduleIn(2, function() NetworkMgr:connectivityCheck(iter + 1, callback, widget) end) UIManager:scheduleIn(2, self.connectivityCheck, self, iter + 1, callback, widget)
end end
end end
function NetworkMgr:scheduleConnectivityCheck(callback, widget) function NetworkMgr:scheduleConnectivityCheck(callback, widget)
UIManager:scheduleIn(2, function() NetworkMgr:connectivityCheck(1, callback, widget) end) UIManager:scheduleIn(2, self.connectivityCheck, self, 1, callback, widget)
end end
function NetworkMgr:init() function NetworkMgr:init()
@ -79,18 +83,19 @@ function NetworkMgr:init()
self:turnOffWifi() self:turnOffWifi()
end end
self:queryNetworkState()
self.wifi_was_on = G_reader_settings:isTrue("wifi_was_on") self.wifi_was_on = G_reader_settings:isTrue("wifi_was_on")
if self.wifi_was_on and G_reader_settings:isTrue("auto_restore_wifi") then if self.wifi_was_on and G_reader_settings:isTrue("auto_restore_wifi") then
-- Don't bother if WiFi is already up... -- Don't bother if WiFi is already up...
if not (self:isWifiOn() and self:isConnected()) then if not self.is_connected then
self:restoreWifiAsync() self:restoreWifiAsync()
end end
self:scheduleConnectivityCheck() self:scheduleConnectivityCheck()
else else
-- Trigger an initial NetworkConnected event if WiFi was already up when we were launched -- Trigger an initial NetworkConnected event if WiFi was already up when we were launched
if NetworkMgr:isWifiOn() and NetworkMgr:isConnected() then if self.is_connected then
-- NOTE: This needs to be delayed because NetworkListener is initialized slightly later by the FM/Reader app... -- NOTE: This needs to be delayed because NetworkListener is initialized slightly later by the FM/Reader app...
UIManager:scheduleIn(2, function() UIManager:broadcastEvent(Event:new("NetworkConnected")) end) UIManager:scheduleIn(2, UIManager.broadcastEvent, UIManager, Event:new("NetworkConnected"))
end end
end end
end end
@ -108,7 +113,7 @@ function NetworkMgr:authenticateNetwork() end
function NetworkMgr:disconnectNetwork() end function NetworkMgr:disconnectNetwork() end
function NetworkMgr:obtainIP() end function NetworkMgr:obtainIP() end
function NetworkMgr:releaseIP() end function NetworkMgr:releaseIP() end
-- This function should unblockly call both turnOnWifi() and obtainIP(). -- This function should call both turnOnWifi() and obtainIP() in a non-blocking manner.
function NetworkMgr:restoreWifiAsync() end function NetworkMgr:restoreWifiAsync() end
-- End of device specific methods -- End of device specific methods
@ -212,9 +217,9 @@ function NetworkMgr:beforeWifiAction(callback)
local wifi_enable_action = G_reader_settings:readSetting("wifi_enable_action") local wifi_enable_action = G_reader_settings:readSetting("wifi_enable_action")
if wifi_enable_action == "turn_on" then if wifi_enable_action == "turn_on" then
NetworkMgr:turnOnWifiAndWaitForConnection(callback) self:turnOnWifiAndWaitForConnection(callback)
else else
NetworkMgr:promptWifiOn(callback) self:promptWifiOn(callback)
end end
end end
@ -234,9 +239,9 @@ function NetworkMgr:afterWifiAction(callback)
callback() callback()
end end
elseif wifi_disable_action == "turn_off" then elseif wifi_disable_action == "turn_off" then
NetworkMgr:turnOffWifi(callback) self:turnOffWifi(callback)
else else
NetworkMgr:promptWifiOff(callback) self:promptWifiOff(callback)
end end
end end
@ -274,6 +279,27 @@ function NetworkMgr:isOnline()
return socket.dns.toip("dns.msftncsi.com") ~= nil return socket.dns.toip("dns.msftncsi.com") ~= nil
end end
-- Update our cached network status
function NetworkMgr:queryNetworkState()
self.is_wifi_on = self:isWifiOn()
self.is_connected = self.is_wifi_on and self:isConnected()
end
-- These do not call the actual Device methods, but what we, NetworkMgr, think the state is based on our own behavior.
function NetworkMgr:getWifiState()
return self.is_wifi_on
end
function NetworkMgr:setWifiState(bool)
self.is_wifi_on = bool
end
function NetworkMgr:getConnectionState()
return self.is_connected
end
function NetworkMgr:setConnectionState(bool)
self.is_connected = bool
end
function NetworkMgr:isNetworkInfoAvailable() function NetworkMgr:isNetworkInfoAvailable()
if Device:isAndroid() then if Device:isAndroid() then
-- always available -- always available
@ -362,7 +388,7 @@ function NetworkMgr:getWifiMenuTable()
return { return {
text = _("Wi-Fi settings"), text = _("Wi-Fi settings"),
enabled_func = function() return true end, enabled_func = function() return true end,
callback = function() NetworkMgr:openSettings() end, callback = function() self:openSettings() end,
} }
else else
return self:getWifiToggleMenuTable() return self:getWifiToggleMenuTable()
@ -371,10 +397,11 @@ end
function NetworkMgr:getWifiToggleMenuTable() function NetworkMgr:getWifiToggleMenuTable()
local toggleCallback = function(touchmenu_instance, long_press) local toggleCallback = function(touchmenu_instance, long_press)
local is_wifi_on = NetworkMgr:isWifiOn() self:queryNetworkState()
local is_connected = NetworkMgr:isConnected() local fully_connected = self.is_wifi_on and self.is_connected
local fully_connected = is_wifi_on and is_connected
local complete_callback = function() local complete_callback = function()
-- Check the connection status again
self:queryNetworkState()
-- Notify TouchMenu to update item check state -- Notify TouchMenu to update item check state
touchmenu_instance:updateItems() touchmenu_instance:updateItems()
-- If Wi-Fi was on when the menu was shown, this means the tap meant to turn the Wi-Fi *off*, -- If Wi-Fi was on when the menu was shown, this means the tap meant to turn the Wi-Fi *off*,
@ -385,9 +412,9 @@ function NetworkMgr:getWifiToggleMenuTable()
-- On hasWifiManager devices that play with kernel modules directly, -- On hasWifiManager devices that play with kernel modules directly,
-- double-check that the connection attempt was actually successful... -- double-check that the connection attempt was actually successful...
if Device:isKobo() or Device:isCervantes() then if Device:isKobo() or Device:isCervantes() then
if NetworkMgr:isWifiOn() and NetworkMgr:isConnected() then if self.is_wifi_on and self.is_connected then
UIManager:broadcastEvent(Event:new("NetworkConnected")) UIManager:broadcastEvent(Event:new("NetworkConnected"))
elseif NetworkMgr:isWifiOn() and not NetworkMgr:isConnected() then elseif self.is_wifi_on and not self.is_connected then
-- If we can't ping the gateway, despite a successful authentication w/ the AP, display a warning, -- If we can't ping the gateway, despite a successful authentication w/ the AP, display a warning,
-- because this means that Wi-Fi is technically still enabled (e.g., modules are loaded). -- because this means that Wi-Fi is technically still enabled (e.g., modules are loaded).
-- We can't really enforce a turnOffWifi right now, because the user might want to try another AP or something. -- We can't really enforce a turnOffWifi right now, because the user might want to try another AP or something.
@ -408,19 +435,19 @@ function NetworkMgr:getWifiToggleMenuTable()
end end
end end
if fully_connected then if fully_connected then
NetworkMgr:toggleWifiOff(complete_callback) self:toggleWifiOff(complete_callback)
elseif is_wifi_on and not is_connected then elseif self.is_wifi_on and not self.is_connected then
-- ask whether user wants to connect or turn off wifi -- ask whether user wants to connect or turn off wifi
NetworkMgr:promptWifi(complete_callback, long_press) self:promptWifi(complete_callback, long_press)
else else
NetworkMgr:toggleWifiOn(complete_callback, long_press) self:toggleWifiOn(complete_callback, long_press)
end end
end end
return { return {
text = _("Wi-Fi connection"), text = _("Wi-Fi connection"),
enabled_func = function() return Device:hasWifiToggle() end, enabled_func = function() return Device:hasWifiToggle() end,
checked_func = function() return NetworkMgr:isWifiOn() end, checked_func = function() return self:isWifiOn() end,
callback = toggleCallback, callback = toggleCallback,
hold_callback = function(touchmenu_instance) hold_callback = function(touchmenu_instance)
toggleCallback(touchmenu_instance, true) toggleCallback(touchmenu_instance, true)
@ -442,9 +469,9 @@ function NetworkMgr:getProxyMenuTable()
checked_func = function() return proxy_enabled() end, checked_func = function() return proxy_enabled() end,
callback = function() callback = function()
if not proxy_enabled() and proxy() then if not proxy_enabled() and proxy() then
NetworkMgr:setHTTPProxy(proxy()) self:setHTTPProxy(proxy())
elseif proxy_enabled() then elseif proxy_enabled() then
NetworkMgr:setHTTPProxy(nil) self:setHTTPProxy(nil)
end end
if not proxy() then if not proxy() then
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
@ -458,7 +485,7 @@ function NetworkMgr:getProxyMenuTable()
hint = proxy() or "", hint = proxy() or "",
callback = function(input) callback = function(input)
if input ~= "" then if input ~= "" then
NetworkMgr:setHTTPProxy(input) self:setHTTPProxy(input)
end end
end, end,
} }
@ -644,7 +671,7 @@ function NetworkMgr:reconnectOrShowNetworkMenu(complete_callback)
if network.password then if network.password then
-- If we hit a preferred network and we're not already connected, -- If we hit a preferred network and we're not already connected,
-- attempt to connect to said preferred network.... -- attempt to connect to said preferred network....
success, err_msg = NetworkMgr:authenticateNetwork(network) success, err_msg = self:authenticateNetwork(network)
if success then if success then
ssid = network.ssid ssid = network.ssid
break break
@ -654,7 +681,7 @@ function NetworkMgr:reconnectOrShowNetworkMenu(complete_callback)
end end
if success then if success then
NetworkMgr:obtainIP() self:obtainIP()
if complete_callback then if complete_callback then
complete_callback() complete_callback()
end end

@ -197,10 +197,15 @@ function NetworkListener:_scheduleActivityCheck()
end end
function NetworkListener:onNetworkConnected() function NetworkListener:onNetworkConnected()
logger.dbg("NetworkListener: onNetworkConnected")
if not (Device:hasWifiManager() and not Device:isEmulator()) then if not (Device:hasWifiManager() and not Device:isEmulator()) then
return return
end end
-- This is for the sake of events that don't emanate from NetworkMgr itself...
NetworkMgr:setWifiState(true)
NetworkMgr:setConnectionState(true)
if not G_reader_settings:isTrue("auto_disable_wifi") then if not G_reader_settings:isTrue("auto_disable_wifi") then
return return
end end
@ -212,10 +217,14 @@ function NetworkListener:onNetworkConnected()
end end
function NetworkListener:onNetworkDisconnected() function NetworkListener:onNetworkDisconnected()
logger.dbg("NetworkListener: onNetworkDisconnected")
if not (Device:hasWifiManager() and not Device:isEmulator()) then if not (Device:hasWifiManager() and not Device:isEmulator()) then
return return
end end
NetworkMgr:setWifiState(false)
NetworkMgr:setConnectionState(false)
if not G_reader_settings:isTrue("auto_disable_wifi") then if not G_reader_settings:isTrue("auto_disable_wifi") then
return return
end end

@ -61,51 +61,29 @@ function UIManager:init()
self.poweroff_action = function() self.poweroff_action = function()
self._entered_poweroff_stage = true self._entered_poweroff_stage = true
Device.orig_rotation_mode = Device.screen:getRotationMode() Device.orig_rotation_mode = Device.screen:getRotationMode()
self:broadcastEvent(Event:new("Close"))
Screen:setRotationMode(Screen.ORIENTATION_PORTRAIT) Screen:setRotationMode(Screen.ORIENTATION_PORTRAIT)
local Screensaver = require("ui/screensaver") local Screensaver = require("ui/screensaver")
Screensaver:setup("poweroff", _("Powered off")) Screensaver:setup("poweroff", _("Powered off"))
if Device:hasEinkScreen() and Screensaver:modeIsImage() then
if Screensaver:withBackground() then
Screen:clear()
end
Screen:refreshFull()
end
Screensaver:show() Screensaver:show()
if Device:needsScreenRefreshAfterResume() then self:nextTick(function()
Screen:refreshFull()
end
UIManager:nextTick(function()
Device:saveSettings() Device:saveSettings()
if Device:isKobo() then
self._exit_code = 88
end
self:broadcastEvent(Event:new("Close"))
Device:powerOff() Device:powerOff()
self:quit(Device:isKobo() and 88)
end) end)
end end
self.reboot_action = function() self.reboot_action = function()
self._entered_poweroff_stage = true self._entered_poweroff_stage = true
Device.orig_rotation_mode = Device.screen:getRotationMode() Device.orig_rotation_mode = Device.screen:getRotationMode()
self:broadcastEvent(Event:new("Close"))
Screen:setRotationMode(Screen.ORIENTATION_PORTRAIT) Screen:setRotationMode(Screen.ORIENTATION_PORTRAIT)
local Screensaver = require("ui/screensaver") local Screensaver = require("ui/screensaver")
Screensaver:setup("reboot", _("Rebooting…")) Screensaver:setup("reboot", _("Rebooting…"))
if Device:hasEinkScreen() and Screensaver:modeIsImage() then
if Screensaver:withBackground() then
Screen:clear()
end
Screen:refreshFull()
end
Screensaver:show() Screensaver:show()
if Device:needsScreenRefreshAfterResume() then self:nextTick(function()
Screen:refreshFull()
end
UIManager:nextTick(function()
Device:saveSettings() Device:saveSettings()
if Device:isKobo() then
self._exit_code = 88
end
self:broadcastEvent(Event:new("Close"))
Device:reboot() Device:reboot()
self:quit(Device:isKobo() and 88)
end) end)
end end
@ -126,7 +104,7 @@ For more details about refreshtype, refreshregion & refreshdither see the descri
If refreshtype is omitted, no refresh will be enqueued at this time. If refreshtype is omitted, no refresh will be enqueued at this time.
@param widget a @{ui.widget.widget|widget} object @param widget a @{ui.widget.widget|widget} object
@string refreshtype `"full"`, `"flashpartial"`, `"flashui"`, `"partial"`, `"ui"`, `"fast"` (optional) @string refreshtype `"full"`, `"flashpartial"`, `"flashui"`, `"[partial]"`, `"[ui]"`, `"partial"`, `"ui"`, `"fast"`, `"a2"` (optional)
@param refreshregion a rectangle @{ui.geometry.Geom|Geom} object (optional, requires refreshtype to be set) @param refreshregion a rectangle @{ui.geometry.Geom|Geom} object (optional, requires refreshtype to be set)
@int x horizontal screen offset (optional, `0` if omitted) @int x horizontal screen offset (optional, `0` if omitted)
@int y vertical screen offset (optional, `0` if omitted) @int y vertical screen offset (optional, `0` if omitted)
@ -135,7 +113,7 @@ If refreshtype is omitted, no refresh will be enqueued at this time.
]] ]]
function UIManager:show(widget, refreshtype, refreshregion, x, y, refreshdither) function UIManager:show(widget, refreshtype, refreshregion, x, y, refreshdither)
if not widget then if not widget then
logger.dbg("widget not exist to be shown") logger.dbg("attempted to show a nil widget")
return return
end end
logger.dbg("show widget:", widget.id or widget.name or tostring(widget)) logger.dbg("show widget:", widget.id or widget.name or tostring(widget))
@ -181,14 +159,14 @@ For more details about refreshtype, refreshregion & refreshdither see the descri
If refreshtype is omitted, no extra refresh will be enqueued at this time, leaving only those from the uncovered widgets. If refreshtype is omitted, no extra refresh will be enqueued at this time, leaving only those from the uncovered widgets.
@param widget a @{ui.widget.widget|widget} object @param widget a @{ui.widget.widget|widget} object
@string refreshtype `"full"`, `"flashpartial"`, `"flashui"`, `"partial"`, `"ui"`, `"fast"` (optional) @string refreshtype `"full"`, `"flashpartial"`, `"flashui"`, `"[partial]"`, `"[ui]"`, `"partial"`, `"ui"`, `"fast"`, `"a2"` (optional)
@param refreshregion a rectangle @{ui.geometry.Geom|Geom} object (optional, requires refreshtype to be set) @param refreshregion a rectangle @{ui.geometry.Geom|Geom} object (optional, requires refreshtype to be set)
@bool refreshdither `true` if the refresh requires dithering (optional, requires refreshtype to be set) @bool refreshdither `true` if the refresh requires dithering (optional, requires refreshtype to be set)
@see setDirty @see setDirty
]] ]]
function UIManager:close(widget, refreshtype, refreshregion, refreshdither) function UIManager:close(widget, refreshtype, refreshregion, refreshdither)
if not widget then if not widget then
logger.dbg("widget to be closed does not exist") logger.dbg("attempted to close a nil widget")
return return
end end
logger.dbg("close widget:", widget.name or widget.id or tostring(widget)) logger.dbg("close widget:", widget.name or widget.id or tostring(widget))
@ -204,38 +182,39 @@ function UIManager:close(widget, refreshtype, refreshregion, refreshdither)
local start_idx = 1 local start_idx = 1
-- Then remove all references to that widget on stack and refresh. -- Then remove all references to that widget on stack and refresh.
for i = #self._window_stack, 1, -1 do for i = #self._window_stack, 1, -1 do
if self._window_stack[i].widget == widget then local w = self._window_stack[i].widget
self._dirty[self._window_stack[i].widget] = nil if w == widget then
self._dirty[w] = nil
table.remove(self._window_stack, i) table.remove(self._window_stack, i)
dirty = true dirty = true
else else
-- If anything else on the stack not already hidden by (i.e., below) a fullscreen widget was dithered, honor the hint -- If anything else on the stack not already hidden by (i.e., below) a fullscreen widget was dithered, honor the hint
if self._window_stack[i].widget.dithered and not is_covered then if w.dithered and not is_covered then
refreshdither = true refreshdither = true
logger.dbg("Lower widget", self._window_stack[i].widget.name or self._window_stack[i].widget.id or tostring(self._window_stack[i].widget), "was dithered, honoring the dithering hint") logger.dbg("Lower widget", w.name or w.id or tostring(w), "was dithered, honoring the dithering hint")
end end
-- Remember the uppermost widget that covers the full screen, so we don't bother calling setDirty on hidden (i.e., lower) widgets in the following dirty loop. -- Remember the uppermost widget that covers the full screen, so we don't bother calling setDirty on hidden (i.e., lower) widgets in the following dirty loop.
-- _repaint already does that later on to skip the actual paintTo calls, so this ensures we limit the refresh queue to stuff that will actually get painted. -- _repaint already does that later on to skip the actual paintTo calls, so this ensures we limit the refresh queue to stuff that will actually get painted.
if not is_covered and self._window_stack[i].widget.covers_fullscreen then if not is_covered and w.covers_fullscreen then
is_covered = true is_covered = true
start_idx = i start_idx = i
logger.dbg("Lower widget", self._window_stack[i].widget.name or self._window_stack[i].widget.id or tostring(self._window_stack[i].widget), "covers the full screen") logger.dbg("Lower widget", w.name or w.id or tostring(w), "covers the full screen")
if i > 1 then if i > 1 then
logger.dbg("not refreshing", i-1, "covered widget(s)") logger.dbg("not refreshing", i-1, "covered widget(s)")
end end
end end
-- Set double tap to how the topmost specifying widget wants it -- Set double tap to how the topmost specifying widget wants it
if requested_disable_double_tap == nil and self._window_stack[i].widget.disable_double_tap ~= nil then if requested_disable_double_tap == nil and w.disable_double_tap ~= nil then
requested_disable_double_tap = self._window_stack[i].widget.disable_double_tap requested_disable_double_tap = w.disable_double_tap
end end
end end
end end
if requested_disable_double_tap ~= nil then if requested_disable_double_tap ~= nil then
Input.disable_double_tap = requested_disable_double_tap Input.disable_double_tap = requested_disable_double_tap
end end
if #self._window_stack > 0 then if self._window_stack[1] then
-- set tap interval override to what the topmost widget specifies (when it doesn't, nil restores the default) -- set tap interval override to what the topmost widget specifies (when it doesn't, nil restores the default)
Input.tap_interval_override = self._window_stack[#self._window_stack].widget.tap_interval_override Input.tap_interval_override = self._window_stack[#self._window_stack].widget.tap_interval_override
end end
@ -281,7 +260,7 @@ function UIManager:schedule(sched_time, action, ...)
table.insert(self._task_queue, p, { table.insert(self._task_queue, p, {
time = sched_time, time = sched_time,
action = action, action = action,
argc = select('#', ...), argc = select("#", ...),
args = {...}, args = {...},
}) })
self._task_queue_dirty = true self._task_queue_dirty = true
@ -336,16 +315,12 @@ necessary if the caller wants to unschedule action *before* it actually gets ins
@see nextTick @see nextTick
]] ]]
function UIManager:tickAfterNext(action, ...) function UIManager:tickAfterNext(action, ...)
-- Storing varargs is a bit iffy as we don't build LuaJIT w/ 5.2 compat, so we don't have access to table.pack...
-- c.f., http://lua-users.org/wiki/VarargTheSecondClassCitizen
local n = select('#', ...)
local va = {...}
-- We need to keep a reference to this anonymous function, as it is *NOT* quite `action` yet, -- We need to keep a reference to this anonymous function, as it is *NOT* quite `action` yet,
-- and the caller might want to unschedule it early... -- and the caller might want to unschedule it early...
local action_wrapper = function() local action_wrapper = function(...)
self:nextTick(action, unpack(va, 1, n)) self:nextTick(action, ...)
end end
self:nextTick(action_wrapper) self:nextTick(action_wrapper, ...)
return action_wrapper return action_wrapper
end end
@ -416,14 +391,20 @@ Here's a quick rundown of what each refreshtype should be used for:
Can be promoted to flashing after `FULL_REFRESH_COUNT` refreshes. Can be promoted to flashing after `FULL_REFRESH_COUNT` refreshes.
Don't abuse to avoid spurious flashes. Don't abuse to avoid spurious flashes.
In practice, this means this should mostly always be limited to ReaderUI. In practice, this means this should mostly always be limited to ReaderUI.
* `[partial]`: variant of partial that asks the driver not to merge this update with surrounding ones.
Equivalent to partial on platforms where this distinction is not implemented.
* `ui`: medium fidelity refresh (e.g., mixed content). * `ui`: medium fidelity refresh (e.g., mixed content).
Should apply to most UI elements. Should apply to most UI elements.
When in doubt, use this. When in doubt, use this.
* `fast`: low fidelity refresh (e.g., monochrome content). * `[ui]`: variant of ui that asks the driver not to merge this update with surrounding ones.
Equivalent to ui on platforms where this distinction is not implemented.
* `fast`: low fidelity refresh (e.g., monochrome content (technically, from any to B&W)).
Should apply to most highlighting effects achieved through inversion. Should apply to most highlighting effects achieved through inversion.
Note that if your highlighted element contains text, Note that if your highlighted element contains text,
you might want to keep the unhighlight refresh as `"ui"` instead, for crisper text. you might want to keep the unhighlight refresh as `"ui"` instead, for crisper text.
(Or optimize that refresh away entirely, if you can get away with it). (Or optimize that refresh away entirely, if you can get away with it).
* `a2`: low fidelity refresh (e.g., monochrome content (technically, from B&W to B&W only)).
Should be limited to very specific use-cases (e.g., keyboard)
* `flashui`: like `ui`, but flashing. * `flashui`: like `ui`, but flashing.
Can be used when showing a UI element for the first time, or when closing one, to avoid ghosting. Can be used when showing a UI element for the first time, or when closing one, to avoid ghosting.
* `flashpartial`: like `partial`, but flashing (and not counting towards flashing promotions). * `flashpartial`: like `partial`, but flashing (and not counting towards flashing promotions).
@ -501,7 +482,7 @@ UIManager:setDirty(self.widget, "partial", Geom:new{x=10,y=10,w=100,h=50})
UIManager:setDirty(self.widget, function() return "ui", self.someelement.dimen end) UIManager:setDirty(self.widget, function() return "ui", self.someelement.dimen end)
@param widget a window-level widget object, `"all"`, or `nil` @param widget a window-level widget object, `"all"`, or `nil`
@param refreshtype `"full"`, `"flashpartial"`, `"flashui"`, `"partial"`, `"ui"`, `"fast"` (or a lambda, see description above) @param refreshtype `"full"`, `"flashpartial"`, `"flashui"`, `"[partial]"`, `"[ui]"`, `"partial"`, `"ui"`, `"fast"`, `"a2"` (or a lambda, see description above)
@param refreshregion a rectangle @{ui.geometry.Geom|Geom} object (optional, omitting it means the region will cover the full screen) @param refreshregion a rectangle @{ui.geometry.Geom|Geom} object (optional, omitting it means the region will cover the full screen)
@bool refreshdither `true` if widget requires dithering (optional) @bool refreshdither `true` if widget requires dithering (optional)
]] ]]
@ -509,10 +490,11 @@ function UIManager:setDirty(widget, refreshtype, refreshregion, refreshdither)
if widget then if widget then
if widget == "all" then if widget == "all" then
-- special case: set all top-level widgets as being "dirty". -- special case: set all top-level widgets as being "dirty".
for i = 1, #self._window_stack do for _, window in ipairs(self._window_stack) do
self._dirty[self._window_stack[i].widget] = true local w = window.widget
self._dirty[w] = true
-- If any of 'em were dithered, honor their dithering hint -- If any of 'em were dithered, honor their dithering hint
if self._window_stack[i].widget.dithered then if w.dithered then
-- NOTE: That works when refreshtype is NOT a function, -- NOTE: That works when refreshtype is NOT a function,
-- which is why _repaint does another pass of this check ;). -- which is why _repaint does another pass of this check ;).
logger.dbg("setDirty on all widgets: found a dithered widget, infecting the refresh queue") logger.dbg("setDirty on all widgets: found a dithered widget, infecting the refresh queue")
@ -528,16 +510,17 @@ function UIManager:setDirty(widget, refreshtype, refreshregion, refreshdither)
-- NOTE: We only ever check the dirty flag on top-level widgets, so only set it there! -- NOTE: We only ever check the dirty flag on top-level widgets, so only set it there!
-- Enable verbose debug to catch misbehaving widgets via our post-guard. -- Enable verbose debug to catch misbehaving widgets via our post-guard.
for i = #self._window_stack, 1, -1 do for i = #self._window_stack, 1, -1 do
local w = self._window_stack[i].widget
if handle_alpha then if handle_alpha then
self._dirty[self._window_stack[i].widget] = true self._dirty[w] = true
logger.dbg("setDirty: Marking as dirty widget:", self._window_stack[i].widget.name or self._window_stack[i].widget.id or tostring(self._window_stack[i].widget), "because it's below translucent widget:", widget.name or widget.id or tostring(widget)) logger.dbg("setDirty: Marking as dirty widget:", w.name or w.id or tostring(w), "because it's below translucent widget:", widget.name or widget.id or tostring(widget))
-- Stop flagging widgets at the uppermost one that covers the full screen -- Stop flagging widgets at the uppermost one that covers the full screen
if self._window_stack[i].widget.covers_fullscreen then if w.covers_fullscreen then
break break
end end
end end
if self._window_stack[i].widget == widget then if w == widget then
self._dirty[widget] = true self._dirty[widget] = true
-- We've got a match, now check if it's translucent... -- We've got a match, now check if it's translucent...
@ -592,6 +575,11 @@ function UIManager:setDirty(widget, refreshtype, refreshregion, refreshdither)
end end
end end
end end
--[[
-- NOTE: While nice in theory, this is *extremely* verbose in practice,
-- because most widgets will call setDirty at least once during their initialization,
-- and that happens before they make it to the window stack...
-- Plus, setDirty(nil, ...) is a completely valid use-case with documented semantics...
dbg:guard(UIManager, 'setDirty', dbg:guard(UIManager, 'setDirty',
nil, nil,
function(self, widget, refreshtype, refreshregion, refreshdither) function(self, widget, refreshtype, refreshregion, refreshdither)
@ -609,6 +597,7 @@ dbg:guard(UIManager, 'setDirty',
dbg:v("INFO: invalid widget for setDirty()", debug.traceback()) dbg:v("INFO: invalid widget for setDirty()", debug.traceback())
end end
end) end)
--]]
--[[-- --[[--
Clear the full repaint & refresh queues. Clear the full repaint & refresh queues.
@ -684,14 +673,16 @@ end
--- Get top widget (name if possible, ref otherwise). --- Get top widget (name if possible, ref otherwise).
function UIManager:getTopWidget() function UIManager:getTopWidget()
local top = self._window_stack[#self._window_stack] if not self._window_stack[1] then
if not top or not top.widget then -- No widgets in the stack, bye!
return nil return nil
end end
if top.widget.name then
return top.widget.name local widget = self._window_stack[#self._window_stack].widget
if widget.name then
return widget.name
end end
return top.widget return widget
end end
--[[-- --[[--
@ -710,19 +701,16 @@ function UIManager:getSecondTopmostWidget()
-- Because everything is terrible, you can actually instantiate multiple VirtualKeyboards, -- Because everything is terrible, you can actually instantiate multiple VirtualKeyboards,
-- and they'll stack at the top, so, loop until we get something that *isn't* VK... -- and they'll stack at the top, so, loop until we get something that *isn't* VK...
for i = #self._window_stack - 1, 1, -1 do for i = #self._window_stack - 1, 1, -1 do
local sec = self._window_stack[i] local widget = self._window_stack[i].widget
if not sec or not sec.widget then
return nil
end
if sec.widget.name then if widget.name then
if sec.widget.name ~= "VirtualKeyboard" then if widget.name ~= "VirtualKeyboard" then
return sec.widget.name return widget.name
end end
-- Meaning if name is set, and is set to VK => continue, as we want the *next* widget. -- Meaning if name is set, and is set to VK => continue, as we want the *next* widget.
-- I *really* miss the continue keyword, Lua :/. -- I *really* miss the continue keyword, Lua :/.
else else
return sec.widget return widget
end end
end end
@ -732,9 +720,10 @@ end
--- Check if a widget is still in the window stack, or is a subwidget of a widget still in the window stack. --- Check if a widget is still in the window stack, or is a subwidget of a widget still in the window stack.
function UIManager:isSubwidgetShown(widget, max_depth) function UIManager:isSubwidgetShown(widget, max_depth)
for i = #self._window_stack, 1, -1 do for i = #self._window_stack, 1, -1 do
local matched, depth = util.arrayReferences(self._window_stack[i].widget, widget, max_depth) local w = self._window_stack[i].widget
local matched, depth = util.arrayReferences(w, widget, max_depth)
if matched then if matched then
return matched, depth, self._window_stack[i].widget return matched, depth, w
end end
end end
return false return false
@ -750,19 +739,11 @@ function UIManager:isWidgetShown(widget)
return false return false
end end
--[[--
Returns the region of the previous refresh.
@return a rectangle @{ui.geometry.Geom|Geom} object
]]
function UIManager:getPreviousRefreshRegion()
return self._last_refresh_region
end
--- Signals to quit. --- Signals to quit.
function UIManager:quit() function UIManager:quit(exit_code)
if not self._running then return end if not self._running then return end
logger.info("quitting uimanager") logger.info("quitting uimanager with exit code:", exit_code or 0)
self._exit_code = exit_code
self._task_queue_dirty = false self._task_queue_dirty = false
self._running = false self._running = false
self._run_forever = nil self._run_forever = nil
@ -791,25 +772,29 @@ which itself will take care of propagating an event to its members.
@param event an @{ui.event.Event|Event} object @param event an @{ui.event.Event|Event} object
]] ]]
function UIManager:sendEvent(event) function UIManager:sendEvent(event)
if #self._window_stack == 0 then return end if not self._window_stack[1] then
-- No widgets in the stack!
return
end
-- The top widget gets to be the first to get the event -- The top widget gets to be the first to get the event
local top_widget = self._window_stack[#self._window_stack] local top_widget = self._window_stack[#self._window_stack].widget
-- A toast widget gets closed by any event, and -- A toast widget gets closed by any event, and lets the event be handled by a lower widget.
-- lets the event be handled by a lower widget -- (Notification is our only widget flagged as such).
-- (Notification is our single widget with toast=true) while top_widget.toast do -- close them all
while top_widget.widget.toast do -- close them all self:close(top_widget)
self:close(top_widget.widget) if not self._window_stack[1] then
if #self._window_stack == 0 then return end return
top_widget = self._window_stack[#self._window_stack] end
top_widget = self._window_stack[#self._window_stack].widget
end end
if top_widget.widget:handleEvent(event) then if top_widget:handleEvent(event) then
return return
end end
if top_widget.widget.active_widgets then if top_widget.active_widgets then
for _, active_widget in ipairs(top_widget.widget.active_widgets) do for _, active_widget in ipairs(top_widget.active_widgets) do
if active_widget:handleEvent(event) then return end if active_widget:handleEvent(event) then return end
end end
end end
@ -824,20 +809,24 @@ function UIManager:sendEvent(event)
local checked_widgets = {top_widget} local checked_widgets = {top_widget}
local i = #self._window_stack local i = #self._window_stack
while i > 0 do while i > 0 do
local widget = self._window_stack[i] local widget = self._window_stack[i].widget
if checked_widgets[widget] == nil then if not checked_widgets[widget] then
checked_widgets[widget] = true checked_widgets[widget] = true
-- Widget's active widgets have precedence to handle this event -- Widget's active widgets have precedence to handle this event
-- NOTE: While FileManager only has a single (screenshotter), ReaderUI has a few active_widgets. -- NOTE: ReaderUI & FileManager have their registered modules referenced as such.
if widget.widget.active_widgets then if widget.active_widgets then
for _, active_widget in ipairs(widget.widget.active_widgets) do for _, active_widget in ipairs(widget.active_widgets) do
if active_widget:handleEvent(event) then return end if active_widget:handleEvent(event) then
return
end
end end
end end
if widget.widget.is_always_active then if widget.is_always_active then
-- Widget itself is flagged always active, let it handle the event -- Widget itself is flagged always active, let it handle the event
-- NOTE: is_always_active widgets currently are widgets that want to show a VirtualKeyboard or listen to Dispatcher events -- NOTE: is_always_active widgets are currently widgets that want to show a VirtualKeyboard or listen to Dispatcher events
if widget.widget:handleEvent(event) then return end if widget:handleEvent(event) then
return
end
end end
i = #self._window_stack i = #self._window_stack
else else
@ -857,10 +846,10 @@ function UIManager:broadcastEvent(event)
local checked_widgets = {} local checked_widgets = {}
local i = #self._window_stack local i = #self._window_stack
while i > 0 do while i > 0 do
local widget = self._window_stack[i] local widget = self._window_stack[i].widget
if checked_widgets[widget] == nil then if not checked_widgets[widget] then
checked_widgets[widget] = true checked_widgets[widget] = true
widget.widget:handleEvent(event) widget:handleEvent(event)
i = #self._window_stack i = #self._window_stack
else else
i = i - 1 i = i - 1
@ -944,17 +933,20 @@ function UIManager:getElapsedTimeSinceBoot()
end end
-- precedence of refresh modes: -- precedence of refresh modes:
local refresh_modes = { fast = 1, ui = 2, partial = 3, flashui = 4, flashpartial = 5, full = 6 } local refresh_modes = { a2 = 1, fast = 2, ui = 3, partial = 4, ["[ui]"] = 5, ["[partial]"] = 6, flashui = 7, flashpartial = 8, full = 9 }
-- NOTE: We might want to introduce a "force_fast" that points to fast, but has the highest priority, -- NOTE: We might want to introduce a "force_a2" that points to fast, but has the highest priority,
-- for the few cases where we might *really* want to enforce fast (for stuff like panning or skimming?). -- for the few cases where we might *really* want to enforce fast (for stuff like panning or skimming?).
-- refresh methods in framebuffer implementation -- refresh methods in framebuffer implementation
local refresh_methods = { local refresh_methods = {
fast = "refreshFast", a2 = Screen.refreshA2,
ui = "refreshUI", fast = Screen.refreshFast,
partial = "refreshPartial", ui = Screen.refreshUI,
flashui = "refreshFlashUI", partial = Screen.refreshPartial,
flashpartial = "refreshFlashPartial", ["[ui]"] = Screen.refreshNoMergeUI,
full = "refreshFull", ["[partial]"] = Screen.refreshNoMergePartial,
flashui = Screen.refreshFlashUI,
flashpartial = Screen.refreshFlashPartial,
full = Screen.refreshFull,
} }
--[[ --[[
@ -992,7 +984,7 @@ Widgets call this in their `paintTo()` method in order to notify
UIManager that a certain part of the screen is to be refreshed. UIManager that a certain part of the screen is to be refreshed.
@string mode @string mode
refresh mode (`"full"`, `"flashpartial"`, `"flashui"`, `"partial"`, `"ui"`, `"fast"`) refresh mode (`"full"`, `"flashpartial"`, `"flashui"`, `"[partial]"`, `"[ui]"`, `"partial"`, `"ui"`, `"fast"`, `"a2"`)
@param region @param region
A rectangle @{ui.geometry.Geom|Geom} object that specifies the region to be updated. A rectangle @{ui.geometry.Geom|Geom} object that specifies the region to be updated.
Optional, update will affect whole screen if not specified. Optional, update will affect whole screen if not specified.
@ -1071,16 +1063,16 @@ function UIManager:_refresh(mode, region, dither)
-- (e.g., multiple setDirty calls queued when showing/closing a widget because of update mechanisms), -- (e.g., multiple setDirty calls queued when showing/closing a widget because of update mechanisms),
-- as well as a few actually effective merges -- as well as a few actually effective merges
-- (e.g., the disappearance of a selection HL with the following menu update). -- (e.g., the disappearance of a selection HL with the following menu update).
for i = 1, #self._refresh_stack do for i, refresh in ipairs(self._refresh_stack) do
-- Check for collision with refreshes that are already enqueued -- Check for collision with refreshes that are already enqueued
-- NOTE: intersect *means* intersect: we won't merge edge-to-edge regions (but the EPDC probably will). -- NOTE: intersect *means* intersect: we won't merge edge-to-edge regions (but the EPDC probably will).
if region:intersectWith(self._refresh_stack[i].region) then if region:intersectWith(refresh.region) then
-- combine both refreshes' regions -- combine both refreshes' regions
local combined = region:combine(self._refresh_stack[i].region) local combined = region:combine(refresh.region)
-- update the mode, if needed -- update the mode, if needed
mode = update_mode(mode, self._refresh_stack[i].mode) mode = update_mode(mode, refresh.mode)
-- dithering hints are viral, one is enough to infect the whole queue -- dithering hints are viral, one is enough to infect the whole queue
dither = update_dither(dither, self._refresh_stack[i].dither) dither = update_dither(dither, refresh.dither)
-- remove colliding refresh -- remove colliding refresh
table.remove(self._refresh_stack, i) table.remove(self._refresh_stack, i)
-- and try again with combined data -- and try again with combined data
@ -1134,26 +1126,27 @@ function UIManager:_repaint()
--]] --]]
for i = start_idx, #self._window_stack do for i = start_idx, #self._window_stack do
local widget = self._window_stack[i] local window = self._window_stack[i]
local widget = window.widget
-- paint if current widget or any widget underneath is dirty -- paint if current widget or any widget underneath is dirty
if dirty or self._dirty[widget.widget] then if dirty or self._dirty[widget] then
-- pass hint to widget that we got when setting widget dirty -- pass hint to widget that we got when setting widget dirty
-- the widget can use this to decide which parts should be refreshed -- the widget can use this to decide which parts should be refreshed
logger.dbg("painting widget:", widget.widget.name or widget.widget.id or tostring(widget)) logger.dbg("painting widget:", widget.name or widget.id or tostring(widget))
Screen:beforePaint() Screen:beforePaint()
-- NOTE: Nothing actually seems to use the final argument? -- NOTE: Nothing actually seems to use the final argument?
-- Could be used by widgets to know whether they're being repainted because they're actually dirty (it's true), -- Could be used by widgets to know whether they're being repainted because they're actually dirty (it's true),
-- or because something below them was (it's nil). -- or because something below them was (it's nil).
widget.widget:paintTo(Screen.bb, widget.x, widget.y, self._dirty[widget.widget]) widget:paintTo(Screen.bb, window.x, window.y, self._dirty[widget])
-- and remove from list after painting -- and remove from list after painting
self._dirty[widget.widget] = nil self._dirty[widget] = nil
-- trigger a repaint for every widget above us, too -- trigger a repaint for every widget above us, too
dirty = true dirty = true
-- if any of 'em were dithered, we'll want to dither the final refresh -- if any of 'em were dithered, we'll want to dither the final refresh
if widget.widget.dithered then if widget.dithered then
logger.dbg("_repaint: it was dithered, infecting the refresh queue") logger.dbg("_repaint: it was dithered, infecting the refresh queue")
dithered = true dithered = true
end end
@ -1165,13 +1158,15 @@ function UIManager:_repaint()
local refreshtype, region, dither = refreshfunc() local refreshtype, region, dither = refreshfunc()
-- honor dithering hints from *anywhere* in the dirty stack -- honor dithering hints from *anywhere* in the dirty stack
dither = update_dither(dither, dithered) dither = update_dither(dither, dithered)
if refreshtype then self:_refresh(refreshtype, region, dither) end if refreshtype then
self:_refresh(refreshtype, region, dither)
end
end end
self._refresh_func_stack = {} self._refresh_func_stack = {}
-- we should have at least one refresh if we did repaint. If we don't, we -- we should have at least one refresh if we did repaint. If we don't, we
-- add one now and log a warning if we are debugging -- add one now and log a warning if we are debugging
if dirty and #self._refresh_stack == 0 then if dirty and not self._refresh_stack[1] then
logger.dbg("no refresh got enqueued. Will do a partial full screen refresh, which might be inefficient") logger.dbg("no refresh got enqueued. Will do a partial full screen refresh, which might be inefficient")
self:_refresh("partial") self:_refresh("partial")
end end
@ -1185,9 +1180,12 @@ function UIManager:_repaint()
refresh.dither = nil refresh.dither = nil
end end
dbg:v("triggering refresh", refresh) dbg:v("triggering refresh", refresh)
--[[
-- Remember the refresh region -- Remember the refresh region
self._last_refresh_region = refresh.region self._last_refresh_region = refresh.region:copy()
Screen[refresh_methods[refresh.mode]](Screen, --]]
refresh_methods[refresh.mode](Screen,
refresh.region.x, refresh.region.y, refresh.region.x, refresh.region.y,
refresh.region.w, refresh.region.h, refresh.region.w, refresh.region.h,
refresh.dither) refresh.dither)
@ -1313,7 +1311,7 @@ function UIManager:handleInputEvent(input_event)
if handler then if handler then
handler(input_event) handler(input_event)
else else
self.event_handlers["__default__"](input_event) self.event_handlers.__default__(input_event)
end end
end end
@ -1345,7 +1343,7 @@ function UIManager:handleInput()
--dbg("---------------------------------------------------") --dbg("---------------------------------------------------")
-- stop when we have no window to show -- stop when we have no window to show
if #self._window_stack == 0 and not self._run_forever then if not self._window_stack[1] and not self._run_forever then
logger.info("no dialog left to show") logger.info("no dialog left to show")
self:quit() self:quit()
return nil return nil
@ -1365,7 +1363,7 @@ function UIManager:handleInput()
local wait_us = self.INPUT_TIMEOUT local wait_us = self.INPUT_TIMEOUT
-- If we have any ZMQs registered, ZMQ_TIMEOUT is another upper bound. -- If we have any ZMQs registered, ZMQ_TIMEOUT is another upper bound.
if #self._zeromqs > 0 then if self._zeromqs[1] then
wait_us = math.min(wait_us or math.huge, self.ZMQ_TIMEOUT) wait_us = math.min(wait_us or math.huge, self.ZMQ_TIMEOUT)
end end
@ -1419,7 +1417,6 @@ function UIManager:handleInput()
xpcall(function() self:handleInput() end, function(err) xpcall(function() self:handleInput() end, function(err)
io.stderr:write(err .. "\n") io.stderr:write(err .. "\n")
io.stderr:write(debug.traceback() .. "\n") io.stderr:write(debug.traceback() .. "\n")
io.stderr:flush()
self.looper:close() self.looper:close()
os.exit(1, true) os.exit(1, true)
end) end)
@ -1480,28 +1477,28 @@ This function usually puts the device into suspension.
]] ]]
function UIManager:suspend() function UIManager:suspend()
-- Should always exist, as defined in `generic/device` or overwritten with `setEventHandlers` -- Should always exist, as defined in `generic/device` or overwritten with `setEventHandlers`
if self.event_handlers["Suspend"] then if self.event_handlers.Suspend then
-- Give the other event handlers a chance to be executed. -- Give the other event handlers a chance to be executed.
-- `Suspend` and `Resume` events will be sent by the handler -- `Suspend` and `Resume` events will be sent by the handler
UIManager:nextTick(self.event_handlers["Suspend"]) UIManager:nextTick(self.event_handlers.Suspend)
end end
end end
function UIManager:reboot() function UIManager:reboot()
-- Should always exist, as defined in `generic/device` or overwritten with `setEventHandlers` -- Should always exist, as defined in `generic/device` or overwritten with `setEventHandlers`
if self.event_handlers["Reboot"] then if self.event_handlers.Reboot then
-- Give the other event handlers a chance to be executed. -- Give the other event handlers a chance to be executed.
-- 'Reboot' event will be sent by the handler -- 'Reboot' event will be sent by the handler
UIManager:nextTick(self.event_handlers["Reboot"]) UIManager:nextTick(self.event_handlers.Reboot)
end end
end end
function UIManager:powerOff() function UIManager:powerOff()
-- Should always exist, as defined in `generic/device` or overwritten with `setEventHandlers` -- Should always exist, as defined in `generic/device` or overwritten with `setEventHandlers`
if self.event_handlers["PowerOff"] then if self.event_handlers.PowerOff then
-- Give the other event handlers a chance to be executed. -- Give the other event handlers a chance to be executed.
-- 'PowerOff' event will be sent by the handler -- 'PowerOff' event will be sent by the handler
UIManager:nextTick(self.event_handlers["PowerOff"]) UIManager:nextTick(self.event_handlers.PowerOff)
end end
end end
@ -1557,15 +1554,13 @@ end
--- Sanely restart KOReader (on supported platforms). --- Sanely restart KOReader (on supported platforms).
function UIManager:restartKOReader() function UIManager:restartKOReader()
self:quit()
-- This is just a magic number to indicate the restart request for shell scripts. -- This is just a magic number to indicate the restart request for shell scripts.
self._exit_code = 85 self:quit(85)
end end
--- Sanely abort KOReader (e.g., exit sanely, but with a non-zero return code). --- Sanely abort KOReader (e.g., exit sanely, but with a non-zero return code).
function UIManager:abort() function UIManager:abort()
self:quit() self:quit(1)
self._exit_code = 1
end end
UIManager:init() UIManager:init()

@ -319,24 +319,28 @@ function VirtualKey:genKeyboardLayoutKeyChars()
end end
-- NOTE: We currently don't ever set want_flash to true (c.f., our invert method). -- NOTE: We currently don't ever set want_flash to true (c.f., our invert method).
function VirtualKey:update_keyboard(want_flash, want_fast) function VirtualKey:update_keyboard(want_flash, want_a2)
-- NOTE: We mainly use "fast" when inverted & "ui" when not, with a cherry on top: -- NOTE: We use "a2" for the highlights.
-- we flash the *full* keyboard instead when we release a hold. -- We flash the *full* keyboard when we release a hold.
if want_flash then if want_flash then
UIManager:setDirty(self.keyboard, function() UIManager:setDirty(self.keyboard, function()
return "flashui", self.keyboard[1][1].dimen return "flashui", self.keyboard[1][1].dimen
end) end)
else else
local refresh_type = "ui" local refresh_type = "ui"
if want_fast then if want_a2 then
refresh_type = "fast" refresh_type = "a2"
end end
-- Only repaint the key itself, not the full board... -- Only repaint the key itself, not the full board...
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y) UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
UIManager:setDirty(nil, function() logger.dbg("update key", self.key)
logger.dbg("update key region", self[1].dimen) UIManager:setDirty(nil, refresh_type, self[1].dimen)
return refresh_type, self[1].dimen
end) -- NOTE: On MTK, we'd have to forcibly stall a bit for the highlights to actually show.
--[[
UIManager:forceRePaint()
UIManager:yieldToEPDC(3000)
--]]
end end
end end
@ -449,7 +453,7 @@ function VirtualKey:invert(invert, hold)
else else
self[1].inner_bordersize = 0 self[1].inner_bordersize = 0
end end
self:update_keyboard(hold, false) self:update_keyboard(hold, true)
end end
VirtualKeyPopup = FocusManager:new{ VirtualKeyPopup = FocusManager:new{

@ -220,12 +220,16 @@ function AutoSuspend:_schedule_standby()
-- When we're in a state where entering suspend is undesirable, we simply postpone the check by the full delay. -- When we're in a state where entering suspend is undesirable, we simply postpone the check by the full delay.
local standby_delay_seconds local standby_delay_seconds
if NetworkMgr:isWifiOn() then -- NOTE: As this may fire repeatedly, we don't want to poke the actual Device implementation every few seconds,
-- instead, we rely on NetworkMgr's last known status. (i.e., this *should* match NetworkMgr:isWifiOn).
if NetworkMgr:getWifiState() then
-- Don't enter standby if wifi is on, as this will break in fun and interesting ways (from Wi-Fi issues to kernel deadlocks). -- Don't enter standby if wifi is on, as this will break in fun and interesting ways (from Wi-Fi issues to kernel deadlocks).
--logger.dbg("AutoSuspend: WiFi is on, delaying standby") --logger.dbg("AutoSuspend: WiFi is on, delaying standby")
standby_delay_seconds = self.auto_standby_timeout_seconds standby_delay_seconds = self.auto_standby_timeout_seconds
elseif Device.powerd:isCharging() and not Device:canPowerSaveWhileCharging() then elseif Device.powerd:isCharging() and not Device:canPowerSaveWhileCharging() then
-- Don't enter standby when charging on devices where charging prevents entering low power states. -- Don't enter standby when charging on devices where charging *may* prevent entering low power states.
-- (*May*, because depending on the USB controller, it might depend on what it's plugged to, and how it's setup:
-- e.g., generally, on those devices, USBNet being enabled is guaranteed to prevent PM).
-- NOTE: Minor simplification here, we currently don't do the hasAuxBattery dance like in _schedule, -- NOTE: Minor simplification here, we currently don't do the hasAuxBattery dance like in _schedule,
-- because all the hasAuxBattery devices can currently enter PM states while charging ;). -- because all the hasAuxBattery devices can currently enter PM states while charging ;).
--logger.dbg("AutoSuspend: charging, delaying standby") --logger.dbg("AutoSuspend: charging, delaying standby")

@ -1,5 +1,15 @@
#!./luajit #!./luajit
io.stdout:write([[
-- Enforce line-buffering for stdout (this is the default if it points to a tty, but we redirect to a file on most platforms).
local ffi = require("ffi")
local C = ffi.C
ffi.cdef[[
extern struct _IO_FILE *stdout;
void setlinebuf(struct _IO_FILE *);
]]
C.setlinebuf(C.stdout)
io.write([[
--------------------------------------------- ---------------------------------------------
launching... launching...
_ _____ ____ _ _ _____ ____ _
@ -11,7 +21,6 @@ io.stdout:write([[
It's a scroll... It's a codex... It's KOReader! It's a scroll... It's a codex... It's KOReader!
[*] Current time: ]], os.date("%x-%X"), "\n") [*] Current time: ]], os.date("%x-%X"), "\n")
io.stdout:flush()
-- Set up Lua and ffi search paths -- Set up Lua and ffi search paths
require("setupkoenv") require("setupkoenv")
@ -21,8 +30,7 @@ local userpatch = require("userpatch")
userpatch.applyPatches(userpatch.early_once) userpatch.applyPatches(userpatch.early_once)
userpatch.applyPatches(userpatch.early) userpatch.applyPatches(userpatch.early)
io.stdout:write(" [*] Version: ", require("version"):getCurrentRevision(), "\n\n") io.write(" [*] Version: ", require("version"):getCurrentRevision(), "\n\n")
io.stdout:flush()
-- Load default settings -- Load default settings
G_defaults = require("luadefaults"):open() G_defaults = require("luadefaults"):open()

@ -34,7 +34,6 @@ if not busted_ok then
end end
end end
require "defaults"
package.path = "?.lua;common/?.lua;rocks/share/lua/5.1/?.lua;frontend/?.lua;" .. package.path package.path = "?.lua;common/?.lua;rocks/share/lua/5.1/?.lua;frontend/?.lua;" .. package.path
package.cpath = "?.so;common/?.so;/usr/lib/lua/?.so;rocks/lib/lua/5.1/?.so;" .. package.cpath package.cpath = "?.so;common/?.so;/usr/lib/lua/?.so;rocks/lib/lua/5.1/?.so;" .. package.cpath

@ -299,8 +299,8 @@ describe("device module", function()
ReaderUI:doShowReader(sample_pdf) ReaderUI:doShowReader(sample_pdf)
local readerui = ReaderUI._getRunningInstance() local readerui = ReaderUI._getRunningInstance()
stub(readerui, "onFlushSettings") stub(readerui, "onFlushSettings")
UIManager.event_handlers["PowerPress"]() UIManager.event_handlers.PowerPress()
UIManager.event_handlers["PowerRelease"]() UIManager.event_handlers.PowerRelease()
assert.stub(readerui.onFlushSettings).was_called() assert.stub(readerui.onFlushSettings).was_called()
Device.suspend:revert() Device.suspend:revert()
@ -343,8 +343,8 @@ describe("device module", function()
ReaderUI:doShowReader(sample_pdf) ReaderUI:doShowReader(sample_pdf)
local readerui = ReaderUI._getRunningInstance() local readerui = ReaderUI._getRunningInstance()
stub(readerui, "onFlushSettings") stub(readerui, "onFlushSettings")
UIManager.event_handlers["PowerPress"]() UIManager.event_handlers.PowerPress()
UIManager.event_handlers["PowerRelease"]() UIManager.event_handlers.PowerRelease()
assert.stub(readerui.onFlushSettings).was_called() assert.stub(readerui.onFlushSettings).was_called()
Device.suspend:revert() Device.suspend:revert()
@ -374,8 +374,8 @@ describe("device module", function()
ReaderUI:doShowReader(sample_pdf) ReaderUI:doShowReader(sample_pdf)
local readerui = ReaderUI._getRunningInstance() local readerui = ReaderUI._getRunningInstance()
stub(readerui, "onFlushSettings") stub(readerui, "onFlushSettings")
UIManager.event_handlers["PowerPress"]() UIManager.event_handlers.PowerPress()
UIManager.event_handlers["PowerRelease"]() UIManager.event_handlers.PowerRelease()
assert.stub(readerui.onFlushSettings).was_called() assert.stub(readerui.onFlushSettings).was_called()
Device.suspend:revert() Device.suspend:revert()
@ -424,8 +424,8 @@ describe("device module", function()
ReaderUI:doShowReader(sample_pdf) ReaderUI:doShowReader(sample_pdf)
local readerui = ReaderUI._getRunningInstance() local readerui = ReaderUI._getRunningInstance()
stub(readerui, "onFlushSettings") stub(readerui, "onFlushSettings")
UIManager.event_handlers["PowerPress"]() UIManager.event_handlers.PowerPress()
UIManager.event_handlers["PowerRelease"]() UIManager.event_handlers.PowerRelease()
assert.stub(readerui.onFlushSettings).was_called() assert.stub(readerui.onFlushSettings).was_called()
Device.suspend:revert() Device.suspend:revert()

Loading…
Cancel
Save