diff --git a/base b/base index d75933ef2..c69ef86a4 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit d75933ef2d7ca0a8f113a71bccf48f440df9bb0d +Subproject commit c69ef86a42b8e788676b38b843bc8e0abd114ce2 diff --git a/frontend/device/generic/device.lua b/frontend/device/generic/device.lua index eb45d1cc0..d998ba2f9 100644 --- a/frontend/device/generic/device.lua +++ b/frontend/device/generic/device.lua @@ -569,7 +569,7 @@ function Device:exit() G_reader_settings:close() -- I/O teardown - require("ffi/input"):closeAll() + self.input.teardown() end -- Lifted from busybox's libbb/inet_cksum.c diff --git a/frontend/device/input.lua b/frontend/device/input.lua index 0f84c3c69..c8b7ac43f 100644 --- a/frontend/device/input.lua +++ b/frontend/device/input.lua @@ -195,6 +195,10 @@ local Input = { setClipboardText = function(text) _internal_clipboard_text = text or "" end, + + -- open'ed input devices hashmap (key: path, value: fd number) + -- Must be a class member, both because Input is a singleton and that state is process-wide anyway. + opened_devices = {}, } function Input:new(o) @@ -295,14 +299,74 @@ function Input:disableRotationMap() end --[[-- -Wrapper for FFI input open. +Wrapper for our Lua/C input module's open. Note that we adhere to the "." syntax here for compatibility. -@todo Clean up separation FFI/this. +The `name` argument is optional, and used for logging purposes only. --]] -function Input.open(device, is_emu_events) - return input.open(device, is_emu_events and 1 or 0) +function Input.open(path, name) + -- Make sure we don't open the same device twice. + if not Input.opened_devices[path] then + local fd = input.open(path) + if fd then + Input.opened_devices[path] = fd + if name then + logger.dbg("Opened fd", fd, "for input device", name, "@", path) + else + logger.dbg("Opened fd", fd, "for input device @", path) + end + end + -- No need to log failures, input will have raised an error already, + -- and we want to make those fatal, so we don't protect this call. + return fd + end +end + +--[[-- +Wrapper for our Lua/C input module's close. + +Note that we adhere to the "." syntax here for compatibility. +--]] +function Input.close(path) + -- Make sure we actually know about this device + local fd = Input.opened_devices[path] + if fd then + local ok, err = input.close(fd) + if ok or err == C.ENODEV then + -- Either the call succeeded, + -- or the backend had already caught an ENODEV in waitForInput and closed the fd internally. + -- (Because the EvdevInputRemove Event comes from an UsbDevicePlugOut uevent forwarded as an... *input* EV_KEY event ;)). + -- Regardless, that device is gone, so clear its spot in the hashmap. + Input.opened_devices[path] = nil + end + else + logger.warn("Tried to close an unknown input device @", path) + end +end + +--[[-- +Wrapper for our Lua/C input module's closeAll. + +Note that we adhere to the "." syntax here for compatibility. +--]] +function Input.teardown() + input.closeAll() + Input.opened_devices = {} +end + +-- Wrappers for the custom FFI implementations with no concept of paths or fd +if input.is_ffi then + -- Pass args as-is. None of 'em actually *take* arguments, but some may be invoked as methods... + function Input.open(...) + return input.open(...) + end + function Input.close(...) + return input.close(...) + end + function Input.teardown(...) + return input.closeAll(...) + end end --[[-- diff --git a/frontend/device/kindle/device.lua b/frontend/device/kindle/device.lua index ff66b5a35..b897c0a4f 100644 --- a/frontend/device/kindle/device.lua +++ b/frontend/device/kindle/device.lua @@ -3,7 +3,6 @@ local UIManager local time = require("ui/time") local lfs = require("libs/libkoreader-lfs") local logger = require("logger") -local util = require("util") -- We're going to need a few & constants... local ffi = require("ffi") @@ -11,6 +10,7 @@ local C = ffi.C require("ffi/linux_fb_h") require("ffi/linux_input_h") require("ffi/posix_h") +require("ffi/fbink_input_h") local function yes() return true end local function no() return false end -- luacheck: ignore @@ -206,6 +206,58 @@ function Kindle:supportsScreensaver() end end +function Kindle:openInputDevices() + -- Auto-detect input devices (via FBInk's fbink_input_scan) + local ok, FBInkInput = pcall(ffi.load, "fbink_input") + if not ok then + -- NOP fallback for the testsuite... + FBInkInput = { fbink_input_scan = function() end } + end + local dev_count = ffi.new("size_t[1]") + -- We care about: the touchscreen, a properly scaled stylus, pagination buttons and a home button. + local match_mask = bit.bor(C.INPUT_TOUCHSCREEN, C.INPUT_SCALED_TABLET, C.INPUT_PAGINATION_BUTTONS, C.INPUT_HOME_BUTTON) + local devices = FBInkInput.fbink_input_scan(match_mask, 0, C.SCAN_ONLY, dev_count) + if devices ~= nil then + for i = 0, tonumber(dev_count[0]) - 1 do + local dev = devices[i] + if dev.matched then + self.input.open(ffi.string(dev.path), ffi.string(dev.name)) + end + end + C.free(devices) + else + -- Auto-detection failed, warn and fall back to defaults + logger.warn("We failed to auto-detect the proper input devices, input handling may be inconsistent!") + if self.touch_dev then + -- We've got a preferred path specified for the touch panel + self.input.open(self.touch_dev) + else + -- That generally works out well enough on legacy devices... + self.input.open("/dev/input/event0") + self.input.open("/dev/input/event1") + end + end + + -- Getting the device where rotation events end up without catching a bunch of false-positives is... trickier, + -- thanks to the inane event code being used... + if self:hasGSensor() then + -- i.e., we want something that reports EV_ABS:ABS_PRESSURE that isn't *also* a pen (because those are pretty much guaranteed to report pressure...). + -- And let's add that isn't also a touchscreen to the mix, because while not true at time of writing, that's an event touchscreens sure can support... + devices = FBInkInput.fbink_input_scan(C.INPUT_ROTATION_EVENT, bit.bor(C.INPUT_TABLET, C.INPUT_TOUCHSCREEN), bit.bor(C.SCAN_ONLY, C.NO_RECAP), dev_count) + if devices ~= nil then + for i = 0, tonumber(dev_count[0]) - 1 do + local dev = devices[i] + if dev.matched then + self.input.open(ffi.string(dev.path), ffi.string(dev.name)) + end + end + C.free(devices) + end + end + + self.input.open("fake_events") +end + function Kindle:init() -- Check if the device supports deep sleep/quick boot if lfs.attributes("/sys/devices/platform/falconblk/uevent", "mode") == "file" then @@ -234,6 +286,14 @@ function Kindle:init() self.canDeepSleep = false end + -- If the device-specific init hasn't done so already (devices without keys don't), instantiate Input. + if not self.input then + self.input = require("device/input"):new{ device = self } + end + + -- Auto-detect & open input devices + self:openInputDevices() + Generic.init(self) end @@ -683,9 +743,6 @@ function Kindle2:init() device = self, event_map = require("device/kindle/event_map_keyboard"), } - self.input.open("/dev/input/event0") - self.input.open("/dev/input/event1") - self.input.open("fake_events") Kindle.init(self) end @@ -700,9 +757,6 @@ function KindleDXG:init() event_map = require("device/kindle/event_map_keyboard"), } self.keyboard_layout = require("device/kindle/keyboard_layout") - self.input.open("/dev/input/event0") - self.input.open("/dev/input/event1") - self.input.open("fake_events") Kindle.init(self) end @@ -718,9 +772,6 @@ function Kindle3:init() event_map = require("device/kindle/event_map_kindle4"), } self.keyboard_layout = require("device/kindle/keyboard_layout") - self.input.open("/dev/input/event0") - self.input.open("/dev/input/event1") - self.input.open("fake_events") Kindle.init(self) end @@ -735,9 +786,6 @@ function Kindle4:init() device = self, event_map = require("device/kindle/event_map_kindle4"), } - self.input.open("/dev/input/event0") - self.input.open("/dev/input/event1") - self.input.open("fake_events") Kindle.init(self) end @@ -757,11 +805,6 @@ function KindleTouch:init() -- 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(self.touch_dev) -- touchscreen - self.input.open("fake_events") Kindle.init(self) end @@ -775,9 +818,6 @@ function KindlePaperWhite:init() } Kindle.init(self) - - self.input.open(self.touch_dev) - self.input.open("fake_events") end function KindlePaperWhite2:init() @@ -791,9 +831,6 @@ function KindlePaperWhite2:init() } Kindle.init(self) - - self.input.open(self.touch_dev) - self.input.open("fake_events") end function KindleBasic:init() @@ -806,9 +843,6 @@ function KindleBasic:init() } Kindle.init(self) - - self.input.open(self.touch_dev) - self.input.open("fake_events") end function KindleVoyage:init() @@ -859,11 +893,7 @@ function KindleVoyage:init() Kindle.init(self) - self.input.open(self.touch_dev) - self.input.open("/dev/input/event2") -- WhisperTouch - self.input.open("fake_events") - - -- reenable WhisperTouch keys when started without framework + -- Re-enable WhisperTouch keys when started without framework if self.framework_lipc_handle then self.framework_lipc_handle:set_int_property("com.lab126.deviced", "fsrkeypadEnable", 1) self.framework_lipc_handle:set_int_property("com.lab126.deviced", "fsrkeypadPrevEnable", 1) @@ -882,9 +912,6 @@ function KindlePaperWhite3:init() } Kindle.init(self) - - self.input.open(self.touch_dev) - self.input.open("fake_events") end -- HAL for gyro orientation switches (EV_ABS:ABS_PRESSURE (?!) w/ custom values to EV_MSC:MSC_GYRO w/ our own custom values) @@ -992,21 +1019,6 @@ function KindleOasis:init() return this:handleGyroEv(ev) end end - - self.input.open(self.touch_dev) - self.input.open("/dev/input/by-path/platform-gpiokey.0-event") - - -- get rotate dev by EV=d - local std_out = io.popen("grep -e 'Handlers\\|EV=' /proc/bus/input/devices | grep -B1 'EV=d' | grep -o 'event[0-9]'", "r") - if std_out then - local rotation_dev = std_out:read("*line") - std_out:close() - if rotation_dev then - self.input.open("/dev/input/"..rotation_dev) - end - end - - self.input.open("fake_events") end -- HAL for gyro orientation switches (EV_ABS:ABS_PRESSURE (?!) w/ custom values to EV_MSC:MSC_GYRO w/ our own custom values) @@ -1118,21 +1130,6 @@ function KindleOasis2:init() return this:handleGyroEv(ev) end end - - self.input.open(self.touch_dev) - self.input.open("/dev/input/by-path/platform-gpio-keys-event") - - -- Get accelerometer device by looking for EV=d - local std_out = io.popen("grep -e 'Handlers\\|EV=' /proc/bus/input/devices | grep -B1 'EV=d' | grep -o 'event[0-9]\\{1,2\\}'", "r") - if std_out then - local rotation_dev = std_out:read("*line") - std_out:close() - if rotation_dev then - self.input.open("/dev/input/"..rotation_dev) - end - end - - self.input.open("fake_events") end function KindleOasis3:init() @@ -1201,21 +1198,6 @@ function KindleOasis3:init() return this:handleGyroEv(ev) end end - - self.input.open(self.touch_dev) - self.input.open("/dev/input/by-path/platform-gpio-keys-event") - - -- Get accelerometer device by looking for EV=d - local std_out = io.popen("grep -e 'Handlers\\|EV=' /proc/bus/input/devices | grep -B1 'EV=d' | grep -o 'event[0-9]\\{1,2\\}'", "r") - if std_out then - local rotation_dev = std_out:read("*line") - std_out:close() - if rotation_dev then - self.input.open("/dev/input/"..rotation_dev) - end - end - - self.input.open("fake_events") end function KindleBasic2:init() @@ -1229,9 +1211,6 @@ function KindleBasic2:init() } Kindle.init(self) - - self.input.open(self.touch_dev) - self.input.open("fake_events") end function KindlePaperWhite4:init() @@ -1246,19 +1225,6 @@ function KindlePaperWhite4:init() } Kindle.init(self) - - -- So, look for a goodix TS input device (c.f., #5110)... - local std_out = io.popen("grep -e 'Handlers\\|Name=' /proc/bus/input/devices | grep -A1 'goodix-ts' | grep -o 'event[0-9]'", "r") - if std_out then - local goodix_dev = std_out:read("*line") - std_out:close() - if goodix_dev then - self.touch_dev = "/dev/input/" .. goodix_dev - end - end - - self.input.open(self.touch_dev) - self.input.open("fake_events") end function KindleBasic3:init() @@ -1278,29 +1244,6 @@ function KindleBasic3:init() -- so we have to rely on contact lift detection via BTN_TOUCH:0, -- c.f., https://github.com/koreader/koreader/issues/5070 self.input.snow_protocol = true - self.input.open(self.touch_dev) - self.input.open("fake_events") -end - -local function findInputDevices() - -- Walk /sys/class/input and pick up any evdev input device with *any* EV_ABS capabilities - local devices = {} - for evdev in lfs.dir("/sys/class/input/") do - if evdev:match("event.*") then - local abs_cap = "/sys/class/input/" .. evdev .. "/device/capabilities/abs" - local f = io.open(abs_cap, "r") - if f then - local bitmap_str = f:read("l") - f:close() - if bitmap_str ~= "0" then - logger.info("Potential input device found at", evdev, "because of ABS caps:", bitmap_str) - table.insert(devices, "/dev/input/" .. evdev) - end - end - end - end - - return devices end function KindlePaperWhite5:init() @@ -1319,25 +1262,10 @@ function KindlePaperWhite5:init() self.screen:_MTK_ToggleFastMode(true) Kindle.init(self) - - -- Some HW/FW variants stash their input device without a by-path symlink... - if util.pathExists("/dev/input/by-path/platform-1001e000.i2c-event") then - self.touch_dev = "/dev/input/by-path/platform-1001e000.i2c-event" - self.input.open(self.touch_dev) - else - local devices = findInputDevices() - for _, touch in ipairs(devices) do - -- There should only be one match on the PW5 anyway... - self.touch_dev = touch - self.input.open(touch) - end - end - self.input.open("fake_events") end function KindleBasic4:init() self.screen = require("ffi/framebuffer_mxcfb"):new{device = self, debug = logger.dbg} - -- TBD, assume PW5 for now self.powerd = require("device/kindle/powerd"):new{ device = self, fl_intensity_file = "/sys/class/backlight/fp9966-bl1/brightness", @@ -1351,20 +1279,6 @@ function KindleBasic4:init() self.screen:_MTK_ToggleFastMode(true) Kindle.init(self) - - -- Some HW/FW variants stash their input device without a by-path symlink... - if util.pathExists("/dev/input/by-path/platform-1001e000.i2c-event") then - self.touch_dev = "/dev/input/by-path/platform-1001e000.i2c-event" - self.input.open(self.touch_dev) - else - local devices = findInputDevices() - for _, touch in ipairs(devices) do - -- There should only be one match on the PW5 anyway... - self.touch_dev = touch - self.input.open(touch) - end - end - self.input.open("fake_events") end function KindleScribe:init() @@ -1384,11 +1298,11 @@ function KindleScribe:init() hall_file = "/sys/devices/platform/eink_hall/hall_enable", } - Kindle.init(self) - -- Enable the so-called "fast" mode, so as to prevent the driver from silently promoting refreshes to REAGL. self.screen:_MTK_ToggleFastMode(true) + Kindle.init(self) + --- @note The same quirks as on the Oasis 2 and 3 apply ;). local haslipc, lipc = pcall(require, "liblipclua") if haslipc and lipc then @@ -1424,23 +1338,9 @@ function KindleScribe:init() return this:handleGyroEv(ev) end end - -- Get accelerometer device - local std_out = io.popen("grep -A4 'acc' /proc/bus/input/devices | grep -o 'event[0-9]'", "r") - if std_out then - local gyro_dev = std_out:read("*line") - std_out:close() - logger.dbg("gyro_dev", gyro_dev) - if gyro_dev then - self.input.open("/dev/input/"..gyro_dev) - end - end - - self.input.open(self.touch_dev) - self.input.open("fake_events") -- Setup pen input self.input.wacom_protocol = true - self.input.open("/dev/input/event4") end function KindleTouch:exit() diff --git a/frontend/device/kobo/device.lua b/frontend/device/kobo/device.lua index 4cc84f9c5..b9ce0a4ae 100644 --- a/frontend/device/kobo/device.lua +++ b/frontend/device/kobo/device.lua @@ -15,6 +15,7 @@ local C = ffi.C require("ffi/linux_fb_h") require("ffi/linux_input_h") require("ffi/posix_h") +require("ffi/fbink_input_h") local function yes() return true end local function no() return false end @@ -140,17 +141,16 @@ local Kobo = Generic:extend{ battery_sysfs = "/sys/class/power_supply/mc13892_bat", -- Stable path to the NTX input device ntx_dev = "/dev/input/event0", + ntx_fd = nil, -- Stable path to the Touch input device touch_dev = "/dev/input/event1", - -- Stable path to the Power Button input device - power_dev = nil, -- Event code to use to detect contact pressure pressure_event = nil, -- Device features multiple CPU cores isSMP = no, -- Device supports "eclipse" waveform modes (i.e., optimized for nightmode). hasEclipseWfm = no, - -- Device ships with various hardware revisions under the same device code, requiring automatic hardware detection... + -- Device ships with various hardware revisions under the same device code, requiring automatic hardware detection (PMIC & FL)... automagic_sysfs = false, -- The standard "standby" power state standby_state = "standby", @@ -417,8 +417,6 @@ local KoboEuropa = Kobo:extend{ display_dpi = 227, boot_rota = C.FB_ROTATE_CCW, battery_sysfs = "/sys/class/power_supply/battery", - ntx_dev = "/dev/input/by-path/platform-ntx_event0-event", - touch_dev = "/dev/input/by-path/platform-0-0010-event", isSMP = yes, } @@ -448,8 +446,6 @@ local KoboCadmus = Kobo:extend{ battery_sysfs = "/sys/class/power_supply/battery", hasAuxBattery = yes, aux_battery_sysfs = "/sys/class/misc/cilix", - ntx_dev = "/dev/input/by-path/platform-ntx_event0-event", - touch_dev = "/dev/input/by-path/platform-0-0010-event", isSMP = yes, -- Much like the Libra 2, there are at least two different HW revisions, with different PMICs... automagic_sysfs = true, @@ -503,7 +499,6 @@ local KoboGoldfinch = Kobo:extend{ nl_inverted = true, }, battery_sysfs = "/sys/class/power_supply/battery", - power_dev = "/dev/input/by-path/platform-bd71828-pwrkey-event", -- Board is eerily similar to the Libra 2, so, it inherits the same quirks... -- c.f., https://github.com/koreader/koreader/issues/9552#issuecomment-1293000313 hasReliableMxcWaitFor = no, @@ -530,9 +525,6 @@ local KoboCondor = Kobo:extend{ nl_inverted = true, }, battery_sysfs = "/sys/class/power_supply/bd71827_bat", - touch_dev = "/dev/input/by-path/platform-2-0010-event", - ntx_dev = "/dev/input/by-path/platform-ntx_event0-event", - power_dev = "/dev/input/by-path/platform-bd71828-pwrkey.6.auto-event", isSMP = yes, } @@ -557,9 +549,9 @@ local KoboMonza = Kobo:extend{ nl_max = 10, nl_inverted = true, }, + battery_sysfs = "/sys/class/power_supply/bd71827_bat", isSMP = yes, hasColorScreen = yes, - automagic_sysfs = true, } -- Kobo Clara B/W: @@ -579,7 +571,7 @@ local KoboSpaBW = Kobo:extend{ nl_max = 10, nl_inverted = true, }, - automagic_sysfs = true, + battery_sysfs = "/sys/class/power_supply/bd71827_bat", } -- Kobo Clara Colour: @@ -599,9 +591,9 @@ local KoboSpaColour = Kobo:extend{ nl_max = 10, nl_inverted = true, }, + battery_sysfs = "/sys/class/power_supply/bd71827_bat", isSMP = yes, hasColorScreen = yes, - automagic_sysfs = true, } function Kobo:setupChargingLED() @@ -623,7 +615,7 @@ function Kobo:getKeyRepeat() self.key_repeat = ffi.new("unsigned int[?]", C.REP_CNT) if C.ioctl(self.ntx_fd, C.EVIOCGREP, self.key_repeat) < 0 then local err = ffi.errno() - logger.warn("Device:getKeyRepeat: EVIOCGREP ioctl failed:", ffi.string(C.strerror(err))) + logger.warn("Device:getKeyRepeat: EVIOCGREP ioctl on fd", self.ntx_fd, "failed:", ffi.string(C.strerror(err))) return false else logger.dbg("Key repeat is set up to repeat every", self.key_repeat[C.REP_PERIOD], "ms after a delay of", self.key_repeat[C.REP_DELAY], "ms") @@ -637,14 +629,14 @@ function Kobo:disableKeyRepeat() local key_repeat = ffi.new("unsigned int[?]", C.REP_CNT) if C.ioctl(self.ntx_fd, C.EVIOCSREP, key_repeat) < 0 then local err = ffi.errno() - logger.warn("Device:disableKeyRepeat: EVIOCSREP ioctl failed:", ffi.string(C.strerror(err))) + logger.warn("Device:disableKeyRepeat: EVIOCSREP ioctl on fd", self.ntx_fd, "failed:", ffi.string(C.strerror(err))) end end function Kobo:restoreKeyRepeat() if C.ioctl(self.ntx_fd, C.EVIOCSREP, self.key_repeat) < 0 then local err = ffi.errno() - logger.warn("Device:restoreKeyRepeat: EVIOCSREP ioctl failed:", ffi.string(C.strerror(err))) + logger.warn("Device:restoreKeyRepeat: EVIOCSREP ioctl on fd", self.ntx_fd, "failed:", ffi.string(C.strerror(err))) end end @@ -661,7 +653,7 @@ function Kobo:toggleKeyRepeat(toggle) -- Check the current (kernel) state to know what to do if C.ioctl(self.ntx_fd, C.EVIOCGREP, key_repeat) < 0 then local err = ffi.errno() - logger.warn("Device:toggleKeyRepeat: EVIOCGREP ioctl failed:", ffi.string(C.strerror(err))) + logger.warn("Device:toggleKeyRepeat: EVIOCGREP ioctl on fd", self.ntx_fd, "failed:", ffi.string(C.strerror(err))) return false else if key_repeat[C.REP_DELAY] == 0 and key_repeat[C.REP_PERIOD] == 0 then @@ -674,7 +666,7 @@ function Kobo:toggleKeyRepeat(toggle) if C.ioctl(self.ntx_fd, C.EVIOCSREP, key_repeat) < 0 then local err = ffi.errno() - logger.warn("Device:toggleKeyRepeat: EVIOCSREP ioctl failed:", ffi.string(C.strerror(err))) + logger.warn("Device:toggleKeyRepeat: EVIOCSREP ioctl on fd", self.ntx_fd, "failed:", ffi.string(C.strerror(err))) return false end @@ -758,46 +750,6 @@ function Kobo:init() self.frontlight_settings.frontlight_mixer = "/sys/class/backlight/tlc5947_bl/color" end end - - -- Touch panel input - if util.fileExists("/dev/input/by-path/platform-2-0010-event") then - -- Elan (HWConfig TouchCtrl is ekth6) on i2c bus 2 - self.touch_dev = "/dev/input/by-path/platform-2-0010-event" - elseif util.fileExists("/dev/input/by-path/platform-1-0010-event") then - -- Elan (HWConfig TouchCtrl is ekth6) on i2c bus 1 - self.touch_dev = "/dev/input/by-path/platform-1-0010-event" - elseif util.fileExists("/dev/input/by-path/platform-0-0010-event") then - -- Elan (HWConfig TouchCtrl is ekth6) on i2c bus 0 - self.touch_dev = "/dev/input/by-path/platform-0-0010-event" - else - self.touch_dev = "/dev/input/event1" - end - - -- Physical buttons & synthetic NTX events - if util.fileExists("/dev/input/by-path/platform-gpio-keys-event") then - -- Libra 2 w/ a BD71828 PMIC - self.ntx_dev = "/dev/input/by-path/platform-gpio-keys-event" - elseif util.fileExists("/dev/input/by-path/platform-ntx_event0-event") then - -- MTK, sunxi & Mk. 7 - self.ntx_dev = "/dev/input/by-path/platform-ntx_event0-event" - elseif util.fileExists("/dev/input/by-path/platform-mxckpd-event") then - -- circa Mk. 5 i.MX - self.ntx_dev = "/dev/input/by-path/platform-mxckpd-event" - else - self.ntx_dev = "/dev/input/event0" - end - - -- Power button (this usually ends up in ntx_dev, except with some PMICs) - if util.fileExists("/dev/input/by-path/platform-bd71828-pwrkey-event") then - -- Libra 2 & Nia w/ a BD71828 PMIC - self.power_dev = "/dev/input/by-path/platform-bd71828-pwrkey-event" - elseif util.fileExists("/dev/input/by-path/platform-bd71828-pwrkey.4.auto-event") then - -- Sage w/ a BD71828 PMIC - self.power_dev = "/dev/input/by-path/platform-bd71828-pwrkey.4.auto-event" - elseif util.fileExists("/dev/input/by-path/platform-bd71828-pwrkey.6.auto-event") then - -- MTK w/ a BD71828 PMIC - self.power_dev = "/dev/input/by-path/platform-bd71828-pwrkey.6.auto-event" - end end -- NOTE: i.MX5 devices have a wonky RTC that doesn't like alarms set further away that UINT16_MAX seconds from now... @@ -904,14 +856,41 @@ function Kobo:init() -- And then handle the extra shenanigans if necessary. self:initEventAdjustHooks() - -- Various HW Buttons, Switches & Synthetic NTX events - self.ntx_fd = self.input.open(self.ntx_dev) - -- Dedicated Power Button input device (if any) - if self.power_dev then - self.input.open(self.power_dev) + -- Auto-detect input devices (via FBInk's fbink_input_scan) + local ok, FBInkInput = pcall(ffi.load, "fbink_input") + if not ok then + -- NOP fallback for the testsuite... + FBInkInput = { fbink_input_scan = NOP } end - -- Touch panel - self.input.open(self.touch_dev) + local dev_count = ffi.new("size_t[1]") + -- We care about: the touchscreen, the stylus, the power button, the sleep cover, and pagination buttons + -- (and technically rotation events, but we'll get it with the device that provides the buttons on NTX). + -- We exclude keyboards to play nice with the ExternalKeyboard plugin, which will handle potential keyboards on its own. + local match_mask = bit.bor(C.INPUT_TOUCHSCREEN, C.INPUT_TABLET, C.INPUT_POWER_BUTTON, C.INPUT_SLEEP_COVER, C.INPUT_PAGINATION_BUTTONS) + local devices = FBInkInput.fbink_input_scan(match_mask, C.INPUT_KEYBOARD, C.SCAN_ONLY, dev_count) + if devices ~= nil then + for i = 0, tonumber(dev_count[0]) - 1 do + local dev = devices[i] + if dev.matched then + -- We need to single out whichever device provides pagination buttons or sleep cover events, as we'll want to tweak key repeat there... + -- The first one will do, as it's extremely likely to be event0, and that's pretty fairly set in stone on NTX boards. + if (bit.band(dev.type, C.INPUT_PAGINATION_BUTTONS) ~= 0 or bit.band(dev.type, C.INPUT_SLEEP_COVER) ~= 0) and not self.ntx_fd then + self.ntx_fd = self.input.open(ffi.string(dev.path), ffi.string(dev.name)) + else + self.input.open(ffi.string(dev.path), ffi.string(dev.name)) + end + end + end + C.free(devices) + else + -- Auto-detection failed, warn and fall back to defaults + logger.warn("We failed to auto-detect the proper input devices, input handling may be inconsistent!") + -- Various HW Buttons, Switches & Synthetic NTX events + self.ntx_fd = self.input.open(self.ntx_dev) + -- Touch panel + self.input.open(self.touch_dev) + end + -- NOTE: On devices with a gyro, there may be a dedicated input device outputting the raw accelerometer data -- (3-Axis Orientation/Motion Detection). -- We skip it because we don't need it (synthetic rotation change events are sent to the main ntx input device), diff --git a/frontend/device/pocketbook/device.lua b/frontend/device/pocketbook/device.lua index fdb5717c4..e49aee33e 100644 --- a/frontend/device/pocketbook/device.lua +++ b/frontend/device/pocketbook/device.lua @@ -56,9 +56,7 @@ local PocketBook = Generic:extend{ -- Works same as input.event_map, but for raw input EV_KEY translation keymap = { [scan] = event }, }]] - -- Runtime state: whether raw input is actually used - --- @fixme: Never actually set anywhere? - is_using_raw_input = nil, + -- We'll nil raw_input at runtime if it cannot be used. -- InkView may have started translating button codes based on rotation on newer devices... -- That historically wasn't the case, hence this defaulting to false. @@ -242,7 +240,9 @@ function PocketBook:init() -- NOTE: This all happens in ffi/input_pocketbook.lua self._model_init() - if (not self.input.raw_input) or (not pcall(self.input.open, self.input, self.raw_input)) then + -- NOTE: This is the odd one out actually calling input.open as a *method*, + -- which the imp supports to get access to self.input.raw_input + if (not self.input.raw_input) or (not pcall(self.input.open, self.input)) then inkview.OpenScreen() -- Raw mode open failed (no permissions?), so we'll run the usual way. -- Disable touch coordinate translation as inkview will do that. diff --git a/frontend/ui/uimanager.lua b/frontend/ui/uimanager.lua index 3e643ac4c..cf793280d 100644 --- a/frontend/ui/uimanager.lua +++ b/frontend/ui/uimanager.lua @@ -62,11 +62,15 @@ function UIManager:init() UsbDevicePlugIn = function(input_event) -- Retrieve the argument set by Input:handleKeyBoardEv local evdev = table.remove(Input.fake_event_args[input_event]) - self:broadcastEvent(Event:new("EvdevInputInsert", evdev)) + local path = "/dev/input/event" .. tostring(evdev) + + self:broadcastEvent(Event:new("EvdevInputInsert", path)) end, UsbDevicePlugOut = function(input_event) local evdev = table.remove(Input.fake_event_args[input_event]) - self:broadcastEvent(Event:new("EvdevInputRemove", evdev)) + local path = "/dev/input/event" .. tostring(evdev) + + self:broadcastEvent(Event:new("EvdevInputRemove", path)) end, } self.poweroff_action = function() diff --git a/plugins/externalkeyboard.koplugin/main.lua b/plugins/externalkeyboard.koplugin/main.lua index 9fd64d385..7a43f2130 100644 --- a/plugins/externalkeyboard.koplugin/main.lua +++ b/plugins/externalkeyboard.koplugin/main.lua @@ -1,5 +1,4 @@ local Event = require("ui/event") -local FindKeyboard = require("find-keyboard") local Device = require("device") local InfoMessage = require("ui/widget/infomessage") local InputText = require("ui/widget/inputtext") @@ -14,6 +13,7 @@ local _ = require("gettext") local ffi = require("ffi") local C = ffi.C require("ffi/posix_h") +require("ffi/fbink_input_h") -- The include/linux/usb/role.h calls the USB roles "host" and "device". local USB_ROLE_DEVICE = "device" @@ -224,18 +224,17 @@ function ExternalKeyboard:onExit() end end -function ExternalKeyboard:_onEvdevInputInsert(evdev) - self:setupKeyboard("/dev/input/event" .. tostring(evdev)) +function ExternalKeyboard:_onEvdevInputInsert(event_path) + self:setupKeyboard(event_path) end -function ExternalKeyboard:onEvdevInputInsert(evdev) +function ExternalKeyboard:onEvdevInputInsert(path) -- Leave time for the kernel to actually create the device - UIManager:scheduleIn(0.5, self._onEvdevInputInsert, self, evdev) + UIManager:scheduleIn(0.5, self._onEvdevInputInsert, self, path) end -function ExternalKeyboard:_onEvdevInputRemove(evdev) +function ExternalKeyboard:_onEvdevInputRemove(event_path) -- Check that a keyboard we know about really was disconnected. Another input device could've been unplugged. - local event_path = "/dev/input/event" .. tostring(evdev) if not ExternalKeyboard.keyboard_fds[event_path] then logger.dbg("ExternalKeyboard:onEvdevInputRemove:", event_path, "was not a keyboard we knew about") return @@ -248,6 +247,9 @@ function ExternalKeyboard:_onEvdevInputRemove(evdev) return end + -- Close our Input handle on it + Device.input.close(event_path) + ExternalKeyboard.keyboard_fds[event_path] = nil ExternalKeyboard.connected_keyboards = ExternalKeyboard.connected_keyboards - 1 logger.dbg("ExternalKeyboard: USB keyboard", event_path, "was disconnected; total:", ExternalKeyboard.connected_keyboards) @@ -277,34 +279,84 @@ function ExternalKeyboard:_onEvdevInputRemove(evdev) self:_broadcastDisconnected() end +function ExternalKeyboard:onEvdevInputRemove(path) + UIManager:scheduleIn(0.5, self._onEvdevInputRemove, self, path) +end + ExternalKeyboard._broadcastDisconnected = UIManager:debounce(0.5, false, function() InputText.initInputEvents() UIManager:broadcastEvent(Event:new("PhysicalKeyboardDisconnected")) end) +-- Implement FindKeyboard:find & check via FBInkInput +local function findKeyboards() + local keyboards = {} + + local FBInkInput = ffi.load("fbink_input") + local dev_count = ffi.new("size_t[1]") + local devices = FBInkInput.fbink_input_scan(C.INPUT_KEYBOARD, 0, C.SCAN_ONLY, dev_count) + if devices ~= nil then + for i = 0, tonumber(dev_count[0]) - 1 do + local dev = devices[i] + if dev.matched then + -- Check if it provides a DPad, too. + local has_dpad = bit.band(dev.type, C.INPUT_DPAD) ~= 0 + table.insert(keyboards, { event_path = ffi.string(dev.path), has_dpad = has_dpad }) + end + end + C.free(devices) + end + + return keyboards +end + +local function checkKeyboard(path) + local keyboard + + local FBInkInput = ffi.load("fbink_input") + local dev = FBInkInput.fbink_input_check(path, C.INPUT_KEYBOARD, 0, C.SCAN_ONLY) + if dev ~= nil then + if dev.matched then + keyboard = { + event_path = ffi.string(dev.path), + has_dpad = bit.band(dev.type, C.INPUT_DPAD) ~= 0 + } + end + C.free(dev) + end + + return keyboard +end + -- The keyboard events with the same key codes would override the original events. -- That may cause embedded buttons to lose their original function and produce letters, -- as we cannot tell which device a key press comes from. function ExternalKeyboard:findAndSetupKeyboards() - local keyboards = FindKeyboard:find() + local keyboards = findKeyboards() -- A USB keyboard may be recognized as several devices under a hub. And several of them may -- have keyboard capabilities set. Yet, only one would emit the events. The solution is to open all of them. for __, keyboard_info in ipairs(keyboards) do - self:setupKeyboard(keyboard_info.event_path) + self:setupKeyboard(keyboard_info) end end -function ExternalKeyboard:onEvdevInputRemove(evdev) - UIManager:scheduleIn(0.5, self._onEvdevInputRemove, self, evdev) -end +function ExternalKeyboard:setupKeyboard(data) + local keyboard_info + if type(data) == "table" then + -- We came from findAndSetupKeyboards, no need to-re-check the device + keyboard_info = data + else + -- We came from a USB hotplug event handler, check the specified path + local event_path = data -function ExternalKeyboard:setupKeyboard(event_path) - local keyboard_info = FindKeyboard:check(event_path:match(".+/(.+)")) -- FindKeyboard only wants eventN, not the full path - if not keyboard_info then - logger.dbg("ExternalKeyboard:setupKeyboard:", event_path, "doesn't look like a keyboard") - return + keyboard_info = checkKeyboard(event_path) + if not keyboard_info then + logger.dbg("ExternalKeyboard:setupKeyboard:", event_path, "doesn't look like a keyboard") + return + end end + local has_dpad_func = Device.hasDPad logger.dbg("ExternalKeyboard:setupKeyboard", keyboard_info.event_path, "has_dpad", keyboard_info.has_dpad)