Files
koreader/plugins/externalkeyboard.koplugin/find-keyboard.lua
Borys Lykah 9b2201a438 Initial hotpluggable keyboard handling (#9540)
* Added a new plugin external-keyboard. It listens to USB events. When keyboard is plugged in or plugged out, it updates device and input configuration accordingly.
* Added new fake events UsbDevicePlugIn and UsbDevicePlugOut that are emitted when a device is connected to a book reader that plays the role of USB host. The usage of the existing events UsbPlugIn and UsbPlugOut has not changed - they are used when a reader is connected to a host. The koreader-base has a related PR for those events.
* Did a small refactoring of initialization for the modules FocusManager and InputText. They check device keyboard capabilities on their when the module is first loaded and store it. Some of the initialization code has been extracted into functions, so that we can re-initialize them when keyboard is (dis)connected.
* Initial implementation centered around text input, and tested with USB keyboards on devices with OTG support.
* Said OTG shenanigans are so far supported on devices with debugfs & the chipidea driver, or sunxi devices.
2022-10-29 22:46:35 +02:00

103 lines
3.2 KiB
Lua

local bit = require("bit")
local ffi = require("ffi")
local lfs = require("libs/libkoreader-lfs")
-- Constants from the linux kernel input-event-codes.h
local KEY_UP = 103
local BTN_DPAD_UP = 0x220
local FindKeyboard = {}
local function count_set_bits(n)
-- Brian Kernighan's algorithm
local count = 0
while n ~= 0 do
count = count + 1
n = bit.band(n, n - 1)
end
return count
end
local function capabilities_str_to_long_bitmap_array(str)
-- The format for capabilities is at include/linux/mod_devicetable.h.
-- They are long's split by spaces. See linux/drivers/input/input.c::input_print_bitmap.
local long_bitmap_arr = {}
for c in str:gmatch "([0-9a-fA-F]+)" do
local long_bitmap = tonumber(c, 16)
table.insert(long_bitmap_arr, 1, long_bitmap)
end
return long_bitmap_arr
end
local function count_set_bits_in_array(arr)
local count = 0
for __, number in ipairs(arr) do
local count_in_number = count_set_bits(number)
count = count + count_in_number
end
return count
end
local function is_capabilities_bit_set(long_bitmap_arr, bit_offset)
local long_bitsize = ffi.sizeof("long") * 8
local arr_index = math.floor(bit_offset / long_bitsize)
local long_mask = bit.lshift(1, bit_offset % long_bitsize)
local long_bitmap = long_bitmap_arr[arr_index + 1] -- Array index starts from 1 in Lua
if long_bitmap then
return bit.band(long_bitmap, long_mask) ~= 0
else
return false
end
end
local function read_key_capabilities(sys_event_path)
local key_path = sys_event_path .. "/device/capabilities/key"
local file = io.open(key_path, "r")
if not file then
-- This should not happen - the kernel creates key capabilities file for all devices.
return nil
end
local keys_bitmap_str = file:read("l")
file:close()
return capabilities_str_to_long_bitmap_array(keys_bitmap_str)
end
local function analyze_key_capabilities(long_bitmap_arr)
-- The heuristic is that a keyboard has at least as many keys as there are alphabet letters and some more.
local keyboard_min_number_keys = 64
local keys_count = count_set_bits_in_array(long_bitmap_arr)
local is_keyboard = keys_count >= keyboard_min_number_keys
local has_dpad = is_capabilities_bit_set(long_bitmap_arr, KEY_UP) or
is_capabilities_bit_set(long_bitmap_arr, BTN_DPAD_UP)
return {
is_keyboard = is_keyboard,
has_dpad = has_dpad,
}
end
function FindKeyboard:find()
local keyboards = {}
for event_file_name in lfs.dir("/sys/class/input/") do
if event_file_name:match("event.*") then
local capabilities_long_bitmap_arr = read_key_capabilities("/sys/class/input/" .. event_file_name)
if capabilities_long_bitmap_arr then
local keyboard_info = analyze_key_capabilities(capabilities_long_bitmap_arr)
if keyboard_info.is_keyboard then
table.insert(keyboards, {
event_path = "/dev/input/" .. event_file_name,
has_dpad = keyboard_info.has_dpad
})
end
end
end
end
return keyboards
end
return FindKeyboard