mirror of
https://github.com/koreader/koreader
synced 2024-11-13 19:11:25 +00:00
12bea3b14b
Need to poke elsewhere for its Input device Fix #11048
1684 lines
63 KiB
Lua
1684 lines
63 KiB
Lua
local Generic = require("device/generic/device")
|
|
local Geom = require("ui/geometry")
|
|
local UIManager
|
|
local WakeupMgr = require("device/wakeupmgr")
|
|
local time = require("ui/time")
|
|
local ffiUtil = require("ffi/util")
|
|
local lfs = require("libs/libkoreader-lfs")
|
|
local logger = require("logger")
|
|
local util = require("util")
|
|
local _ = require("gettext")
|
|
|
|
-- We're going to need a few <linux/fb.h> & <linux/input.h> constants...
|
|
local ffi = require("ffi")
|
|
local C = ffi.C
|
|
require("ffi/linux_fb_h")
|
|
require("ffi/linux_input_h")
|
|
require("ffi/posix_h")
|
|
|
|
local function yes() return true end
|
|
local function no() return false end
|
|
local function NOP() return end
|
|
|
|
local function koboEnableWifi(toggle)
|
|
if toggle == true then
|
|
logger.info("Kobo Wi-Fi: enabling Wi-Fi")
|
|
os.execute("./enable-wifi.sh")
|
|
else
|
|
logger.info("Kobo Wi-Fi: disabling Wi-Fi")
|
|
os.execute("./disable-wifi.sh")
|
|
end
|
|
end
|
|
|
|
-- checks if standby is available on the device
|
|
local function checkStandby()
|
|
logger.dbg("Kobo: checking if standby is possible ...")
|
|
local f = io.open("/sys/power/state")
|
|
if not f then
|
|
return no
|
|
end
|
|
local mode = f:read()
|
|
f:close()
|
|
logger.dbg("Kobo: available power states:", mode)
|
|
if mode and mode:find("standby") then
|
|
logger.dbg("Kobo: standby state is supported")
|
|
return yes
|
|
end
|
|
logger.dbg("Kobo: standby state is unsupported")
|
|
return no
|
|
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:extend{
|
|
model = "Kobo",
|
|
isKobo = yes,
|
|
isTouchDevice = yes, -- all of them are
|
|
hasOTAUpdates = yes,
|
|
hasFastWifiStatusQuery = yes,
|
|
hasWifiManager = yes,
|
|
hasWifiRestore = yes,
|
|
canStandby = no, -- will get updated by checkStandby()
|
|
canReboot = yes,
|
|
canPowerOff = yes,
|
|
canSuspend = yes,
|
|
supportsScreensaver = yes,
|
|
-- most Kobos are MT-capable
|
|
hasMultitouch = yes,
|
|
-- most Kobos have X/Y switched for the touch screen
|
|
touch_switch_xy = true,
|
|
-- most Kobos have also mirrored X coordinates
|
|
touch_mirrored_x = true,
|
|
-- but a few mirror on the Y axis instead
|
|
touch_mirrored_y = false,
|
|
-- enforce portrait mode on Kobos
|
|
--- @note: In practice, the check that is used for in ffi/framebuffer is no longer relevant,
|
|
--- since, in almost every case, we enforce a hardware Portrait rotation via fbdepth on startup by default ;).
|
|
--- We still want to keep it in case an unfortunate soul on an older device disables the bitdepth switch...
|
|
isAlwaysPortrait = yes,
|
|
-- we don't need an extra refreshFull on resume, thank you very much.
|
|
needsScreenRefreshAfterResume = no,
|
|
-- some devices have coloured frontlights
|
|
hasNaturalLight = no,
|
|
hasNaturalLightMixer = no,
|
|
-- HW inversion is generally safe on Kobo, except on a few boards/kernels
|
|
canHWInvert = yes,
|
|
home_dir = "/mnt/onboard",
|
|
canToggleMassStorage = yes,
|
|
-- New devices *may* be REAGL-aware, but generally don't expect explicit REAGL requests, default to not.
|
|
isREAGL = no,
|
|
-- Mark 7 devices sport an updated driver.
|
|
isMk7 = no,
|
|
-- MXCFB_WAIT_FOR_UPDATE_COMPLETE ioctls are generally reliable
|
|
hasReliableMxcWaitFor = yes,
|
|
-- AllWinner SoCs require a completely different fb backend...
|
|
isSunxi = no,
|
|
-- The fb backend also needs to know if we're on a MediaTek SoC.
|
|
isMTK = no,
|
|
-- On sunxi, "native" panel layout used to compute the G2D rotation handle (e.g., deviceQuirks.nxtBootRota in FBInk).
|
|
boot_rota = nil,
|
|
-- Standard sysfs path to the battery directory
|
|
battery_sysfs = "/sys/class/power_supply/mc13892_bat",
|
|
-- Stable path to the NTX input device
|
|
ntx_dev = "/dev/input/event0",
|
|
-- Stable path to the Touch input device
|
|
touch_dev = "/dev/input/event1",
|
|
-- Stable path to the Power Button input device
|
|
power_dev = nil,
|
|
-- Event code to use to detect contact pressure
|
|
pressure_event = nil,
|
|
-- Device features multiple CPU cores
|
|
isSMP = no,
|
|
-- Device supports "eclipse" waveform modes (i.e., optimized for nightmode).
|
|
hasEclipseWfm = no,
|
|
-- Device ships with various hardware revisions under the same device code, requiring automatic hardware detection...
|
|
automagic_sysfs = false,
|
|
|
|
unexpected_wakeup_count = 0,
|
|
}
|
|
|
|
local KoboTrilogyA = Kobo:extend{
|
|
model = "Kobo_trilogy_A",
|
|
-- Unlike its B brethren, this one doesn't do the weird translation dance when ABS_PRESSURE is 0...
|
|
hasKeys = yes,
|
|
hasMultitouch = no,
|
|
}
|
|
-- Kobo Touch B:
|
|
local KoboTrilogyB = Kobo:extend{
|
|
model = "Kobo_trilogy_B",
|
|
touch_kobo_mk3_protocol = true,
|
|
hasKeys = yes,
|
|
hasMultitouch = no,
|
|
}
|
|
-- Kobo Touch C:
|
|
local KoboTrilogyC = Kobo:extend{
|
|
model = "Kobo_trilogy_C",
|
|
hasKeys = yes,
|
|
hasMultitouch = no,
|
|
}
|
|
|
|
-- Kobo Mini:
|
|
local KoboPixie = Kobo:extend{
|
|
model = "Kobo_pixie",
|
|
display_dpi = 200,
|
|
hasMultitouch = no,
|
|
-- bezel:
|
|
viewport = Geom:new{x=0, y=2, w=596, h=794},
|
|
}
|
|
|
|
-- Kobo Aura One:
|
|
local KoboDaylight = Kobo:extend{
|
|
model = "Kobo_daylight",
|
|
hasFrontlight = yes,
|
|
touch_phoenix_protocol = true,
|
|
display_dpi = 300,
|
|
hasNaturalLight = yes,
|
|
frontlight_settings = {
|
|
frontlight_white = "/sys/class/backlight/lm3630a_led1b",
|
|
frontlight_red = "/sys/class/backlight/lm3630a_led1a",
|
|
frontlight_green = "/sys/class/backlight/lm3630a_ledb",
|
|
},
|
|
}
|
|
|
|
-- Kobo Aura H2O:
|
|
local KoboDahlia = Kobo:extend{
|
|
model = "Kobo_dahlia",
|
|
canToggleChargingLED = yes,
|
|
led_uses_channel_3 = true,
|
|
hasFrontlight = yes,
|
|
touch_phoenix_protocol = true,
|
|
-- There's no slot 0, the first finger gets assigned slot 1, and the second slot 2.
|
|
-- NOTE: Could be queried at runtime via EVIOCGABS on C.ABS_MT_TRACKING_ID (minimum field).
|
|
-- Used to be handled via an adjustTouchAlyssum hook that just mangled ABS_MT_TRACKING_ID values.
|
|
main_finger_slot = 1,
|
|
display_dpi = 265,
|
|
-- the bezel covers the top 11 pixels:
|
|
viewport = Geom:new{x=0, y=11, w=1080, h=1429},
|
|
}
|
|
|
|
-- Kobo Aura HD:
|
|
local KoboDragon = Kobo:extend{
|
|
model = "Kobo_dragon",
|
|
hasFrontlight = yes,
|
|
hasMultitouch = no,
|
|
display_dpi = 265,
|
|
}
|
|
|
|
-- Kobo Glo:
|
|
local KoboKraken = Kobo:extend{
|
|
model = "Kobo_kraken",
|
|
hasFrontlight = yes,
|
|
hasMultitouch = no,
|
|
display_dpi = 212,
|
|
}
|
|
|
|
-- Kobo Aura:
|
|
local KoboPhoenix = Kobo:extend{
|
|
model = "Kobo_phoenix",
|
|
hasFrontlight = yes,
|
|
touch_phoenix_protocol = true,
|
|
display_dpi = 212,
|
|
-- The bezel covers 10 pixels at the bottom:
|
|
viewport = Geom:new{x=0, y=0, w=758, h=1014},
|
|
-- NOTE: AFAICT, the Aura was the only one explicitly requiring REAGL requests...
|
|
isREAGL = yes,
|
|
-- NOTE: May have a buggy kernel, according to the nightmode hack...
|
|
canHWInvert = no,
|
|
}
|
|
|
|
-- Kobo Aura H2O2:
|
|
local KoboSnow = Kobo:extend{
|
|
model = "Kobo_snow",
|
|
hasFrontlight = yes,
|
|
touch_snow_protocol = true,
|
|
touch_mirrored_x = false,
|
|
display_dpi = 265,
|
|
hasNaturalLight = yes,
|
|
frontlight_settings = {
|
|
frontlight_white = "/sys/class/backlight/lm3630a_ledb",
|
|
frontlight_red = "/sys/class/backlight/lm3630a_led",
|
|
frontlight_green = "/sys/class/backlight/lm3630a_leda",
|
|
},
|
|
}
|
|
|
|
-- Kobo Aura H2O2, Rev2:
|
|
--- @fixme Check if the Clara fix actually helps here... (#4015)
|
|
local KoboSnowRev2 = Kobo:extend{
|
|
model = "Kobo_snow_r2",
|
|
isMk7 = yes,
|
|
hasFrontlight = yes,
|
|
touch_snow_protocol = true,
|
|
display_dpi = 265,
|
|
hasNaturalLight = yes,
|
|
frontlight_settings = {
|
|
frontlight_white = "/sys/class/backlight/lm3630a_ledb",
|
|
frontlight_red = "/sys/class/backlight/lm3630a_leda",
|
|
},
|
|
}
|
|
|
|
-- Kobo Aura second edition:
|
|
local KoboStar = Kobo:extend{
|
|
model = "Kobo_star",
|
|
hasFrontlight = yes,
|
|
touch_phoenix_protocol = true,
|
|
display_dpi = 212,
|
|
}
|
|
|
|
-- Kobo Aura second edition, Rev 2:
|
|
local KoboStarRev2 = Kobo:extend{
|
|
model = "Kobo_star_r2",
|
|
isMk7 = yes,
|
|
hasFrontlight = yes,
|
|
touch_phoenix_protocol = true,
|
|
display_dpi = 212,
|
|
}
|
|
|
|
-- Kobo Glo HD:
|
|
local KoboAlyssum = Kobo:extend{
|
|
model = "Kobo_alyssum",
|
|
hasFrontlight = yes,
|
|
touch_phoenix_protocol = true,
|
|
main_finger_slot = 1,
|
|
display_dpi = 300,
|
|
}
|
|
|
|
-- Kobo Touch 2.0:
|
|
local KoboPika = Kobo:extend{
|
|
model = "Kobo_pika",
|
|
touch_phoenix_protocol = true,
|
|
main_finger_slot = 1,
|
|
}
|
|
|
|
-- Kobo Clara HD:
|
|
local KoboNova = Kobo:extend{
|
|
model = "Kobo_nova",
|
|
isMk7 = yes,
|
|
canToggleChargingLED = yes,
|
|
hasFrontlight = yes,
|
|
touch_snow_protocol = true,
|
|
display_dpi = 300,
|
|
hasNaturalLight = yes,
|
|
frontlight_settings = {
|
|
frontlight_white = "/sys/class/backlight/mxc_msp430.0/brightness",
|
|
frontlight_mixer = "/sys/class/backlight/lm3630a_led/color",
|
|
nl_min = 0,
|
|
nl_max = 10,
|
|
nl_inverted = true,
|
|
},
|
|
}
|
|
|
|
-- Kobo Forma:
|
|
-- NOTE: Right now, we enforce Portrait orientation on startup to avoid getting touch coordinates wrong,
|
|
-- no matter the rotation we were started from (c.f., platform/kobo/koreader.sh).
|
|
-- NOTE: For the FL, assume brightness is WO, and actual_brightness is RO!
|
|
-- i.e., we could have a real KoboPowerD:frontlightIntensityHW() by reading actual_brightness ;).
|
|
-- NOTE: Rotation events *may* not be enabled if Nickel has never been brought up in that power cycle.
|
|
-- i.e., this will affect KSM users.
|
|
-- c.f., https://github.com/koreader/koreader/pull/4414#issuecomment-449652335
|
|
-- There's also a CM_ROTARY_ENABLE command, but which seems to do as much nothing as the STATUS one...
|
|
local KoboFrost = Kobo:extend{
|
|
model = "Kobo_frost",
|
|
isMk7 = yes,
|
|
canToggleChargingLED = yes,
|
|
hasFrontlight = yes,
|
|
hasKeys = yes,
|
|
hasGSensor = yes,
|
|
touch_snow_protocol = true,
|
|
display_dpi = 300,
|
|
hasNaturalLight = yes,
|
|
frontlight_settings = {
|
|
frontlight_white = "/sys/class/backlight/mxc_msp430.0/brightness",
|
|
frontlight_mixer = "/sys/class/backlight/tlc5947_bl/color",
|
|
-- Warmth goes from 0 to 10 on the device's side (our own internal scale is still normalized to [0...100])
|
|
-- NOTE: Those three extra keys are *MANDATORY* if frontlight_mixer is set!
|
|
nl_min = 0,
|
|
nl_max = 10,
|
|
nl_inverted = true,
|
|
},
|
|
}
|
|
|
|
-- Kobo Libra:
|
|
-- NOTE: Assume the same quirks as the Forma apply.
|
|
local KoboStorm = Kobo:extend{
|
|
model = "Kobo_storm",
|
|
isMk7 = yes,
|
|
canToggleChargingLED = yes,
|
|
hasFrontlight = yes,
|
|
hasKeys = yes,
|
|
hasGSensor = yes,
|
|
touch_snow_protocol = true,
|
|
display_dpi = 300,
|
|
hasNaturalLight = yes,
|
|
frontlight_settings = {
|
|
frontlight_white = "/sys/class/backlight/mxc_msp430.0/brightness",
|
|
frontlight_mixer = "/sys/class/backlight/lm3630a_led/color",
|
|
-- Warmth goes from 0 to 10 on the device's side (our own internal scale is still normalized to [0...100])
|
|
-- NOTE: Those three extra keys are *MANDATORY* if frontlight_mixer is set!
|
|
nl_min = 0,
|
|
nl_max = 10,
|
|
nl_inverted = true,
|
|
},
|
|
-- NOTE: The Libra apparently suffers from a mysterious issue where completely innocuous WAIT_FOR_UPDATE_COMPLETE ioctls
|
|
-- will mysteriously fail with a timeout (5s)...
|
|
-- This obviously leads to *terrible* user experience,
|
|
-- so we've tried a few things over the years to attempt to deal with it.
|
|
-- c.f., https://github.com/koreader/koreader/issues/7340 for the genesis of all that.
|
|
-- NOTE: On a possibly related note, on NXP devices (even earlier ones), Nickel will *always* wait for markers in pairs:
|
|
-- the "expected" marker to wait for, and the *previous* one right before that.
|
|
-- Of course, that first wait will mostly always return early, because that refresh is usually much older and already dealt with.
|
|
-- This weird quirk was dropped on sunxi & MTK, FWIW.
|
|
hasReliableMxcWaitFor = no,
|
|
}
|
|
|
|
-- Kobo Nia:
|
|
local KoboLuna = Kobo:extend{
|
|
model = "Kobo_luna",
|
|
isMk7 = yes,
|
|
canToggleChargingLED = yes,
|
|
hasFrontlight = yes,
|
|
touch_phoenix_protocol = true,
|
|
display_dpi = 212,
|
|
hasReliableMxcWaitFor = no, -- Board is similar to the Libra 2, but it's such an unpopular device that reports are scarce.
|
|
-- Handle the HW revision w/ a BD71828 PMIC
|
|
automagic_sysfs = true,
|
|
}
|
|
|
|
-- Kobo Elipsa
|
|
local KoboEuropa = Kobo:extend{
|
|
model = "Kobo_europa",
|
|
isSunxi = yes,
|
|
hasEclipseWfm = yes,
|
|
canToggleChargingLED = yes,
|
|
led_uses_channel_3 = true,
|
|
hasFrontlight = yes,
|
|
hasGSensor = yes,
|
|
pressure_event = C.ABS_MT_PRESSURE,
|
|
display_dpi = 227,
|
|
boot_rota = C.FB_ROTATE_CCW,
|
|
battery_sysfs = "/sys/class/power_supply/battery",
|
|
ntx_dev = "/dev/input/by-path/platform-ntx_event0-event",
|
|
touch_dev = "/dev/input/by-path/platform-0-0010-event",
|
|
isSMP = yes,
|
|
}
|
|
|
|
-- Kobo Sage
|
|
local KoboCadmus = Kobo:extend{
|
|
model = "Kobo_cadmus",
|
|
isSunxi = yes,
|
|
hasEclipseWfm = yes,
|
|
canToggleChargingLED = yes,
|
|
led_uses_channel_3 = true,
|
|
hasFrontlight = yes,
|
|
hasKeys = yes,
|
|
hasGSensor = yes,
|
|
pressure_event = C.ABS_MT_PRESSURE,
|
|
display_dpi = 300,
|
|
hasNaturalLight = yes,
|
|
frontlight_settings = {
|
|
frontlight_white = "/sys/class/backlight/mxc_msp430.0/brightness",
|
|
frontlight_mixer = "/sys/class/leds/aw99703-bl_FL1/color",
|
|
-- Warmth goes from 0 to 10 on the device's side (our own internal scale is still normalized to [0...100])
|
|
-- NOTE: Those three extra keys are *MANDATORY* if frontlight_mixer is set!
|
|
nl_min = 0,
|
|
nl_max = 10,
|
|
nl_inverted = false,
|
|
},
|
|
boot_rota = C.FB_ROTATE_CW,
|
|
battery_sysfs = "/sys/class/power_supply/battery",
|
|
hasAuxBattery = yes,
|
|
aux_battery_sysfs = "/sys/class/misc/cilix",
|
|
ntx_dev = "/dev/input/by-path/platform-ntx_event0-event",
|
|
touch_dev = "/dev/input/by-path/platform-0-0010-event",
|
|
isSMP = yes,
|
|
-- Much like the Libra 2, there are at least two different HW revisions, with different PMICs...
|
|
automagic_sysfs = true,
|
|
}
|
|
|
|
-- Kobo Libra 2:
|
|
local KoboIo = Kobo:extend{
|
|
model = "Kobo_io",
|
|
isMk7 = yes,
|
|
hasEclipseWfm = yes,
|
|
canToggleChargingLED = yes,
|
|
hasFrontlight = yes,
|
|
hasKeys = yes,
|
|
hasGSensor = yes,
|
|
pressure_event = C.ABS_MT_PRESSURE,
|
|
touch_mirrored_x = false,
|
|
display_dpi = 300,
|
|
hasNaturalLight = yes,
|
|
frontlight_settings = {
|
|
frontlight_white = "/sys/class/backlight/mxc_msp430.0/brightness",
|
|
-- Warmth goes from 0 to 10 on the device's side (our own internal scale is still normalized to [0...100])
|
|
-- NOTE: Those three extra keys are *MANDATORY* if frontlight_mixer is set!
|
|
nl_min = 0,
|
|
nl_max = 10,
|
|
nl_inverted = true,
|
|
},
|
|
-- It would appear that the Libra 2 inherited its ancestor's quirks, and more...
|
|
-- c.f., https://github.com/koreader/koreader/issues/8414 & https://github.com/koreader/koreader/issues/8664
|
|
hasReliableMxcWaitFor = no,
|
|
-- NOTE: There are at least two hardware revisions of this device (*without* a device code change, this time),
|
|
-- with *significant* hardware changes, so we'll handle this by making the sysfs path discovery automagic.
|
|
-- c.f., https://github.com/koreader/koreader/issues/9218
|
|
automagic_sysfs = true,
|
|
}
|
|
|
|
-- Kobo Clara 2E:
|
|
local KoboGoldfinch = Kobo:extend{
|
|
model = "Kobo_goldfinch",
|
|
isMk7 = yes,
|
|
hasEclipseWfm = yes,
|
|
canToggleChargingLED = yes,
|
|
led_uses_channel_3 = true,
|
|
hasFrontlight = yes,
|
|
display_dpi = 300,
|
|
hasNaturalLight = yes,
|
|
frontlight_settings = {
|
|
frontlight_white = "/sys/class/backlight/mxc_msp430.0/brightness",
|
|
frontlight_mixer = "/sys/class/leds/aw99703-bl_FL1/color",
|
|
nl_min = 0,
|
|
nl_max = 10,
|
|
nl_inverted = true,
|
|
},
|
|
battery_sysfs = "/sys/class/power_supply/battery",
|
|
power_dev = "/dev/input/by-path/platform-bd71828-pwrkey-event",
|
|
-- Board is eerily similar to the Libra 2, so, it inherits the same quirks...
|
|
-- c.f., https://github.com/koreader/koreader/issues/9552#issuecomment-1293000313
|
|
hasReliableMxcWaitFor = no,
|
|
}
|
|
|
|
-- Kobo Elipsa 2E:
|
|
local KoboCondor = Kobo:extend{
|
|
model = "Kobo_condor",
|
|
isMTK = yes,
|
|
hasEclipseWfm = yes,
|
|
canToggleChargingLED = yes,
|
|
hasFrontlight = yes,
|
|
hasGSensor = yes,
|
|
display_dpi = 227,
|
|
pressure_event = C.ABS_MT_PRESSURE,
|
|
touch_mirrored_x = false,
|
|
touch_mirrored_y = true,
|
|
hasNaturalLight = yes,
|
|
frontlight_settings = {
|
|
frontlight_white = "/sys/class/backlight/mxc_msp430.0/brightness",
|
|
frontlight_mixer = "/sys/class/leds/aw99703-bl_FL1/color",
|
|
nl_min = 0,
|
|
nl_max = 10,
|
|
nl_inverted = true,
|
|
},
|
|
battery_sysfs = "/sys/class/power_supply/bd71827_bat",
|
|
touch_dev = "/dev/input/by-path/platform-2-0010-event",
|
|
ntx_dev = "/dev/input/by-path/platform-ntx_event0-event",
|
|
power_dev = "/dev/input/by-path/platform-bd71828-pwrkey.6.auto-event",
|
|
isSMP = yes,
|
|
}
|
|
|
|
function Kobo:setupChargingLED()
|
|
if G_reader_settings:nilOrTrue("enable_charging_led") then
|
|
if self:hasAuxBattery() and self.powerd:isAuxBatteryConnected() then
|
|
self:toggleChargingLED(self.powerd:isAuxCharging() and not self.powerd:isAuxCharged())
|
|
else
|
|
self:toggleChargingLED(self.powerd:isCharging() and not self.powerd:isCharged())
|
|
end
|
|
end
|
|
end
|
|
|
|
function Kobo:getKeyRepeat()
|
|
-- Sanity check (mostly for the testsuite's benefit...)
|
|
if not self.ntx_fd then
|
|
return false
|
|
end
|
|
|
|
self.key_repeat = ffi.new("unsigned int[?]", C.REP_CNT)
|
|
if C.ioctl(self.ntx_fd, C.EVIOCGREP, self.key_repeat) < 0 then
|
|
local err = ffi.errno()
|
|
logger.warn("Device:getKeyRepeat: EVIOCGREP ioctl failed:", ffi.string(C.strerror(err)))
|
|
return false
|
|
else
|
|
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")
|
|
self.canKeyRepeat = yes
|
|
return true
|
|
end
|
|
end
|
|
|
|
function Kobo:disableKeyRepeat()
|
|
-- NOTE: LuaJIT zero inits, and PERIOD == 0 with DELAY == 0 disables repeats ;).
|
|
local key_repeat = ffi.new("unsigned int[?]", C.REP_CNT)
|
|
if C.ioctl(self.ntx_fd, C.EVIOCSREP, key_repeat) < 0 then
|
|
local err = ffi.errno()
|
|
logger.warn("Device:disableKeyRepeat: EVIOCSREP ioctl failed:", ffi.string(C.strerror(err)))
|
|
end
|
|
end
|
|
|
|
function Kobo:restoreKeyRepeat()
|
|
if C.ioctl(self.ntx_fd, C.EVIOCSREP, self.key_repeat) < 0 then
|
|
local err = ffi.errno()
|
|
logger.warn("Device:restoreKeyRepeat: EVIOCSREP ioctl failed:", ffi.string(C.strerror(err)))
|
|
end
|
|
end
|
|
|
|
function Kobo:toggleKeyRepeat(toggle)
|
|
local key_repeat = ffi.new("unsigned int[?]", C.REP_CNT)
|
|
if toggle == true then
|
|
-- Use the defaults from a Sage, as we can't guarantee the state of the setup on startup, so we can't just use self.key_repeat
|
|
key_repeat[C.REP_DELAY] = 400
|
|
key_repeat[C.REP_PERIOD] = 80
|
|
elseif toggle == false then
|
|
key_repeat[C.REP_DELAY] = 0
|
|
key_repeat[C.REP_PERIOD] = 0
|
|
else
|
|
-- Check the current (kernel) state to know what to do
|
|
if C.ioctl(self.ntx_fd, C.EVIOCGREP, key_repeat) < 0 then
|
|
local err = ffi.errno()
|
|
logger.warn("Device:toggleKeyRepeat: EVIOCGREP ioctl failed:", ffi.string(C.strerror(err)))
|
|
return false
|
|
else
|
|
if key_repeat[C.REP_DELAY] == 0 and key_repeat[C.REP_PERIOD] == 0 then
|
|
return self:toggleKeyRepeat(true)
|
|
else
|
|
return self:toggleKeyRepeat(false)
|
|
end
|
|
end
|
|
end
|
|
|
|
if C.ioctl(self.ntx_fd, C.EVIOCSREP, key_repeat) < 0 then
|
|
local err = ffi.errno()
|
|
logger.warn("Device:toggleKeyRepeat: EVIOCSREP ioctl failed:", ffi.string(C.strerror(err)))
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function Kobo:init()
|
|
-- Check if we need to disable MXCFB_WAIT_FOR_UPDATE_COMPLETE ioctls...
|
|
local mxcfb_bypass_wait_for
|
|
if G_reader_settings:has("mxcfb_bypass_wait_for") then
|
|
mxcfb_bypass_wait_for = G_reader_settings:isTrue("mxcfb_bypass_wait_for")
|
|
else
|
|
mxcfb_bypass_wait_for = not self:hasReliableMxcWaitFor()
|
|
end
|
|
|
|
if self:isSunxi() then
|
|
self.screen = require("ffi/framebuffer_sunxi"):new{
|
|
device = self,
|
|
debug = logger.dbg,
|
|
is_always_portrait = self.isAlwaysPortrait(),
|
|
mxcfb_bypass_wait_for = mxcfb_bypass_wait_for,
|
|
boot_rota = self.boot_rota,
|
|
}
|
|
|
|
-- Sunxi means no HW inversion :(
|
|
self.canHWInvert = no
|
|
else
|
|
self.screen = require("ffi/framebuffer_mxcfb"):new{
|
|
device = self,
|
|
debug = logger.dbg,
|
|
is_always_portrait = self.isAlwaysPortrait(),
|
|
mxcfb_bypass_wait_for = mxcfb_bypass_wait_for,
|
|
}
|
|
if self.screen.fb_bpp == 32 then
|
|
-- Ensure we decode images properly, as our framebuffer is BGRA...
|
|
logger.info("Enabling Kobo @ 32bpp BGR tweaks")
|
|
self.hasBGRFrameBuffer = yes
|
|
end
|
|
end
|
|
|
|
-- So far, MTK kernels do not export a per-request inversion flag
|
|
if self:isMTK() then
|
|
-- Instead, there's a global flag that we can *set* (but not *get*) via a procfs knob...
|
|
-- Overload the HWNightMode stuff to implement that properly, like we do on Kindle
|
|
function self.screen:setHWNightmode(toggle)
|
|
-- No getter, so, keep track of our own state
|
|
self.hw_night_mode = toggle
|
|
-- Flip the global invert_fb flag
|
|
ffiUtil.writeToSysfs(toggle and "night_mode 4" or "night_mode 0", "/proc/hwtcon/cmd")
|
|
end
|
|
|
|
function self.screen:getHWNightmode()
|
|
-- Return false on nil for reader.lua's sake, mostly.
|
|
-- (We want to disable this on exit, always, as it will never be used by Nickel, which does SW inversion).
|
|
return self.hw_night_mode == true
|
|
end
|
|
end
|
|
|
|
-- Automagic sysfs discovery
|
|
if self.automagic_sysfs then
|
|
-- Battery
|
|
if util.pathExists("/sys/class/power_supply/battery") then
|
|
-- Newer devices (circa sunxi)
|
|
self.battery_sysfs = "/sys/class/power_supply/battery"
|
|
else
|
|
self.battery_sysfs = "/sys/class/power_supply/mc13892_bat"
|
|
end
|
|
|
|
-- Frontlight
|
|
if self:hasNaturalLight() then
|
|
if util.fileExists("/sys/class/leds/aw99703-bl_FL1/color") then
|
|
-- HWConfig FL_PWM is AW99703x2
|
|
self.frontlight_settings.frontlight_mixer = "/sys/class/leds/aw99703-bl_FL1/color"
|
|
elseif util.fileExists("/sys/class/backlight/lm3630a_led/color") then
|
|
-- HWConfig FL_PWM is LM3630
|
|
self.frontlight_settings.frontlight_mixer = "/sys/class/backlight/lm3630a_led/color"
|
|
elseif util.fileExists("/sys/class/backlight/tlc5947_bl/color") then
|
|
-- HWConfig FL_PWM is TLC5947
|
|
self.frontlight_settings.frontlight_mixer = "/sys/class/backlight/tlc5947_bl/color"
|
|
end
|
|
end
|
|
|
|
-- Touch panel input
|
|
if util.fileExists("/dev/input/by-path/platform-2-0010-event") then
|
|
-- Elan (HWConfig TouchCtrl is ekth6) on i2c bus 2
|
|
self.touch_dev = "/dev/input/by-path/platform-2-0010-event"
|
|
elseif util.fileExists("/dev/input/by-path/platform-1-0010-event") then
|
|
-- Elan (HWConfig TouchCtrl is ekth6) on i2c bus 1
|
|
self.touch_dev = "/dev/input/by-path/platform-1-0010-event"
|
|
elseif util.fileExists("/dev/input/by-path/platform-0-0010-event") then
|
|
-- Elan (HWConfig TouchCtrl is ekth6) on i2c bus 0
|
|
self.touch_dev = "/dev/input/by-path/platform-0-0010-event"
|
|
else
|
|
self.touch_dev = "/dev/input/event1"
|
|
end
|
|
|
|
-- Physical buttons & synthetic NTX events
|
|
if util.fileExists("/dev/input/by-path/platform-gpio-keys-event") then
|
|
-- Libra 2 w/ a BD71828 PMIC
|
|
self.ntx_dev = "/dev/input/by-path/platform-gpio-keys-event"
|
|
elseif util.fileExists("/dev/input/by-path/platform-ntx_event0-event") then
|
|
-- MTK, sunxi & Mk. 7
|
|
self.ntx_dev = "/dev/input/by-path/platform-ntx_event0-event"
|
|
elseif util.fileExists("/dev/input/by-path/platform-mxckpd-event") then
|
|
-- circa Mk. 5 i.MX
|
|
self.ntx_dev = "/dev/input/by-path/platform-mxckpd-event"
|
|
else
|
|
self.ntx_dev = "/dev/input/event0"
|
|
end
|
|
|
|
-- Power button (this usually ends up in ntx_dev, except with some PMICs)
|
|
if util.fileExists("/dev/input/by-path/platform-bd71828-pwrkey-event") then
|
|
-- Libra 2 & Nia w/ a BD71828 PMIC
|
|
self.power_dev = "/dev/input/by-path/platform-bd71828-pwrkey-event"
|
|
elseif util.fileExists("/dev/input/by-path/platform-bd71828-pwrkey.4.auto-event") then
|
|
-- Sage w/ a BD71828 PMIC
|
|
self.power_dev = "/dev/input/by-path/platform-bd71828-pwrkey.4.auto-event"
|
|
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 = NOP
|
|
self.defaultCPUGovernor = NOP
|
|
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 = NOP
|
|
end
|
|
|
|
-- 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
|
|
self.hasNaturalLightMixer = yes
|
|
end
|
|
-- Ditto
|
|
if self:isMk7() then
|
|
self.canHWDither = yes
|
|
end
|
|
|
|
-- NOTE: Devices with an AW99703 frontlight PWM controller feature a hardware smooth ramp when setting the frontlight intensity.
|
|
--- A side-effect of this behavior is that if you queue a series of intensity changes ending at 0,
|
|
--- it won't ramp *at all*, jumping straight to zero instead.
|
|
--- So we delay the final ramp off step to prevent (both) the native and our ramping from being optimized out.
|
|
if self:hasNaturalLightMixer() and self.frontlight_settings.frontlight_mixer:find("aw99703", 12, true) then
|
|
self.frontlight_settings.ramp_off_delay = 0.5
|
|
end
|
|
|
|
self.powerd = require("device/kobo/powerd"):new{
|
|
device = self,
|
|
battery_sysfs = self.battery_sysfs,
|
|
aux_battery_sysfs = self.aux_battery_sysfs,
|
|
}
|
|
-- NOTE: For the Forma, with the buttons on the right, 193 is Top, 194 Bottom.
|
|
self.input = require("device/input"):new{
|
|
device = self,
|
|
event_map = {
|
|
[35] = "SleepCover", -- KEY_H, Elipsa
|
|
[59] = "SleepCover",
|
|
[90] = "LightButton",
|
|
[102] = "Home",
|
|
[116] = "Power",
|
|
[193] = "RPgBack",
|
|
[194] = "RPgFwd",
|
|
[331] = "Eraser",
|
|
[332] = "Highlighter",
|
|
},
|
|
event_map_adapter = {
|
|
SleepCover = function(ev)
|
|
if self.input:isEvKeyPress(ev) then
|
|
return "SleepCoverClosed"
|
|
elseif self.input:isEvKeyRelease(ev) then
|
|
return "SleepCoverOpened"
|
|
end
|
|
end,
|
|
LightButton = function(ev)
|
|
if self.input:isEvKeyRelease(ev) then
|
|
return "Light"
|
|
end
|
|
end,
|
|
},
|
|
main_finger_slot = self.main_finger_slot or 0,
|
|
pressure_event = self.pressure_event,
|
|
}
|
|
self.wakeup_mgr = WakeupMgr:new{
|
|
dodgy_rtc = dodgy_rtc,
|
|
}
|
|
|
|
-- Input handling on Kobo is a thing of nightmares, start by setting up the actual evdev handler...
|
|
self:setTouchEventHandler()
|
|
-- And then handle the extra shenanigans if necessary.
|
|
self:initEventAdjustHooks()
|
|
|
|
-- Various HW Buttons, Switches & Synthetic NTX events
|
|
self.ntx_fd = self.input.open(self.ntx_dev)
|
|
-- Dedicated Power Button input device (if any)
|
|
if self.power_dev then
|
|
self.input.open(self.power_dev)
|
|
end
|
|
-- Touch panel
|
|
self.input.open(self.touch_dev)
|
|
-- NOTE: On devices with a gyro, there may be a dedicated input device outputting the raw accelerometer data
|
|
-- (3-Axis Orientation/Motion Detection).
|
|
-- We skip it because we don't need it (synthetic rotation change events are sent to the main ntx input device),
|
|
-- and it's usually *extremely* verbose, so it'd just be a waste of processing power.
|
|
-- fake_events is only used for usb plug & charge events so far (generated via uevent, c.f., input/iput-kobo.h in base).
|
|
-- NOTE: usb hotplug event is also available in /tmp/nickel-hardware-status (... but only when Nickel is running ;p)
|
|
self.input.open("fake_events")
|
|
|
|
-- See if the device supports key repeat
|
|
-- This is *not* behind a hasKeys check, because we mainly use it to stop SleepCover chatter,
|
|
-- and sleep covers are available on a number of devices without keys ;).
|
|
self:getKeyRepeat()
|
|
if not self:canKeyRepeat() then
|
|
-- NOP unsupported methods
|
|
self.disableKeyRepeat = NOP
|
|
self.restoreKeyRepeat = NOP
|
|
self.toggleKeyRepeat = NOP
|
|
end
|
|
|
|
-- Detect the NTX charging LED sysfs knob
|
|
if util.pathExists("/sys/class/leds/bd71828-green-led") then
|
|
-- Standard Linux LED class, wheee!
|
|
self.charging_led_sysfs_knob = "/sys/class/leds/bd71828-green-led"
|
|
elseif util.pathExists("/sys/devices/platform/ntx_led/lit") then
|
|
self.ntx_lit_sysfs_knob = "/sys/devices/platform/ntx_led/lit"
|
|
elseif util.pathExists("/sys/devices/platform/pmic_light.1/lit") then
|
|
self.ntx_lit_sysfs_knob = "/sys/devices/platform/pmic_light.1/lit"
|
|
else
|
|
self.canToggleChargingLED = no
|
|
end
|
|
|
|
-- Switch to the simple standard implementation if available
|
|
if self.charging_led_sysfs_knob then
|
|
self.charging_led_imp = self._LinuxChargingLEDToggle
|
|
else
|
|
self.charging_led_imp = self._NTXChargingLEDToggle
|
|
end
|
|
|
|
-- NOP unsupported methods
|
|
if not self:canToggleChargingLED() then
|
|
self.toggleChargingLED = NOP
|
|
self.setupChargingLED = NOP
|
|
end
|
|
|
|
-- 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.
|
|
self:toggleChargingLED(false)
|
|
self:setupChargingLED()
|
|
|
|
-- Only enable a single core on startup
|
|
self:enableCPUCores(1)
|
|
|
|
self.canStandby = checkStandby()
|
|
if self.canStandby() and (self:isMk7() or self:isSunxi() or self:isMTK()) then
|
|
self.canPowerSaveWhileCharging = yes
|
|
end
|
|
|
|
-- Check if the device has a Neonode IR grid (to tone down the chatter on resume ;)).
|
|
if lfs.attributes("/sys/devices/virtual/input/input1/neocmd", "mode") == "file" then
|
|
-- As found on (at least), the Aura H2O
|
|
self.hasIRGridSysfsKnob = "/sys/devices/virtual/input/input1/neocmd"
|
|
elseif lfs.attributes("/sys/devices/platform/imx-i2c.1/i2c-1/1-0050/neocmd", "mode") == "file" then
|
|
-- As found on (at least) the Glo HD (c.f., https://github.com/koreader/koreader/pull/9377#issuecomment-1213544478)
|
|
self.hasIRGridSysfsKnob = "/sys/devices/platform/imx-i2c.1/i2c-1/1-0050/neocmd"
|
|
end
|
|
|
|
-- Disable key repeat if requested
|
|
if G_reader_settings:isTrue("input_no_key_repeat") then
|
|
self:toggleKeyRepeat(false)
|
|
end
|
|
|
|
-- Finally, Let Generic properly setup the standard stuff.
|
|
-- (Of particular import, this needs to come *after* we've set our input hooks, so that the viewport translation runs last).
|
|
Generic.init(self)
|
|
end
|
|
|
|
function Kobo:exit()
|
|
-- Re-enable key repeat on exit, that's the default state
|
|
self:toggleKeyRepeat(true)
|
|
|
|
Generic.exit(self)
|
|
end
|
|
|
|
function Kobo:setDateTime(year, month, day, hour, min, sec)
|
|
if hour == nil or min == nil then return true end
|
|
local command
|
|
if year and month and day then
|
|
command = string.format("date -s '%d-%d-%d %d:%d:%d'", year, month, day, hour, min, sec)
|
|
else
|
|
command = string.format("date -s '%d:%d'",hour, min)
|
|
end
|
|
if os.execute(command) == 0 then
|
|
os.execute("hwclock -u -w")
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
function Kobo:initNetworkManager(NetworkMgr)
|
|
function NetworkMgr:turnOffWifi(complete_callback)
|
|
self:releaseIP()
|
|
koboEnableWifi(false)
|
|
if complete_callback then
|
|
complete_callback()
|
|
end
|
|
end
|
|
|
|
function NetworkMgr:turnOnWifi(complete_callback, interactive)
|
|
koboEnableWifi(true)
|
|
return self:reconnectOrShowNetworkMenu(complete_callback, interactive)
|
|
end
|
|
|
|
local net_if = os.getenv("INTERFACE") or "eth0"
|
|
function NetworkMgr:getNetworkInterfaceName()
|
|
return net_if
|
|
end
|
|
NetworkMgr:setWirelessBackend(
|
|
"wpa_supplicant", {ctrl_interface = "/var/run/wpa_supplicant/" .. net_if})
|
|
|
|
function NetworkMgr:obtainIP()
|
|
os.execute("./obtain-ip.sh")
|
|
end
|
|
|
|
function NetworkMgr:releaseIP()
|
|
os.execute("./release-ip.sh")
|
|
end
|
|
|
|
function NetworkMgr:restoreWifiAsync()
|
|
os.execute("./restore-wifi-async.sh")
|
|
end
|
|
|
|
NetworkMgr.isWifiOn = NetworkMgr.sysfsWifiOn
|
|
NetworkMgr.isConnected = NetworkMgr.ifHasAnAddress
|
|
-- Usually handled in NetworkMgr:init, but we'll need it *now*
|
|
NetworkMgr.interface = net_if
|
|
|
|
-- Kill Wi-Fi if NetworkMgr:isWifiOn() and NOT NetworkMgr:isConnected()
|
|
-- (i.e., if the launcher left the Wi-Fi in an inconsistent state: modules loaded, but no route to gateway).
|
|
if NetworkMgr:isWifiOn() and not NetworkMgr:isConnected() then
|
|
logger.info("Kobo Wi-Fi: Left in an inconsistent state by launcher!")
|
|
NetworkMgr:turnOffWifi()
|
|
end
|
|
end
|
|
|
|
function Kobo:setTouchEventHandler()
|
|
if self.touch_snow_protocol then
|
|
self.input.snow_protocol = true
|
|
elseif self.touch_phoenix_protocol then
|
|
self.input.handleTouchEv = self.input.handleTouchEvPhoenix
|
|
elseif not self:hasMultitouch() then
|
|
self.input.handleTouchEv = self.input.handleTouchEvLegacy
|
|
if self.touch_kobo_mk3_protocol then
|
|
self.input.touch_kobo_mk3_protocol = true
|
|
end
|
|
end
|
|
|
|
-- Accelerometer
|
|
if self:hasGSensor() then
|
|
self.input.handleMiscEv = function(this, ev)
|
|
-- As generated by gyroTranslation below
|
|
if ev.code == C.MSC_GYRO then
|
|
return this:handleGyroEv(ev)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- HAL for gyro orientation switches (NTX's EV_MSC:MSC_RAW w/ custom values to EV_MSC:MSC_GYRO w/ our own custom values)
|
|
local function gyroTranslation(ev)
|
|
-- c.f., include/uapi/linux/input.h,
|
|
-- implementations in drivers/hwmon/mma8x5x.c & drivers/input/touchscreen/kx122.c
|
|
local MSC_RAW_GSENSOR_PORTRAIT_DOWN = 0x17
|
|
local MSC_RAW_GSENSOR_PORTRAIT_UP = 0x18
|
|
local MSC_RAW_GSENSOR_LANDSCAPE_RIGHT = 0x19
|
|
local MSC_RAW_GSENSOR_LANDSCAPE_LEFT = 0x1a
|
|
-- Not that we care about those, but they are reported, and accurate ;).
|
|
--[[
|
|
local MSC_RAW_GSENSOR_BACK = 0x1b
|
|
local MSC_RAW_GSENSOR_FRONT = 0x1c
|
|
--]]
|
|
|
|
if ev.value == MSC_RAW_GSENSOR_PORTRAIT_UP then
|
|
-- i.e., UR
|
|
ev.code = C.MSC_GYRO
|
|
ev.value = C.DEVICE_ROTATED_UPRIGHT
|
|
elseif ev.value == MSC_RAW_GSENSOR_LANDSCAPE_RIGHT then
|
|
-- i.e., CW
|
|
ev.code = C.MSC_GYRO
|
|
ev.value = C.DEVICE_ROTATED_CLOCKWISE
|
|
elseif ev.value == MSC_RAW_GSENSOR_PORTRAIT_DOWN then
|
|
-- i.e., UD
|
|
ev.code = C.MSC_GYRO
|
|
ev.value = C.DEVICE_ROTATED_UPSIDE_DOWN
|
|
elseif ev.value == MSC_RAW_GSENSOR_LANDSCAPE_LEFT then
|
|
-- i.e., CCW
|
|
ev.code = C.MSC_GYRO
|
|
ev.value = C.DEVICE_ROTATED_COUNTER_CLOCKWISE
|
|
end
|
|
end
|
|
|
|
function Kobo:initEventAdjustHooks()
|
|
-- Build a single composite hook, to avoid duplicated branches...
|
|
local koboInputMangling
|
|
-- NOTE: touch_switch_xy is *always* true, but not touch_mirrored_x or touch_mirrored_y...
|
|
if self.touch_switch_xy and self.touch_mirrored_x then
|
|
local max_x = self.screen:getWidth() - 1
|
|
koboInputMangling = function(this, ev)
|
|
if ev.type == C.EV_ABS then
|
|
this:adjustABS_SwitchAxesAndMirrorX(ev, max_x)
|
|
elseif ev.type == C.EV_MSC and ev.code == C.MSC_RAW then
|
|
gyroTranslation(ev)
|
|
end
|
|
end
|
|
elseif self.touch_switch_xy and self.touch_mirrored_y then
|
|
local max_y = self.screen:getHeight() - 1
|
|
koboInputMangling = function(this, ev)
|
|
if ev.type == C.EV_ABS then
|
|
this:adjustABS_SwitchAxesAndMirrorY(ev, max_y)
|
|
elseif ev.type == C.EV_MSC and ev.code == C.MSC_RAW then
|
|
gyroTranslation(ev)
|
|
end
|
|
end
|
|
elseif self.touch_switch_xy and not self.touch_mirrored_x and not self.touch_mirrored_y then
|
|
koboInputMangling = function(this, ev)
|
|
if ev.type == C.EV_ABS then
|
|
this:adjustABS_SwitchXY(ev)
|
|
elseif ev.type == C.EV_MSC and ev.code == C.MSC_RAW then
|
|
gyroTranslation(ev)
|
|
end
|
|
end
|
|
end
|
|
if koboInputMangling then
|
|
self.input:registerEventAdjustHook(koboInputMangling)
|
|
end
|
|
end
|
|
|
|
local function getCodeName()
|
|
-- Try to get it from the env first
|
|
local codename = os.getenv("PRODUCT")
|
|
-- If that fails, run the script ourselves
|
|
if not codename then
|
|
local std_out = io.popen("/bin/kobo_config.sh 2>/dev/null", "re")
|
|
if std_out then
|
|
codename = std_out:read("*line")
|
|
std_out:close()
|
|
end
|
|
end
|
|
return codename
|
|
end
|
|
|
|
function Kobo:getFirmwareVersion()
|
|
local version_file = io.open("/mnt/onboard/.kobo/version", "re")
|
|
if not version_file then
|
|
self.firmware_rev = "none"
|
|
return
|
|
end
|
|
local version_str = version_file:read("*line")
|
|
version_file:close()
|
|
|
|
local i = 1
|
|
for field in version_str:gmatch("([^,]+)") do
|
|
if i == 3 then
|
|
self.firmware_rev = field
|
|
break
|
|
end
|
|
i = i + 1
|
|
end
|
|
end
|
|
|
|
local function getProductId()
|
|
-- Try to get it from the env first (KSM only)
|
|
local product_id = os.getenv("MODEL_NUMBER")
|
|
-- If that fails, devise it ourselves
|
|
if not product_id then
|
|
local version_file = io.open("/mnt/onboard/.kobo/version", "re")
|
|
if not version_file then
|
|
return "000"
|
|
end
|
|
local version_str = version_file:read("*line")
|
|
version_file:close()
|
|
|
|
product_id = string.sub(version_str, -3, -1)
|
|
end
|
|
|
|
return product_id
|
|
end
|
|
|
|
-- NOTE: We overload this to make sure checkUnexpectedWakeup doesn't trip *before* the newly scheduled suspend
|
|
function Kobo:rescheduleSuspend()
|
|
UIManager:unschedule(self.suspend)
|
|
UIManager:unschedule(self.checkUnexpectedWakeup)
|
|
UIManager:scheduleIn(self.suspend_wait_timeout, self.suspend, self)
|
|
end
|
|
|
|
function Kobo:checkUnexpectedWakeup()
|
|
-- Just in case another event like SleepCoverClosed also scheduled a suspend
|
|
UIManager:unschedule(self.suspend)
|
|
|
|
-- The proximity window is rather large, because we're scheduled to run 15 seconds after resuming,
|
|
-- so we're already guaranteed to be at least 15s away from the alarm ;).
|
|
if self.wakeup_mgr:isWakeupAlarmScheduled() and self.wakeup_mgr:wakeupAction(30) then
|
|
-- Assume we want to go back to sleep after running the scheduled action
|
|
-- (Kobo:resume will unschedule this on an user-triggered resume).
|
|
logger.info("Kobo suspend: scheduled wakeup; the device will go back to sleep in 30s.")
|
|
-- We need significant leeway for the poweroff action to send out close events to all requisite widgets,
|
|
-- since we don't actually want to suspend behind its back ;).
|
|
UIManager:scheduleIn(30, self.suspend, self)
|
|
else
|
|
-- We've hit an early resume, assume this is unexpected (as we only run if Kobo:resume hasn't already).
|
|
logger.dbg("Kobo suspend: checking unexpected wakeup number", self.unexpected_wakeup_count)
|
|
if self.unexpected_wakeup_count > 20 then
|
|
-- If we've failed to put the device back to sleep over 20 consecutive times, we give up.
|
|
-- Broadcast a specific event, so that AutoSuspend can pick up the baton...
|
|
local Event = require("ui/event")
|
|
UIManager:broadcastEvent(Event:new("UnexpectedWakeupLimit"))
|
|
return
|
|
end
|
|
|
|
logger.err("Kobo suspend: putting device back to sleep after", self.unexpected_wakeup_count, "unexpected wakeups.")
|
|
self:suspend()
|
|
end
|
|
end
|
|
|
|
--- The function to put the device into standby, with enabled touchscreen.
|
|
-- max_duration ... maximum time for the next standby, can wake earlier (e.g. Tap, Button ...)
|
|
function Kobo:standby(max_duration)
|
|
-- 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 NetworkMgr:isWifiOn() 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 ;).
|
|
local function standby_alarm()
|
|
end
|
|
|
|
if max_duration then
|
|
self.wakeup_mgr:addTask(max_duration, standby_alarm)
|
|
end
|
|
|
|
logger.dbg("Kobo standby: asking to enter standby . . .")
|
|
local standby_time = time.boottime_or_realtime_coarse()
|
|
|
|
-- The odd Sunxi needs some time to settle before entering standby.
|
|
-- This will avoid the screen puzzling effect documented in
|
|
-- https://github.com/koreader/koreader/pull/10306#issue-1659242042 not only for
|
|
-- WiFi toggle, but (almost) everywhere.
|
|
ffiUtil.usleep(90000) -- sleep 0.09s (0.08s would also work)
|
|
|
|
local ret = ffiUtil.writeToSysfs("standby", "/sys/power/state")
|
|
|
|
self.last_standby_time = time.boottime_or_realtime_coarse() - standby_time
|
|
self.total_standby_time = self.total_standby_time + self.last_standby_time
|
|
|
|
if ret then
|
|
logger.dbg("Kobo standby: zZz zZz zZz zZz... And woke up!")
|
|
if G_reader_settings:isTrue("pm_debug_entry_failure") then
|
|
-- NOTE: This is a debug option where we coopt the charging LED, hence us not using setupChargingLED here.
|
|
-- (It's called on resume anyway).
|
|
self:toggleChargingLED(false)
|
|
end
|
|
else
|
|
logger.warn("Kobo standby: the kernel refused to enter standby!")
|
|
if G_reader_settings:isTrue("pm_debug_entry_failure") then
|
|
self:toggleChargingLED(true)
|
|
end
|
|
end
|
|
|
|
if max_duration then
|
|
-- NOTE: We don't actually care about discriminating exactly *why* we woke up,
|
|
-- and our scheduled wakeup action is a NOP anyway,
|
|
-- so we can just drop the task instead of doing things the right way like suspend ;).
|
|
-- This saves us some pointless RTC shenanigans, so, everybody wins.
|
|
--[[
|
|
-- There's no scheduling shenanigans like in suspend, so the proximity window can be much tighter...
|
|
if self.wakeup_mgr:isWakeupAlarmScheduled() and self.wakeup_mgr:wakeupAction(5) then
|
|
-- We tripped the standby alarm, UIManager will be able to run whatever was actually scheduled,
|
|
-- and AutoSuspend will handle going back to standby if necessary.
|
|
logger.dbg("Kobo standby: tripped rtc wake alarm")
|
|
end
|
|
--]]
|
|
self.wakeup_mgr:removeTasks(nil, standby_alarm)
|
|
end
|
|
|
|
-- And restore the standard CPU scheduler once we're done dealing with the wakeup event.
|
|
UIManager:tickAfterNext(self.defaultCPUGovernor, self)
|
|
end
|
|
|
|
function Kobo:suspend()
|
|
logger.info("Kobo suspend: going to sleep . . .")
|
|
UIManager:unschedule(self.checkUnexpectedWakeup)
|
|
-- NOTE: Sleep as little as possible here, sleeping has a tendency to make
|
|
-- everything mysteriously hang...
|
|
|
|
-- Depending on device/FW version, some kernels do not support
|
|
-- wakeup_count, account for that
|
|
--
|
|
-- NOTE: ... and of course, it appears to be broken, which probably
|
|
-- explains why nickel doesn't use this facility...
|
|
-- (By broken, I mean that the system wakes up right away).
|
|
-- So, unless that changes, unconditionally disable it.
|
|
|
|
--[[
|
|
local f, re, err_msg, err_code
|
|
local has_wakeup_count = false
|
|
f = io.open("/sys/power/wakeup_count", "re")
|
|
if f ~= nil then
|
|
f:close()
|
|
has_wakeup_count = true
|
|
end
|
|
|
|
-- Clear the kernel ring buffer... (we're missing a proper -C flag...)
|
|
--dmesg -c >/dev/null
|
|
|
|
-- Go to sleep
|
|
local curr_wakeup_count
|
|
if has_wakeup_count then
|
|
curr_wakeup_count = "$(cat /sys/power/wakeup_count)"
|
|
logger.dbg("Kobo suspend: Current WakeUp count:", curr_wakeup_count)
|
|
end
|
|
-]]
|
|
|
|
-- NOTE: Sets gSleep_Mode_Suspend to 1. Used as a flag throughout the
|
|
-- kernel to suspend/resume various subsystems
|
|
-- c.f., state_extended_store @ kernel/power/main.c
|
|
local ret = ffiUtil.writeToSysfs("1", "/sys/power/state-extended")
|
|
if ret then
|
|
logger.dbg("Kobo suspend: successfully asked the kernel to put subsystems to sleep")
|
|
else
|
|
logger.warn("Kobo suspend: the kernel refused to flag subsystems for suspend!")
|
|
end
|
|
|
|
ffiUtil.sleep(2)
|
|
logger.dbg("Kobo suspend: waited for 2s because of reasons...")
|
|
|
|
os.execute("sync")
|
|
logger.dbg("Kobo suspend: synced FS")
|
|
|
|
--[[
|
|
if has_wakeup_count then
|
|
f = io.open("/sys/power/wakeup_count", "we")
|
|
if not f then
|
|
logger.err("cannot open /sys/power/wakeup_count")
|
|
return false
|
|
end
|
|
re, err_msg, err_code = f:write(tostring(curr_wakeup_count), "\n")
|
|
logger.dbg("Kobo suspend: wrote WakeUp count:", curr_wakeup_count)
|
|
if not re then
|
|
logger.err("Kobo suspend: failed to write WakeUp count:",
|
|
err_msg,
|
|
err_code)
|
|
end
|
|
f:close()
|
|
end
|
|
--]]
|
|
|
|
logger.dbg("Kobo suspend: asking for a suspend to RAM . . .")
|
|
local suspend_time = time.boottime_or_realtime_coarse()
|
|
|
|
ret = ffiUtil.writeToSysfs("mem", "/sys/power/state")
|
|
|
|
-- NOTE: At this point, we *should* be in suspend to RAM, as such,
|
|
-- execution should only resume on wakeup...
|
|
self.last_suspend_time = time.boottime_or_realtime_coarse() - suspend_time
|
|
self.total_suspend_time = self.total_suspend_time + self.last_suspend_time
|
|
|
|
if ret then
|
|
logger.info("Kobo suspend: ZzZ ZzZ ZzZ... And woke up!")
|
|
if G_reader_settings:isTrue("pm_debug_entry_failure") then
|
|
self:toggleChargingLED(false)
|
|
end
|
|
else
|
|
logger.warn("Kobo suspend: the kernel refused to enter suspend!")
|
|
-- Reset state-extended back to 0 since we are giving up.
|
|
ffiUtil.writeToSysfs("0", "/sys/power/state-extended")
|
|
if G_reader_settings:isTrue("pm_debug_entry_failure") then
|
|
self:toggleChargingLED(true)
|
|
end
|
|
end
|
|
|
|
-- NOTE: Ideally, we'd need a way to warn the user that suspending
|
|
-- gloriously failed at this point...
|
|
-- We can safely assume that just from a non-zero return code, without
|
|
-- looking at the detailed stderr message
|
|
-- (most of the failures we'll see are -EBUSY anyway)
|
|
-- For reference, when that happens to nickel, it appears to keep retrying
|
|
-- to wakeup & sleep ad nauseam,
|
|
-- which is where the non-sensical 1 -> mem -> 0 loop idea comes from...
|
|
-- cf. nickel_suspend_strace.txt for more details.
|
|
-- NOTE: On recent enough kernels, with debugfs enabled and mounted, see also
|
|
-- /sys/kernel/debug/suspend_stats & /sys/kernel/debug/wakeup_sources
|
|
|
|
--[[
|
|
if has_wakeup_count then
|
|
logger.dbg("wakeup count: $(cat /sys/power/wakeup_count)")
|
|
end
|
|
|
|
-- Print tke kernel log since our attempt to sleep...
|
|
--dmesg -c
|
|
--]]
|
|
|
|
-- NOTE: We unflag /sys/power/state-extended in Kobo:resume() to keep
|
|
-- things tidy and easier to follow
|
|
|
|
-- Kobo:resume() will reset unexpected_wakeup_count and unschedule the check to signal a sane wakeup.
|
|
self.unexpected_wakeup_count = self.unexpected_wakeup_count + 1
|
|
logger.dbg("Kobo suspend: scheduling unexpected wakeup guard")
|
|
UIManager:scheduleIn(15, self.checkUnexpectedWakeup, self)
|
|
end
|
|
|
|
function Kobo:resume()
|
|
logger.dbg("Kobo resume: clean up after wakeup")
|
|
-- Reset unexpected_wakeup_count ASAP
|
|
self.unexpected_wakeup_count = 0
|
|
-- Unschedule the checkUnexpectedWakeup shenanigans.
|
|
UIManager:unschedule(self.checkUnexpectedWakeup)
|
|
UIManager:unschedule(self.suspend)
|
|
|
|
-- Now that we're up, unflag subsystems for suspend...
|
|
-- NOTE: Sets gSleep_Mode_Suspend to 0. Used as a flag throughout the
|
|
-- kernel to suspend/resume various subsystems
|
|
-- cf. kernel/power/main.c @ L#207
|
|
-- Among other things, this sets up the wakeup pins (e.g., resume on input).
|
|
local ret = ffiUtil.writeToSysfs("0", "/sys/power/state-extended")
|
|
if ret then
|
|
logger.dbg("Kobo resume: successfully asked the kernel to resume subsystems")
|
|
else
|
|
logger.warn("Kobo resume: the kernel refused to flag subsystems for resume!")
|
|
end
|
|
|
|
-- HACK: wait a bit (0.1 sec) for the kernel to catch up
|
|
ffiUtil.usleep(100000)
|
|
|
|
if self.hasIRGridSysfsKnob then
|
|
-- cf. #1862, I can reliably break IR touch input on resume...
|
|
-- cf. also #1943 for the rationale behind applying this workaround in every case...
|
|
-- c.f., neo_ctl @ drivers/input/touchscreen/zforce_i2c.c,
|
|
-- basically, a is wakeup (for activate), d is sleep (for deactivate), and we don't care about s (set res),
|
|
-- and l (led signal level, actually a NOP on NTX kernels).
|
|
ffiUtil.writeToSysfs("a", self.hasIRGridSysfsKnob)
|
|
end
|
|
|
|
-- A full suspend may have toggled the LED off.
|
|
self:setupChargingLED()
|
|
end
|
|
|
|
function Kobo:usbPlugOut()
|
|
-- Rewind the unexpected wakeup counter, since we're no longer charging, meaning power savings are now critical again ;).
|
|
-- NOTE: We don't reset it to 0 because, semantically, only resume should ever be allowed to do so.
|
|
if self.unexpected_wakeup_count > 0 then
|
|
self.unexpected_wakeup_count = 1
|
|
end
|
|
end
|
|
|
|
function Kobo:saveSettings()
|
|
-- save frontlight state to G_reader_settings (and NickelConf if needed)
|
|
self.powerd:saveSettings()
|
|
end
|
|
|
|
function Kobo:powerOff()
|
|
-- Much like Nickel itself, disable the RTC alarm before powering down.
|
|
self.wakeup_mgr:unsetWakeupAlarm()
|
|
|
|
--- @todo: Check on MTK
|
|
if self:isSunxi() or self:isMTK() then
|
|
-- On sunxi, apparently, we *do* go through init
|
|
os.execute("sleep 1 && poweroff &")
|
|
else
|
|
-- Then shut down without init's help
|
|
os.execute("sleep 1 && poweroff -f &")
|
|
end
|
|
end
|
|
|
|
function Kobo:reboot()
|
|
os.execute("sleep 1 && reboot &")
|
|
end
|
|
|
|
function Kobo:_NTXChargingLEDToggle(toggle)
|
|
-- NOTE: While most/all Kobos actually have a charging LED, and it can usually be fiddled with in a similar fashion,
|
|
-- we've seen *extremely* weird behavior in the past when playing with it on older devices (c.f., #5479).
|
|
-- In fact, Nickel itself doesn't provide this feature on said older devices
|
|
-- (when it does, it's an option in the Energy saving settings),
|
|
-- which is why we also limit ourselves to "true" on devices where this was tested.
|
|
-- c.f., drivers/misc/ntx_misc_light.c
|
|
local fd = C.open(self.ntx_lit_sysfs_knob, bit.bor(C.O_WRONLY, C.O_CLOEXEC)) -- procfs/sysfs, we shouldn't need O_TRUNC
|
|
if fd == -1 then
|
|
logger.err("Cannot open file `" .. self.ntx_lit_sysfs_knob .. "`:", ffi.string(C.strerror(ffi.errno())))
|
|
return false
|
|
end
|
|
|
|
-- c.f., strace -fittTvyy -e trace=ioctl,file,signal,ipc,desc -s 256 -o /tmp/nickel.log -p $(pidof -s nickel) &
|
|
-- This was observed on a Forma, so I'm mildly hopeful that it's safe on other Mk. 7 devices ;).
|
|
-- NOTE: ch stands for channel, cur for current, dc for duty cycle. c.f., the driver source.
|
|
if toggle == true then
|
|
-- NOTE: Technically, Nickel forces a toggle off before that, too.
|
|
-- But since we do that on startup, it shouldn't be necessary here...
|
|
for ch = self.led_uses_channel_3 and 3 or 4, 4 do
|
|
C.write(fd, "ch " .. tostring(ch), 4)
|
|
C.write(fd, "cur 1", 5)
|
|
C.write(fd, "dc 63", 5)
|
|
end
|
|
else
|
|
for ch = 3, 5 do
|
|
C.write(fd, "ch " .. tostring(ch), 4)
|
|
C.write(fd, "cur 1", 5)
|
|
C.write(fd, "dc 0", 4)
|
|
end
|
|
end
|
|
|
|
C.close(fd)
|
|
end
|
|
|
|
function Kobo:_LinuxChargingLEDToggle(toggle)
|
|
ffiUtil.writeToSysfs(toggle and "1" or "0", self.charging_led_sysfs_knob)
|
|
end
|
|
|
|
function Kobo:toggleChargingLED(toggle)
|
|
-- We have no way of querying the current state from the HW!
|
|
if toggle == nil then
|
|
return
|
|
end
|
|
-- Don't do anything if the state is already correct
|
|
-- NOTE: What happens to the LED when attempting/successfully entering PM is... kind of a mess.
|
|
-- On a H2O, even *attempting* to enter PM will kill the light (and it'll stay off).
|
|
-- On a Forma, a failed attempt will *not* affect the light, but a successful one *will* kill it,
|
|
-- be that standby or suspend, but it'll be restored on wakeup...
|
|
-- On sunxi, PM appears to have zero effect on the LED.
|
|
if self.charging_led_state == toggle then
|
|
return
|
|
end
|
|
self.charging_led_state = toggle
|
|
logger.dbg("Kobo: Turning the charging LED", toggle and "on" or "off")
|
|
|
|
return self:charging_led_imp(toggle)
|
|
end
|
|
|
|
-- Return the highest core number
|
|
function Kobo: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
|
|
|
|
function Kobo:enableCPUCores(amount)
|
|
-- CPU0 is *always* online ;).
|
|
for n = 1, self.cpu_count do
|
|
local path = "/sys/devices/system/cpu/cpu" .. n .. "/online"
|
|
local up
|
|
if n >= amount then
|
|
up = "0"
|
|
else
|
|
up = "1"
|
|
end
|
|
|
|
ffiUtil.writeToSysfs(up, path)
|
|
end
|
|
end
|
|
|
|
function Kobo:performanceCPUGovernor()
|
|
ffiUtil.writeToSysfs("performance", self.cpu_governor_knob)
|
|
end
|
|
|
|
function Kobo:defaultCPUGovernor()
|
|
ffiUtil.writeToSysfs(self.default_cpu_governor, self.cpu_governor_knob)
|
|
end
|
|
|
|
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)
|
|
local current_script = "/tmp/koreader.sh"
|
|
local new_script = os.getenv("KOREADER_DIR") .. "/" .. "koreader.sh"
|
|
|
|
local md5 = require("ffi/MD5")
|
|
return md5.sumFile(current_script) == md5.sumFile(new_script)
|
|
end
|
|
|
|
function Kobo:UIManagerReady(uimgr)
|
|
UIManager = uimgr
|
|
end
|
|
|
|
function Kobo:setEventHandlers(uimgr)
|
|
-- We do not want auto suspend procedure to waste battery during
|
|
-- suspend. So let's unschedule it when suspending, and restart it after
|
|
-- resume. Done via the plugin's onSuspend/onResume handlers.
|
|
UIManager.event_handlers.Suspend = function()
|
|
self:onPowerEvent("Suspend")
|
|
end
|
|
UIManager.event_handlers.Resume = function()
|
|
self:onPowerEvent("Resume")
|
|
end
|
|
UIManager.event_handlers.PowerPress = function()
|
|
-- Always schedule power off.
|
|
-- Press the power button for 2+ seconds to shutdown directly from suspend.
|
|
UIManager:scheduleIn(2, UIManager.poweroff_action)
|
|
end
|
|
UIManager.event_handlers.PowerRelease = function()
|
|
if not UIManager._entered_poweroff_stage then
|
|
UIManager:unschedule(UIManager.poweroff_action)
|
|
-- resume if we were suspended
|
|
if self.screen_saver_mode then
|
|
if self.screen_saver_lock then
|
|
UIManager.event_handlers.Suspend()
|
|
else
|
|
UIManager.event_handlers.Resume()
|
|
end
|
|
else
|
|
UIManager.event_handlers.Suspend()
|
|
end
|
|
end
|
|
end
|
|
UIManager.event_handlers.Light = function()
|
|
self:getPowerDevice():toggleFrontlight()
|
|
end
|
|
-- USB plug events with a power-only charger
|
|
UIManager.event_handlers.Charging = function()
|
|
self:_beforeCharging()
|
|
-- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep.
|
|
if self.screen_saver_mode and not self.screen_saver_lock then
|
|
UIManager.event_handlers.Suspend()
|
|
end
|
|
end
|
|
UIManager.event_handlers.NotCharging = function()
|
|
-- We need to put the device into suspension, other things need to be done before it.
|
|
self:usbPlugOut()
|
|
self:_afterNotCharging()
|
|
if self.screen_saver_mode and not self.screen_saver_lock then
|
|
UIManager.event_handlers.Suspend()
|
|
end
|
|
end
|
|
-- USB plug events with a data-aware host
|
|
UIManager.event_handlers.UsbPlugIn = function()
|
|
self:_beforeCharging()
|
|
-- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep.
|
|
if self.screen_saver_mode and not self.screen_saver_lock then
|
|
UIManager.event_handlers.Suspend()
|
|
elseif not self.screen_saver_lock then
|
|
-- Potentially start an USBMS session
|
|
local MassStorage = require("ui/elements/mass_storage")
|
|
MassStorage:start()
|
|
end
|
|
end
|
|
UIManager.event_handlers.UsbPlugOut = function()
|
|
-- We need to put the device into suspension, other things need to be done before it.
|
|
self:usbPlugOut()
|
|
self:_afterNotCharging()
|
|
if self.screen_saver_mode and not self.screen_saver_lock then
|
|
UIManager.event_handlers.Suspend()
|
|
elseif not self.screen_saver_lock then
|
|
-- Potentially dismiss the USBMS ConfirmBox
|
|
local MassStorage = require("ui/elements/mass_storage")
|
|
MassStorage:dismiss()
|
|
end
|
|
end
|
|
-- Sleep Cover handling
|
|
if G_reader_settings:isTrue("ignore_power_sleepcover") then
|
|
-- NOTE: The hardware event itself will wake the kernel up if it's in suspend (:/).
|
|
-- Let the unexpected wakeup guard handle that.
|
|
UIManager.event_handlers.SleepCoverClosed = nil
|
|
UIManager.event_handlers.SleepCoverOpened = nil
|
|
elseif G_reader_settings:isTrue("ignore_open_sleepcover") then
|
|
-- Just ignore wakeup events, and do NOT set is_cover_closed,
|
|
-- so device/generic/device will let us use the power button to wake ;).
|
|
UIManager.event_handlers.SleepCoverClosed = function()
|
|
UIManager.event_handlers.Suspend()
|
|
end
|
|
UIManager.event_handlers.SleepCoverOpened = function()
|
|
self.is_cover_closed = false
|
|
end
|
|
else
|
|
UIManager.event_handlers.SleepCoverClosed = function()
|
|
self.is_cover_closed = true
|
|
UIManager.event_handlers.Suspend()
|
|
end
|
|
UIManager.event_handlers.SleepCoverOpened = function()
|
|
self.is_cover_closed = false
|
|
UIManager.event_handlers.Resume()
|
|
end
|
|
end
|
|
end
|
|
|
|
-------------- device probe ------------
|
|
|
|
local codename = getCodeName()
|
|
local product_id = getProductId()
|
|
|
|
if codename == "dahlia" then
|
|
return KoboDahlia
|
|
elseif codename == "dragon" then
|
|
return KoboDragon
|
|
elseif codename == "kraken" then
|
|
return KoboKraken
|
|
elseif codename == "phoenix" then
|
|
return KoboPhoenix
|
|
elseif codename == "trilogy" and product_id == "310" then
|
|
-- This is where things get interesting...
|
|
-- The early 'A' variant (the actual model name being N905, without any letter suffix, unlike the two other variants)
|
|
-- does *NOT* feature an internal SD card, and is manufactured in China instead of Taiwan... because it is *NOT* an NTX board.
|
|
-- cf. https://github.com/koreader/koreader/issues/9742
|
|
if os.getenv("PLATFORM") == "freescale" then
|
|
return KoboTrilogyA
|
|
else
|
|
return KoboTrilogyB
|
|
end
|
|
elseif codename == "trilogy" and product_id == "320" then
|
|
return KoboTrilogyC
|
|
elseif codename == "pixie" then
|
|
return KoboPixie
|
|
elseif codename == "alyssum" then
|
|
return KoboAlyssum
|
|
elseif codename == "pika" then
|
|
return KoboPika
|
|
elseif codename == "star" and product_id == "379" then
|
|
return KoboStarRev2
|
|
elseif codename == "star" then
|
|
return KoboStar
|
|
elseif codename == "daylight" then
|
|
return KoboDaylight
|
|
elseif codename == "snow" and product_id == "378" then
|
|
return KoboSnowRev2
|
|
elseif codename == "snow" then
|
|
return KoboSnow
|
|
elseif codename == "nova" then
|
|
return KoboNova
|
|
elseif codename == "frost" then
|
|
return KoboFrost
|
|
elseif codename == "storm" then
|
|
return KoboStorm
|
|
elseif codename == "luna" then
|
|
return KoboLuna
|
|
elseif codename == "europa" then
|
|
return KoboEuropa
|
|
elseif codename == "cadmus" then
|
|
return KoboCadmus
|
|
elseif codename == "io" then
|
|
return KoboIo
|
|
elseif codename == "goldfinch" then
|
|
return KoboGoldfinch
|
|
elseif codename == "condor" then
|
|
return KoboCondor
|
|
else
|
|
error("unrecognized Kobo model ".. codename .. " with device id " .. product_id)
|
|
end
|