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/kobo/device.lua

489 lines
15 KiB
Lua

local Generic = require("device/generic/device")
local TimeVal = require("ui/timeval")
local Geom = require("ui/geometry")
local util = require("ffi/util")
local _ = require("gettext")
local logger = require("logger")
local function yes() return true end
local function koboEnableWifi(toggle)
if toggle == 1 then
os.execute("./enable-wifi.sh")
else
os.execute("./disable-wifi.sh")
end
end
local Kobo = Generic:new{
model = "Kobo",
isKobo = yes,
isTouchDevice = yes, -- all of them are
-- 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,
-- enforce protrait mode on Kobos:
isAlwaysPortrait = yes,
-- the internal storage mount point users can write to
internal_storage_mount_point = "/mnt/onboard/"
}
-- TODO: hasKeys for some devices?
-- Kobo Touch:
local KoboTrilogy = Kobo:new{
model = "Kobo_trilogy",
needsTouchScreenProbe = yes,
touch_switch_xy = false,
-- Some Kobo Touch models' kernel does not generate touch event with epoch
-- timestamp. This flag will probe for those models and setup event adjust
-- hook accordingly
touch_probe_ev_epoch_time = true,
hasKeys = yes,
}
-- Kobo Mini:
local KoboPixie = Kobo:new{
model = "Kobo_pixie",
display_dpi = 200,
-- bezel:
viewport = Geom:new{x=0, y=2, w=596, h=794},
}
-- Kobo Aura One:
local KoboDaylight = Kobo:new{
model = "Kobo_daylight",
hasFrontlight = yes,
touch_probe_ev_epoch_time = true,
touch_phoenix_protocol = true,
display_dpi = 300,
}
-- Kobo Aura H2O:
local KoboDahlia = Kobo:new{
model = "Kobo_dahlia",
hasFrontlight = yes,
touch_phoenix_protocol = true,
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:new{
model = "Kobo_dragon",
hasFrontlight = yes,
display_dpi = 265,
}
-- Kobo Glo:
local KoboKraken = Kobo:new{
model = "Kobo_kraken",
hasFrontlight = yes,
display_dpi = 212,
}
-- Kobo Aura:
local KoboPhoenix = Kobo:new{
model = "Kobo_phoenix",
hasFrontlight = yes,
touch_phoenix_protocol = true,
display_dpi = 212,
-- the bezel covers 12 pixels at the bottom:
viewport = Geom:new{x=0, y=0, w=758, h=1012},
}
-- Kobo Aura second edition:
local KoboStar = Kobo:new{
model = "Kobo_star",
hasFrontlight = yes,
touch_probe_ev_epoch_time = true,
touch_phoenix_protocol = true,
display_dpi = 212,
-- the bezel covers 12 pixels at the bottom:
viewport = Geom:new{x=0, y=0, w=758, h=1012},
}
-- Kobo Glo HD:
local KoboAlyssum = Kobo:new{
model = "Kobo_alyssum",
hasFrontlight = yes,
touch_phoenix_protocol = true,
touch_alyssum_protocol = true,
display_dpi = 300,
}
-- Kobo Touch 2.0:
local KoboPika = Kobo:new{
model = "Kobo_pika",
touch_phoenix_protocol = true,
touch_alyssum_protocol = true,
}
function Kobo:init()
self.screen = require("ffi/framebuffer_mxcfb"):new{device = self, debug = logger.dbg}
self.powerd = require("device/kobo/powerd"):new{device = self}
self.input = require("device/input"):new{
device = self,
event_map = {
[59] = function(ev)
if self.input:isEvKeyPress(ev) then
return "SleepCoverClosed"
else
return "SleepCoverOpened"
end
end,
[90] = function(ev)
if self.input:isEvKeyRelease(ev) then
return "Light"
end
end,
[102] = "Home",
[116] = "Power",
}
}
Generic.init(self)
-- event2 is for MMA7660 sensor (3-Axis Orientation/Motion Detection)
self.input.open("/dev/input/event0") -- Light button and sleep slider
self.input.open("/dev/input/event1")
-- fake_events is only used for usb plug event so far
-- NOTE: usb hotplug event is also available in /tmp/nickel-hardware-status
self.input.open("fake_events")
if not self.needsTouchScreenProbe() then
self:initEventAdjustHooks()
else
-- if touch probe is required, we postpone EventAdjustHook
-- initialization to when self:touchScreenProbe is called
self.touchScreenProbe = function()
-- if user has not set KOBO_TOUCH_MIRRORED yet
if KOBO_TOUCH_MIRRORED == nil then
local switch_xy = G_reader_settings:readSetting("kobo_touch_switch_xy")
-- and has no probe before
if switch_xy == nil then
local TouchProbe = require("tools/kobo_touch_probe")
local UIManager = require("ui/uimanager")
UIManager:show(TouchProbe:new{})
UIManager:run()
-- assuming TouchProbe sets kobo_touch_switch_xy config
switch_xy = G_reader_settings:readSetting("kobo_touch_switch_xy")
end
self.touch_switch_xy = switch_xy
end
self:initEventAdjustHooks()
end
end
-- TODO: get rid of KOBO_LIGHT_ON_START
local kobo_light_on_start = tonumber(KOBO_LIGHT_ON_START)
if kobo_light_on_start then
local new_intensity
local is_frontlight_on
if kobo_light_on_start > 0 then
new_intensity = math.min(kobo_light_on_start, 100)
is_frontlight_on = true
elseif kobo_light_on_start == 0 then
is_frontlight_on = false
elseif kobo_light_on_start == -2 then
local NickelConf = require("device/kobo/nickel_conf")
new_intensity = NickelConf.frontLightLevel.get()
is_frontlight_on = NickelConf.frontLightState:get()
if is_frontlight_on == nil then
-- this device does not support frontlight toggle,
-- we set the value based on frontlight intensity.
if new_intensity > 0 then
is_frontlight_on = true
else
is_frontlight_on = false
end
end
end
-- Since this is kobo-specific, we save all values in settings here
-- and let the code (reader.lua) pick it up later during bootstrap.
if new_intensity then
G_reader_settings:saveSetting("frontlight_intensity", new_intensity)
end
G_reader_settings:saveSetting("is_frontlight_on", is_frontlight_on)
end
end
function Kobo:initNetworkManager(NetworkMgr)
function NetworkMgr:turnOffWifi(complete_callback)
koboEnableWifi(0)
if complete_callback then
complete_callback()
end
end
function NetworkMgr:turnOnWifi(complete_callback)
koboEnableWifi(1)
self:showNetworkMenu(complete_callback)
end
NetworkMgr:setWirelessBackend(
"wpa_supplicant", {ctrl_interface = "/var/run/wpa_supplicant/eth0"})
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
end
local probeEvEpochTime
-- this function will update itself after the first touch event
probeEvEpochTime = function(self, ev)
local now = TimeVal:now()
-- This check should work as long as main UI loop is not blocked for more
-- than 10 minute before handling the first touch event.
if ev.time.sec <= now.sec - 600 then
-- time is seconds since boot, force it to epoch
probeEvEpochTime = function(_, _ev)
_ev.time = TimeVal:now()
end
ev.time = now
else
-- time is already epoch time, no need to do anything
probeEvEpochTime = function(_, _) end
end
end
local ABS_MT_TRACKING_ID = 57
local EV_ABS = 3
local adjustTouchAlyssum = function(self, ev)
ev.time = TimeVal:now()
if ev.type == EV_ABS and ev.code == ABS_MT_TRACKING_ID then
ev.value = ev.value - 1
end
end
function Kobo:initEventAdjustHooks()
-- it's called KOBO_TOUCH_MIRRORED in defaults.lua, but what it
-- actually did in its original implementation was to switch X/Y.
-- NOTE: for kobo touch, adjustTouchSwitchXY needs to be called before
-- adjustTouchMirrorX
if (self.touch_switch_xy and not KOBO_TOUCH_MIRRORED)
or (not self.touch_switch_xy and KOBO_TOUCH_MIRRORED)
then
self.input:registerEventAdjustHook(self.input.adjustTouchSwitchXY)
end
if self.touch_mirrored_x then
self.input:registerEventAdjustHook(
self.input.adjustTouchMirrorX,
-- FIXME: what if we change the screen protrait mode?
self.screen:getWidth()
)
end
if self.touch_alyssum_protocol then
self.input:registerEventAdjustHook(adjustTouchAlyssum)
end
if self.touch_probe_ev_epoch_time then
self.input:registerEventAdjustHook(function(_, ev)
probeEvEpochTime(_, ev)
end)
end
if self.touch_phoenix_protocol then
self.input.handleTouchEv = self.input.handleTouchEvPhoenix
end
end
function Kobo: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", "r")
codename = std_out:read()
std_out:close()
end
return codename
end
function Kobo:getFirmwareVersion()
local version_file = io.open("/mnt/onboard/.kobo/version", "r")
self.firmware_rev = string.sub(version_file:read(),24,28)
version_file:close()
end
function Kobo:suspend()
logger.info("Kobo Suspend: Going to sleep . . .")
local f, re, err_msg, err_code
-- 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 has_wakeup_count = false
f = io.open("/sys/power/wakeup_count", "r")
if f ~= nil then
io.close(f)
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.info("Kobo Suspend: Current WakeUp count:", curr_wakeup_count)
end
-]]
f = io.open("/sys/power/state-extended", "w")
if not f then
logger.err("Cannot open /sys/power/state-extended for writing!")
return false
end
-- NOTE: Sets gSleep_Mode_Suspend to 1. Used as a flag throughout the
-- kernel to suspend/resume various subsystems
-- cf. kernel/power/main.c @ L#207
re, err_msg, err_code = f:write("1\n")
logger.info("Write syscall returned: ", re)
if not re then
logger.err('write error: ', err_msg, err_code)
end
io.close(f)
logger.info("Kobo Suspend: Asked the kernel to put subsystems to sleep")
util.sleep(2)
logger.info("Kobo Suspend: Waited for 2s because of reasons...")
os.execute("sync")
logger.info("Kobo Suspend: Synced FS")
--[[
if has_wakeup_count then
f = io.open("/sys/power/wakeup_count", "w")
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.info("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
io.close(f)
end
--]]
logger.info("Kobo Suspend: Asking for a suspend to RAM . . .")
f = io.open("/sys/power/state", "w")
if not f then
-- TODO: update state-extended?
return false
end
re, err_msg, err_code = f:write("mem\n")
-- NOTE: At this point, we *should* be in suspend to RAM, as such,
-- execution should only resume on wakeup...
logger.info("Kobo Suspend: ZzZ ZzZ ZzZ? Write syscall returned: ", re)
if not re then
logger.err('write error: ', err_msg, err_code)
end
io.close(f)
-- 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.
logger.info("Kobo Suspend: Woke up!")
--[[
if has_wakeup_count then
logger.info("WakeUp count: $(cat /sys/power/wakeup_count)")
end
-- Print tke kernel log since our attempt to sleep...
--dmesg -c
--]]
-- NOTE: We unflag /sys/power/state-extended in Kobo:resume() to keep
-- things tidy and easier to follow
end
function Kobo:resume()
-- Now that we're up, unflag subsystems for suspend...
os.execute("echo 0 > /sys/power/state-extended")
logger.info("Kobo Suspend: Unflagged kernel subsystems for suspend")
-- HACK: wait a bit (0.1 sec) for the kernel to catch up
util.usleep(100000)
-- cf. #1862, I can reliably break IR touch input on resume...
-- cf. also #1943 for the rationale behind applying this workaorund in every case...
local f = io.open("/sys/devices/virtual/input/input1/neocmd", "r")
if f ~= nil then
io.close(f)
os.execute("echo 'a' > /sys/devices/virtual/input/input1/neocmd")
end
end
function Kobo:powerOff()
os.execute("poweroff")
end
-------------- device probe ------------
local codename = Kobo:getCodeName()
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" then
return KoboTrilogy
elseif codename == "pixie" then
return KoboPixie
elseif codename == "alyssum" then
return KoboAlyssum
elseif codename == "pika" then
return KoboPika
elseif codename == "star" then
return KoboStar
elseif codename == "daylight" then
return KoboDaylight
else
error("unrecognized Kobo model "..codename)
end