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 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() 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