require "ui/event" require "ui/device" require "ui/time" require "ui/gesturedetector" require "settings" -- constants from EV_SYN = 0 EV_KEY = 1 EV_ABS = 3 -- key press event values (KEY.value) EVENT_VALUE_KEY_PRESS = 1 EVENT_VALUE_KEY_REPEAT = 2 EVENT_VALUE_KEY_RELEASE = 0 -- Synchronization events (SYN.code). SYN_REPORT = 0 SYN_CONFIG = 1 SYN_MT_REPORT = 2 -- For multi-touch events (ABS.code). ABS_MT_SLOT = 47 ABS_MT_POSITION_X = 53 ABS_MT_POSITION_Y = 54 ABS_MT_TRACKING_ID = 57 ABS_MT_PRESSURE = 58 --[[ an interface for key presses ]] 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 ]] Input = { event_map = {}, rotation = 0, timer_callbacks = {}, } 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] [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.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" } } 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_ev = {} end function Input:init() -- Screen module must have been initilized by now. self.rotation = Screen.fb:getOrientation() if Device:hasKeyboard() then self:initKeyMap() end 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()==1 then self:initKeyMap() os.remove("emu_event") os.execute("mkfifo emu_event") input.open("emu_event") -- SDL key codes self.event_map = self.sdl_event_map else input.open("fake_events") local dev_mod = Device:getModel() if dev_mod ~= "KindleTouch" then -- event0 in KindleTouch is "WM8962 Beep Generator" (useless) input.open("/dev/input/event0") end if dev_mod ~= "KindleTouch" and dev_mod ~= "KindlePaperWhite" then -- event1 in KindleTouch is "imx-yoshi Headset" (useless) -- and we don't have event1 in KindlePaperWhite input.open("/dev/input/event1") end if dev_mod == "KindlePaperWhite" then print("Auto-detected Kindle PaperWhite") elseif dev_mod == "KindleTouch" then input.open("/dev/input/event2") -- Home button input.open("/dev/input/event3") -- touchscreen -- 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 print("Auto-detected Kindle Touch") elseif dev_mod == "Kindle4" then print("Auto-detected Kindle 4") self:adjustKindle4EventMap() elseif dev_mod == "Kindle3" then input.open("/dev/input/event2") print("Auto-detected Kindle 3") elseif dev_mod == "KindleDXG" then print("Auto-detected Kindle DXG") elseif dev_mod == "Kindle2" then print("Auto-detected Kindle 2") else print("Not supported device model!") os.exit(-1) 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:setTimeout(cb, tv_out) local item = { callback = cb, deadline = tv_out, } for k,v in ipairs(self.timer_callbacks) do if v.deadline > tv_out then table.insert(self.timer_callbacks, k, item) break end end if #self.timer_callbacks <= 0 then self.timer_callbacks[1] = item 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.rotation][keycode] then keycode = self.rotation_map[self.rotation][keycode] end if keycode == "IntoSS" or keycode == "OutOfSS" or keycode == "Charging" or keycode == "NotCharging" 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 --[[ 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. --]] function Input:handleTouchEv(ev) if ev.type == EV_SYN then if ev.code == SYN_REPORT then self.cur_ev.timev = TimeVal:new(ev.time) --self.cur_x = self.cur_ev.x or self.cur_x --self.cur_y = self.cur_ev.y or self.cur_y -- send ev to state machine local touch_ges = GestureDetector:feedEvent(self.cur_ev) --self.last_ev_timev = self.cur_ev.timev --self.cur_ev = {} if touch_ges then return Event:new("Gesture", Screen:adjustGesCoordinate(touch_ges) ) end end elseif ev.type == EV_ABS then if ev.code == ABS_MT_SLOT then self.cur_ev.slot = ev.value elseif ev.code == ABS_MT_TRACKING_ID then self.cur_ev.id = ev.value elseif ev.code == ABS_MT_POSITION_X then self.cur_ev.x = ev.value elseif ev.code == ABS_MT_POSITION_Y then self.cur_ev.y = ev.value 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", Screen: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 ev = self:eventAdjustHook(ev) if ev.type == EV_KEY then return self:handleKeyBoardEv(ev) elseif ev.type == EV_ABS or ev.type == EV_SYN then return self:handleTouchEv(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