mirror of https://github.com/koreader/koreader
commit
a300f1e981
@ -1 +1 @@
|
||||
Subproject commit 3260f0494f7558f234806e68adb92606af391f8f
|
||||
Subproject commit af763e714395a9bf1a2356f7fb99a020c0387684
|
@ -0,0 +1,29 @@
|
||||
local isAndroid, android = pcall(require, "android")
|
||||
local util = require("ffi/util")
|
||||
|
||||
local function probeDevice()
|
||||
if util.isEmulated() then
|
||||
return require("device/emulator/device")
|
||||
end
|
||||
|
||||
if isAndroid then
|
||||
return require("device/android/device")
|
||||
end
|
||||
|
||||
local kindle_sn = io.open("/proc/usid", "r")
|
||||
if kindle_sn then
|
||||
kindle_sn:close()
|
||||
return require("device/kindle/device")
|
||||
end
|
||||
|
||||
local kg_test_fd = lfs.attributes("/bin/kobo_config.sh")
|
||||
if kg_test_fd then
|
||||
return require("device/kobo/device")
|
||||
end
|
||||
|
||||
error("did not find a hardware abstraction for this platform")
|
||||
end
|
||||
|
||||
local dev = probeDevice()
|
||||
dev:init()
|
||||
return dev
|
@ -0,0 +1,43 @@
|
||||
local Generic = require("device/generic/device")
|
||||
local isAndroid, android = pcall(require, "android")
|
||||
local ffi = require("ffi")
|
||||
|
||||
local function yes() return true end
|
||||
|
||||
local Device = Generic:new{
|
||||
model = "Android",
|
||||
isAndroid = yes,
|
||||
firmware_rev = "none",
|
||||
display_dpi = ffi.C.AConfiguration_getDensity(android.app.config),
|
||||
}
|
||||
|
||||
function Device:init()
|
||||
self.screen = require("device/screen"):new{device = self}
|
||||
self.powerd = require("device/android/powerd"):new{device = self}
|
||||
self.input = require("device/input"):new{
|
||||
device = self,
|
||||
event_map = require("device/android/event_map"),
|
||||
handleMiscEv = function(self, ev)
|
||||
if ev.code == ffi.C.APP_CMD_SAVE_STATE then
|
||||
return "SaveState"
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
-- check if we have a keyboard
|
||||
if ffi.C.AConfiguration_getKeyboard(android.app.config)
|
||||
== ffi.C.ACONFIGURATION_KEYBOARD_QWERTY
|
||||
then
|
||||
self.hasKeyboard = yes
|
||||
end
|
||||
-- check if we have a touchscreen
|
||||
if ffi.C.AConfiguration_getTouchscreen(android.app.config)
|
||||
~= ffi.C.ACONFIGURATION_TOUCHSCREEN_NOTOUCH
|
||||
then
|
||||
self.isTouchDevice = yes
|
||||
end
|
||||
|
||||
Generic:init()
|
||||
end
|
||||
|
||||
return Device
|
@ -0,0 +1,33 @@
|
||||
return {
|
||||
[29] = "A", [30] = "B", [31] = "C", [32] = "D", [33] = "E", [34] = "F",
|
||||
[35] = "G", [36] = "H", [37] = "I", [38] = "J", [39] = "K", [40] = "L",
|
||||
[41] = "M", [42] = "N", [43] = "O", [44] = "P", [45] = "Q", [46] = "R",
|
||||
[47] = "S", [48] = "T", [49] = "U", [50] = "V", [51] = "W", [52] = "X",
|
||||
[53] = "Y", [54] = "Z", [ 7] = "0", [ 8] = "1", [ 9] = "2", [10] = "3",
|
||||
[11] = "4", [12] = "5", [13] = "6", [14] = "7", [15] = "8", [16] = "9",
|
||||
|
||||
[4] = "Back", -- BACK
|
||||
[19] = "Up", -- DPAD_UP
|
||||
[20] = "Down", -- DPAD_UP
|
||||
[21] = "Left", -- DPAD_LEFT
|
||||
[22] = "Right", -- DPAD_RIGHT
|
||||
[23] = "Press", -- DPAD_CENTER
|
||||
[24] = "LPgBack", -- VOLUME_UP
|
||||
[25] = "LPgFwd", -- VOLUME_DOWN
|
||||
[56] = ".", -- PERIOD
|
||||
[59] = "Shift", -- SHIFT_LEFT
|
||||
[60] = "Shift", -- SHIFT_RIGHT
|
||||
[62] = " ", -- SPACE
|
||||
[63] = "Sym", -- SYM
|
||||
[66] = "Enter", -- ENTER
|
||||
[67] = "Del", -- DEL
|
||||
[76] = "/", -- SLASH
|
||||
[82] = "Menu", -- MENU
|
||||
[84] = "Search",--SEARCH
|
||||
[92] = "LPgBack", -- PAGE_UP
|
||||
[93] = "LPgFwd", -- PAGE_DOWN
|
||||
|
||||
[104] = "LPgBack", -- T68 PageUp
|
||||
[109] = "LPgFwd", -- T68 PageDown
|
||||
[139] = "Menu", -- T68 Menu
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
local BasePowerD = require("ui/device/basepowerd")
|
||||
local BasePowerD = require("device/generic/powerd")
|
||||
|
||||
local AndroidPowerD = BasePowerD:new{
|
||||
batt_capacity_file = "/sys/class/power_supply/battery/capacity",
|
@ -0,0 +1,27 @@
|
||||
local Generic = require("device/generic/device")
|
||||
local util = require("ffi/util")
|
||||
|
||||
local function yes() return true end
|
||||
|
||||
local Device = Generic:new{
|
||||
model = "Emulator",
|
||||
isEmulator = yes,
|
||||
hasKeyboard = yes,
|
||||
hasKeys = yes,
|
||||
hasFrontlight = yes,
|
||||
isTouchDevice = yes,
|
||||
}
|
||||
|
||||
function Device:init()
|
||||
self.screen = require("device/screen"):new{device = self}
|
||||
self.input = require("device/input"):new{
|
||||
device = self,
|
||||
event_map = util.haveSDL2()
|
||||
and require("device/emulator/event_map_sdl2")
|
||||
or require("device/emulator/event_map_sdl"),
|
||||
}
|
||||
|
||||
Generic.init(self)
|
||||
end
|
||||
|
||||
return Device
|
@ -0,0 +1,31 @@
|
||||
return {
|
||||
[10] = "1", [11] = "2", [12] = "3", [13] = "4", [14] = "5", [15] = "6", [16] = "7", [17] = "8", [18] = "9", [19] = "0",
|
||||
[24] = "Q", [25] = "W", [26] = "E", [27] = "R", [28] = "T", [29] = "Y", [30] = "U", [31] = "I", [32] = "O", [33] = "P",
|
||||
[38] = "A", [39] = "S", [40] = "D", [41] = "F", [42] = "G", [43] = "H", [44] = "J", [45] = "K", [46] = "L",
|
||||
[52] = "Z", [53] = "X", [54] = "C", [55] = "V", [56] = "B", [57] = "N", [58] = "M",
|
||||
|
||||
[22] = "Back", -- Backspace
|
||||
[36] = "Enter", -- Enter
|
||||
[50] = "Shift", -- left shift
|
||||
[60] = ".",
|
||||
[61] = "/",
|
||||
[62] = "Sym", -- right shift key
|
||||
[64] = "Alt", -- left alt
|
||||
[65] = " ", -- Spacebar
|
||||
[67] = "Menu", -- F[1]
|
||||
[68] = "Power", -- F[2]
|
||||
[72] = "LPgBack", -- F[6]
|
||||
[73] = "LPgFwd", -- F[7]
|
||||
[95] = "VPlus", -- F[11]
|
||||
[96] = "VMinus", -- F[12]
|
||||
[105] = "AA", -- right alt key
|
||||
[110] = "Home", -- Home
|
||||
[111] = "Up", -- arrow up
|
||||
[112] = "RPgBack", -- normal PageUp
|
||||
[113] = "Left", -- arrow left
|
||||
[114] = "Right", -- arrow right
|
||||
[115] = "Press", -- End (above arrows)
|
||||
[116] = "Down", -- arrow down
|
||||
[117] = "RPgFwd", -- normal PageDown
|
||||
[119] = "Del", -- Delete
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
return {
|
||||
[ 4] = "A", [ 5] = "B", [ 6] = "C", [ 7] = "D", [ 8] = "E", [ 9] = "F",
|
||||
[10] = "G", [11] = "H", [12] = "I", [13] = "J", [14] = "K", [15] = "L",
|
||||
[16] = "M", [17] = "N", [18] = "O", [19] = "P", [20] = "Q", [21] = "R",
|
||||
[22] = "S", [23] = "T", [24] = "U", [25] = "V", [26] = "W", [27] = "X",
|
||||
[28] = "Y", [29] = "Z", [30] = "1", [31] = "2", [32] = "3", [33] = "4",
|
||||
[34] = "5", [35] = "6", [36] = "7", [37] = "8", [38] = "9", [39] = "0",
|
||||
|
||||
[42] = "Back", -- Backspace
|
||||
[40] = "Enter", -- Enter
|
||||
[225] = "Shift", -- left shift
|
||||
[55] = ".",
|
||||
[56] = "/",
|
||||
[229] = "Sym", -- right shift key
|
||||
[226] = "Alt", -- left alt
|
||||
[44] = " ", -- Spacebar
|
||||
[58] = "Menu", -- F[1]
|
||||
[59] = "Power", -- F[2]
|
||||
[63] = "LPgBack", -- F[6]
|
||||
[64] = "LPgFwd", -- F[7]
|
||||
[68] = "VPlus", -- F[11]
|
||||
[69] = "VMinus", -- F[12]
|
||||
[230] = "AA", -- right alt key
|
||||
[74] = "Home", -- Home
|
||||
[82] = "Up", -- arrow up
|
||||
[75] = "RPgBack", -- normal PageUp
|
||||
[80] = "Left", -- arrow left
|
||||
[79] = "Right", -- arrow right
|
||||
[77] = "Press", -- End (above arrows)
|
||||
[81] = "Down", -- arrow down
|
||||
[78] = "RPgFwd", -- normal PageDown
|
||||
[76] = "Del", -- Delete
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
local util = require("ffi/util")
|
||||
local DEBUG = require("dbg")
|
||||
|
||||
local function no() return false end
|
||||
|
||||
local Device = {
|
||||
screen_saver_mode = false,
|
||||
charging_mode = false,
|
||||
survive_screen_saver = false,
|
||||
model = nil,
|
||||
powerd = nil,
|
||||
screen = nil,
|
||||
input = nil,
|
||||
|
||||
-- hardware feature tests: (these are functions!)
|
||||
hasKeyboard = no,
|
||||
hasKeys = no,
|
||||
isTouchDevice = no,
|
||||
hasFrontlight = no,
|
||||
|
||||
-- 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!)
|
||||
isKindle = no,
|
||||
isKobo = no,
|
||||
isAndroid = no,
|
||||
isEmulator = no,
|
||||
|
||||
-- some devices have part of their screen covered by the bezel
|
||||
viewport = nil,
|
||||
}
|
||||
|
||||
function Device:new(o)
|
||||
local o = o or {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end
|
||||
|
||||
function Device:init()
|
||||
if not self.screen then
|
||||
self.screen = require("device/screen"):new{device = self}
|
||||
end
|
||||
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
|
||||
|
||||
if self.viewport then
|
||||
self.screen:setViewport(self.viewport)
|
||||
self.input:registerEventAdjustHook(
|
||||
self.input.adjustTouchTranslate,
|
||||
{x = 0 - self.viewport.x, y = 0 - self.viewport.y})
|
||||
end
|
||||
end
|
||||
|
||||
function Device:getPowerDevice()
|
||||
return self.powerd
|
||||
end
|
||||
|
||||
function Device:intoScreenSaver()
|
||||
if self.charging_mode == false and self.screen_saver_mode == false then
|
||||
self.screen:saveCurrentBB()
|
||||
self.screen_saver_mode = true
|
||||
end
|
||||
end
|
||||
|
||||
function Device:outofScreenSaver()
|
||||
if self.screen_saver_mode == true and self.charging_mode == false then
|
||||
-- wait for native system update screen before we recover saved
|
||||
-- Blitbuffer.
|
||||
util.usleep(1500000)
|
||||
self.screen:restoreFromSavedBB()
|
||||
self.screen:refresh(0)
|
||||
self.survive_screen_saver = true
|
||||
end
|
||||
self.screen_saver_mode = false
|
||||
end
|
||||
|
||||
function Device:onPowerEvent(ev)
|
||||
local Screensaver = require("ui/screensaver")
|
||||
if (ev == "Power" or ev == "Suspend") and not self.screen_saver_mode then
|
||||
local UIManager = require("ui/uimanager")
|
||||
DEBUG("Suspending...")
|
||||
-- always suspend in portrait mode
|
||||
self.orig_rotation_mode = self.screen:getRotationMode()
|
||||
self.screen:setRotationMode(0)
|
||||
Screensaver:show()
|
||||
self:prepareSuspend()
|
||||
UIManager:scheduleIn(2, function() self:Suspend() end)
|
||||
elseif (ev == "Power" or ev == "Resume") and self.screen_saver_mode then
|
||||
DEBUG("Resuming...")
|
||||
-- restore to previous rotation mode
|
||||
self.screen:setRotationMode(self.orig_rotation_mode)
|
||||
self:Resume()
|
||||
Screensaver:close()
|
||||
end
|
||||
end
|
||||
|
||||
function Device:prepareSuspend()
|
||||
if self.powerd and self.powerd.fl ~= nil then
|
||||
-- in no case should the frontlight be turned on in suspend mode
|
||||
self.powerd.fl:sleep()
|
||||
end
|
||||
self.screen:refresh(0)
|
||||
self.screen_saver_mode = true
|
||||
end
|
||||
|
||||
function Device:Suspend()
|
||||
end
|
||||
|
||||
function Device:Resume()
|
||||
self.screen:refresh(1)
|
||||
self.screen_saver_mode = false
|
||||
end
|
||||
|
||||
function Device:usbPlugIn()
|
||||
if self.charging_mode == false and self.screen_saver_mode == false then
|
||||
self.screen:saveCurrentBB()
|
||||
end
|
||||
self.charging_mode = true
|
||||
end
|
||||
|
||||
function Device:usbPlugOut()
|
||||
if self.charging_mode == true and self.screen_saver_mode == false then
|
||||
self.screen:restoreFromSavedBB()
|
||||
self.screen:refresh(0)
|
||||
end
|
||||
|
||||
--@TODO signal filemanager for file changes 13.06 2012 (houqp)
|
||||
self.charging_mode = false
|
||||
end
|
||||
|
||||
--[[
|
||||
prepare for application shutdown
|
||||
--]]
|
||||
function Device:exit()
|
||||
require("ffi/input"):closeAll()
|
||||
self.screen:close()
|
||||
end
|
||||
|
||||
return Device
|
@ -0,0 +1,518 @@
|
||||
local Event = require("ui/event")
|
||||
local TimeVal = require("ui/timeval")
|
||||
local input = require("ffi/input")
|
||||
local util = require("ffi/util")
|
||||
local Math = require("optmath")
|
||||
local DEBUG = require("dbg")
|
||||
local ffi = require("ffi")
|
||||
local _ = require("gettext")
|
||||
local Key = require("device/key")
|
||||
local GestureDetector = require("device/gesturedetector")
|
||||
|
||||
-- constants from <linux/input.h>
|
||||
local EV_SYN = 0
|
||||
local EV_KEY = 1
|
||||
local EV_ABS = 3
|
||||
local EV_MSC = 4
|
||||
|
||||
-- key press event values (KEY.value)
|
||||
local EVENT_VALUE_KEY_PRESS = 1
|
||||
local EVENT_VALUE_KEY_REPEAT = 2
|
||||
local EVENT_VALUE_KEY_RELEASE = 0
|
||||
|
||||
-- Synchronization events (SYN.code).
|
||||
local SYN_REPORT = 0
|
||||
local SYN_CONFIG = 1
|
||||
local SYN_MT_REPORT = 2
|
||||
|
||||
-- For single-touch events (ABS.code).
|
||||
local ABS_X = 00
|
||||
local ABS_Y = 01
|
||||
local ABS_PRESSURE = 24
|
||||
|
||||
-- For multi-touch events (ABS.code).
|
||||
local ABS_MT_SLOT = 47
|
||||
local ABS_MT_TOUCH_MAJOR = 48
|
||||
local ABS_MT_WIDTH_MAJOR = 50
|
||||
|
||||
local ABS_MT_POSITION_X = 53
|
||||
local ABS_MT_POSITION_Y = 54
|
||||
local ABS_MT_TRACKING_ID = 57
|
||||
local ABS_MT_PRESSURE = 58
|
||||
|
||||
--[[
|
||||
an interface to get input events
|
||||
]]
|
||||
local Input = {
|
||||
-- this depends on keyboard layout and should be overridden:
|
||||
event_map = {},
|
||||
|
||||
group = {
|
||||
Cursor = { "Up", "Down", "Left", "Right" },
|
||||
PgFwd = { "RPgFwd", "LPgFwd" },
|
||||
PgBack = { "RPgBack", "LPgBack" },
|
||||
Alphabet = {
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
|
||||
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
|
||||
},
|
||||
AlphaNumeric = {
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
|
||||
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
|
||||
},
|
||||
Numeric = {
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
|
||||
},
|
||||
Text = {
|
||||
" ", ".", "/",
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
|
||||
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
|
||||
},
|
||||
Any = {
|
||||
" ", ".", "/",
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
|
||||
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
|
||||
"Up", "Down", "Left", "Right", "Press",
|
||||
"Back", "Enter", "Sym", "AA", "Menu", "Home", "Del",
|
||||
"LPgBack", "RPgBack", "LPgFwd", "RPgFwd"
|
||||
},
|
||||
},
|
||||
|
||||
rotation_map = {
|
||||
[0] = {},
|
||||
[1] = { Up = "Right", Right = "Down", Down = "Left", Left = "Up" },
|
||||
[2] = { Up = "Down", Right = "Left", Down = "Up", Left = "Right" },
|
||||
[3] = { Up = "Left", Right = "Up", Down = "Right", Left = "Down" }
|
||||
},
|
||||
|
||||
timer_callbacks = {},
|
||||
disable_double_tap = DGESDETECT_DISABLE_DOUBLE_TAP,
|
||||
|
||||
-- keyboard state:
|
||||
modifiers = {
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
},
|
||||
|
||||
-- touch state:
|
||||
cur_slot = 0,
|
||||
MTSlots = {},
|
||||
ev_slots = {
|
||||
[0] = {
|
||||
slot = 0,
|
||||
}
|
||||
},
|
||||
gesture_detector = nil,
|
||||
}
|
||||
|
||||
function Input:new(o)
|
||||
local o = o or {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
if o.init then o:init() end
|
||||
return o
|
||||
end
|
||||
|
||||
function Input:init()
|
||||
self.gesture_detector = GestureDetector:new{
|
||||
screen = self.device.screen,
|
||||
input = self,
|
||||
}
|
||||
|
||||
-- set up fake event map
|
||||
self.event_map[10000] = "IntoSS" -- go into screen saver
|
||||
self.event_map[10001] = "OutOfSS" -- go out of screen saver
|
||||
self.event_map[10020] = "Charging"
|
||||
self.event_map[10021] = "NotCharging"
|
||||
end
|
||||
|
||||
--[[
|
||||
wrapper for FFI input open
|
||||
|
||||
Note that we adhere to the "." syntax here for compatibility.
|
||||
TODO: clean up separation FFI/this
|
||||
--]]
|
||||
function Input.open(device)
|
||||
input.open(device)
|
||||
end
|
||||
|
||||
--[[
|
||||
Different device models can implement their own hooks
|
||||
and register them.
|
||||
--]]
|
||||
function Input:registerEventAdjustHook(hook, hook_params)
|
||||
local old = self.eventAdjustHook
|
||||
self.eventAdjustHook = function(self, ev)
|
||||
old(self, ev)
|
||||
hook(self, ev, hook_params)
|
||||
end
|
||||
end
|
||||
function Input:eventAdjustHook(ev)
|
||||
-- do nothing by default
|
||||
end
|
||||
-- catalogue of predefined hooks:
|
||||
function Input:adjustTouchSwitchXY(ev)
|
||||
if ev.type == EV_ABS then
|
||||
if ev.code == ABS_X then ev.code = ABS_Y end
|
||||
if ev.code == ABS_Y then ev.code = ABS_X end
|
||||
if ev.code == ABS_MT_POSITION_X then ev.code = ABS_MT_POSITION_Y end
|
||||
if ev.code == ABS_MT_POSITION_Y then ev.code = ABS_MT_POSITION_X end
|
||||
end
|
||||
end
|
||||
function Input:adjustTouchScale(ev, by)
|
||||
if ev.type == EV_ABS then
|
||||
if ev.code == ABS_X or ev.code == ABS_MT_POSITION_X then
|
||||
ev.value = by.x * ev.value
|
||||
end
|
||||
if ev.code == ABS_Y or ev.code == ABS_MT_POSITION_Y then
|
||||
ev.value = by.y * ev.value
|
||||
end
|
||||
end
|
||||
end
|
||||
function Input:adjustTouchMirrorX(ev, width)
|
||||
if ev.type == EV_ABS then
|
||||
if ev.code == ABS_X or ev.code == ABS_MT_POSITION_X then
|
||||
ev.value = width - ev.value
|
||||
end
|
||||
end
|
||||
end
|
||||
function Input:adjustTouchMirrorY(ev, height)
|
||||
if ev.type == EV_ABS then
|
||||
if ev.code == ABS_Y or ev.code == ABS_MT_POSITION_Y then
|
||||
ev.value = height - ev.value
|
||||
end
|
||||
end
|
||||
end
|
||||
function Input:adjustTouchTranslate(ev, by)
|
||||
if ev.type == EV_ABS then
|
||||
if ev.code == ABS_X or ev.code == ABS_MT_POSITION_X then
|
||||
ev.value = by.x + ev.value
|
||||
end
|
||||
if ev.code == ABS_Y or ev.code == ABS_MT_POSITION_Y then
|
||||
ev.value = by.y + ev.value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Input:setTimeout(cb, tv_out)
|
||||
local item = {
|
||||
callback = cb,
|
||||
deadline = tv_out,
|
||||
}
|
||||
table.insert(self.timer_callbacks, item)
|
||||
table.sort(self.timer_callbacks, function(v1,v2)
|
||||
return v1.deadline < v2.deadline
|
||||
end)
|
||||
end
|
||||
|
||||
function Input:handleKeyBoardEv(ev)
|
||||
local keycode = self.event_map[ev.code]
|
||||
if not keycode then
|
||||
-- do not handle keypress for keys we don't know
|
||||
return
|
||||
end
|
||||
|
||||
-- take device rotation into account
|
||||
if self.rotation_map[self.device.screen:getRotationMode()][keycode] then
|
||||
keycode = self.rotation_map[self.device.screen:getRotationMode()][keycode]
|
||||
end
|
||||
|
||||
if keycode == "IntoSS" or keycode == "OutOfSS"
|
||||
or keycode == "Charging" or keycode == "NotCharging" then
|
||||
return keycode
|
||||
end
|
||||
|
||||
-- Kobo sleep
|
||||
if keycode == "Power_SleepCover" then
|
||||
if ev.value == EVENT_VALUE_KEY_PRESS then
|
||||
return "Suspend"
|
||||
else
|
||||
return "Resume"
|
||||
end
|
||||
end
|
||||
|
||||
if ev.value == EVENT_VALUE_KEY_RELEASE
|
||||
and (keycode == "Light" or keycode == "Power") then
|
||||
return keycode
|
||||
end
|
||||
|
||||
-- handle modifier keys
|
||||
if self.modifiers[keycode] ~= nil then
|
||||
if ev.value == EVENT_VALUE_KEY_PRESS then
|
||||
self.modifiers[keycode] = true
|
||||
elseif ev.value == EVENT_VALUE_KEY_RELEASE then
|
||||
self.modifiers[keycode] = false
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local key = Key:new(keycode, self.modifiers)
|
||||
|
||||
if ev.value == EVENT_VALUE_KEY_PRESS then
|
||||
return Event:new("KeyPress", key)
|
||||
elseif ev.value == EVENT_VALUE_KEY_RELEASE then
|
||||
return Event:new("KeyRelease", key)
|
||||
end
|
||||
end
|
||||
|
||||
function Input:handleMiscEv(ev)
|
||||
-- should be handled by a misc event protocol plugin
|
||||
end
|
||||
|
||||
--[[
|
||||
parse each touch ev from kernel and build up tev.
|
||||
tev will be sent to GestureDetector:feedEvent
|
||||
|
||||
Events for a single tap motion from Linux kernel (MT protocol B):
|
||||
|
||||
MT_TRACK_ID: 0
|
||||
MT_X: 222
|
||||
MT_Y: 207
|
||||
SYN REPORT
|
||||
MT_TRACK_ID: -1
|
||||
SYN REPORT
|
||||
|
||||
Notice that each line is a single event.
|
||||
|
||||
From kernel document:
|
||||
For type B devices, the kernel driver should associate a slot with each
|
||||
identified contact, and use that slot to propagate changes for the contact.
|
||||
Creation, replacement and destruction of contacts is achieved by modifying
|
||||
the ABS_MT_TRACKING_ID of the associated slot. A non-negative tracking id
|
||||
is interpreted as a contact, and the value -1 denotes an unused slot. A
|
||||
tracking id not previously present is considered new, and a tracking id no
|
||||
longer present is considered removed. Since only changes are propagated,
|
||||
the full state of each initiated contact has to reside in the receiving
|
||||
end. Upon receiving an MT event, one simply updates the appropriate
|
||||
attribute of the current slot.
|
||||
--]]
|
||||
function Input:handleTouchEv(ev)
|
||||
if ev.type == EV_ABS then
|
||||
if #self.MTSlots == 0 then
|
||||
table.insert(self.MTSlots, self:getMtSlot(self.cur_slot))
|
||||
end
|
||||
if ev.code == ABS_MT_SLOT then
|
||||
if self.cur_slot ~= ev.value then
|
||||
table.insert(self.MTSlots, self:getMtSlot(ev.value))
|
||||
end
|
||||
self.cur_slot = ev.value
|
||||
elseif ev.code == ABS_MT_TRACKING_ID then
|
||||
self:setCurrentMtSlot("id", ev.value)
|
||||
elseif ev.code == ABS_MT_POSITION_X then
|
||||
self:setCurrentMtSlot("x", ev.value)
|
||||
elseif ev.code == ABS_MT_POSITION_Y then
|
||||
self:setCurrentMtSlot("y", ev.value)
|
||||
|
||||
-- code to emulate mt protocol on kobos
|
||||
-- we "confirm" abs_x, abs_y only when pressure ~= 0
|
||||
elseif ev.code == ABS_X then
|
||||
self:setCurrentMtSlot("abs_x", ev.value)
|
||||
elseif ev.code == ABS_Y then
|
||||
self:setCurrentMtSlot("abs_y", ev.value)
|
||||
elseif ev.code == ABS_PRESSURE then
|
||||
if ev.value ~= 0 then
|
||||
self:setCurrentMtSlot("id", 1)
|
||||
self:confirmAbsxy()
|
||||
else
|
||||
self:cleanAbsxy()
|
||||
self:setCurrentMtSlot("id", -1)
|
||||
end
|
||||
end
|
||||
elseif ev.type == EV_SYN then
|
||||
if ev.code == SYN_REPORT then
|
||||
for _, MTSlot in pairs(self.MTSlots) do
|
||||
self:setMtSlot(MTSlot.slot, "timev", TimeVal:new(ev.time))
|
||||
end
|
||||
-- feed ev in all slots to state machine
|
||||
local touch_ges = self.gesture_detector:feedEvent(self.MTSlots)
|
||||
self.MTSlots = {}
|
||||
if touch_ges then
|
||||
return Event:new("Gesture",
|
||||
self.gesture_detector:adjustGesCoordinate(touch_ges)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
function Input:handleTouchEvPhoenix(ev)
|
||||
-- Hack on handleTouchEV for the Kobo Aura
|
||||
-- It seems to be using a custom protocol:
|
||||
-- finger 0 down:
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_TRACKING_ID, 0);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_TOUCH_MAJOR, 1);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_WIDTH_MAJOR, 1);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_POSITION_X, x1);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_POSITION_Y, y1);
|
||||
-- input_mt_sync (elan_touch_data.input);
|
||||
-- finger 1 down:
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_TRACKING_ID, 1);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_TOUCH_MAJOR, 1);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_WIDTH_MAJOR, 1);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_POSITION_X, x2);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_POSITION_Y, y2);
|
||||
-- input_mt_sync (elan_touch_data.input);
|
||||
-- finger 0 up:
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_TRACKING_ID, 0);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_TOUCH_MAJOR, 0);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_WIDTH_MAJOR, 0);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_POSITION_X, last_x);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_POSITION_Y, last_y);
|
||||
-- input_mt_sync (elan_touch_data.input);
|
||||
-- finger 1 up:
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_TRACKING_ID, 1);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_TOUCH_MAJOR, 0);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_WIDTH_MAJOR, 0);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_POSITION_X, last_x2);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_POSITION_Y, last_y2);
|
||||
-- input_mt_sync (elan_touch_data.input);
|
||||
if ev.type == EV_ABS then
|
||||
if #self.MTSlots == 0 then
|
||||
table.insert(self.MTSlots, self:getMtSlot(self.cur_slot))
|
||||
end
|
||||
if ev.code == ABS_MT_TRACKING_ID then
|
||||
if self.cur_slot ~= ev.value then
|
||||
table.insert(self.MTSlots, self:getMtSlot(ev.value))
|
||||
end
|
||||
self.cur_slot = ev.value
|
||||
self:setCurrentMtSlot("id", ev.value)
|
||||
elseif ev.code == ABS_MT_TOUCH_MAJOR and ev.value == 0 then
|
||||
self:setCurrentMtSlot("id", -1)
|
||||
elseif ev.code == ABS_MT_POSITION_X then
|
||||
self:setCurrentMtSlot("x", ev.value)
|
||||
elseif ev.code == ABS_MT_POSITION_Y then
|
||||
self:setCurrentMtSlot("y", ev.value)
|
||||
end
|
||||
elseif ev.type == EV_SYN then
|
||||
if ev.code == SYN_REPORT then
|
||||
for _, MTSlot in pairs(self.MTSlots) do
|
||||
self:setMtSlot(MTSlot.slot, "timev", TimeVal:new(ev.time))
|
||||
end
|
||||
-- feed ev in all slots to state machine
|
||||
local touch_ges = self.gesture_detector:feedEvent(self.MTSlots)
|
||||
self.MTSlots = {}
|
||||
if touch_ges then
|
||||
return Event:new("Gesture",
|
||||
self.gesture_detector:adjustGesCoordinate(touch_ges)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- helpers for touch event data management:
|
||||
|
||||
function Input:setMtSlot(slot, key, val)
|
||||
if not self.ev_slots[slot] then
|
||||
self.ev_slots[slot] = {
|
||||
slot = slot
|
||||
}
|
||||
end
|
||||
|
||||
self.ev_slots[slot][key] = val
|
||||
end
|
||||
|
||||
function Input:setCurrentMtSlot(key, val)
|
||||
self:setMtSlot(self.cur_slot, key, val)
|
||||
end
|
||||
|
||||
function Input:getMtSlot(slot)
|
||||
return self.ev_slots[slot]
|
||||
end
|
||||
|
||||
function Input:getCurrentMtSlot()
|
||||
return self:getMtSlot(self.cur_slot)
|
||||
end
|
||||
|
||||
function Input:confirmAbsxy()
|
||||
self:setCurrentMtSlot("x", self.ev_slots[self.cur_slot]["abs_x"])
|
||||
self:setCurrentMtSlot("y", self.ev_slots[self.cur_slot]["abs_y"])
|
||||
end
|
||||
|
||||
function Input:cleanAbsxy()
|
||||
self:setCurrentMtSlot("abs_x", nil)
|
||||
self:setCurrentMtSlot("abs_y", nil)
|
||||
end
|
||||
|
||||
|
||||
-- main event handling:
|
||||
|
||||
function Input:waitEvent(timeout_us, timeout_s)
|
||||
-- wrapper for input.waitForEvents that will retry for some cases
|
||||
local ok, ev
|
||||
local wait_deadline = TimeVal:now() + TimeVal:new{
|
||||
sec = timeout_s,
|
||||
usec = timeout_us
|
||||
}
|
||||
while true do
|
||||
if #self.timer_callbacks > 0 then
|
||||
-- we don't block if there is any timer, set wait to 10us
|
||||
while #self.timer_callbacks > 0 do
|
||||
ok, ev = pcall(input.waitForEvent, 100)
|
||||
if ok then break end
|
||||
local tv_now = TimeVal:now()
|
||||
if ((not timeout_us and not timeout_s) or tv_now < wait_deadline) then
|
||||
-- check whether timer is up
|
||||
if tv_now >= self.timer_callbacks[1].deadline then
|
||||
local touch_ges = self.timer_callbacks[1].callback()
|
||||
table.remove(self.timer_callbacks, 1)
|
||||
if touch_ges then
|
||||
-- Do we really need to clear all setTimeout after
|
||||
-- decided a gesture? FIXME
|
||||
self.timer_callbacks = {}
|
||||
return Event:new("Gesture",
|
||||
self.gesture_detector:adjustGesCoordinate(touch_ges)
|
||||
)
|
||||
end -- EOF if touch_ges
|
||||
end -- EOF if deadline reached
|
||||
else
|
||||
break
|
||||
end -- EOF if not exceed wait timeout
|
||||
end -- while #timer_callbacks > 0
|
||||
else
|
||||
ok, ev = pcall(input.waitForEvent, timeout_us)
|
||||
end -- EOF if #timer_callbacks > 0
|
||||
if ok then
|
||||
break
|
||||
end
|
||||
|
||||
-- ev does contain an error message:
|
||||
if ev == "Waiting for input failed: timeout\n" then
|
||||
-- don't report an error on timeout
|
||||
ev = nil
|
||||
break
|
||||
elseif ev == "application forced to quit" then
|
||||
-- TODO: return an event that can be handled
|
||||
os.exit(0)
|
||||
end
|
||||
--DEBUG("got error waiting for events:", ev)
|
||||
if ev ~= "Waiting for input failed: 4\n" then
|
||||
-- we only abort if the error is not EINTR
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if ok and ev then
|
||||
if DEBUG.is_on and ev then
|
||||
DEBUG:logEv(ev)
|
||||
end
|
||||
self:eventAdjustHook(ev)
|
||||
if ev.type == EV_KEY then
|
||||
DEBUG("key ev", ev)
|
||||
return self:handleKeyBoardEv(ev)
|
||||
elseif ev.type == EV_ABS or ev.type == EV_SYN then
|
||||
return self:handleTouchEv(ev)
|
||||
elseif ev.type == EV_MSC then
|
||||
return self:handleMiscEv(ev)
|
||||
else
|
||||
-- some other kind of event that we do not know yet
|
||||
return Event:new("GenericInput", ev)
|
||||
end
|
||||
elseif not ok and ev then
|
||||
return Event:new("InputError", ev)
|
||||
end
|
||||
end
|
||||
|
||||
return Input
|
@ -0,0 +1,91 @@
|
||||
--[[
|
||||
an interface for key presses
|
||||
]]
|
||||
|
||||
local Key = {}
|
||||
|
||||
function Key:new(key, modifiers)
|
||||
local o = { key = key, modifiers = modifiers }
|
||||
|
||||
-- we're a hash map, too
|
||||
o[key] = true
|
||||
for mod, pressed in pairs(modifiers) do
|
||||
if pressed then
|
||||
o[mod] = true
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end
|
||||
|
||||
function Key:__tostring()
|
||||
return table.concat(self:getSequence(), "-")
|
||||
end
|
||||
|
||||
--[[
|
||||
get a sequence that can be matched against later
|
||||
|
||||
use this to let the user press a sequence and then
|
||||
store this as configuration data (configurable
|
||||
shortcuts)
|
||||
]]
|
||||
function Key:getSequence()
|
||||
local seq = {}
|
||||
for mod, pressed in pairs(self.modifiers) do
|
||||
if pressed then
|
||||
table.insert(seq, mod)
|
||||
end
|
||||
end
|
||||
table.insert(seq, self.key)
|
||||
end
|
||||
|
||||
--[[
|
||||
this will match a key against a sequence
|
||||
|
||||
the sequence should be a table of key names that
|
||||
must be pressed together to match.
|
||||
if an entry in this table is itself a table, at
|
||||
least one key in this table must match.
|
||||
|
||||
E.g.:
|
||||
|
||||
Key:match({ "Alt", "K" }) -- match Alt-K
|
||||
Key:match({ "Alt", { "K", "L" }}) -- match Alt-K _or_ Alt-L
|
||||
]]
|
||||
function Key:match(sequence)
|
||||
local mod_keys = {} -- a hash table for checked modifiers
|
||||
for _, key in ipairs(sequence) do
|
||||
if type(key) == "table" then
|
||||
local found = false
|
||||
for _, variant in ipairs(key) do
|
||||
if self[variant] then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
-- one of the needed keys is not pressed
|
||||
return false
|
||||
end
|
||||
elseif not self[key] then
|
||||
-- needed key not pressed
|
||||
return false
|
||||
elseif self.modifiers[key] ~= nil then
|
||||
-- checked key is a modifier key
|
||||
mod_keys[key] = true
|
||||
end
|
||||
end
|
||||
|
||||
for mod, pressed in pairs(self.modifiers) do
|
||||
if pressed and not mod_keys[mod] then
|
||||
-- additional modifier keys are pressed, don't match
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return Key
|
@ -0,0 +1,245 @@
|
||||
local Generic = require("device/generic/device")
|
||||
local DEBUG = require("dbg")
|
||||
|
||||
local function yes() return true end
|
||||
|
||||
local Kindle = Generic:new{
|
||||
model = "Kindle",
|
||||
isKindle = yes,
|
||||
}
|
||||
|
||||
local Kindle2 = Kindle:new{
|
||||
model = "Kindle2",
|
||||
hasKeyboard = yes,
|
||||
hasKeys = yes,
|
||||
}
|
||||
|
||||
local KindleDXG = Kindle:new{
|
||||
model = "KindleDXG",
|
||||
hasKeyboard = yes,
|
||||
hasKeys = yes,
|
||||
}
|
||||
|
||||
local Kindle3 = Kindle2:new{
|
||||
model = "Kindle3",
|
||||
hasKeyboard = yes,
|
||||
hasKeys = yes,
|
||||
}
|
||||
|
||||
local Kindle4 = Kindle:new{
|
||||
model = "Kindle4",
|
||||
hasKeys = yes,
|
||||
}
|
||||
|
||||
local KindleTouch = Kindle:new{
|
||||
model = "KindleTouch",
|
||||
isTouchDevice = yes,
|
||||
touch_dev = "/dev/input/event3",
|
||||
}
|
||||
|
||||
local KindlePaperWhite = Kindle:new{
|
||||
model = "KindlePaperWhite",
|
||||
isTouchDevice = yes,
|
||||
hasFrontlight = yes,
|
||||
display_dpi = 212,
|
||||
touch_dev = "/dev/input/event0",
|
||||
}
|
||||
|
||||
local KindlePaperWhite2 = Kindle:new{
|
||||
model = "KindlePaperWhite2",
|
||||
isTouchDevice = yes,
|
||||
hasFrontlight = yes,
|
||||
display_dpi = 212,
|
||||
touch_dev = "/dev/input/event1",
|
||||
}
|
||||
|
||||
function Kindle2:init()
|
||||
self.screen = require("device/screen"):new{device = self}
|
||||
self.input = require("device/input"):new{
|
||||
device = self,
|
||||
event_map = require("device/kindle/event_map_keyboard"),
|
||||
}
|
||||
self.input.open("/dev/input/event1")
|
||||
Kindle.init(self)
|
||||
end
|
||||
|
||||
function KindleDXG:init()
|
||||
self.screen = require("device/screen"):new{device = self}
|
||||
self.input = require("device/input"):new{
|
||||
device = self,
|
||||
event_map = require("device/kindle/event_map_keyboard"),
|
||||
}
|
||||
self.input.open("/dev/input/event0")
|
||||
self.input.open("/dev/input/event1")
|
||||
Kindle.init(self)
|
||||
end
|
||||
|
||||
function Kindle3:init()
|
||||
self.screen = require("device/screen"):new{device = self}
|
||||
self.input = require("device/input"):new{
|
||||
device = self,
|
||||
event_map = require("device/kindle/event_map_keyboard"),
|
||||
}
|
||||
self.input.open("/dev/input/event1")
|
||||
self.input.open("/dev/input/event2")
|
||||
Kindle.init(self)
|
||||
end
|
||||
|
||||
function Kindle4:init()
|
||||
self.screen = require("device/screen"):new{device = self}
|
||||
self.input = require("device/input"):new{
|
||||
device = self,
|
||||
event_map = require("device/kindle/event_map_kindle4"),
|
||||
}
|
||||
self.input.event_map = require("device/kindle/event_map_kindle4")
|
||||
self.input.open("/dev/input/event1")
|
||||
Kindle.init(self)
|
||||
end
|
||||
|
||||
local ABS_MT_POSITION_X = 53
|
||||
local ABS_MT_POSITION_Y = 54
|
||||
function KindleTouch:init()
|
||||
self.screen = require("device/screen"):new{device = self}
|
||||
self.powerd = require("device/kindle/powerd"):new{
|
||||
device = self,
|
||||
batt_capacity_file = "/sys/devices/system/yoshi_battery/yoshi_battery0/battery_capacity",
|
||||
is_charging_file = "/sys/devices/platform/fsl-usb2-udc/charging",
|
||||
}
|
||||
self.input = require("device/input"):new{
|
||||
device = self,
|
||||
-- Kindle Touch has a single button
|
||||
event_map = { [102] = "Home" },
|
||||
}
|
||||
|
||||
-- Kindle Touch needs event modification for proper coordinates
|
||||
self.input:registerEventAdjustHook(self.input.adjustTouchScale, {x=600/4095, y=800/4095})
|
||||
|
||||
-- event0 in KindleTouch is "WM8962 Beep Generator" (useless)
|
||||
-- event1 in KindleTouch is "imx-yoshi Headset" (useless)
|
||||
self.input.open("/dev/input/event2") -- Home button
|
||||
self.input.open("/dev/input/event3") -- touchscreen
|
||||
Kindle.init(self)
|
||||
end
|
||||
|
||||
function KindlePaperWhite:init()
|
||||
self.screen = require("device/screen"):new{device = self}
|
||||
self.powerd = require("device/kindle/powerd"):new{
|
||||
device = self,
|
||||
fl_intensity_file = "/sys/devices/system/fl_tps6116x/fl_tps6116x0/fl_intensity",
|
||||
batt_capacity_file = "/sys/devices/system/yoshi_battery/yoshi_battery0/battery_capacity",
|
||||
is_charging_file = "/sys/devices/platform/aplite_charger.0/charging",
|
||||
}
|
||||
|
||||
Kindle.init(self)
|
||||
|
||||
self.input.open("/dev/input/event0")
|
||||
end
|
||||
|
||||
function KindlePaperWhite2:init()
|
||||
self.screen = require("device/screen"):new{device = self}
|
||||
self.powerd = require("device/kindle/powerd"):new{
|
||||
device = self,
|
||||
fl_intensity_file = "/sys/class/backlight/max77696-bl/brightness",
|
||||
batt_capacity_file = "/sys/devices/system/yoshi_battery/yoshi_battery0/battery_capacity",
|
||||
is_charging_file = "/sys/devices/platform/aplite_charger.0/charging",
|
||||
}
|
||||
|
||||
Kindle.init(self)
|
||||
|
||||
self.input.open("/dev/input/event1")
|
||||
end
|
||||
|
||||
--[[
|
||||
Test if a kindle device has Special Offers
|
||||
--]]
|
||||
local function isSpecialOffers()
|
||||
-- Look at the current blanket modules to see if the SO screensavers are enabled...
|
||||
local lipc = require("liblipclua")
|
||||
if not lipc then
|
||||
DEBUG("could not load liblibclua")
|
||||
return false
|
||||
end
|
||||
local lipc_handle = lipc.init("com.github.koreader.device")
|
||||
if not lipc_handle then
|
||||
DEBUG("could not get lipc handle")
|
||||
return false
|
||||
end
|
||||
local so = false
|
||||
local loaded_blanket_modules = lipc_handle:get_string_property("com.lab126.blanket", "load")
|
||||
if string.find(loaded_blanket_modules, "ad_screensaver") then
|
||||
so = true
|
||||
end
|
||||
lipc_handle:close()
|
||||
return so
|
||||
end
|
||||
|
||||
function KindleTouch:exit()
|
||||
if isSpecialOffers() then
|
||||
-- fake a touch event
|
||||
if self.touch_dev then
|
||||
local width, height = Screen:getScreenWidth(), Screen:getScreenHeight()
|
||||
require("ffi/input").fakeTapInput(self.touch_dev,
|
||||
math.min(width, height)/2,
|
||||
math.max(width, height)-30
|
||||
)
|
||||
end
|
||||
end
|
||||
Generic.exit(self)
|
||||
end
|
||||
KindlePaperWhite.exit = KindleTouch.exit
|
||||
KindlePaperWhite2.exit = KindleTouch.exit
|
||||
|
||||
function Kindle3:exit()
|
||||
-- send double menu key press events to trigger screen refresh
|
||||
os.execute("echo 'send 139' > /proc/keypad;echo 'send 139' > /proc/keypad")
|
||||
|
||||
Generic.exit(self)
|
||||
end
|
||||
|
||||
KindleDXG.exit = Kindle3.exit
|
||||
|
||||
|
||||
----------------- device recognition: -------------------
|
||||
|
||||
local function Set(list)
|
||||
local set = {}
|
||||
for _, l in ipairs(list) do set[l] = true end
|
||||
return set
|
||||
end
|
||||
|
||||
|
||||
local kindle_sn = io.open("/proc/usid", "r")
|
||||
if not kindle_sn then return end
|
||||
local kindle_devcode = string.sub(kindle_sn:read(),3,4)
|
||||
kindle_sn:close()
|
||||
|
||||
-- NOTE: Update me when new devices come out :)
|
||||
local k2_set = Set { "02", "03" }
|
||||
local dx_set = Set { "04", "05" }
|
||||
local dxg_set = Set { "09" }
|
||||
local k3_set = Set { "08", "06", "0A" }
|
||||
local k4_set = Set { "0E", "23" }
|
||||
local touch_set = Set { "0F", "11", "10", "12" }
|
||||
local pw_set = Set { "24", "1B", "1D", "1F", "1C", "20" }
|
||||
local pw2_set = Set { "D4", "5A", "D5", "D6", "D7", "D8", "F2", "17",
|
||||
"60", "F4", "F9", "62", "61", "5F" }
|
||||
|
||||
if k2_set[kindle_devcode] then
|
||||
return Kindle2
|
||||
elseif dx_set[kindle_devcode] then
|
||||
return Kindle2
|
||||
elseif dxg_set[kindle_devcode] then
|
||||
return KindleDXG
|
||||
elseif k3_set[kindle_devcode] then
|
||||
return Kindle3
|
||||
elseif k4_set[kindle_devcode] then
|
||||
return Kindle4
|
||||
elseif touch_set[kindle_devcode] then
|
||||
return KindleTouch
|
||||
elseif pw_set[kindle_devcode] then
|
||||
return KindlePaperWhite
|
||||
elseif pw2_set[kindle_devcode] then
|
||||
return KindlePaperWhite2
|
||||
end
|
||||
|
||||
error("unknown Kindle model "..kindle_devcode)
|
@ -0,0 +1,39 @@
|
||||
--[[
|
||||
event map for Kindle devices with an alphabetic and/or alphanumeric keyboard
|
||||
--]]
|
||||
|
||||
return {
|
||||
[2] = "1", [3] = "2", [4] = "3", [5] = "4", [6] = "5", [7] = "6", [8] = "7", [9] = "8", [10] = "9", [11] = "0",
|
||||
[16] = "Q", [17] = "W", [18] = "E", [19] = "R", [20] = "T", [21] = "Y", [22] = "U", [23] = "I", [24] = "O", [25] = "P",
|
||||
[30] = "A", [31] = "S", [32] = "D", [33] = "F", [34] = "G", [35] = "H", [36] = "J", [37] = "K", [38] = "L", [14] = "Del",
|
||||
[44] = "Z", [45] = "X", [46] = "C", [47] = "V", [48] = "B", [49] = "N", [50] = "M", [52] = ".", [53] = "/", -- only KDX
|
||||
|
||||
[28] = "Enter",
|
||||
[42] = "Shift",
|
||||
[56] = "Alt",
|
||||
[57] = " ",
|
||||
[90] = "AA", -- KDX
|
||||
[91] = "Back", -- KDX
|
||||
[92] = "Press", -- KDX
|
||||
[94] = "Sym", -- KDX
|
||||
[98] = "Home", -- KDX
|
||||
[102] = "Home", -- K[3] & k[4]
|
||||
[104] = "LPgBack", -- K[3] only
|
||||
[103] = "Up", -- K[3] & k[4]
|
||||
[105] = "Left",
|
||||
[106] = "Right",
|
||||
[108] = "Down", -- K[3] & k[4]
|
||||
[109] = "RPgBack",
|
||||
[114] = "VMinus",
|
||||
[115] = "VPlus",
|
||||
[122] = "Up", -- KDX
|
||||
[123] = "Down", -- KDX
|
||||
[124] = "RPgFwd", -- KDX
|
||||
[126] = "Sym", -- K[3]
|
||||
[139] = "Menu",
|
||||
[158] = "Back", -- K[3] & K[4]
|
||||
[190] = "AA", -- K[3]
|
||||
[191] = "RPgFwd", -- K[3] & k[4]
|
||||
[193] = "LPgFwd", -- K[3] only
|
||||
[194] = "Press", -- K[3] & k[4]
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
--[[
|
||||
event map for Kindle devices with control buttons & DPad
|
||||
--]]
|
||||
|
||||
return {
|
||||
[29] = "ScreenKB",
|
||||
[102] = "Home",
|
||||
[103] = "Up",
|
||||
[104] = "LPgFwd",
|
||||
[108] = "Down",
|
||||
[158] = "Back",
|
||||
[191] = "RPgFwd",
|
||||
[193] = "LPgBack",
|
||||
[194] = "Press",
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
local Generic = require("device/generic/device")
|
||||
local lfs = require("libs/libkoreader-lfs")
|
||||
local Geom = require("ui/geometry")
|
||||
|
||||
local function yes() return true 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,
|
||||
}
|
||||
|
||||
-- TODO: hasKeys for some devices?
|
||||
|
||||
local KoboTrilogy = Kobo:new{
|
||||
model = "Kobo_trilogy",
|
||||
touch_switch_xy = false,
|
||||
}
|
||||
|
||||
local KoboPixie = Kobo:new{
|
||||
model = "Kobo_pixie",
|
||||
display_dpi = 200,
|
||||
}
|
||||
|
||||
local KoboDahlia = Kobo:new{
|
||||
model = "Kobo_dahlia",
|
||||
hasFrontlight = yes,
|
||||
touch_phoenix_protocol = true,
|
||||
display_dpi = 265,
|
||||
-- bezel:
|
||||
viewport = Geom:new{x=0, y=10, w=1080, h=1430},
|
||||
}
|
||||
|
||||
local KoboDragon = Kobo:new{
|
||||
model = "Kobo_dragon",
|
||||
hasFrontlight = yes,
|
||||
display_dpi = 265,
|
||||
}
|
||||
|
||||
local KoboKraken = Kobo:new{
|
||||
model = "Kobo_kraken",
|
||||
hasFrontlight = yes,
|
||||
display_dpi = 212,
|
||||
}
|
||||
|
||||
local KoboPhoenix = Kobo:new{
|
||||
model = "Kobo_phoenix",
|
||||
hasFrontlight = yes,
|
||||
touch_phoenix_protocol = true,
|
||||
display_dpi = 212.8,
|
||||
-- bezel:
|
||||
viewport = Geom:new{x=6, y=12, w=752, h=1012},
|
||||
}
|
||||
|
||||
function Kobo:init()
|
||||
self.screen = require("device/screen"):new{device = self}
|
||||
self.powerd = require("device/kobo/powerd"):new{device = self}
|
||||
self.input = require("device/input"):new{
|
||||
device = self,
|
||||
event_map = {
|
||||
[59] = "Power_SleepCover",
|
||||
[90] = "Light",
|
||||
[116] = "Power",
|
||||
}
|
||||
}
|
||||
|
||||
Generic.init(self)
|
||||
|
||||
self.input.open("/dev/input/event0") -- Light button and sleep slider
|
||||
self.input.open("/dev/input/event1")
|
||||
|
||||
-- it's called KOBO_TOUCH_MIRRORED in defaults.lua, but what it
|
||||
-- actually did in its original implementation was to switch X/Y.
|
||||
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,
|
||||
self.screen:getScreenWidth()
|
||||
)
|
||||
end
|
||||
|
||||
if self.touch_phoenix_protocol then
|
||||
self.input.handleTouchEv = self.input.handleTouchEvPhoenix
|
||||
end
|
||||
end
|
||||
|
||||
function Kobo:getCodeName()
|
||||
local std_out = io.popen("/bin/kobo_config.sh 2>/dev/null", "r")
|
||||
local codename = std_out:read()
|
||||
std_out:close()
|
||||
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()
|
||||
if KOBO_LIGHT_OFF_ON_SUSPEND then
|
||||
self.powerd:setIntensity(0)
|
||||
end
|
||||
os.execute("./suspend.sh")
|
||||
end
|
||||
|
||||
function Kobo:Resume()
|
||||
os.execute("echo 0 > /sys/power/state-extended")
|
||||
if self.powerd then
|
||||
if KOBO_LIGHT_ON_START and tonumber(KOBO_LIGHT_ON_START) > -1 then
|
||||
self.powerd:setIntensity(math.max(math.min(KOBO_LIGHT_ON_START,100),0))
|
||||
elseif powerd.fl ~= nil then
|
||||
self.powerd.fl:restore()
|
||||
end
|
||||
end
|
||||
|
||||
Generic.Resume(self)
|
||||
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
|
||||
else
|
||||
error("unrecognized Kobo model "..codename)
|
||||
end
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
local BasePowerD = require("ui/device/basepowerd")
|
||||
local BasePowerD = require("device/generic/powerd")
|
||||
|
||||
local KoboPowerD = BasePowerD:new{
|
||||
fl_min = 0, fl_max = 100,
|
@ -0,0 +1,183 @@
|
||||
local Blitbuffer = require("ffi/blitbuffer")
|
||||
local einkfb = require("ffi/framebuffer")
|
||||
local Geom = require("ui/geometry")
|
||||
local util = require("ffi/util")
|
||||
local DEBUG = require("dbg")
|
||||
|
||||
--[[
|
||||
Codes for rotation modes:
|
||||
|
||||
1 for no rotation,
|
||||
2 for landscape with bottom on the right side of screen, etc.
|
||||
|
||||
2
|
||||
+--------------+
|
||||
| +----------+ |
|
||||
| | | |
|
||||
| | Freedom! | |
|
||||
| | | |
|
||||
| | | |
|
||||
3 | | | | 1
|
||||
| | | |
|
||||
| | | |
|
||||
| +----------+ |
|
||||
| |
|
||||
| |
|
||||
+--------------+
|
||||
0
|
||||
--]]
|
||||
|
||||
|
||||
local Screen = {
|
||||
cur_rotation_mode = 0,
|
||||
native_rotation_mode = nil,
|
||||
blitbuffer_rotation_mode = 0,
|
||||
|
||||
bb = nil,
|
||||
saved_bb = nil,
|
||||
|
||||
screen_size = Geom:new(),
|
||||
viewport = nil,
|
||||
|
||||
fb = einkfb.open("/dev/fb0"),
|
||||
-- will be set upon loading by Device class:
|
||||
device = nil,
|
||||
}
|
||||
|
||||
function Screen:new(o)
|
||||
local o = o or {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
if o.init then o:init() end
|
||||
return o
|
||||
end
|
||||
|
||||
function Screen:init()
|
||||
self.bb = self.fb.bb
|
||||
self.blitbuffer_rotation_mode = self.bb:getRotation()
|
||||
-- asking the framebuffer for orientation is error prone,
|
||||
-- so we do this simple heuristic (for now)
|
||||
self.screen_size.w = self.bb:getWidth()
|
||||
self.screen_size.h = self.bb:getHeight()
|
||||
if self.screen_size.w > self.screen_size.h then
|
||||
self.native_rotation_mode = 1
|
||||
self.screen_size.w, self.screen_size.h = self.screen_size.h, self.screen_size.w
|
||||
else
|
||||
self.native_rotation_mode = 0
|
||||
end
|
||||
self.cur_rotation_mode = self.native_rotation_mode
|
||||
end
|
||||
|
||||
--[[
|
||||
set a rectangle that represents the area of the screen we are working on
|
||||
--]]
|
||||
function Screen:setViewport(viewport)
|
||||
self.viewport = self.screen_size:intersect(viewport)
|
||||
self.bb = self.fb.bb:viewport(
|
||||
self.viewport.x, self.viewport.y,
|
||||
self.viewport.w, self.viewport.h)
|
||||
end
|
||||
|
||||
function Screen:refresh(refresh_type, waveform_mode, x, y, w, h)
|
||||
if self.viewport and x and y then
|
||||
-- adapt to viewport
|
||||
x = x + self.viewport.x
|
||||
y = y + self.viewport.y
|
||||
end
|
||||
self.fb:refresh(refresh_type, waveform_mode, x, y, w, h)
|
||||
end
|
||||
|
||||
function Screen:getSize()
|
||||
return Geom:new{w = self.bb:getWidth(), h = self.bb:getHeight()}
|
||||
end
|
||||
|
||||
function Screen:getWidth()
|
||||
return self.bb:getWidth()
|
||||
end
|
||||
|
||||
function Screen:getScreenWidth()
|
||||
return self.screen_size.w
|
||||
end
|
||||
|
||||
function Screen:getScreenHeight()
|
||||
return self.screen_size.h
|
||||
end
|
||||
|
||||
function Screen:getHeight()
|
||||
return self.bb:getHeight()
|
||||
end
|
||||
|
||||
function Screen:getDPI()
|
||||
if self.dpi == nil then
|
||||
self.dpi = G_reader_settings:readSetting("screen_dpi")
|
||||
end
|
||||
if self.dpi == nil then
|
||||
self.dpi = self.device.display_dpi
|
||||
end
|
||||
if self.dpi == nil then
|
||||
self.dpi = 160
|
||||
end
|
||||
return self.dpi
|
||||
end
|
||||
|
||||
function Screen:setDPI(dpi)
|
||||
G_reader_settings:saveSetting("screen_dpi", dpi)
|
||||
end
|
||||
|
||||
function Screen:scaleByDPI(px)
|
||||
-- scaled positive px should also be positive
|
||||
return math.ceil(px * self:getDPI()/167)
|
||||
end
|
||||
|
||||
function Screen:getRotationMode()
|
||||
return self.cur_rotation_mode
|
||||
end
|
||||
|
||||
function Screen:getScreenMode()
|
||||
if self:getWidth() > self:getHeight() then
|
||||
return "landscape"
|
||||
else
|
||||
return "portrait"
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:setRotationMode(mode)
|
||||
self.fb.bb:rotateAbsolute(-90 * (mode - self.native_rotation_mode - self.blitbuffer_rotation_mode))
|
||||
self.cur_rotation_mode = mode
|
||||
end
|
||||
|
||||
function Screen:setScreenMode(mode)
|
||||
if mode == "portrait" then
|
||||
if self.cur_rotation_mode ~= 0 then
|
||||
self:setRotationMode(0)
|
||||
end
|
||||
elseif mode == "landscape" then
|
||||
if self.cur_rotation_mode == 0 or self.cur_rotation_mode == 2 then
|
||||
self:setRotationMode(DLANDSCAPE_CLOCKWISE_ROTATION and 1 or 3)
|
||||
elseif self.cur_rotation_mode == 1 or self.cur_rotation_mode == 3 then
|
||||
self:setRotationMode((self.cur_rotation_mode + 2) % 4)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:saveCurrentBB()
|
||||
if self.saved_bb then self.saved_bb:free() end
|
||||
self.saved_bb = self.bb:copy()
|
||||
end
|
||||
|
||||
function Screen:restoreFromSavedBB()
|
||||
if self.saved_bb then
|
||||
self.bb:blitFullFrom(self.saved_bb)
|
||||
-- free data
|
||||
self.saved_bb:free()
|
||||
self.saved_bb = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:close()
|
||||
DEBUG("close screen framebuffer")
|
||||
self.fb:close()
|
||||
end
|
||||
|
||||
|
||||
return Screen
|
@ -1,326 +0,0 @@
|
||||
local isAndroid, android = pcall(require, "android")
|
||||
local lfs = require("libs/libkoreader-lfs")
|
||||
local Screen = require("ui/device/screen")
|
||||
local util = require("ffi/util")
|
||||
local DEBUG = require("dbg")
|
||||
local ffi = require("ffi")
|
||||
|
||||
local Device = {
|
||||
screen_saver_mode = false,
|
||||
charging_mode = false,
|
||||
survive_screen_saver = false,
|
||||
is_special_offers = nil,
|
||||
touch_dev = nil,
|
||||
model = nil,
|
||||
firmware_rev = nil,
|
||||
powerd = nil,
|
||||
has_no_keyboard = nil,
|
||||
is_touch_device = nil,
|
||||
has_front_light = nil,
|
||||
screen = Screen
|
||||
}
|
||||
|
||||
Screen.device = Device
|
||||
|
||||
function Set(list)
|
||||
local set = {}
|
||||
for _, l in ipairs(list) do set[l] = true end
|
||||
return set
|
||||
end
|
||||
|
||||
function Device:getModel()
|
||||
if self.model then return self.model end
|
||||
if util.isEmulated() then
|
||||
self.model = "Emulator"
|
||||
return self.model
|
||||
end
|
||||
self.model = ""
|
||||
local kindle_sn = io.open("/proc/usid", "r")
|
||||
if kindle_sn then
|
||||
local kindle_devcode = string.sub(kindle_sn:read(),3,4)
|
||||
kindle_sn:close()
|
||||
-- NOTE: Update me when new devices come out :)
|
||||
local k2_set = Set { "02", "03" }
|
||||
local dx_set = Set { "04", "05" }
|
||||
local dxg_set = Set { "09" }
|
||||
local k3_set = Set { "08", "06", "0A" }
|
||||
local k4_set = Set { "0E", "23" }
|
||||
local touch_set = Set { "0F", "11", "10", "12" }
|
||||
local pw_set = Set { "24", "1B", "1D", "1F", "1C", "20" }
|
||||
local pw2_set = Set { "D4", "5A", "D5", "D6", "D7", "D8", "F2", "17",
|
||||
"60", "F4", "F9", "62", "61", "5F" }
|
||||
|
||||
if k2_set[kindle_devcode] then
|
||||
self.model = "Kindle2"
|
||||
elseif dx_set[kindle_devcode] then
|
||||
self.model = "Kindle2"
|
||||
elseif dxg_set[kindle_devcode] then
|
||||
self.model = "KindleDXG"
|
||||
elseif k3_set[kindle_devcode] then
|
||||
self.model = "Kindle3"
|
||||
elseif k4_set[kindle_devcode] then
|
||||
self.model = "Kindle4"
|
||||
elseif touch_set[kindle_devcode] then
|
||||
self.model = "KindleTouch"
|
||||
elseif pw_set[kindle_devcode] then
|
||||
self.model = "KindlePaperWhite"
|
||||
elseif pw2_set[kindle_devcode] then
|
||||
self.model = "KindlePaperWhite2"
|
||||
end
|
||||
else
|
||||
local kg_test_fd = lfs.attributes("/bin/kobo_config.sh")
|
||||
if kg_test_fd then
|
||||
local std_out = io.popen("/bin/kobo_config.sh 2>/dev/null", "r")
|
||||
local codename = std_out:read()
|
||||
self.model = "Kobo_" .. codename
|
||||
local version_file = io.open("/mnt/onboard/.kobo/version", "r")
|
||||
self.firmware_rev = string.sub(version_file:read(),24,28)
|
||||
version_file:close()
|
||||
end
|
||||
end
|
||||
return self.model
|
||||
end
|
||||
|
||||
function Device:getFirmVer()
|
||||
if not self.model then self:getModel() end
|
||||
return self.firmware_rev
|
||||
end
|
||||
|
||||
function Device:isKindle4()
|
||||
return (self:getModel() == "Kindle4")
|
||||
end
|
||||
|
||||
function Device:isKindle3()
|
||||
return (self:getModel() == "Kindle3")
|
||||
end
|
||||
|
||||
function Device:isKindle2()
|
||||
return (self:getModel() == "Kindle2")
|
||||
end
|
||||
|
||||
function Device:isKindle()
|
||||
local is_kindle = false
|
||||
local kindle_sn = io.open("/proc/usid", "r")
|
||||
if kindle_sn then
|
||||
is_kindle = true
|
||||
kindle_sn:close()
|
||||
end
|
||||
return is_kindle
|
||||
end
|
||||
|
||||
function Device:isKobo()
|
||||
return string.find(self:getModel() or "", "Kobo_") == 1
|
||||
end
|
||||
|
||||
Device.isAndroid = util.isAndroid
|
||||
|
||||
-- device has qwerty keyboard
|
||||
function Device:hasKeyboard()
|
||||
if self.has_keyboard ~= nil then return self.has_keyboard end
|
||||
if not isAndroid then
|
||||
local model = self:getModel()
|
||||
self.has_keyboard = (model == "Kindle2") or (model == "Kindle3")
|
||||
or (model == "KindleDXG") or util.isEmulated()
|
||||
else
|
||||
self.has_keyboard = ffi.C.AConfiguration_getKeyboard(android.app.config)
|
||||
== ffi.C.ACONFIGURATION_KEYBOARD_QWERTY
|
||||
end
|
||||
return self.has_keyboard
|
||||
end
|
||||
|
||||
function Device:hasNoKeyboard()
|
||||
return not self:hasKeyboard()
|
||||
end
|
||||
|
||||
-- device has hardware keys for pagedown/pageup
|
||||
function Device:hasKeys()
|
||||
if self.has_keys ~= nil then return self.has_keys end
|
||||
local model = self:getModel()
|
||||
self.has_keys = (model ~= "KindlePaperWhite") and (model ~= "KindlePaperWhite2")
|
||||
and (model ~= "KindleTouch") and not self:isKobo()
|
||||
return self.has_keys
|
||||
end
|
||||
|
||||
function Device:isTouchDevice()
|
||||
if self.is_touch_device ~= nil then return self.is_touch_device end
|
||||
if not isAndroid then
|
||||
local model = self:getModel()
|
||||
self.is_touch_device = (model == "KindlePaperWhite") or (model == "KindlePaperWhite2")
|
||||
or (model == "KindleTouch") or self:isKobo() or util.isEmulated()
|
||||
else
|
||||
self.is_touch_device = ffi.C.AConfiguration_getTouchscreen(android.app.config)
|
||||
~= ffi.C.ACONFIGURATION_TOUCHSCREEN_NOTOUCH
|
||||
end
|
||||
return self.is_touch_device
|
||||
end
|
||||
|
||||
function Device:hasFrontlight()
|
||||
if self.has_front_light ~= nil then return self.has_front_light end
|
||||
local model = self:getModel()
|
||||
self.has_front_light = (model == "KindlePaperWhite") or (model == "KindlePaperWhite2")
|
||||
or (model == "Kobo_dahlia") or (model == "Kobo_dragon") or (model == "Kobo_kraken") or (model == "Kobo_phoenix")
|
||||
or util.isEmulated()
|
||||
return self.has_front_light
|
||||
end
|
||||
|
||||
function Device:setTouchInputDev(dev)
|
||||
self.touch_dev = dev
|
||||
end
|
||||
|
||||
function Device:getTouchInputDev()
|
||||
return self.touch_dev
|
||||
end
|
||||
|
||||
function Device:intoScreenSaver()
|
||||
--os.execute("echo 'screensaver in' >> /mnt/us/event_test.txt")
|
||||
if self.charging_mode == false and self.screen_saver_mode == false then
|
||||
self.screen:saveCurrentBB()
|
||||
--UIManager:show(InfoMessage:new{
|
||||
--text = "Going into screensaver... ",
|
||||
--timeout = 2,
|
||||
--})
|
||||
--util.sleep(1)
|
||||
--os.execute("killall -cont cvm")
|
||||
self.screen_saver_mode = true
|
||||
end
|
||||
end
|
||||
|
||||
function Device:outofScreenSaver()
|
||||
--os.execute("echo 'screensaver out' >> /mnt/us/event_test.txt")
|
||||
if self.screen_saver_mode == true and self.charging_mode == false then
|
||||
-- wait for native system update screen before we recover saved
|
||||
-- Blitbuffer.
|
||||
util.usleep(1500000)
|
||||
--os.execute("killall -stop cvm")
|
||||
self.screen:restoreFromSavedBB()
|
||||
self.screen:refresh(0)
|
||||
self.survive_screen_saver = true
|
||||
end
|
||||
self.screen_saver_mode = false
|
||||
end
|
||||
|
||||
function Device:onPowerEvent(ev)
|
||||
local Screensaver = require("ui/screensaver")
|
||||
if (ev == "Power" or ev == "Suspend") and not self.screen_saver_mode then
|
||||
local UIManager = require("ui/uimanager")
|
||||
DEBUG("Suspending...")
|
||||
-- always suspend in portrait mode
|
||||
self.orig_rotation_mode = Screen:getRotationMode()
|
||||
Screen:setRotationMode(0)
|
||||
Screensaver:show()
|
||||
self:prepareSuspend()
|
||||
UIManager:scheduleIn(2, function() self:Suspend() end)
|
||||
elseif (ev == "Power" or ev == "Resume") and self.screen_saver_mode then
|
||||
DEBUG("Resuming...")
|
||||
-- restore to previous rotation mode
|
||||
Screen:setRotationMode(self.orig_rotation_mode)
|
||||
self:Resume()
|
||||
Screensaver:close()
|
||||
end
|
||||
end
|
||||
|
||||
function Device:prepareSuspend()
|
||||
local powerd = self:getPowerDevice()
|
||||
if powerd.fl ~= nil then
|
||||
-- in no case should the frontlight be turned on in suspend mode
|
||||
powerd.fl:sleep()
|
||||
end
|
||||
self.screen:refresh(0)
|
||||
self.screen_saver_mode = true
|
||||
end
|
||||
|
||||
function Device:Suspend()
|
||||
if self:isKobo() then
|
||||
if KOBO_LIGHT_OFF_ON_SUSPEND then self:getPowerDevice():setIntensity(0) end
|
||||
os.execute("./suspend.sh")
|
||||
end
|
||||
end
|
||||
|
||||
function Device:Resume()
|
||||
if self:isKobo() then
|
||||
os.execute("echo 0 > /sys/power/state-extended")
|
||||
local powerd = self:getPowerDevice()
|
||||
if powerd then
|
||||
if KOBO_LIGHT_ON_START and tonumber(KOBO_LIGHT_ON_START) > -1 then
|
||||
powerd:setIntensity(math.max(math.min(KOBO_LIGHT_ON_START,100),0))
|
||||
elseif powerd.fl ~= nil then
|
||||
powerd.fl:restore()
|
||||
end
|
||||
end
|
||||
end
|
||||
self.screen:refresh(1)
|
||||
self.screen_saver_mode = false
|
||||
end
|
||||
|
||||
function Device:usbPlugIn()
|
||||
--os.execute("echo 'usb in' >> /mnt/us/event_test.txt")
|
||||
if self.charging_mode == false and self.screen_saver_mode == false then
|
||||
self.screen:saveCurrentBB()
|
||||
--UIManager:show(InfoMessage:new{
|
||||
--text = "Going into USB mode... ",
|
||||
--timeout = 2,
|
||||
--})
|
||||
--util.sleep(1)
|
||||
--os.execute("killall -cont cvm")
|
||||
end
|
||||
self.charging_mode = true
|
||||
end
|
||||
|
||||
function Device:usbPlugOut()
|
||||
--os.execute("echo 'usb out' >> /mnt/us/event_test.txt")
|
||||
if self.charging_mode == true and self.screen_saver_mode == false then
|
||||
--util.usleep(1500000)
|
||||
--os.execute("killall -stop cvm")
|
||||
self.screen:restoreFromSavedBB()
|
||||
self.screen:refresh(0)
|
||||
end
|
||||
|
||||
--@TODO signal filemanager for file changes 13.06 2012 (houqp)
|
||||
self.charging_mode = false
|
||||
end
|
||||
|
||||
function Device:getPowerDevice()
|
||||
if self.powerd ~= nil then
|
||||
return self.powerd
|
||||
else
|
||||
local model = self:getModel()
|
||||
if model == "KindleTouch" or model == "KindlePaperWhite" or model == "KindlePaperWhite2" then
|
||||
local KindlePowerD = require("ui/device/kindlepowerd")
|
||||
self.powerd = KindlePowerD:new{model = model}
|
||||
elseif self:isKobo() then
|
||||
local KoboPowerD = require("ui/device/kobopowerd")
|
||||
self.powerd = KoboPowerD:new()
|
||||
elseif self.isAndroid then
|
||||
local AndroidPowerd = require("ui/device/androidpowerd")
|
||||
self.powerd = AndroidPowerd:new()
|
||||
else -- emulated FrontLight
|
||||
local BasePowerD = require("ui/device/basepowerd")
|
||||
self.powerd = BasePowerD:new()
|
||||
end
|
||||
end
|
||||
return self.powerd
|
||||
end
|
||||
|
||||
function Device:isSpecialOffers()
|
||||
if self.is_special_offers ~= nil then return self.is_special_offers end
|
||||
-- K5 only
|
||||
if self:isTouchDevice() and self:isKindle() then
|
||||
-- Look at the current blanket modules to see if the SO screensavers are enabled...
|
||||
local lipc = require("liblipclua")
|
||||
local lipc_handle = nil
|
||||
if lipc then
|
||||
lipc_handle = lipc.init("com.github.koreader.device")
|
||||
end
|
||||
if lipc_handle then
|
||||
local loaded_blanket_modules = lipc_handle:get_string_property("com.lab126.blanket", "load")
|
||||
if string.find(loaded_blanket_modules, "ad_screensaver") then
|
||||
self.is_special_offers = true
|
||||
end
|
||||
lipc_handle:close()
|
||||
else
|
||||
end
|
||||
end
|
||||
return self.is_special_offers
|
||||
end
|
||||
|
||||
return Device
|
@ -1,352 +0,0 @@
|
||||
local Blitbuffer = require("ffi/blitbuffer")
|
||||
local einkfb = require("ffi/framebuffer")
|
||||
local Geom = require("ui/geometry")
|
||||
local util = require("ffi/util")
|
||||
local DEBUG = require("dbg")
|
||||
local _ = require("gettext")
|
||||
|
||||
--[[
|
||||
Codes for rotation modes:
|
||||
|
||||
1 for no rotation,
|
||||
2 for landscape with bottom on the right side of screen, etc.
|
||||
|
||||
2
|
||||
+--------------+
|
||||
| +----------+ |
|
||||
| | | |
|
||||
| | Freedom! | |
|
||||
| | | |
|
||||
| | | |
|
||||
3 | | | | 1
|
||||
| | | |
|
||||
| | | |
|
||||
| +----------+ |
|
||||
| |
|
||||
| |
|
||||
+--------------+
|
||||
0
|
||||
--]]
|
||||
|
||||
|
||||
local Screen = {
|
||||
cur_rotation_mode = 0,
|
||||
native_rotation_mode = nil,
|
||||
blitbuffer_rotation_mode = 0,
|
||||
|
||||
bb = nil,
|
||||
saved_bb = nil,
|
||||
|
||||
fb = einkfb.open("/dev/fb0"),
|
||||
-- will be set upon loading by Device class:
|
||||
device = nil,
|
||||
}
|
||||
|
||||
function Screen:init()
|
||||
self.bb = self.fb.bb
|
||||
if self.device:getModel() == 'Kobo_phoenix' then
|
||||
function Screen:getSize()
|
||||
return Screen:getSizePhoenix()
|
||||
end
|
||||
function Screen:getWidth()
|
||||
return Screen:getWidthPhoenix()
|
||||
end
|
||||
function Screen:getHeight()
|
||||
return Screen:getHeightPhoenix()
|
||||
end
|
||||
function self:offsetX()
|
||||
if Screen.cur_rotation_mode == 0 then
|
||||
return 6
|
||||
elseif Screen.cur_rotation_mode == 1 then
|
||||
return 12
|
||||
elseif Screen.cur_rotation_mode == 2 then
|
||||
return 12
|
||||
elseif Screen.cur_rotation_mode == 3 then
|
||||
return 6
|
||||
end
|
||||
end
|
||||
function self:offsetY()
|
||||
return 1
|
||||
end
|
||||
elseif self.device:getModel() == 'Kobo_dahlia' then
|
||||
function Screen:getSize()
|
||||
return Screen:getSizePhoenix()
|
||||
end
|
||||
function Screen:getWidth()
|
||||
return Screen:getWidthDahlia()
|
||||
end
|
||||
function Screen:getHeight()
|
||||
return Screen:getHeightDahlia()
|
||||
end
|
||||
function self:offsetX()
|
||||
if Screen.cur_rotation_mode == 3 then
|
||||
return 10
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
function self:offsetY()
|
||||
if Screen.cur_rotation_mode == 0 then
|
||||
return 10
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
else
|
||||
function Screen:getSize()
|
||||
return Screen:getSizeBB()
|
||||
end
|
||||
function Screen:getWidth()
|
||||
return Screen:getWidthBB()
|
||||
end
|
||||
function Screen:getHeight()
|
||||
return Screen:getHeightBB()
|
||||
end
|
||||
function self:offsetX() return 0 end
|
||||
function self:offsetY() return 0 end
|
||||
end
|
||||
self.blitbuffer_rotation_mode = self.bb:getRotation()
|
||||
-- asking the framebuffer for orientation is error prone,
|
||||
-- so we do this simple heuristic (for now)
|
||||
if self:getWidth() > self:getHeight() then
|
||||
self.native_rotation_mode = 1
|
||||
else
|
||||
self.native_rotation_mode = 0
|
||||
end
|
||||
self.cur_rotation_mode = self.native_rotation_mode
|
||||
end
|
||||
|
||||
-- For the Kobo Aura an offset is needed, because the bezel make the
|
||||
-- visible screen smaller.
|
||||
function Screen:PhoenixBezelCleaner()
|
||||
self.bb:paintRect(0,0, Screen:getWidth(), Screen:offsetY(), Blitbuffer.COLOR_WHITE)
|
||||
self.bb:paintRect(0,0, Screen:offsetX(), Screen:getHeight(), Blitbuffer.COLOR_WHITE)
|
||||
self.bb:paintRect(Screen:getWidth() + Screen:offsetX(), 0,
|
||||
Screen:getWidth() - Screen:getWidth() - Screen:offsetX(), Screen:getHeight(),
|
||||
Blitbuffer.COLOR_WHITE)
|
||||
self.bb:paintRect(0, Screen:getHeight() + Screen:offsetY(),
|
||||
Screen:offsetX(), Screen:getWidth(),
|
||||
Blitbuffer.COLOR_WHITE)
|
||||
end
|
||||
|
||||
function Screen:refresh(refresh_type, waveform_mode, x, y, w, h)
|
||||
self.fb:refresh(refresh_type, waveform_mode, x, y, w, h)
|
||||
if self.device:getModel() == 'Kobo_phoenix' and refresh_type == 1 then
|
||||
Screen:PhoenixBezelCleaner()
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:getSizeBB()
|
||||
return Geom:new{w = self.bb:getWidth(), h = self.bb:getHeight()}
|
||||
end
|
||||
|
||||
function Screen:getSizePhoenix()
|
||||
return Geom:new{w = self.getWidth(), h = self.getHeight()}
|
||||
end
|
||||
|
||||
function Screen:getWidthBB()
|
||||
return self.bb:getWidth()
|
||||
end
|
||||
|
||||
function Screen:getWidthDahlia()
|
||||
if self.cur_rotation_mode == 0 then return 1080
|
||||
else return 1430
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:getWidthPhoenix()
|
||||
if self.cur_rotation_mode == 0 then return 752
|
||||
else return 1012
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:getHeightBB()
|
||||
return self.bb:getHeight()
|
||||
end
|
||||
|
||||
function Screen:getHeightDahlia()
|
||||
if self.cur_rotation_mode == 0 then return 1430
|
||||
else return 1080
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:getHeightPhoenix()
|
||||
if self.cur_rotation_mode == 0 then return 1012
|
||||
else return 752
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:getDPI()
|
||||
if self.dpi == nil then
|
||||
self.dpi = G_reader_settings:readSetting("screen_dpi")
|
||||
end
|
||||
if self.dpi ~= nil then return self.dpi end
|
||||
local model = self.device:getModel()
|
||||
if model == "KindlePaperWhite" or model == "KindlePaperWhite2"
|
||||
or model == "Kobo_kraken" then
|
||||
self.dpi = 212
|
||||
elseif model == "Kobo_phoenix" then
|
||||
self.dpi = 212.8
|
||||
elseif model == "Kobo_dragon" or model == "Kobo_dahlia" then
|
||||
self.dpi = 265
|
||||
elseif model == "Kobo_pixie" then
|
||||
self.dpi = 200
|
||||
elseif util.isAndroid() then
|
||||
local android = require("android")
|
||||
local ffi = require("ffi")
|
||||
self.dpi = ffi.C.AConfiguration_getDensity(android.app.config)
|
||||
else
|
||||
self.dpi = 160
|
||||
end
|
||||
return self.dpi
|
||||
end
|
||||
|
||||
function Screen:setDPI(dpi)
|
||||
G_reader_settings:saveSetting("screen_dpi", dpi)
|
||||
end
|
||||
|
||||
function Screen:scaleByDPI(px)
|
||||
-- scaled positive px should also be positive
|
||||
return math.ceil(px * self:getDPI()/167)
|
||||
end
|
||||
|
||||
function Screen:getRotationMode()
|
||||
return self.cur_rotation_mode
|
||||
end
|
||||
|
||||
function Screen:getScreenMode()
|
||||
if self:getWidth() > self:getHeight() then
|
||||
return "landscape"
|
||||
else
|
||||
return "portrait"
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:setRotationMode(mode)
|
||||
self.fb.bb:rotateAbsolute(-90 * (mode - self.native_rotation_mode - self.blitbuffer_rotation_mode))
|
||||
self.cur_rotation_mode = mode
|
||||
end
|
||||
|
||||
function Screen:setScreenMode(mode)
|
||||
if mode == "portrait" then
|
||||
if self.cur_rotation_mode ~= 0 then
|
||||
self:setRotationMode(0)
|
||||
end
|
||||
elseif mode == "landscape" then
|
||||
if self.cur_rotation_mode == 0 or self.cur_rotation_mode == 2 then
|
||||
self:setRotationMode(DLANDSCAPE_CLOCKWISE_ROTATION and 1 or 3)
|
||||
elseif self.cur_rotation_mode == 1 or self.cur_rotation_mode == 3 then
|
||||
self:setRotationMode((self.cur_rotation_mode + 2) % 4)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:saveCurrentBB()
|
||||
local width, height = self:getWidth(), self:getHeight()
|
||||
|
||||
if not self.saved_bb then
|
||||
self.saved_bb = Blitbuffer.new(width, height)
|
||||
end
|
||||
if self.saved_bb:getWidth() ~= width then
|
||||
self.saved_bb:free()
|
||||
self.saved_bb = Blitbuffer.new(width, height)
|
||||
end
|
||||
self.saved_bb:blitFullFrom(self.bb)
|
||||
end
|
||||
|
||||
function Screen:restoreFromSavedBB()
|
||||
self:restoreFromBB(self.saved_bb)
|
||||
-- free data
|
||||
self.saved_bb = nil
|
||||
end
|
||||
|
||||
function Screen:getCurrentScreenBB()
|
||||
local bb = Blitbuffer.new(self:getWidth(), self:getHeight())
|
||||
bb:blitFullFrom(self.bb)
|
||||
return bb
|
||||
end
|
||||
|
||||
function Screen:restoreFromBB(bb)
|
||||
if bb then
|
||||
self.bb:blitFullFrom(bb)
|
||||
else
|
||||
DEBUG("Got nil bb in restoreFromSavedBB!")
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:close()
|
||||
DEBUG("close screen framebuffer")
|
||||
self.fb:close()
|
||||
end
|
||||
|
||||
function Screen:getDPIMenuTable()
|
||||
local function dpi() return G_reader_settings:readSetting("screen_dpi") end
|
||||
local function custom() return G_reader_settings:readSetting("custom_screen_dpi") end
|
||||
local function setDPI(dpi)
|
||||
local InfoMessage = require("ui/widget/infomessage")
|
||||
local UIManager = require("ui/uimanager")
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = _("This will take effect on next restart."),
|
||||
})
|
||||
Screen:setDPI(dpi)
|
||||
end
|
||||
return {
|
||||
text = _("Screen DPI"),
|
||||
sub_item_table = {
|
||||
{
|
||||
text = _("Auto"),
|
||||
checked_func = function()
|
||||
return dpi() == nil
|
||||
end,
|
||||
callback = function() setDPI() end
|
||||
},
|
||||
{
|
||||
text = _("Small"),
|
||||
checked_func = function()
|
||||
local dpi, custom = dpi(), custom()
|
||||
return dpi and dpi <= 140 and dpi ~= custom
|
||||
end,
|
||||
callback = function() setDPI(120) end
|
||||
},
|
||||
{
|
||||
text = _("Medium"),
|
||||
checked_func = function()
|
||||
local dpi, custom = dpi(), custom()
|
||||
return dpi and dpi > 140 and dpi <= 200 and dpi ~= custom
|
||||
end,
|
||||
callback = function() setDPI(160) end
|
||||
},
|
||||
{
|
||||
text = _("Large"),
|
||||
checked_func = function()
|
||||
local dpi, custom = dpi(), custom()
|
||||
return dpi and dpi > 200 and dpi ~= custom
|
||||
end,
|
||||
callback = function() setDPI(240) end
|
||||
},
|
||||
{
|
||||
text = _("Custom DPI") .. ": " .. (custom() or 160),
|
||||
checked_func = function()
|
||||
local dpi, custom = dpi(), custom()
|
||||
return custom and dpi == custom
|
||||
end,
|
||||
callback = function() setDPI(custom() or 160) end,
|
||||
hold_input = {
|
||||
title = _("Input screen DPI"),
|
||||
type = "number",
|
||||
hint = "(90 - 330)",
|
||||
callback = function(input)
|
||||
local dpi = tonumber(input)
|
||||
dpi = dpi < 90 and 90 or dpi
|
||||
dpi = dpi > 330 and 330 or dpi
|
||||
G_reader_settings:saveSetting("custom_screen_dpi", dpi)
|
||||
setDPI(dpi)
|
||||
end,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
return Screen
|
||||
|
@ -0,0 +1,74 @@
|
||||
local _ = require("gettext")
|
||||
local Screen = require("device").screen
|
||||
|
||||
|
||||
local function dpi() return G_reader_settings:readSetting("screen_dpi") end
|
||||
|
||||
local function custom() return G_reader_settings:readSetting("custom_screen_dpi") end
|
||||
|
||||
local function setDPI(dpi)
|
||||
local InfoMessage = require("ui/widget/infomessage")
|
||||
local UIManager = require("ui/uimanager")
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = _("This will take effect on next restart."),
|
||||
})
|
||||
Screen:setDPI(dpi)
|
||||
end
|
||||
|
||||
|
||||
return {
|
||||
text = _("Screen DPI"),
|
||||
sub_item_table = {
|
||||
{
|
||||
text = _("Auto"),
|
||||
checked_func = function()
|
||||
return dpi() == nil
|
||||
end,
|
||||
callback = function() setDPI() end
|
||||
},
|
||||
{
|
||||
text = _("Small"),
|
||||
checked_func = function()
|
||||
local dpi, custom = dpi(), custom()
|
||||
return dpi and dpi <= 140 and dpi ~= custom
|
||||
end,
|
||||
callback = function() setDPI(120) end
|
||||
},
|
||||
{
|
||||
text = _("Medium"),
|
||||
checked_func = function()
|
||||
local dpi, custom = dpi(), custom()
|
||||
return dpi and dpi > 140 and dpi <= 200 and dpi ~= custom
|
||||
end,
|
||||
callback = function() setDPI(160) end
|
||||
},
|
||||
{
|
||||
text = _("Large"),
|
||||
checked_func = function()
|
||||
local dpi, custom = dpi(), custom()
|
||||
return dpi and dpi > 200 and dpi ~= custom
|
||||
end,
|
||||
callback = function() setDPI(240) end
|
||||
},
|
||||
{
|
||||
text = _("Custom DPI") .. ": " .. (custom() or 160),
|
||||
checked_func = function()
|
||||
local dpi, custom = dpi(), custom()
|
||||
return custom and dpi == custom
|
||||
end,
|
||||
callback = function() setDPI(custom() or 160) end,
|
||||
hold_input = {
|
||||
title = _("Input screen DPI"),
|
||||
type = "number",
|
||||
hint = "(90 - 330)",
|
||||
callback = function(input)
|
||||
local dpi = tonumber(input)
|
||||
dpi = dpi < 90 and 90 or dpi
|
||||
dpi = dpi > 330 and 330 or dpi
|
||||
G_reader_settings:saveSetting("custom_screen_dpi", dpi)
|
||||
setDPI(dpi)
|
||||
end,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -1,861 +0,0 @@
|
||||
local Device = require("ui/device")
|
||||
local GestureDetector = require("ui/gesturedetector")
|
||||
local Event = require("ui/event")
|
||||
local TimeVal = require("ui/timeval")
|
||||
local Screen = require("ui/screen")
|
||||
local input = require("ffi/input")
|
||||
local util = require("ffi/util")
|
||||
local Math = require("optmath")
|
||||
local DEBUG = require("dbg")
|
||||
local ffi = require("ffi")
|
||||
local _ = require("gettext")
|
||||
|
||||
-- constants from <linux/input.h>
|
||||
local EV_SYN = 0
|
||||
local EV_KEY = 1
|
||||
local EV_ABS = 3
|
||||
local EV_MSC = 4
|
||||
|
||||
-- key press event values (KEY.value)
|
||||
local EVENT_VALUE_KEY_PRESS = 1
|
||||
local EVENT_VALUE_KEY_REPEAT = 2
|
||||
local EVENT_VALUE_KEY_RELEASE = 0
|
||||
|
||||
-- Synchronization events (SYN.code).
|
||||
local SYN_REPORT = 0
|
||||
local SYN_CONFIG = 1
|
||||
local SYN_MT_REPORT = 2
|
||||
|
||||
-- For single-touch events (ABS.code).
|
||||
local ABS_X = 00
|
||||
local ABS_Y = 01
|
||||
local ABS_PRESSURE = 24
|
||||
|
||||
-- For multi-touch events (ABS.code).
|
||||
local ABS_MT_SLOT = 47
|
||||
local ABS_MT_TOUCH_MAJOR = 48
|
||||
local ABS_MT_WIDTH_MAJOR = 50
|
||||
|
||||
local ABS_MT_POSITION_X = 53
|
||||
local ABS_MT_POSITION_Y = 54
|
||||
if KOBO_TOUCH_MIRRORED then
|
||||
ABS_X = 01
|
||||
ABS_Y = 00
|
||||
ABS_MT_POSITION_X = 54
|
||||
ABS_MT_POSITION_Y = 53
|
||||
end
|
||||
local ABS_MT_TRACKING_ID = 57
|
||||
local ABS_MT_PRESSURE = 58
|
||||
|
||||
--[[
|
||||
an interface for key presses
|
||||
]]
|
||||
|
||||
local Key = {}
|
||||
|
||||
function Key:new(key, modifiers)
|
||||
local o = { key = key, modifiers = modifiers }
|
||||
|
||||
-- we're a hash map, too
|
||||
o[key] = true
|
||||
for mod, pressed in pairs(modifiers) do
|
||||
if pressed then
|
||||
o[mod] = true
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end
|
||||
|
||||
function Key:__tostring()
|
||||
return table.concat(self:getSequence(), "-")
|
||||
end
|
||||
|
||||
--[[
|
||||
get a sequence that can be matched against later
|
||||
|
||||
use this to let the user press a sequence and then
|
||||
store this as configuration data (configurable
|
||||
shortcuts)
|
||||
]]
|
||||
function Key:getSequence()
|
||||
local seq = {}
|
||||
for mod, pressed in pairs(self.modifiers) do
|
||||
if pressed then
|
||||
table.insert(seq, mod)
|
||||
end
|
||||
end
|
||||
table.insert(seq, self.key)
|
||||
end
|
||||
|
||||
--[[
|
||||
this will match a key against a sequence
|
||||
|
||||
the sequence should be a table of key names that
|
||||
must be pressed together to match.
|
||||
if an entry in this table is itself a table, at
|
||||
least one key in this table must match.
|
||||
|
||||
E.g.:
|
||||
|
||||
Key:match({ "Alt", "K" }) -- match Alt-K
|
||||
Key:match({ "Alt", { "K", "L" }}) -- match Alt-K _or_ Alt-L
|
||||
]]
|
||||
function Key:match(sequence)
|
||||
local mod_keys = {} -- a hash table for checked modifiers
|
||||
for _, key in ipairs(sequence) do
|
||||
if type(key) == "table" then
|
||||
local found = false
|
||||
for _, variant in ipairs(key) do
|
||||
if self[variant] then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
-- one of the needed keys is not pressed
|
||||
return false
|
||||
end
|
||||
elseif not self[key] then
|
||||
-- needed key not pressed
|
||||
return false
|
||||
elseif self.modifiers[key] ~= nil then
|
||||
-- checked key is a modifier key
|
||||
mod_keys[key] = true
|
||||
end
|
||||
end
|
||||
|
||||
for mod, pressed in pairs(self.modifiers) do
|
||||
if pressed and not mod_keys[mod] then
|
||||
-- additional modifier keys are pressed, don't match
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--[[
|
||||
an interface to get input events
|
||||
]]
|
||||
local Input = {
|
||||
event_map = {},
|
||||
modifiers = {},
|
||||
rotation_map = {
|
||||
[0] = {},
|
||||
[1] = { Up = "Right", Right = "Down", Down = "Left", Left = "Up" },
|
||||
[2] = { Up = "Down", Right = "Left", Down = "Up", Left = "Right" },
|
||||
[3] = { Up = "Left", Right = "Up", Down = "Right", Left = "Down" }
|
||||
},
|
||||
timer_callbacks = {},
|
||||
disable_double_tap = DGESDETECT_DISABLE_DOUBLE_TAP,
|
||||
}
|
||||
|
||||
function Input:initKeyMap()
|
||||
self.event_map = {
|
||||
[2] = "1", [3] = "2", [4] = "3", [5] = "4", [6] = "5", [7] = "6", [8] = "7", [9] = "8", [10] = "9", [11] = "0",
|
||||
[16] = "Q", [17] = "W", [18] = "E", [19] = "R", [20] = "T", [21] = "Y", [22] = "U", [23] = "I", [24] = "O", [25] = "P",
|
||||
[30] = "A", [31] = "S", [32] = "D", [33] = "F", [34] = "G", [35] = "H", [36] = "J", [37] = "K", [38] = "L", [14] = "Del",
|
||||
[44] = "Z", [45] = "X", [46] = "C", [47] = "V", [48] = "B", [49] = "N", [50] = "M", [52] = ".", [53] = "/", -- only KDX
|
||||
|
||||
[28] = "Enter",
|
||||
[29] = "ScreenKB", -- K[4]
|
||||
[42] = "Shift",
|
||||
[56] = "Alt",
|
||||
[57] = " ",
|
||||
[90] = "AA", -- KDX
|
||||
[91] = "Back", -- KDX
|
||||
[92] = "Press", -- KDX
|
||||
[94] = "Sym", -- KDX
|
||||
[98] = "Home", -- KDX
|
||||
[102] = "Home", -- K[3] & k[4]
|
||||
[104] = "LPgBack", -- K[3] only
|
||||
[103] = "Up", -- K[3] & k[4]
|
||||
[105] = "Left",
|
||||
[106] = "Right",
|
||||
[108] = "Down", -- K[3] & k[4]
|
||||
[109] = "RPgBack",
|
||||
[114] = "VMinus",
|
||||
[115] = "VPlus",
|
||||
[122] = "Up", -- KDX
|
||||
[123] = "Down", -- KDX
|
||||
[124] = "RPgFwd", -- KDX
|
||||
[126] = "Sym", -- K[3]
|
||||
[139] = "Menu",
|
||||
[158] = "Back", -- K[3] & K[4]
|
||||
[190] = "AA", -- K[3]
|
||||
[191] = "RPgFwd", -- K[3] & k[4]
|
||||
[193] = "LPgFwd", -- K[3] only
|
||||
[194] = "Press", -- K[3] & k[4]
|
||||
}
|
||||
self.sdl_event_map = {
|
||||
[10] = "1", [11] = "2", [12] = "3", [13] = "4", [14] = "5", [15] = "6", [16] = "7", [17] = "8", [18] = "9", [19] = "0",
|
||||
[24] = "Q", [25] = "W", [26] = "E", [27] = "R", [28] = "T", [29] = "Y", [30] = "U", [31] = "I", [32] = "O", [33] = "P",
|
||||
[38] = "A", [39] = "S", [40] = "D", [41] = "F", [42] = "G", [43] = "H", [44] = "J", [45] = "K", [46] = "L",
|
||||
[52] = "Z", [53] = "X", [54] = "C", [55] = "V", [56] = "B", [57] = "N", [58] = "M",
|
||||
|
||||
[22] = "Back", -- Backspace
|
||||
[36] = "Enter", -- Enter
|
||||
[50] = "Shift", -- left shift
|
||||
[60] = ".",
|
||||
[61] = "/",
|
||||
[62] = "Sym", -- right shift key
|
||||
[64] = "Alt", -- left alt
|
||||
[65] = " ", -- Spacebar
|
||||
[67] = "Menu", -- F[1]
|
||||
[68] = "Power", -- F[2]
|
||||
[72] = "LPgBack", -- F[6]
|
||||
[73] = "LPgFwd", -- F[7]
|
||||
[95] = "VPlus", -- F[11]
|
||||
[96] = "VMinus", -- F[12]
|
||||
[105] = "AA", -- right alt key
|
||||
[110] = "Home", -- Home
|
||||
[111] = "Up", -- arrow up
|
||||
[112] = "RPgBack", -- normal PageUp
|
||||
[113] = "Left", -- arrow left
|
||||
[114] = "Right", -- arrow right
|
||||
[115] = "Press", -- End (above arrows)
|
||||
[116] = "Down", -- arrow down
|
||||
[117] = "RPgFwd", -- normal PageDown
|
||||
[119] = "Del", -- Delete
|
||||
}
|
||||
self.sdl2_event_map = {
|
||||
[ 4] = "A", [ 5] = "B", [ 6] = "C", [ 7] = "D", [ 8] = "E", [ 9] = "F",
|
||||
[10] = "G", [11] = "H", [12] = "I", [13] = "J", [14] = "K", [15] = "L",
|
||||
[16] = "M", [17] = "N", [18] = "O", [19] = "P", [20] = "Q", [21] = "R",
|
||||
[22] = "S", [23] = "T", [24] = "U", [25] = "V", [26] = "W", [27] = "X",
|
||||
[28] = "Y", [29] = "Z", [30] = "1", [31] = "2", [32] = "3", [33] = "4",
|
||||
[34] = "5", [35] = "6", [36] = "7", [37] = "8", [38] = "9", [39] = "0",
|
||||
|
||||
[42] = "Back", -- Backspace
|
||||
[40] = "Enter", -- Enter
|
||||
[225] = "Shift", -- left shift
|
||||
[55] = ".",
|
||||
[56] = "/",
|
||||
[229] = "Sym", -- right shift key
|
||||
[226] = "Alt", -- left alt
|
||||
[44] = " ", -- Spacebar
|
||||
[58] = "Menu", -- F[1]
|
||||
[59] = "Power", -- F[2]
|
||||
[63] = "LPgBack", -- F[6]
|
||||
[64] = "LPgFwd", -- F[7]
|
||||
[68] = "VPlus", -- F[11]
|
||||
[69] = "VMinus", -- F[12]
|
||||
[230] = "AA", -- right alt key
|
||||
[74] = "Home", -- Home
|
||||
[82] = "Up", -- arrow up
|
||||
[75] = "RPgBack", -- normal PageUp
|
||||
[80] = "Left", -- arrow left
|
||||
[79] = "Right", -- arrow right
|
||||
[77] = "Press", -- End (above arrows)
|
||||
[81] = "Down", -- arrow down
|
||||
[78] = "RPgFwd", -- normal PageDown
|
||||
[76] = "Del", -- Delete
|
||||
}
|
||||
self.android_event_map = {
|
||||
[29] = "A", [30] = "B", [31] = "C", [32] = "D", [33] = "E", [34] = "F",
|
||||
[35] = "G", [36] = "H", [37] = "I", [38] = "J", [39] = "K", [40] = "L",
|
||||
[41] = "M", [42] = "N", [43] = "O", [44] = "P", [45] = "Q", [46] = "R",
|
||||
[47] = "S", [48] = "T", [49] = "U", [50] = "V", [51] = "W", [52] = "X",
|
||||
[53] = "Y", [54] = "Z", [ 7] = "0", [ 8] = "1", [ 9] = "2", [10] = "3",
|
||||
[11] = "4", [12] = "5", [13] = "6", [14] = "7", [15] = "8", [16] = "9",
|
||||
|
||||
[4] = "Back", -- BACK
|
||||
[19] = "Up", -- DPAD_UP
|
||||
[20] = "Down", -- DPAD_UP
|
||||
[21] = "Left", -- DPAD_LEFT
|
||||
[22] = "Right", -- DPAD_RIGHT
|
||||
[23] = "Press", -- DPAD_CENTER
|
||||
[24] = "LPgBack", -- VOLUME_UP
|
||||
[25] = "LPgFwd", -- VOLUME_DOWN
|
||||
[56] = ".", -- PERIOD
|
||||
[59] = "Shift", -- SHIFT_LEFT
|
||||
[60] = "Shift", -- SHIFT_RIGHT
|
||||
[62] = " ", -- SPACE
|
||||
[63] = "Sym", -- SYM
|
||||
[66] = "Enter", -- ENTER
|
||||
[67] = "Del", -- DEL
|
||||
[76] = "/", -- SLASH
|
||||
[82] = "Menu", -- MENU
|
||||
[84] = "Search",--SEARCH
|
||||
[92] = "LPgBack", -- PAGE_UP
|
||||
[93] = "LPgFwd", -- PAGE_DOWN
|
||||
}
|
||||
|
||||
self.modifiers = {
|
||||
Alt = false,
|
||||
Shift = false
|
||||
}
|
||||
-- these groups are just helpers:
|
||||
self.group = {
|
||||
Cursor = { "Up", "Down", "Left", "Right" },
|
||||
PgFwd = { "RPgFwd", "LPgFwd" },
|
||||
PgBack = { "RPgBack", "LPgBack" },
|
||||
Alphabet = {
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
|
||||
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
|
||||
},
|
||||
AlphaNumeric = {
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
|
||||
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
|
||||
},
|
||||
Numeric = {
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
|
||||
},
|
||||
Text = {
|
||||
" ", ".", "/",
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
|
||||
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
|
||||
},
|
||||
Any = {
|
||||
" ", ".", "/",
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
|
||||
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
|
||||
"Up", "Down", "Left", "Right", "Press",
|
||||
"Back", "Enter", "Sym", "AA", "Menu", "Home", "Del",
|
||||
"LPgBack", "RPgBack", "LPgFwd", "RPgFwd"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
function Input:initTouchState()
|
||||
self.cur_slot = 0
|
||||
self.MTSlots = {}
|
||||
self.ev_slots = {
|
||||
[0] = {
|
||||
slot = 0,
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
function Input:init()
|
||||
self:initKeyMap()
|
||||
if Device:isTouchDevice() then
|
||||
self:initTouchState()
|
||||
end
|
||||
-- set up fake event map
|
||||
self.event_map[10000] = "IntoSS" -- go into screen saver
|
||||
self.event_map[10001] = "OutOfSS" -- go out of screen saver
|
||||
self.event_map[10020] = "Charging"
|
||||
self.event_map[10021] = "NotCharging"
|
||||
|
||||
if util.isEmulated() then
|
||||
-- open SDL if it's not dummy input device
|
||||
if not self.dummy then input.open() end
|
||||
-- SDL key codes
|
||||
if not util.haveSDL2() then
|
||||
self.event_map = self.sdl_event_map
|
||||
else
|
||||
self.event_map = self.sdl2_event_map
|
||||
end
|
||||
else
|
||||
local dev_mod = Device:getModel()
|
||||
if not Device:isKobo() then
|
||||
input.open("fake_events")
|
||||
end
|
||||
if dev_mod == "KindlePaperWhite" then
|
||||
DEBUG("Auto-detected Kindle PaperWhite")
|
||||
Device:setTouchInputDev("/dev/input/event0")
|
||||
input.open("/dev/input/event0")
|
||||
elseif dev_mod == "KindlePaperWhite2" then
|
||||
DEBUG("Auto-detected Kindle PaperWhite")
|
||||
Device:setTouchInputDev("/dev/input/event1")
|
||||
input.open("/dev/input/event1")
|
||||
elseif dev_mod == "KindleTouch" then
|
||||
-- event0 in KindleTouch is "WM8962 Beep Generator" (useless)
|
||||
-- event1 in KindleTouch is "imx-yoshi Headset" (useless)
|
||||
Device:setTouchInputDev("/dev/input/event3")
|
||||
input.open("/dev/input/event2") -- Home button
|
||||
input.open("/dev/input/event3") -- touchscreen
|
||||
-- KT does have one key!
|
||||
self.event_map[102] = "Home"
|
||||
-- update event hook
|
||||
function Input:eventAdjustHook(ev)
|
||||
if ev.type == EV_ABS then
|
||||
--@TODO handle coordinates properly after
|
||||
--screen rotate. (houqp)
|
||||
if ev.code == ABS_MT_POSITION_X then
|
||||
ev.value = Math.round(ev.value * (600/4095))
|
||||
elseif ev.code == ABS_MT_POSITION_Y then
|
||||
ev.value = Math.round(ev.value * (800/4095))
|
||||
end
|
||||
end
|
||||
return ev
|
||||
end
|
||||
DEBUG("Auto-detected Kindle Touch")
|
||||
elseif Device:isKobo() then
|
||||
local firm_rev = Device:getFirmVer()
|
||||
input.open("/dev/input/event1")
|
||||
Device:setTouchInputDev("/dev/input/event1")
|
||||
input.open("/dev/input/event0") -- Light button and sleep slider
|
||||
DEBUG("Auto-detected Kobo")
|
||||
DEBUG("Device model=", dev_mod)
|
||||
DEBUG("Firmware revision", firm_rev)
|
||||
DEBUG("Screen width =", Screen:getWidth())
|
||||
DEBUG("Screen height =", Screen:getHeight())
|
||||
self:adjustKoboEventMap()
|
||||
if dev_mod ~= 'Kobo_trilogy' then
|
||||
function Input:eventAdjustHook(ev)
|
||||
if ev.type == EV_ABS then
|
||||
if ev.code == ABS_X then
|
||||
ev.code = ABS_Y
|
||||
elseif ev.code == ABS_Y then
|
||||
ev.code = ABS_X
|
||||
-- We always have to substract from the physical x,
|
||||
-- regardless of the orientation
|
||||
if (Screen:getWidth()<Screen:getHeight()) then
|
||||
ev.value = Screen:getWidth() - ev.value
|
||||
else
|
||||
ev.value = Screen:getHeight() - ev.value
|
||||
end
|
||||
end
|
||||
-- same thing for multitouch events (phoenix)
|
||||
if ev.code == ABS_MT_POSITION_X then
|
||||
ev.code = ABS_MT_POSITION_Y
|
||||
elseif ev.code == ABS_MT_POSITION_Y then
|
||||
ev.code = ABS_MT_POSITION_X
|
||||
-- We always have to substract from the physical x,
|
||||
-- regardless of the orientation
|
||||
if (Screen:getWidth()<Screen:getHeight()) then
|
||||
ev.value = Screen:getWidth() - ev.value
|
||||
else
|
||||
ev.value = Screen:getHeight() - ev.value
|
||||
end
|
||||
end
|
||||
end
|
||||
return ev
|
||||
end
|
||||
else -- kobo touch (trilogy)
|
||||
-- FIXME some touch models should be treated as the other models,
|
||||
-- depending on board revision
|
||||
function Input:eventAdjustHook(ev)
|
||||
if ev.code == ABS_X then
|
||||
-- We always have to substract from the physical x,
|
||||
-- regardless of the orientation
|
||||
if (Screen:getWidth()<Screen:getHeight()) then
|
||||
ev.value = Screen:getWidth() - ev.value
|
||||
else
|
||||
ev.value = Screen:getHeight() - ev.value
|
||||
end
|
||||
end
|
||||
return ev
|
||||
end
|
||||
end
|
||||
elseif dev_mod == "Kindle4" then
|
||||
DEBUG("Auto-detected Kindle 4")
|
||||
input.open("/dev/input/event1")
|
||||
self:adjustKindle4EventMap()
|
||||
elseif dev_mod == "Kindle3" then
|
||||
DEBUG("Auto-detected Kindle 3")
|
||||
input.open("/dev/input/event1")
|
||||
input.open("/dev/input/event2")
|
||||
elseif dev_mod == "KindleDXG" then
|
||||
DEBUG("Auto-detected Kindle DXG")
|
||||
input.open("/dev/input/event0")
|
||||
input.open("/dev/input/event1")
|
||||
elseif dev_mod == "Kindle2" then
|
||||
DEBUG("Auto-detected Kindle 2")
|
||||
input.open("/dev/input/event1")
|
||||
elseif util.isAndroid() then
|
||||
DEBUG("Auto-detected Android")
|
||||
self.event_map = self.android_event_map
|
||||
self:adjustAndroidEventMap()
|
||||
function Input:handleMiscEv(ev)
|
||||
return Input:handleAndroidMiscEvent(ev)
|
||||
end
|
||||
else
|
||||
DEBUG("Not supported device model!")
|
||||
end
|
||||
end
|
||||
|
||||
if Device:getModel() == 'Kobo_phoenix' or Device:getModel() == 'Kobo_dahlia' then
|
||||
function Input:handleTouchEv(ev)
|
||||
return Input:handlePhoenixTouchEv(ev)
|
||||
end
|
||||
else
|
||||
function Input:handleTouchEv(ev)
|
||||
return Input:handleTypeBTouchEv(ev)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
different device models shoudl overload this method if
|
||||
necessary to make event compatible to KPV.
|
||||
--]]
|
||||
function Input:eventAdjustHook(ev)
|
||||
-- do nothing by default
|
||||
return ev
|
||||
end
|
||||
|
||||
function Input:adjustKindle4EventMap()
|
||||
self.event_map[193] = "LPgBack"
|
||||
self.event_map[104] = "LPgFwd"
|
||||
end
|
||||
|
||||
function Input:adjustKoboEventMap()
|
||||
self.event_map[59] = "Power_SleepCover"
|
||||
self.event_map[90] = "Light"
|
||||
self.event_map[116] = "Power"
|
||||
end
|
||||
|
||||
function Input:adjustAndroidEventMap()
|
||||
self.event_map[104] = "LPgBack" -- T68 PageUp
|
||||
self.event_map[109] = "LPgFwd" -- T68 PageDown
|
||||
self.event_map[139] = "Menu" -- T68 Menu
|
||||
end
|
||||
|
||||
function Input:setTimeout(cb, tv_out)
|
||||
local item = {
|
||||
callback = cb,
|
||||
deadline = tv_out,
|
||||
}
|
||||
table.insert(self.timer_callbacks, item)
|
||||
table.sort(self.timer_callbacks, function(v1,v2)
|
||||
return v1.deadline < v2.deadline
|
||||
end)
|
||||
end
|
||||
|
||||
function Input:handleKeyBoardEv(ev)
|
||||
local keycode = self.event_map[ev.code]
|
||||
if not keycode then
|
||||
-- do not handle keypress for keys we don't know
|
||||
return
|
||||
end
|
||||
|
||||
-- take device rotation into account
|
||||
if self.rotation_map[Screen:getRotationMode()][keycode] then
|
||||
keycode = self.rotation_map[Screen:getRotationMode()][keycode]
|
||||
end
|
||||
|
||||
if keycode == "IntoSS" or keycode == "OutOfSS"
|
||||
or keycode == "Charging" or keycode == "NotCharging" then
|
||||
return keycode
|
||||
end
|
||||
|
||||
-- Kobo sleep
|
||||
if keycode == "Power_SleepCover" then
|
||||
if ev.value == EVENT_VALUE_KEY_PRESS then
|
||||
return "Suspend"
|
||||
else
|
||||
return "Resume"
|
||||
end
|
||||
end
|
||||
|
||||
if ev.value == EVENT_VALUE_KEY_RELEASE
|
||||
and (keycode == "Light" or keycode == "Power") then
|
||||
return keycode
|
||||
end
|
||||
|
||||
-- handle modifier keys
|
||||
if self.modifiers[keycode] ~= nil then
|
||||
if ev.value == EVENT_VALUE_KEY_PRESS then
|
||||
self.modifiers[keycode] = true
|
||||
elseif ev.value == EVENT_VALUE_KEY_RELEASE then
|
||||
self.modifiers[keycode] = false
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local key = Key:new(keycode, self.modifiers)
|
||||
|
||||
if ev.value == EVENT_VALUE_KEY_PRESS then
|
||||
return Event:new("KeyPress", key)
|
||||
elseif ev.value == EVENT_VALUE_KEY_RELEASE then
|
||||
return Event:new("KeyRelease", key)
|
||||
end
|
||||
end
|
||||
|
||||
function Input:handleMiscEv(ev)
|
||||
end
|
||||
|
||||
function Input:handleAndroidMiscEvent(ev)
|
||||
if ev.code == ffi.C.APP_CMD_SAVE_STATE then
|
||||
return "SaveState"
|
||||
end
|
||||
end
|
||||
|
||||
function Input:setMtSlot(slot, key, val)
|
||||
if not self.ev_slots[slot] then
|
||||
self.ev_slots[slot] = {
|
||||
slot = slot
|
||||
}
|
||||
end
|
||||
|
||||
self.ev_slots[slot][key] = val
|
||||
end
|
||||
|
||||
function Input:setCurrentMtSlot(key, val)
|
||||
self:setMtSlot(self.cur_slot, key, val)
|
||||
end
|
||||
|
||||
function Input:getMtSlot(slot)
|
||||
return self.ev_slots[slot]
|
||||
end
|
||||
|
||||
function Input:getCurrentMtSlot()
|
||||
return self:getMtSlot(self.cur_slot)
|
||||
end
|
||||
|
||||
function Input:confirmAbsxy()
|
||||
self:setCurrentMtSlot("x", self.ev_slots[self.cur_slot]["abs_x"])
|
||||
self:setCurrentMtSlot("y", self.ev_slots[self.cur_slot]["abs_y"])
|
||||
end
|
||||
|
||||
function Input:cleanAbsxy()
|
||||
self:setCurrentMtSlot("abs_x", nil)
|
||||
self:setCurrentMtSlot("abs_y", nil)
|
||||
end
|
||||
|
||||
--[[
|
||||
parse each touch ev from kernel and build up tev.
|
||||
tev will be sent to GestureDetector:feedEvent
|
||||
|
||||
Events for a single tap motion from Linux kernel (MT protocol B):
|
||||
|
||||
MT_TRACK_ID: 0
|
||||
MT_X: 222
|
||||
MT_Y: 207
|
||||
SYN REPORT
|
||||
MT_TRACK_ID: -1
|
||||
SYN REPORT
|
||||
|
||||
Notice that each line is a single event.
|
||||
|
||||
From kernel document:
|
||||
For type B devices, the kernel driver should associate a slot with each
|
||||
identified contact, and use that slot to propagate changes for the contact.
|
||||
Creation, replacement and destruction of contacts is achieved by modifying
|
||||
the ABS_MT_TRACKING_ID of the associated slot. A non-negative tracking id
|
||||
is interpreted as a contact, and the value -1 denotes an unused slot. A
|
||||
tracking id not previously present is considered new, and a tracking id no
|
||||
longer present is considered removed. Since only changes are propagated,
|
||||
the full state of each initiated contact has to reside in the receiving
|
||||
end. Upon receiving an MT event, one simply updates the appropriate
|
||||
attribute of the current slot.
|
||||
--]]
|
||||
function Input:handleTypeBTouchEv(ev)
|
||||
if ev.type == EV_ABS then
|
||||
if #self.MTSlots == 0 then
|
||||
table.insert(self.MTSlots, self:getMtSlot(self.cur_slot))
|
||||
end
|
||||
if ev.code == ABS_MT_SLOT then
|
||||
if self.cur_slot ~= ev.value then
|
||||
table.insert(self.MTSlots, self:getMtSlot(ev.value))
|
||||
end
|
||||
self.cur_slot = ev.value
|
||||
elseif ev.code == ABS_MT_TRACKING_ID then
|
||||
self:setCurrentMtSlot("id", ev.value)
|
||||
elseif ev.code == ABS_MT_POSITION_X then
|
||||
self:setCurrentMtSlot("x", ev.value)
|
||||
elseif ev.code == ABS_MT_POSITION_Y then
|
||||
self:setCurrentMtSlot("y", ev.value)
|
||||
|
||||
-- code to emulate mt protocol on kobos
|
||||
-- we "confirm" abs_x, abs_y only when pressure ~= 0
|
||||
elseif ev.code == ABS_X then
|
||||
self:setCurrentMtSlot("abs_x", ev.value)
|
||||
elseif ev.code == ABS_Y then
|
||||
self:setCurrentMtSlot("abs_y", ev.value)
|
||||
elseif ev.code == ABS_PRESSURE then
|
||||
if ev.value ~= 0 then
|
||||
self:setCurrentMtSlot("id", 1)
|
||||
self:confirmAbsxy()
|
||||
else
|
||||
self:cleanAbsxy()
|
||||
self:setCurrentMtSlot("id", -1)
|
||||
end
|
||||
end
|
||||
elseif ev.type == EV_SYN then
|
||||
if ev.code == SYN_REPORT then
|
||||
for _, MTSlot in pairs(self.MTSlots) do
|
||||
self:setMtSlot(MTSlot.slot, "timev", TimeVal:new(ev.time))
|
||||
end
|
||||
-- feed ev in all slots to state machine
|
||||
local touch_ges = GestureDetector:feedEvent(self.MTSlots)
|
||||
self.MTSlots = {}
|
||||
if touch_ges then
|
||||
return Event:new("Gesture",
|
||||
GestureDetector:adjustGesCoordinate(touch_ges)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Input:handlePhoenixTouchEv(ev)
|
||||
-- Hack on handleTouchEV for the Kobo Aura
|
||||
-- It seems to be using a custom protocol:
|
||||
-- finger 0 down:
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_TRACKING_ID, 0);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_TOUCH_MAJOR, 1);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_WIDTH_MAJOR, 1);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_POSITION_X, x1);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_POSITION_Y, y1);
|
||||
-- input_mt_sync (elan_touch_data.input);
|
||||
-- finger 1 down:
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_TRACKING_ID, 1);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_TOUCH_MAJOR, 1);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_WIDTH_MAJOR, 1);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_POSITION_X, x2);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_POSITION_Y, y2);
|
||||
-- input_mt_sync (elan_touch_data.input);
|
||||
-- finger 0 up:
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_TRACKING_ID, 0);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_TOUCH_MAJOR, 0);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_WIDTH_MAJOR, 0);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_POSITION_X, last_x);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_POSITION_Y, last_y);
|
||||
-- input_mt_sync (elan_touch_data.input);
|
||||
-- finger 1 up:
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_TRACKING_ID, 1);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_TOUCH_MAJOR, 0);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_WIDTH_MAJOR, 0);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_POSITION_X, last_x2);
|
||||
-- input_report_abs(elan_touch_data.input, ABS_MT_POSITION_Y, last_y2);
|
||||
-- input_mt_sync (elan_touch_data.input);
|
||||
if ev.type == EV_ABS then
|
||||
if #self.MTSlots == 0 then
|
||||
table.insert(self.MTSlots, self:getMtSlot(self.cur_slot))
|
||||
end
|
||||
if ev.code == ABS_MT_TRACKING_ID then
|
||||
if self.cur_slot ~= ev.value then
|
||||
table.insert(self.MTSlots, self:getMtSlot(ev.value))
|
||||
end
|
||||
self.cur_slot = ev.value
|
||||
self:setCurrentMtSlot("id", ev.value)
|
||||
elseif ev.code == ABS_MT_TOUCH_MAJOR and ev.value == 0 then
|
||||
self:setCurrentMtSlot("id", -1)
|
||||
elseif ev.code == ABS_MT_POSITION_X then
|
||||
self:setCurrentMtSlot("x", ev.value)
|
||||
elseif ev.code == ABS_MT_POSITION_Y then
|
||||
self:setCurrentMtSlot("y", ev.value)
|
||||
end
|
||||
elseif ev.type == EV_SYN then
|
||||
if ev.code == SYN_REPORT then
|
||||
for _, MTSlot in pairs(self.MTSlots) do
|
||||
self:setMtSlot(MTSlot.slot, "timev", TimeVal:new(ev.time))
|
||||
end
|
||||
-- feed ev in all slots to state machine
|
||||
local touch_ges = GestureDetector:feedEvent(self.MTSlots)
|
||||
self.MTSlots = {}
|
||||
if touch_ges then
|
||||
return Event:new("Gesture",
|
||||
GestureDetector:adjustGesCoordinate(touch_ges)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Input:waitEvent(timeout_us, timeout_s)
|
||||
-- wrapper for input.waitForEvents that will retry for some cases
|
||||
local ok, ev
|
||||
local wait_deadline = TimeVal:now() + TimeVal:new{
|
||||
sec = timeout_s,
|
||||
usec = timeout_us
|
||||
}
|
||||
while true do
|
||||
if #self.timer_callbacks > 0 then
|
||||
-- we don't block if there is any timer, set wait to 10us
|
||||
while #self.timer_callbacks > 0 do
|
||||
ok, ev = pcall(input.waitForEvent, 100)
|
||||
if ok then break end
|
||||
local tv_now = TimeVal:now()
|
||||
if ((not timeout_us and not timeout_s) or tv_now < wait_deadline) then
|
||||
-- check whether timer is up
|
||||
if tv_now >= self.timer_callbacks[1].deadline then
|
||||
local touch_ges = self.timer_callbacks[1].callback()
|
||||
table.remove(self.timer_callbacks, 1)
|
||||
if touch_ges then
|
||||
-- Do we really need to clear all setTimeout after
|
||||
-- decided a gesture? FIXME
|
||||
Input.timer_callbacks = {}
|
||||
return Event:new("Gesture",
|
||||
GestureDetector:adjustGesCoordinate(touch_ges)
|
||||
)
|
||||
end -- EOF if touch_ges
|
||||
end -- EOF if deadline reached
|
||||
else
|
||||
break
|
||||
end -- EOF if not exceed wait timeout
|
||||
end -- while #timer_callbacks > 0
|
||||
else
|
||||
ok, ev = pcall(input.waitForEvent, timeout_us)
|
||||
end -- EOF if #timer_callbacks > 0
|
||||
if ok then
|
||||
break
|
||||
end
|
||||
if ev == "Waiting for input failed: timeout\n" then
|
||||
-- don't report an error on timeout
|
||||
ev = nil
|
||||
break
|
||||
elseif ev == "application forced to quit" then
|
||||
os.exit(0)
|
||||
end
|
||||
--DEBUG("got error waiting for events:", ev)
|
||||
if ev ~= "Waiting for input failed: 4\n" then
|
||||
-- we only abort if the error is not EINTR
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if ok and ev then
|
||||
if DEBUG.is_on and ev then
|
||||
DEBUG:logEv(ev)
|
||||
end
|
||||
ev = self:eventAdjustHook(ev)
|
||||
if ev.type == EV_KEY then
|
||||
DEBUG("key ev", ev)
|
||||
return self:handleKeyBoardEv(ev)
|
||||
elseif ev.type == EV_ABS or ev.type == EV_SYN then
|
||||
return self:handleTouchEv(ev)
|
||||
elseif ev.type == EV_MSC then
|
||||
return self:handleMiscEv(ev)
|
||||
else
|
||||
-- some other kind of event that we do not know yet
|
||||
return Event:new("GenericInput", ev)
|
||||
end
|
||||
elseif not ok and ev then
|
||||
return Event:new("InputError", ev)
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
helper function for formatting sequence definitions for output
|
||||
]]
|
||||
function Input:sequenceToString(sequence)
|
||||
local modifiers = {}
|
||||
local keystring = {"",""} -- first entries reserved for modifier specification
|
||||
for _, key in ipairs(sequence) do
|
||||
if type(key) == "table" then
|
||||
local alternatives = {}
|
||||
for _, alternative in ipairs(key) do
|
||||
table.insert(alternatives, alternative)
|
||||
end
|
||||
table.insert(keystring, "{")
|
||||
table.insert(keystring, table.concat(alternatives, "|"))
|
||||
table.insert(keystring, "}")
|
||||
elseif self.modifiers[key] ~= nil then
|
||||
table.insert(modifiers, key)
|
||||
else
|
||||
table.insert(keystring, key)
|
||||
end
|
||||
end
|
||||
if #modifiers then
|
||||
keystring[1] = table.concat(modifiers, "-")
|
||||
keystring[2] = "-"
|
||||
end
|
||||
return table.concat(keystring)
|
||||
end
|
||||
|
||||
-- initialize the GestureDectector
|
||||
-- so it can modify our (Input) state
|
||||
GestureDetector.input = Input
|
||||
|
||||
return Input
|
@ -1,2 +1,2 @@
|
||||
-- compatibility wrapper
|
||||
return require("ui/device").screen
|
||||
return require("device").screen
|
||||
|
Loading…
Reference in New Issue