You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koreader/frontend/device/generic/device.lua

1142 lines
45 KiB
Lua

--[[--
Generic device abstraction.
This module defines stubs for common methods.
--]]
local DataStorage = require("datastorage")
local Event = require("ui/event")
local Geom = require("ui/geometry")
local UIManager -- Updated on UIManager init
local logger = require("logger")
local ffi = require("ffi")
local time = require("ui/time")
local util = require("util")
local _ = require("gettext")
local ffiUtil = require("ffi/util")
local C = ffi.C
local T = ffiUtil.template
-- We'll need a bunch of stuff for getifaddrs & co in Device:retrieveNetworkInfo
require("ffi/posix_h")
local function yes() return true end
local function no() return false end
local Device = {
screen_saver_mode = false,
screen_saver_lock = false,
is_cover_closed = false,
model = nil,
powerd = nil,
screen = nil,
input = nil,
home_dir = nil,
-- For Kobo, wait at least 15 seconds before calling suspend script. Otherwise, suspend might
-- fail and the battery will be drained while we are in screensaver mode
suspend_wait_timeout = 15,
-- hardware feature tests: (these are functions!)
hasBattery = yes,
hasAuxBattery = no,
hasKeyboard = no,
hasKeys = no,
canKeyRepeat = no,
hasDPad = no,
hasExitOptions = yes,
hasFewKeys = no,
hasWifiToggle = yes,
NetworkMgr: Handle non-blocking turnOnWifi implementations better (#10863) * Device: Add a `hasSeamlessWifiToggle` devcap to complement `hasWifiToggle`, to denote platforms where we can toggle WiFi without losing focus, as this has obvious UX impacts, and less obvious technical impacts on some of the NetworkMgr innards... * Android: Mark as `!hasSeamlessWifiToggle`, as it requires losing focus to the system settings. Moreover, `turnOnWifi` returns *immediately* and we *still* run in the background during that time, for extra spiciness... * NetworkMgr: Ensure only *one* call to `turnOnWifi` will actually go on when stuff gets re-scheduled by the `beforeWifiAction` framework. * NetworkMgr: Ensure the `beforeWifiAction` framework will not re-schedule the same thing *ad vitam aeternam* if a previous connection attempt is still ongoing. (i.e., previously, on Android, if you backed out of the system settings, you entered the Benny Hill dimension, as NetworkMgr would keep throwing you back into the system settings ;p). This has a few implications on callbacks requested by subsequent connection attempts, though. Generally, we'll try to honor *explicitly interactive* callbacks, but `beforeWifiAction` stuff will be dropped (only the original cb is preserved). That's what prevents the aforementioned infinite loop, as the `beforeWifiAction` framework was based on the assumption that `turnOnWifi` somewhat guaranteed `isConnected` to be true on return, something which is only actually true on `hasWifiManager` platforms. * NetworkMgr: In `prompt` mode, the above implies that the prompt will not even be shown for concurrent attempts, as it's otherwise extremely confusing (KOSync on Android being a prime example, as it has a pair of Suspend/Resume handlers, so the initial attempt trips those two because of the focus switch >_<"). * NetworkMgr: Don't attempt to kill wifi when aborting a connection attempt on `!hasSeamlessWifiToggle` (because, again, it'll break UX, and also because it might run at very awkward times (e.g., I managed to go back to KOReader *between* a FM/Reader switch at one point, which promptly caused `UIManager` to exit because there was nothing to show ;p). * NetworkMgr: Don't drop the connectivity callback when `beforeWifiAction` is set to prompt and the target happens to use a connectivity check in its `turnOnWifi` implementation (e.g., on Kindle). * Android: Add an `"ignore"` `beforeWifiAction` mode, that'll do nothing but schedule the connectivity check with its callback (with the intent being the system will eventually enable wifi on its own Soon(TM)). If you're already online, the callback will run immediately, obviously. If you followed the early discussions on this PR, this closely matches what happens on `!hasWifiToggle` platforms (as flagging Android that way was one of the possible approaches here). * NetworkMgr: Bail out early in `goOnlineToRun` if `beforeWifiAction` isn't `"turn_on"`. Prompt cannot work there, and while ignore technically could, it would serve very little purpose given its intended use case. * KOSync: Neuter the Resume/Suspend handlers early on `CloseDocument`, as this is how focus switches are handled on Android, and if `beforeWifiAction` is `turn_on` and you were offline at the time, we'd trip them because of the swap to system settings to enable wifi. * KOSync: Allow `auto_sync` to be enabled regardless of the `beforeWifiAction` mode on `!hasSeamlessWifiToggle` platforms. Prompt is still a terrible idea, but given that `goOnlineToRun` now aborts early if the mode is not supported, it's less of a problem.
8 months ago
hasSeamlessWifiToggle = yes, -- Can toggle Wi-Fi without focus loss and extra user interaction (i.e., not Android)
hasWifiManager = no,
hasWifiRestore = no,
isDefaultFullscreen = yes,
isHapticFeedbackEnabled = no,
isDeprecated = no, -- device no longer receive OTA updates
isTouchDevice = no,
hasFrontlight = no,
hasNaturalLight = no, -- FL warmth implementation specific to NTX boards (Kobo, Cervantes)
hasNaturalLightMixer = no, -- Same, but only found on newer boards
hasNaturalLightApi = no,
hasClipboard = yes, -- generic internal clipboard on all devices
hasEinkScreen = yes,
hasExternalSD = no, -- or other storage volume that cannot be accessed using the File Manager
Enable HW dithering in a few key places (#4541) * Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4) * FileManager and co. (where appropriate, i.e., when covers are shown) * Book Status * Reader, where appropriate: * CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone). * Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices). * ScreenSaver * ImageViewer * Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it). (The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu). * Hunted down a few redundant repaints (unneeded setDirty("all") calls), either by switching the widget to nil when only a refresh was needed, and not a repaint, or by passing the appropritate widget to setDirty. (Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard). There were also a few instances of 'em right behind a widget close. * Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog. We unfortunately do need to do it when switching tabs, because of their variable heights. * On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes! * Fix another debug guard in Kobo sysfs_light * Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not. PS: (Almost :100: commits! :D)
5 years ago
canHWDither = no,
canHWInvert = no,
hasKaleidoWfm = no,
canDoSwipeAnimation = no,
canModifyFBInfo = no, -- some NTX boards do wonky things with the rotate flag after a FBIOPUT_VSCREENINFO ioctl
canUseCBB = yes, -- The C BB maintains a 1:1 feature parity with the Lua BB, except that is has NO support for BB4, and limited support for BBRGB24
hasColorScreen = no,
hasBGRFrameBuffer = no,
canImportFiles = no,
canShareText = no,
hasGSensor = no,
isGSensorLocked = no,
canToggleMassStorage = no,
canToggleChargingLED = no,
_updateChargingLED = nil,
canUseWAL = yes, -- requires mmap'ed I/O on the target FS
canRestart = yes,
canSuspend = no,
canStandby = no,
canPowerSaveWhileCharging = no,
total_standby_time = 0, -- total time spent in standby
last_standby_time = 0,
total_suspend_time = 0, -- total time spent in suspend
last_suspend_time = 0,
canReboot = no,
canPowerOff = no,
canAssociateFileExtensions = no,
-- Start and stop text input mode (e.g. open soft keyboard, etc)
startTextInput = function() end,
stopTextInput = function() end,
-- use these only as a last resort. We should abstract the functionality
-- and have device dependent implementations in the corresponting
-- device/<devicetype>/device.lua file
-- (these are functions!)
isAndroid = no,
isCervantes = no,
isKindle = no,
isKobo = no,
isPocketBook = no,
isRemarkable = no,
isSonyPRSTUX = no,
isSDL = no,
isEmulator = no,
isDesktop = no,
-- some devices have part of their screen covered by the bezel
viewport = nil,
-- enforce portrait orientation of display when FB defaults to landscape
isAlwaysPortrait = no,
-- On some devices (eg newer pocketbook) we can force HW rotation on the fly (before each update)
-- The value here is table of 4 elements mapping the sensible linux constants to whatever
-- nonsense the device actually has. Canonically it should return { 0, 1, 2, 3 } if the device
-- matches <linux/fb.h> FB_ROTATE_* constants.
-- See https://github.com/koreader/koreader-base/blob/master/ffi/framebuffer.lua for full template
-- of the table expected.
usingForcedRotation = function() return nil end,
-- needs full screen refresh when resumed from screensaver?
needsScreenRefreshAfterResume = yes,
-- set to yes on devices that support over-the-air incremental updates.
hasOTAUpdates = no,
-- For devices that have non-blocking OTA updates, this function will return true if the download is currently running.
hasOTARunning = no,
-- set to yes on devices that have a non-blocking isWifiOn implementation
-- (c.f., https://github.com/koreader/koreader/pull/5211#issuecomment-521304139)
hasFastWifiStatusQuery = no,
-- set to yes on devices with system fonts
hasSystemFonts = no,
canOpenLink = no,
openLink = no,
canExternalDictLookup = no,
}
Clarify our OOP semantics across the codebase (#9586) Basically: * Use `extend` for class definitions * Use `new` for object instantiations That includes some minor code cleanups along the way: * Updated `Widget`'s docs to make the semantics clearer. * Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283) * Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass). * Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events. * Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier. * Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references. * ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak). * Terminal: Make sure the shell is killed on plugin teardown. * InputText: Fix Home/End/Del physical keys to behave sensibly. * InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...). * OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of. * ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed! * Kobo: Minor code cleanups.
2 years ago
function Device:extend(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
-- Inverts PageTurn button mappings
-- NOTE: For ref. on Kobo, stored by Nickel in the [Reading] section as invertPageTurnButtons=true
function Device:invertButtons()
if self:hasKeys() and self.input and self.input.event_map then
for key, value in pairs(self.input.event_map) do
if value == "LPgFwd" then
self.input.event_map[key] = "LPgBack"
elseif value == "LPgBack" then
self.input.event_map[key] = "LPgFwd"
elseif value == "RPgFwd" then
self.input.event_map[key] = "RPgBack"
elseif value == "RPgBack" then
self.input.event_map[key] = "RPgFwd"
end
end
-- NOTE: We currently leave self.input.rotation_map alone,
-- which will definitely yield fairly stupid mappings in Landscape...
end
end
function Device:init()
if not self.screen then
error("screen/framebuffer must be implemented")
end
-- opt-out of CBB if the device is broken with it
if not self.canUseCBB() then
local bb = require("ffi/blitbuffer")
bb.has_cblitbuffer = false
bb:enableCBB(false)
end
if self.hasMultitouch == nil then
-- default to assuming multitouch when dealing with a touch device
self.hasMultitouch = self.isTouchDevice
end
self.screen.isColorScreen = self.hasColorScreen
self.screen.isColorEnabled = function()
if G_reader_settings:has("color_rendering") then
return G_reader_settings:isTrue("color_rendering")
else
return self.screen.isColorScreen()
end
end
self.screen.isBGRFrameBuffer = self.hasBGRFrameBuffer
if G_reader_settings:has("low_pan_rate") then
self.screen.low_pan_rate = G_reader_settings:readSetting("low_pan_rate")
else
self.screen.low_pan_rate = self.hasEinkScreen()
end
logger.info("initializing for device", self.model)
logger.info("framebuffer resolution:", self.screen:getRawSize())
if not self.input then
self.input = require("device/input"):new{device = self}
end
if not self.powerd then
self.powerd = require("device/generic/powerd"):new{device = self}
end
-- NOTE: This needs to run *after* implementation-specific event hooks,
-- especially if those require swapping/mirroring...
-- (e.g., Device implementations should setup their own hooks *before* calling this via Generic.init(self)).
if self.viewport then
logger.dbg("setting a viewport:", self.viewport)
self.screen:setViewport(self.viewport)
if self.viewport.x ~= 0 or self.viewport.y ~= 0 then
self.input:registerEventAdjustHook(
self.input.adjustTouchTranslate,
{x = 0 - self.viewport.x, y = 0 - self.viewport.y}
)
end
end
-- Handle button mappings shenanigans
if self:hasKeys() then
if G_reader_settings:isTrue("input_invert_page_turn_keys") then
self:invertButtons()
end
end
if self:hasGSensor() then
-- Setup our standard gyro event handler (EV_MSC:MSC_GYRO)
if G_reader_settings:nilOrFalse("input_ignore_gsensor") then
self.input.handleGyroEv = self.input.handleMiscGyroEv
end
-- Honor the gyro lock
if G_reader_settings:isTrue("input_lock_gsensor") then
self:lockGSensor(true)
end
end
-- Screen:getSize is used throughout the code, and that code usually expects getting a real Geom object...
-- But as implementations come from base, they just return a Geom-like table...
self.screen.getSize = function()
local rect = self.screen.getRawSize(self.screen)
return Geom:new{ x = rect.x, y = rect.y, w = rect.w, h = rect.h }
end
-- DPI
local dpi_override = G_reader_settings:readSetting("screen_dpi")
if dpi_override ~= nil then
self:setScreenDPI(dpi_override)
end
-- Night mode
self.orig_hw_nightmode = self.screen:getHWNightmode()
if G_reader_settings:isTrue("night_mode") then
self.screen:toggleNightMode()
end
-- Ensure the proper rotation on startup.
-- We default to the rotation KOReader closed with.
-- If the rotation is not locked it will be overridden by a book or the FM when opened.
local rotation_mode = G_reader_settings:readSetting("closed_rotation_mode")
if rotation_mode and rotation_mode ~= self.screen:getRotationMode() then
self.screen:setRotationMode(rotation_mode)
end
-- Dithering
if self:hasEinkScreen() then
self.screen:setupDithering()
if self.screen.hw_dithering and G_reader_settings:isTrue("dev_no_hw_dither") then
self.screen:toggleHWDithering(false)
end
if self.screen.sw_dithering and G_reader_settings:isTrue("dev_no_sw_dither") then
self.screen:toggleSWDithering(false)
end
-- NOTE: If device can HW dither (i.e., after setupDithering(), hw_dithering is true, but sw_dithering is false),
-- but HW dither is explicitly disabled, and SW dither enabled, don't leave SW dither disabled (i.e., re-enable sw_dithering)!
if self:canHWDither() and G_reader_settings:isTrue("dev_no_hw_dither") and G_reader_settings:nilOrFalse("dev_no_sw_dither") then
self.screen:toggleSWDithering(true)
end
end
-- Can't be seamless if you can't do it at all ;)
if not self:hasWifiToggle() then
self.hasSeamlessWifiToggle = no
end
end
function Device:setScreenDPI(dpi_override)
-- Passing a nil resets to defaults and clears the override flag
self.screen:setDPI(dpi_override)
self.input.gesture_detector:init()
end
function Device:getDeviceScreenDPI()
return self.display_dpi
end
function Device:getPowerDevice()
return self.powerd
end
function Device:rescheduleSuspend()
UIManager:unschedule(self.suspend)
UIManager:scheduleIn(self.suspend_wait_timeout, self.suspend, self)
end
-- Only used on platforms where we handle suspend ourselves.
function Device:onPowerEvent(ev)
local Screensaver = require("ui/screensaver")
if self.screen_saver_mode then
if ev == "Power" or ev == "Resume" then
if self.is_cover_closed then
-- Don't let power key press wake up device when the cover is in closed state.
logger.dbg("Pressed power while asleep in screen saver mode with a closed sleepcover, going back to suspend...")
self:rescheduleSuspend()
else
logger.dbg("Resuming...")
UIManager:unschedule(self.suspend)
self:resume()
local widget_was_closed = Screensaver:close()
if widget_was_closed and self:needsScreenRefreshAfterResume() then
UIManager:scheduleIn(1, function() self.screen:refreshFull(0, 0, self.screen:getWidth(), self.screen:getHeight()) end)
end
self.powerd:afterResume()
end
elseif ev == "Suspend" then
-- Already in screen saver mode, no need to update the UI (and state, usually) before suspending again.
-- This usually happens when the sleep cover is closed on an already sleeping device,
-- (e.g., it was previously suspended via the Power button).
if self.screen_saver_lock then
-- This can only happen when some sort of screensaver_delay is set,
-- and the user presses the Power button *after* already having woken up the device.
-- In this case, we want to go back to suspend *without* affecting the screensaver,
-- so we simply mimic our own behavior when *not* in screen_saver_mode ;).
logger.dbg("Pressed power while awake in screen saver mode, going back to suspend...")
-- Basically, this is the only difference.
-- We need it because we're actually in a sane post-Resume event state right now.
self.powerd:beforeSuspend()
else
logger.dbg("Already in screen saver mode, going back to suspend...")
end
-- Much like the real suspend codepath below, in case we got here via screen_saver_lock,
-- make sure we murder WiFi again (because restore WiFi on resume could have kicked in).
if self:hasWifiToggle() then
local network_manager = require("ui/network/manager")
if network_manager:isWifiOn() then
network_manager:disableWifi()
end
end
self:rescheduleSuspend()
end
-- else we were not in screensaver mode
elseif ev == "Power" or ev == "Suspend" then
logger.dbg("Suspending...")
-- Add the current state of the SleepCover flag...
logger.dbg("Sleep cover is", self.is_cover_closed and "closed" or "open")
Screensaver:setup()
Screensaver:show()
-- NOTE: show() will return well before the refresh ioctl is even *sent*:
-- the only thing it's done is *enqueued* the refresh in UIManager's stack.
-- Which is why the actual suspension needs to be delayed by suspend_wait_timeout,
-- otherwise, we'd potentially suspend (or attempt to) too soon.
-- On platforms where suspension is done via a sysfs knob, that'd translate to a failed suspend,
-- and on platforms where we defer to a system tool, it'd probably suspend too early!
-- c.f., #6676
Enable HW dithering in a few key places (#4541) * Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4) * FileManager and co. (where appropriate, i.e., when covers are shown) * Book Status * Reader, where appropriate: * CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone). * Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices). * ScreenSaver * ImageViewer * Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it). (The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu). * Hunted down a few redundant repaints (unneeded setDirty("all") calls), either by switching the widget to nil when only a refresh was needed, and not a repaint, or by passing the appropritate widget to setDirty. (Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard). There were also a few instances of 'em right behind a widget close. * Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog. We unfortunately do need to do it when switching tabs, because of their variable heights. * On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes! * Fix another debug guard in Kobo sysfs_light * Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not. PS: (Almost :100: commits! :D)
5 years ago
if self:needsScreenRefreshAfterResume() then
self.screen:refreshFull(0, 0, self.screen:getWidth(), self.screen:getHeight())
Enable HW dithering in a few key places (#4541) * Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4) * FileManager and co. (where appropriate, i.e., when covers are shown) * Book Status * Reader, where appropriate: * CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone). * Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices). * ScreenSaver * ImageViewer * Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it). (The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu). * Hunted down a few redundant repaints (unneeded setDirty("all") calls), either by switching the widget to nil when only a refresh was needed, and not a repaint, or by passing the appropritate widget to setDirty. (Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard). There were also a few instances of 'em right behind a widget close. * Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog. We unfortunately do need to do it when switching tabs, because of their variable heights. * On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes! * Fix another debug guard in Kobo sysfs_light * Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not. PS: (Almost :100: commits! :D)
5 years ago
end
-- NOTE: In the same vein as above, make sure we update the screen *now*, before dealing with Wi-Fi.
UIManager:forceRePaint()
-- NOTE: This side of the check needs to be laxer, some platforms can handle Wi-Fi without WifiManager ;).
if self:hasWifiToggle() then
local network_manager = require("ui/network/manager")
-- NOTE: wifi_was_on does not necessarily mean that Wi-Fi is *currently* on! It means *we* enabled it.
-- This is critical on Kobos (c.f., #3936), where it might still be on from KSM or Nickel,
-- without us being aware of it (i.e., wifi_was_on still unset or false),
-- because suspend will at best fail, and at worst deadlock the system if Wi-Fi is on,
-- regardless of who enabled it!
if network_manager:isWifiOn() then
network_manager:disableWifi()
end
end
-- Only turn off the frontlight *after* we've displayed the screensaver and dealt with Wi-Fi,
-- to prevent that from affecting the smoothness of the frontlight ramp down.
self.powerd:beforeSuspend()
self:rescheduleSuspend()
end
end
function Device:showLightDialog()
local FrontLightWidget = require("ui/widget/frontlightwidget")
UIManager:show(FrontLightWidget:new{})
end
function Device:info()
return self.model
end
function Device:install()
local ConfirmBox = require("ui/widget/confirmbox")
UIManager:show(ConfirmBox:new{
text = _("Update is ready. Install it now?"),
ok_text = _("Install"),
ok_callback = function()
local save_quit = function()
self:saveSettings()
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)
2 years ago
UIManager:quit(85)
end
UIManager:broadcastEvent(Event:new("Exit", save_quit))
end,
cancel_text = _("Later"),
cancel_callback = function()
local InfoMessage = require("ui/widget/infomessage")
UIManager:show(InfoMessage:new{
text = _("The update will be applied the next time KOReader is started."),
unmovable = true,
})
end,
unmovable = true,
})
end
-- Hardware specific method to track opened/closed books (nil on book close)
function Device:notifyBookState(title, document) end
-- Hardware specific method for UI to signal allowed/disallowed standby.
-- The device is allowed to enter standby only from within waitForEvents,
-- and only if allowed state is true at the time of waitForEvents() invocation.
function Device:setAutoStandby(isAllowed) end
-- Hardware specific method to set OS-level file associations to launch koreader. Expects boolean map.
function Device:associateFileExtensions(exts)
logger.dbg("Device:associateFileExtensions():", util.tableSize(exts), "entries, OS handler missing")
end
-- Hardware specific method to handle usb plug in event
function Device:usbPlugIn() end
-- Hardware specific method to handle usb plug out event
function Device:usbPlugOut() end
-- Hardware specific method to suspend the device
function Device:suspend() end
-- Hardware specific method to resume the device
function Device:resume() end
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)
2 years ago
-- 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
function Device:powerOff() end
-- Hardware specific method to reboot the device
function Device:reboot() end
-- Hardware specific method to initialize network manager module
function Device:initNetworkManager() end
function Device:supportsScreensaver() return false end
-- Device specific method to set datetime
function Device:setDateTime(year, month, day, hour, min, sec) end
-- Device specific method if any setting needs being saved
function Device:saveSettings() end
function Device:isAlwaysFullscreen() return true end
function Device:toggleFullscreen() end
4 years ago
-- Simulates suspend/resume
function Device:simulateSuspend() end
function Device:simulateResume() end
-- Put device into standby, input devices (buttons, touchscreen ...) stay enabled
function Device:standby(max_duration) end
--[[--
Device specific method for performing haptic feedback.
@string type Type of haptic feedback. See <https://developer.android.com/reference/android/view/HapticFeedbackConstants.html>.
--]]
function Device:performHapticFeedback(type) end
-- Device specific method for toggling input events
function Device:setIgnoreInput(enable) return true end
-- Device agnostic method for toggling the GSensor
-- (can be reimplemented if need be, but you really, really should try not to. c.f., Kobo, Kindle & PocketBook)
function Device:toggleGSensor(toggle)
if not self:hasGSensor() then
return
end
if self.input then
self.input:toggleGyroEvents(toggle)
end
end
-- Whether or not the GSensor should be locked to the current orientation (i.e. Portrait <-> Inverted Portrait or Landscape <-> Inverted Landscape only)
function Device:lockGSensor(toggle)
if not self:hasGSensor() then
return
end
if toggle == true then
-- Lock GSensor to current roientation
self.isGSensorLocked = yes
elseif toggle == false then
-- Unlock GSensor
self.isGSensorLocked = no
else
-- Toggle it
if self:isGSensorLocked() then
self.isGSensorLocked = no
else
self.isGSensorLocked = yes
end
end
end
-- Device specific method for toggling the charging LED
function Device:toggleChargingLED(toggle) end
-- Device specific method for setting the charging LED to the right state
function Device:setupChargingLED() end
-- Device specific method for enabling a specific amount of CPU cores
-- (Should only be implemented on embedded platforms where we can afford to control that without screwing with the system).
function Device:enableCPUCores(amount) end
-- NOTE: For this to work, all three must be implemented, and getKeyRepeat must be run on init (c.f., Kobo)!
-- Device specific method to get the current key repeat setup (and is responsible for setting the canKeyRepeat cap)
function Device:getKeyRepeat() end
-- Device specific method to disable key repeat
function Device:disableKeyRepeat() end
-- Device specific method to restore the initial key repeat config
function Device:restoreKeyRepeat() end
-- NOTE: This one is for the user-facing toggle, it *ignores* the stock delay/period combo,
-- opting instead for a hard-coded one (as we can't guarantee that key repeat is actually setup properly or at all).
-- Device specific method to toggle key repeat
function Device:toggleKeyRepeat(toggle) end
--[[
prepare for application shutdown
--]]
function Device:exit()
-- Save any implementation-specific settings
self:saveSettings()
-- Save current rotation (or the original rotation if ScreenSaver temporarily modified it) to remember it for next startup
G_reader_settings:saveSetting("closed_rotation_mode", self.orig_rotation_mode or self.screen:getRotationMode())
-- Restore initial HW inversion state
self.screen:setHWNightmode(self.orig_hw_nightmode)
-- Tear down the fb backend
self.screen:close()
-- Flush settings to disk
G_reader_settings:close()
-- I/O teardown
self.input.teardown()
end
-- Lifted from busybox's libbb/inet_cksum.c
local function inet_cksum(ptr, nleft)
local addr = ffi.new("const uint16_t *", ptr)
local sum = ffi.new("unsigned int", 0)
while nleft > 1 do
sum = sum + addr[0]
addr = addr + 1
nleft = nleft - 2
end
if nleft == 1 then
local u8p = ffi.cast("uint8_t *", addr)
sum = sum + u8p[0]
end
sum = bit.rshift(sum, 16) + bit.band(sum, 0xFFFF)
sum = sum + bit.rshift(sum, 16)
sum = bit.bnot(sum)
return ffi.cast("uint16_t", sum)
end
function Device:ping4(ip)
-- Try an unprivileged ICMP socket first
-- NOTE: This is disabled by default, barring custom distro setup during init, c.f., sysctl net.ipv4.ping_group_range
-- It also requires Linux 3.0+ (https://github.com/torvalds/linux/commit/c319b4d76b9e583a5d88d6bf190e079c4e43213d)
local socket, socket_type
socket = C.socket(C.AF_INET, bit.bor(C.SOCK_DGRAM, C.SOCK_NONBLOCK, C.SOCK_CLOEXEC), C.IPPROTO_ICMP)
if socket == -1 then
local errno = ffi.errno()
logger.dbg("Device:ping4: unprivileged ICMP socket:", ffi.string(C.strerror(errno)))
-- Try a raw socket
socket = C.socket(C.AF_INET, bit.bor(C.SOCK_RAW, C.SOCK_NONBLOCK, C.SOCK_CLOEXEC), C.IPPROTO_ICMP)
if socket == -1 then
errno = ffi.errno()
if errno == C.EPERM then
logger.dbg("Device:ping4: opening a RAW ICMP socket requires CAP_NET_RAW capabilities!")
else
logger.dbg("Device:ping4: raw ICMP socket:", ffi.string(C.strerror(errno)))
end
--- Fall-back to the ping CLI tool, in the hope that it's setuid...
if self:isKindle() and self:hasDPad() then
-- NOTE: No -w flag available in the old busybox build used on Legacy Kindles (K4 included)...
return os.execute("ping -q -c1 " .. ip .. " > /dev/null") == 0
else
return os.execute("ping -q -c1 -w2 " .. ip .. " > /dev/null") == 0
end
else
socket_type = C.SOCK_RAW
end
else
socket_type = C.SOCK_DGRAM
end
-- c.f., busybox's networking/ping.c
local DEFDATALEN = 56 -- 64 - 8
local MAXIPLEN = 60
local MAXICMPLEN = 76
-- Base the id on our PID (like busybox)
local myid = ffi.cast("uint16_t", C.getpid())
myid = C.htons(myid)
-- Setup the packet
local packet = ffi.new("char[?]", DEFDATALEN + MAXIPLEN + MAXICMPLEN)
local pkt = ffi.cast("struct icmphdr *", packet)
pkt.type = C.ICMP_ECHO
pkt.un.echo.id = myid
pkt.un.echo.sequence = C.htons(1)
pkt.checksum = inet_cksum(ffi.cast("const void *", pkt), ffi.sizeof(packet))
-- Set the destination address
local addr = ffi.new("struct sockaddr_in")
addr.sin_family = C.AF_INET
local in_addr = ffi.new("struct in_addr")
if C.inet_aton(ip, in_addr) == 0 then
logger.err("Device:ping4: Invalid address:", ip)
C.close(socket)
return false
end
addr.sin_addr = in_addr
addr.sin_port = 0
-- Send the ping
local start_time = time.now()
if C.sendto(socket, packet, DEFDATALEN + C.ICMP_MINLEN, 0, ffi.cast("struct sockaddr*", addr), ffi.sizeof(addr)) == - 1 then
local errno = ffi.errno()
logger.err("Device:ping4: sendto:", ffi.string(C.strerror(errno)))
C.close(socket)
return false
end
-- We'll poll to make timing out easier on us (busybox uses a SIGALRM :s)
local pfd = ffi.new("struct pollfd")
pfd.fd = socket
pfd.events = C.POLLIN
local timeout = 2000
-- Wait for a response
while true do
local poll_num = C.poll(pfd, 1, timeout)
-- Slice the timeout in two on every retry, ensuring we'll bail definitively after 4s...
timeout = bit.rshift(timeout, 1)
if poll_num == -1 then
local errno = ffi.errno()
if errno ~= C.EINTR then
logger.err("Device:ping4: poll:", ffi.string(C.strerror(errno)))
C.close(socket)
return false
end
elseif poll_num > 0 then
local c = C.recv(socket, packet, ffi.sizeof(packet), 0)
if c == -1 then
local errno = ffi.errno()
if errno ~= C.EINTR then
logger.err("Device:ping4: recv:", ffi.string(C.strerror(errno)))
C.close(socket)
return false
end
else
-- Do some minimal verification of the reply's validity.
-- This is mostly based on busybox's ping,
-- with some extra inspiration from iputils's ping, especially as far as SOCK_DGRAM is concerned.
local iphdr = ffi.cast("struct iphdr *", packet) -- ip + icmp
local hlen
if socket_type == C.SOCK_RAW then
hlen = bit.lshift(iphdr.ihl, 2)
if c < (hlen + 8) or iphdr.ihl < 5 then
-- Packet too short (we don't use recvfrom, so we can't log where it's from ;o))
logger.dbg("Device:ping4: received a short packet")
goto continue
end
else
hlen = 0
end
-- Skip ip hdr to get at the ICMP part
local icp = ffi.cast("struct icmphdr *", packet + hlen)
-- Check that we got a *reply* to *our* ping
-- NOTE: The reply's ident is defined by the kernel for SOCK_DGRAM, so we can't do anything with it!
if icp.type == C.ICMP_ECHOREPLY and
(socket_type == C.SOCK_DGRAM or icp.un.echo.id == myid) then
break
end
end
else
local end_time = time.now()
logger.info("Device:ping4: timed out waiting for a response from", ip)
C.close(socket)
return false, end_time - start_time
end
::continue::
end
local end_time = time.now()
-- If we got this far, we've got a reply to our ping in time!
C.close(socket)
return true, end_time - start_time
end
function Device:getDefaultRoute(interface)
local fd = io.open("/proc/net/route", "re")
if not fd then
return
end
local gateway
local l = 1
for line in fd:lines() do
-- Skip the first line (header)
if l > 1 then
local fields = {}
for field in line:gmatch("%S+") do
table.insert(fields, field)
end
-- Check the requested interface or anything that isn't lo
if (interface and fields[1] == interface) or (not interface and fields[1] ~= "lo") then
-- We're looking for something that's up & a gateway
if bit.band(fields[4], C.RTF_UP) ~= 0 and bit.band(fields[4], C.RTF_GATEWAY) ~= 0 then
-- Handle the conversion from network endianness hex string into a human-readable numeric form
local sockaddr_in = ffi.new("struct sockaddr_in")
sockaddr_in.sin_family = C.AF_INET
sockaddr_in.sin_addr.s_addr = tonumber(fields[3], 16)
local host = ffi.new("char[?]", C.NI_MAXHOST)
local s = C.getnameinfo(ffi.cast("struct sockaddr *", sockaddr_in),
ffi.sizeof("struct sockaddr_in"),
host, C.NI_MAXHOST,
nil, 0,
C.NI_NUMERICHOST)
if s ~= 0 then
logger.err("Device:getDefaultRoute: getnameinfo:", ffi.string(C.gai_strerror(s)))
break
else
gateway = ffi.string(host)
-- If we specified an interface, we're done.
-- If we didn't, we'll just keep the last gateway in the routing table...
if interface then
break
end
end
end
end
end
l = l + 1
end
fd:close()
return gateway
end
function Device:retrieveNetworkInfo()
-- We're going to need a random socket for the network & wireless ioctls...
local socket = C.socket(C.PF_INET, C.SOCK_DGRAM, C.IPPROTO_IP);
if socket == -1 then
local errno = ffi.errno()
logger.err("Device:retrieveNetworkInfo: socket:", ffi.string(C.strerror(errno)))
return
end
local ifaddr = ffi.new("struct ifaddrs *[1]")
if C.getifaddrs(ifaddr) == -1 then
local errno = ffi.errno()
logger.err("Device:retrieveNetworkInfo: getifaddrs:", ffi.string(C.strerror(errno)))
return false
end
-- Build a string rope to format the results
local results = {}
local interfaces = {}
local prev_ifname, default_gw
-- Loop over all the network interfaces
local ifa = ifaddr[0]
while ifa ~= nil do
-- Skip over loopback or downed interfaces
if ifa.ifa_addr ~= nil and
bit.band(ifa.ifa_flags, C.IFF_UP) ~= 0 and
bit.band(ifa.ifa_flags, C.IFF_LOOPBACK) == 0 then
local family = ifa.ifa_addr.sa_family
if family == C.AF_INET or family == C.AF_INET6 then
local host = ffi.new("char[?]", C.NI_MAXHOST)
local s = C.getnameinfo(ifa.ifa_addr,
family == C.AF_INET and ffi.sizeof("struct sockaddr_in") or ffi.sizeof("struct sockaddr_in6"),
host, C.NI_MAXHOST,
nil, 0,
C.NI_NUMERICHOST)
if s ~= 0 then
logger.err("Device:retrieveNetworkInfo: getnameinfo:", ffi.string(C.gai_strerror(s)))
else
-- Only print the ifname once
local ifname = ffi.string(ifa.ifa_name)
if not interfaces[ifname] then
if prev_ifname and ifname ~= prev_ifname then
-- Add a linebreak between interfaces
table.insert(results, "")
end
prev_ifname = ifname
table.insert(results, T(_("Interface: %1"), ifname))
interfaces[ifname] = true
-- Get its MAC address
local ifr = ffi.new("struct ifreq")
ffi.copy(ifr.ifr_ifrn.ifrn_name, ifa.ifa_name, C.IFNAMSIZ)
if C.ioctl(socket, C.SIOCGIFHWADDR, ifr) == -1 then
local errno = ffi.errno()
logger.err("Device:retrieveNetworkInfo: SIOCGIFHWADDR ioctl:", ffi.string(C.strerror(errno)))
else
local mac = string.format("%02X:%02X:%02X:%02X:%02X:%02X",
bit.band(ifr.ifr_ifru.ifru_hwaddr.sa_data[0], 0xFF),
bit.band(ifr.ifr_ifru.ifru_hwaddr.sa_data[1], 0xFF),
bit.band(ifr.ifr_ifru.ifru_hwaddr.sa_data[2], 0xFF),
bit.band(ifr.ifr_ifru.ifru_hwaddr.sa_data[3], 0xFF),
bit.band(ifr.ifr_ifru.ifru_hwaddr.sa_data[4], 0xFF),
bit.band(ifr.ifr_ifru.ifru_hwaddr.sa_data[5], 0xFF))
table.insert(results, T(_("MAC: %1"), mac))
end
-- Check if it's a wireless interface (c.f., wireless-tools)
local iwr = ffi.new("struct iwreq")
ffi.copy(iwr.ifr_ifrn.ifrn_name, ifa.ifa_name, C.IFNAMSIZ)
if C.ioctl(socket, C.SIOCGIWNAME, iwr) ~= -1 then
interfaces[ifname] = "wireless"
-- Get its ESSID
local essid = ffi.new("char[?]", C.IW_ESSID_MAX_SIZE + 1)
iwr.u.essid.pointer = ffi.cast("caddr_t", essid)
iwr.u.essid.length = C.IW_ESSID_MAX_SIZE + 1
iwr.u.essid.flags = 0
if C.ioctl(socket, C.SIOCGIWESSID, iwr) == -1 then
local errno = ffi.errno()
logger.err("Device:retrieveNetworkInfo: SIOCGIWESSID ioctl:", ffi.string(C.strerror(errno)))
else
local essid_on = iwr.u.data.flags
if essid_on ~= 0 then
-- Knowing the token index may be fun, bit it isn't in fact, super interesting...
--[[
local token_index = bit.band(essid_on, C.IW_ENCODE_INDEX)
if token_index > 1 then
table.insert(results, T(_("SSID: \"%1\" [%2]"), ffi.string(essid), token_index))
else
table.insert(results, T(_("SSID: \"%1\""), ffi.string(essid)))
end
--]]
table.insert(results, T(_("SSID: \"%1\""), ffi.string(essid)))
else
table.insert(results, _("SSID: off/any"))
end
end
end
end
if family == C.AF_INET then
table.insert(results, T(_("IP: %1"), ffi.string(host)))
local gw = self:getDefaultRoute(ifname)
if gw then
table.insert(results, T(_("Default gateway: %1"), gw))
-- If that's a wireless interface, use *that* one for the ping test
if interfaces[ifname] == "wireless" then
default_gw = gw
end
end
else
table.insert(results, T(_("IPv6: %1"), ffi.string(host)))
--- @todo: Build an IPv6 variant of getDefaultRoute that parses /proc/net/ipv6_route
end
end
end
end
ifa = ifa.ifa_next
end
C.freeifaddrs(ifaddr[0])
C.close(socket)
if prev_ifname then
table.insert(results, "")
end
-- Only ping a single gateway (if we found a wireless interface earlier, we've kept its gateway address around)
if not default_gw then
-- If not, we'll simply use the last one in the list...
default_gw = self:getDefaultRoute()
end
if default_gw then
local ok, rtt = self:ping4(default_gw)
if ok then
table.insert(results, _("Gateway ping successful"))
if rtt then
rtt = string.format("%.3f", rtt * 1/1000) -- i.e., time.to_ms w/o flooring
table.insert(results, T(_("RTT: %1 ms"), rtt))
end
else
table.insert(results, _("Gateway ping FAILED"))
if rtt then
rtt = string.format("%.1f", time.to_s(rtt))
table.insert(results, T(_("Timed out after %1 s"), rtt))
end
end
else
table.insert(results, _("No default gateway to ping"))
end
return table.concat(results, "\n")
end
function Device:setTime(hour, min)
return false
end
-- Return an integer value to indicate the brightness of the environment. The value should be in
-- range [0, 4].
-- 0: dark.
-- 1: dim, frontlight is needed.
-- 2: neutral, turning frontlight on or off does not impact the reading experience.
-- 3: bright, frontlight is not needed.
-- 4: dazzling.
function Device:ambientBrightnessLevel()
return 0
end
--- Returns true if the file is a script we allow running
--- Basically a helper method to check a specific list of file extensions for executable scripts
---- @string filename
---- @treturn boolean
function Device:canExecuteScript(file)
local file_ext = string.lower(util.getFileNameSuffix(file))
if file_ext == "sh" or file_ext == "py" then
return true
end
end
function Device:isValidPath(path)
return util.pathExists(path)
end
-- Device specific method to check if the startup script has been updated
function Device:isStartupScriptUpToDate()
return true
end
function Device:getDefaultCoverPath()
return DataStorage:getDataDir() .. "/cover.jpg"
end
--- Unpack an archive.
-- Extract the contents of an archive, detecting its format by
-- filename extension. Inspired by luarocks archive_unpack()
-- @param archive string: Filename of archive.
-- @param extract_to string: Destination directory.
-- @param with_stripped_root boolean: true if root directory in archive should be stripped
-- @return boolean or (boolean, string): true on success, false and an error message on failure.
function Device:unpackArchive(archive, extract_to, with_stripped_root)
require("dbg").dassert(type(archive) == "string")
local BD = require("ui/bidi")
local ok
if archive:match("%.tar%.bz2$") or archive:match("%.tar%.gz$") or archive:match("%.tar%.lz$") or archive:match("%.tgz$") then
ok = self:untar(archive, extract_to, with_stripped_root)
else
return false, T(_("Couldn't extract archive:\n\n%1\n\nUnrecognized filename extension."), BD.filepath(archive))
end
if not ok then
return false, T(_("Extracting archive failed:\n\n%1"), BD.filepath(archive))
end
return true
end
function Device:untar(archive, extract_to, with_stripped_root)
local cmd = "./tar xf %q -C %q"
if with_stripped_root then
cmd = cmd .. " --strip-components=1"
end
return os.execute(cmd:format(archive, extract_to))
end
-- Update our UIManager reference once it's ready
function Device:_UIManagerReady(uimgr)
-- Our own ref
UIManager = uimgr
-- Let implementations do the same thing
self:UIManagerReady(uimgr)
-- Forward that to PowerD
self.powerd:UIManagerReady(uimgr)
-- And to Input
self.input:UIManagerReady(uimgr)
-- Setup PM event handlers
-- NOTE: We keep forwarding the uimgr reference because some implementations don't actually have a module-local UIManager ref to update
self:_setEventHandlers(uimgr)
-- Returns a self-debouncing scheduling call (~4s to give some leeway to the kernel, and debounce to deal with potential chattering)
self._updateChargingLED = UIManager:debounce(4, false, function() self:setupChargingLED() end)
end
-- In case implementations *also* need a reference to UIManager, *this* is the one to implement!
function Device:UIManagerReady(uimgr) end
-- Set device event handlers common to all devices
function Device:_setEventHandlers(uimgr)
if self:canReboot() then
UIManager.event_handlers.Reboot = function(message_text)
local ConfirmBox = require("ui/widget/confirmbox")
UIManager:show(ConfirmBox:new{
text = message_text or _("Are you sure you want to reboot the device?"),
ok_text = _("Reboot"),
ok_callback = function()
UIManager:nextTick(UIManager.reboot_action)
end,
})
end
else
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)
2 years ago
UIManager.event_handlers.Reboot = function() end
end
if self:canPowerOff() then
UIManager.event_handlers.PowerOff = function(message_text)
local ConfirmBox = require("ui/widget/confirmbox")
UIManager:show(ConfirmBox:new{
text = message_text or _("Are you sure you want to power off the device?"),
ok_text = _("Power off"),
ok_callback = function()
UIManager:nextTick(UIManager.poweroff_action)
end,
})
end
else
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)
2 years ago
UIManager.event_handlers.PowerOff = function() end
end
if self:canRestart() then
UIManager.event_handlers.Restart = function(message_text)
local ConfirmBox = require("ui/widget/confirmbox")
UIManager:show(ConfirmBox:new{
text = message_text or _("This will take effect on next restart."),
ok_text = _("Restart now"),
ok_callback = function()
UIManager:broadcastEvent(Event:new("Restart"))
end,
cancel_text = _("Restart later"),
})
end
else
UIManager.event_handlers.Restart = function(message_text)
local InfoMessage = require("ui/widget/infomessage")
UIManager:show(InfoMessage:new{
text = message_text or _("This will take effect on next restart."),
})
end
end
-- Let implementations expand on that
self:setEventHandlers(uimgr)
end
-- Devices can add additional event handlers by implementing this method.
function Device:setEventHandlers(uimgr)
-- These will most probably be overwritten by device-specific `setEventHandlers` implementations
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)
2 years ago
UIManager.event_handlers.Suspend = function()
self.powerd:beforeSuspend()
end
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)
2 years ago
UIManager.event_handlers.Resume = function()
self.powerd:afterResume()
end
end
-- The common operations that should be performed before suspending the device.
function Device:_beforeSuspend(inhibit)
UIManager:flushSettings()
UIManager:broadcastEvent(Event:new("Suspend"))
if inhibit ~= false then
-- Block input events unrelated to power management
self.input:inhibitInput(true)
-- Disable key repeat to avoid useless chatter (especially where Sleep Covers are concerned...)
self:disableKeyRepeat()
end
end
-- The common operations that should be performed after resuming the device.
function Device:_afterResume(inhibit)
if inhibit ~= false then
-- Restore key repeat if it's not disabled
if G_reader_settings:nilOrFalse("input_no_key_repeat") then
self:restoreKeyRepeat()
end
-- Restore full input handling
self.input:inhibitInput(false)
end
UIManager:broadcastEvent(Event:new("Resume"))
end
-- The common operations that should be performed when the device is plugged to a power source.
function Device:_beforeCharging()
-- Invalidate the capacity cache to make sure we poll up-to-date values for the LED check
self.powerd:invalidateCapacityCache()
self:_updateChargingLED()
UIManager:broadcastEvent(Event:new("Charging"))
end
-- The common operations that should be performed when the device is unplugged from a power source.
function Device:_afterNotCharging()
self.powerd:invalidateCapacityCache()
self:_updateChargingLED()
UIManager:broadcastEvent(Event:new("NotCharging"))
end
return Device