-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuinput.lua
More file actions
242 lines (199 loc) · 5.75 KB
/
Copy pathuinput.lua
File metadata and controls
242 lines (199 loc) · 5.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
local evdev = require "evdev"
local ecodes = evdev.ecodes
local util = evdev._util
local create_uinput = evdev._core.create_uinput
local find_device = evdev.devices.find
local tbl_copy = util.tbl_copy
local tbl_keys = util.tbl_keys
local tbl_update = util.tbl_update
local validate = util.validate
local fmt = string.format
---@type evdev.UInput
---@diagnostic disable-next-line: missing-fields
local UInput = {}
-- stylua: ignore
---@type evdev.deviceInfo
local defaults = {
path = "/dev/uinput",
name = "Lua Virtual Device",
bustype = 3, -- BUS_USB
vendor = 0x1209,
product = 0xE7DE,
version = 1,
}
---@type fun(dev:evdev.UInput, fname:string, ...):...
local function call_uinput(ui, fname, ...)
local core = rawget(ui, "_core")
if not core then
return nil, "uinput device is closed"
end
return core[fname](core, ...)
end
function UInput:__index(k)
local v = rawget(UInput, k)
if v then
return v
end
if not rawget(self, "_metadata") then
local md, err = call_uinput(self, "info")
if not md then
error(err, 3)
end
tbl_update(self, find_device(md.path))
self._metadata = md
end
return rawget(self, k)
end
local function is_metacode(name)
return name:match("_MAX$")
or name:match("_CNT$")
or name:match("_RESERVED$")
or name:match("_MIN_")
or name:match("_MAX_")
end
---@type table<string,{[integer]:true}>
local allowed = { keys = {}, rels = {}, event_types = {} }
---@type table<string,{[integer]:true}>
local ignored = { keys = {}, rels = {}, event_types = {} }
local function collect_codes()
-- stylua: ignore
local families = {
{ prefix = "KEY_", name = "keys" },
{ prefix = "BTN_", name = "keys" },
{ prefix = "REL_", name = "rels" },
{ prefix = "EV_" , name = "event_types" },
}
for name, code in pairs(ecodes) do
for _, family in ipairs(families) do
if name:sub(1, #family.prefix) == family.prefix then
local t = is_metacode(name) and ignored or allowed
t[family.name][code] = true
break
end
end
end
end
local function is_repeatable_key(code)
for name, c in pairs(ecodes) do
if c == code and name:sub(1, 4) == "KEY_" and not is_metacode(name) then
return true
end
end
return false
end
local function has_repeatable_key(keys)
for _, code in ipairs(keys or {}) do
if is_repeatable_key(code) then
return true
end
end
return false
end
---@type fun(spec:evdev.uinputSpec,name:string,expected:string)
local function normalize_code_list(spec, name, expected)
local values = spec[name]
if values == nil then
return
end
validate("spec." .. name, values, "table")
local positions = {}
local filtered = tbl_copy(values)
for i, v in ipairs(values) do
validate("spec." .. name .. "[" .. i .. "]", v, "number")
if ignored[name][v] then
positions[#positions + 1] = i
elseif not allowed[name][v] then
error(fmt("spec.%s[%d]: expected a valid %s code, got %s", name, i, expected, tostring(v)), 2)
end
end
for i = #positions, 1, -1 do
table.remove(filtered, positions[i])
end
spec[name] = filtered
end
---@type fun(spec:evdev.uinputSpec):(spec:evdev.uinputSpec)
local function normalize(spec)
validate("spec", spec, "table", true)
spec = tbl_copy(spec or {})
validate("spec.bustype", spec.bustype, "number", true)
validate("spec.vendor", spec.vendor, "number", true)
validate("spec.product", spec.product, "number", true)
validate("spec.version", spec.version, "number", true)
validate("spec.name", spec.name, "string", true)
validate("spec.path", spec.path, "string", true)
for k, v in pairs(defaults) do
if spec[k] == nil then
spec[k] = v
end
end
normalize_code_list(spec, "keys", "KEY_* or BTN_*")
normalize_code_list(spec, "rels", "REL_*")
normalize_code_list(spec, "event_types", "EV_*")
spec.keys = spec.keys and spec.keys or tbl_keys(allowed.keys)
spec.rels = spec.rels and spec.rels or tbl_keys(allowed.rels)
if spec.event_types == nil then
local event_types = { ecodes.EV_SYN }
local has_keys = spec.keys and #spec.keys > 0
local has_rels = spec.rels and #spec.rels > 0
event_types[#event_types + 1] = has_keys and ecodes.EV_KEY or nil
event_types[#event_types + 1] = has_rels and ecodes.EV_REL or nil
event_types[#event_types + 1] = has_repeatable_key(spec.keys) and ecodes.EV_REP or nil
spec.event_types = event_types
end
return spec
end
function UInput:close()
local core = rawget(self, "_core")
if core then
local ok, err = core:close()
if not ok then
return nil, err
end
self._core = nil
end
return true
end
function UInput:emit(type, code, value)
validate("type", type, "number")
validate("code", code, "number")
validate("value", value, "number")
return call_uinput(self, "emit", type, code, value)
end
function UInput:set_repeat(delay, period)
validate("delay", delay, "number")
validate("period", period, "number")
return call_uinput(self, "set_repeat", delay, period)
end
function UInput:get_repeat()
local delay, period = call_uinput(self, "get_repeat")
if not delay then
return nil, nil, period
end
return delay, period
end
function UInput:is_open()
local core = rawget(self, "_core")
return core ~= nil and core:is_open()
end
function UInput:sync()
return call_uinput(self, "sync")
end
function UInput:fd()
return call_uinput(self, "fd")
end
---@type evdev.uinput
local M = {}
function M.create(spec)
local ui, err = create_uinput(normalize(spec))
if not ui then
return nil, err
end
return setmetatable({ _core = ui }, UInput)
end
---@diagnostic disable-next-line: undefined-global
if _TEST then
---@diagnostic disable-next-line: inject-field
M._normalize = normalize
end
collect_codes()
return M