xinput_wrapper 0.5.1 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +2 -3
- data.tar.gz.sig +0 -0
- data/lib/xinput_wrapper.rb +247 -34
- metadata +33 -33
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 63c289d1a4d26153d10de5a99fa22fa6ef7ca68a1c4f1173a2fc408885912031
|
4
|
+
data.tar.gz: d3cdfaccae1ecb33dd108d53cbb0ba28d64033de2c9b2f2c5d4df72c1dae524f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7cc1c92283066abcd617db362b3769e384e4bce652fb937fe60a52c0d4b3f95b0435b4c5841738b1ccf959afa44a2cb81772d446ce0145e991fe537b40ac63a4
|
7
|
+
data.tar.gz: 4ef3bb300098d1f60efc6420fbe27b8769f402960a853a7fa889a8e9b084eae747bb53c122f1bbc47894b9e289568c52dd381490e27a798bf08a04f103813c19
|
checksums.yaml.gz.sig
CHANGED
@@ -1,3 +1,2 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
3+Q�d/M��0�����07�2��~v�l�S�,��J�l��2��H��~��F$�FPAM���m���b���ցC�܁Ē�[��_��S�����H9h�;߿z�:�e��di�gZ�z_��굹�6�/�He���a} ��l:���*O�}��ZA��j�-l�����Ago��sD������.H���j6� 9��g4,����,^4ȳ�4����ۋ��
|
1
|
+
��Z��b$�y�H,���侪gk�Ҏ�N��'�]1GԔf��.Ou�5�?X:?�9�yd'"/<�MG�}ۻ�K��N)��h6�j�M7R
|
2
|
+
"�țk�O��'��-�Z�.
|
data.tar.gz.sig
CHANGED
Binary file
|
data/lib/xinput_wrapper.rb
CHANGED
@@ -2,14 +2,44 @@
|
|
2
2
|
|
3
3
|
# file: xinput_wrapper.rb
|
4
4
|
|
5
|
+
# Captures keyboard or mouse events using program xinput.
|
6
|
+
|
7
|
+
|
8
|
+
# note: To display a list of xinput devices, run the
|
9
|
+
# xinput program without any options.
|
10
|
+
|
5
11
|
require 'c32'
|
6
12
|
|
7
13
|
|
14
|
+
MOTION = 6
|
15
|
+
RAWKEY_PRESS = 13
|
16
|
+
RAWKEY_RELEASE = 14
|
17
|
+
RAWBUTTON_PRESS = 15
|
18
|
+
RAWBUTTON_RELEASE = 16
|
19
|
+
BUTTONS = %i(left middle right scrollup scrolldown)
|
20
|
+
|
21
|
+
|
8
22
|
class XInputWrapper
|
9
23
|
using ColouredText
|
24
|
+
|
25
|
+
attr_accessor :stop
|
10
26
|
|
11
|
-
|
27
|
+
# device list:
|
28
|
+
# 3 = Virtual core keyboard
|
29
|
+
# 4 = Virtual core XTEST pointer (active when using VNC)
|
30
|
+
# 5 = Virtual core XTEST keyboard (active when using VNC)
|
31
|
+
# 10 = USB Optical Mouse (locally attached)
|
32
|
+
# 11 = Microsoft Wired Keyboard 600 (locally attached)
|
33
|
+
#
|
34
|
+
|
35
|
+
# keys - add the keys you want to be captured. If empty then
|
36
|
+
# all keys are captured.
|
37
|
+
|
38
|
+
def initialize(device: nil, verbose: true, lookup: {}, debug: false,
|
39
|
+
callback: nil, keys: [] )
|
12
40
|
|
41
|
+
@callback, @keys = callback, keys
|
42
|
+
|
13
43
|
# defaults to QWERTY keyboard layout
|
14
44
|
@modifiers = {
|
15
45
|
62 => :shift, # right control
|
@@ -74,31 +104,113 @@ class XInputWrapper
|
|
74
104
|
}.merge(@modifiers).merge(lookup)
|
75
105
|
|
76
106
|
@device, @verbose, @debug = device, verbose, debug
|
107
|
+
@mouse_pos = [0, 0]
|
77
108
|
|
78
109
|
end
|
79
110
|
|
80
111
|
def listen()
|
81
|
-
|
112
|
+
|
113
|
+
@stop = false
|
114
|
+
|
82
115
|
command = "xinput test-xi2 --root #{@device}"
|
83
116
|
|
84
117
|
type = 0
|
85
118
|
raw_keys = []
|
119
|
+
t1 = Time.now
|
120
|
+
lines = []
|
86
121
|
|
87
122
|
IO.popen(command).each_line do |x|
|
88
123
|
|
124
|
+
break if @stop
|
89
125
|
#print "GOT ", x
|
90
|
-
|
126
|
+
if x[/EVENT type \d \(Motion\)/] and (Time.now > (t1 + 0.06125)) then
|
127
|
+
|
128
|
+
type = x[/EVENT type (\d+)/,1].to_i
|
129
|
+
|
130
|
+
r = lines.join[/^\s+root: (\d+\.\d{2}\/\d+\.\d{2})/,1]
|
131
|
+
|
132
|
+
if r then
|
133
|
+
|
134
|
+
x1, y1 = r.split('/').map(&:to_f)
|
135
|
+
puts "x1: %s y1: %s" % [x1, y1] if @debug
|
136
|
+
on_mousemove(x1, y1)
|
137
|
+
@callback.on_mousemove(x1, y1) if @callback
|
138
|
+
@mouse_pos = [x1,y1]
|
139
|
+
t1 = Time.now
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
lines = [x]
|
144
|
+
|
145
|
+
elsif x[/EVENT type \d+ \(Raw(?:Key|Button)(?:Release|Press)\)/]
|
146
|
+
|
147
|
+
type = x[/EVENT type (\d+)/,1].to_i
|
91
148
|
|
92
|
-
|
93
|
-
next unless type == 13 or type == 14
|
149
|
+
lines = [x]
|
94
150
|
|
95
|
-
|
96
|
-
|
151
|
+
|
152
|
+
elsif [MOTION, RAWKEY_PRESS, RAWKEY_RELEASE, RAWBUTTON_PRESS,
|
153
|
+
RAWBUTTON_RELEASE].include? type
|
154
|
+
|
155
|
+
lines << x
|
156
|
+
|
157
|
+
if x == "\n" then
|
158
|
+
case lines.first[/(?<=EVENT type )\d+/].to_i
|
159
|
+
when RAWKEY_PRESS
|
160
|
+
|
161
|
+
r = lines.join[/detail: (\d+)/,1]
|
162
|
+
|
163
|
+
keycode = r.to_i if r
|
164
|
+
|
165
|
+
type = lines.join[/EVENT type (\d+)/,1] .to_i
|
166
|
+
|
167
|
+
when RAWKEY_RELEASE
|
168
|
+
|
169
|
+
r = lines.join[/detail: (\d+)/,1]
|
170
|
+
|
171
|
+
keycode = r.to_i if r
|
172
|
+
|
173
|
+
type = lines.join[/EVENT type (\d+)/,1] .to_i
|
174
|
+
|
175
|
+
|
176
|
+
when RAWBUTTON_PRESS
|
177
|
+
|
178
|
+
r = lines.join[/detail: (\d+)/,1]
|
179
|
+
|
180
|
+
buttoncode = r.to_i if r
|
181
|
+
|
182
|
+
type = lines.join[/EVENT type (\d+)/,1] .to_i
|
183
|
+
|
184
|
+
when RAWBUTTON_RELEASE
|
185
|
+
|
186
|
+
r = lines.join[/detail: (\d+)/,1]
|
187
|
+
|
188
|
+
buttoncode = r.to_i if r
|
189
|
+
|
190
|
+
type = lines.join[/EVENT type (\d+)/,1] .to_i
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
else
|
196
|
+
next
|
197
|
+
end
|
198
|
+
|
199
|
+
else
|
200
|
+
next
|
201
|
+
end
|
202
|
+
|
203
|
+
next unless keycode or buttoncode
|
204
|
+
puts 'keycode: ' + keycode.inspect if @debug
|
205
|
+
puts 'buttoncode: ' + buttoncode.inspect if @debug
|
97
206
|
|
98
207
|
# type = 13 means a key has been pressed
|
99
|
-
if type ==
|
208
|
+
if type == RAWKEY_PRESS then
|
209
|
+
|
210
|
+
anykey_press()
|
100
211
|
|
101
|
-
if @modifiers.include? raw_keys.last or
|
212
|
+
if @modifiers.include? raw_keys.last or \
|
213
|
+
@modifiers.include? keycode then
|
102
214
|
raw_keys << keycode
|
103
215
|
end
|
104
216
|
|
@@ -120,10 +232,29 @@ class XInputWrapper
|
|
120
232
|
|
121
233
|
puts key.to_s + ' key presssed' if @verbose
|
122
234
|
name = "on_#{key}_key".to_sym
|
123
|
-
|
235
|
+
puts 'name: ' + name.inspect if @debug
|
236
|
+
|
237
|
+
if private_methods.include? name and (@keys.empty? or \
|
238
|
+
@keys.include? key.to_sym) then
|
239
|
+
puts 'before method' if @debug
|
240
|
+
method(name).call
|
241
|
+
end
|
124
242
|
|
125
243
|
keystring = ((key.length > 1 or key == ' ') ? "{%s}" % key : key)
|
126
|
-
|
244
|
+
|
245
|
+
if block_given? then
|
246
|
+
|
247
|
+
yield(keystring)
|
248
|
+
|
249
|
+
else
|
250
|
+
|
251
|
+
if @keys.empty? or @keys.include? key.to_sym then
|
252
|
+
on_key_press(keystring, keycode)
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
@callback.on_keypress(keystring, keycode) if @callback
|
127
258
|
|
128
259
|
end
|
129
260
|
|
@@ -135,8 +266,13 @@ class XInputWrapper
|
|
135
266
|
if block_given? then
|
136
267
|
yield(format_key(keys.last, keys[0..-2]))
|
137
268
|
else
|
138
|
-
|
269
|
+
|
270
|
+
if @keys.empty? or (!@keys.empty? and \
|
271
|
+
@keys.include? keys.last) then
|
272
|
+
on_key_press(keys.last, keycode, keys[0..-2])
|
273
|
+
end
|
139
274
|
end
|
275
|
+
@callback.on_keypress(keys.last, keycode, keys[0..-2]) if @callback
|
140
276
|
|
141
277
|
raw_keys = []
|
142
278
|
|
@@ -145,7 +281,7 @@ class XInputWrapper
|
|
145
281
|
|
146
282
|
|
147
283
|
# a key has been released
|
148
|
-
elsif type ==
|
284
|
+
elsif type == RAWKEY_RELEASE
|
149
285
|
|
150
286
|
# here we are only looking to detect a
|
151
287
|
# single modifier key press and release
|
@@ -160,51 +296,127 @@ class XInputWrapper
|
|
160
296
|
yield(format_key(key.to_s))
|
161
297
|
|
162
298
|
else
|
163
|
-
name = "on_#{key}_key".to_sym
|
164
|
-
method
|
299
|
+
name = "on_#{key.to_s}_key".to_sym
|
300
|
+
puts 'calling method' if @debug
|
301
|
+
|
302
|
+
if private_methods.include? name and (@keys.empty? or \
|
303
|
+
@keys.include? key.to_sym) then
|
304
|
+
method(name).call #if self.methods.include? name
|
305
|
+
end
|
306
|
+
|
165
307
|
on_key_press(key, keycode)
|
308
|
+
@callback.on_keypress(key, keycode) if @callback
|
166
309
|
end
|
167
310
|
end
|
168
311
|
|
169
312
|
index = raw_keys.rindex(keycode)
|
170
313
|
raw_keys.delete_at index if index
|
171
|
-
|
314
|
+
|
315
|
+
elsif type == RAWBUTTON_PRESS
|
316
|
+
|
317
|
+
button = BUTTONS[buttoncode-1]
|
318
|
+
|
319
|
+
case button
|
320
|
+
when :scrollup
|
321
|
+
on_mouse_scrollup()
|
322
|
+
@callback.on_mouse_scrollup() if @callback
|
323
|
+
when :scrolldown
|
324
|
+
on_mouse_scrolldown()
|
325
|
+
@callback.on_mouse_scrolldown() if @callback
|
326
|
+
else
|
327
|
+
on_mousedown(button, *@mouse_pos)
|
328
|
+
@callback.on_mousedown(button, *@mouse_pos) if @callback
|
329
|
+
end
|
330
|
+
|
331
|
+
elsif type == RAWBUTTON_RELEASE
|
332
|
+
|
333
|
+
button = BUTTONS[buttoncode-1]
|
334
|
+
on_mouseup(BUTTONS[buttoncode-1], *@mouse_pos)
|
335
|
+
|
172
336
|
end
|
173
337
|
|
174
338
|
end
|
175
339
|
end
|
176
340
|
|
177
|
-
|
341
|
+
private
|
342
|
+
|
343
|
+
def anykey_press()
|
344
|
+
# do nothing; used by xinput_wrapperplus
|
345
|
+
puts 'anykey pressed' if @debug
|
346
|
+
end
|
347
|
+
|
348
|
+
def message(s)
|
349
|
+
puts 'msg: ' + s
|
350
|
+
end
|
178
351
|
|
179
|
-
def
|
180
|
-
|
352
|
+
def on_ctrl_key()
|
353
|
+
message 'ctrl key pressed'
|
181
354
|
end
|
182
355
|
|
183
|
-
def on_key_press(key, keycode, modifier)
|
356
|
+
def on_key_press(key, keycode, modifier=nil)
|
184
357
|
|
185
358
|
if @debug then
|
186
359
|
puts ('key: ' + key.inspect).debug
|
187
360
|
end
|
188
361
|
|
189
362
|
end
|
363
|
+
|
364
|
+
def on_mousedown(button, x,y)
|
365
|
+
|
366
|
+
if @debug then
|
367
|
+
puts "on_mousedown() %s click x: %s y: %s" % [button, x, y]
|
368
|
+
end
|
369
|
+
|
370
|
+
end
|
371
|
+
|
372
|
+
def on_mousemove(x,y)
|
373
|
+
|
374
|
+
if @debug then
|
375
|
+
puts "on_mousemove() x: %s y: %s" % [x, y]
|
376
|
+
end
|
377
|
+
|
378
|
+
end
|
379
|
+
|
380
|
+
def on_mouseup(button, x,y)
|
381
|
+
|
382
|
+
if @debug then
|
383
|
+
puts "on_mousedown() %s click x: %s y: %s" % [button, x, y]
|
384
|
+
end
|
385
|
+
|
386
|
+
end
|
387
|
+
|
388
|
+
def on_mouse_scrolldown()
|
389
|
+
|
390
|
+
if @debug then
|
391
|
+
puts "on_mouse_scrolldown()"
|
392
|
+
end
|
393
|
+
|
394
|
+
end
|
395
|
+
|
396
|
+
def on_mouse_scrollup()
|
397
|
+
|
398
|
+
if @debug then
|
399
|
+
puts "on_mouse_scrollup()"
|
400
|
+
end
|
401
|
+
|
402
|
+
end
|
403
|
+
|
190
404
|
|
191
405
|
def on_shift_key() end
|
192
406
|
def on_super_key() end
|
193
407
|
|
194
|
-
def on_f1_key()
|
195
|
-
def on_f2_key()
|
196
|
-
def on_f3_key()
|
197
|
-
def on_f4_key()
|
198
|
-
def on_f5_key()
|
199
|
-
def on_f6_key()
|
200
|
-
def on_f7_key()
|
201
|
-
def on_f8_key()
|
202
|
-
def on_f9_key()
|
203
|
-
def on_f10_key()
|
204
|
-
def on_f11_key()
|
205
|
-
def on_f12_key()
|
206
|
-
|
207
|
-
private
|
408
|
+
def on_f1_key() message 'f1' end
|
409
|
+
def on_f2_key() message 'f2' end
|
410
|
+
def on_f3_key() message 'f3' end
|
411
|
+
def on_f4_key() message 'f4' end
|
412
|
+
def on_f5_key() message 'f5' end
|
413
|
+
def on_f6_key() message 'f6' end
|
414
|
+
def on_f7_key() message 'f7' end
|
415
|
+
def on_f8_key() message 'f8' end
|
416
|
+
def on_f9_key() message 'f9' end
|
417
|
+
def on_f10_key() message 'f10' end
|
418
|
+
def on_f11_key() message 'f11' end
|
419
|
+
def on_f12_key() message 'f12' end
|
208
420
|
|
209
421
|
def format_key(key, modifier=[])
|
210
422
|
|
@@ -213,6 +425,7 @@ class XInputWrapper
|
|
213
425
|
|
214
426
|
end
|
215
427
|
|
428
|
+
def on_alt_key() message 'alt' end
|
216
429
|
def on_leftcontrol_key() on_control_key() end
|
217
430
|
def on_rightcontrol_key() on_control_key() end
|
218
431
|
def on_left_alt_key() on_alt_key() end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xinput_wrapper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Robertson
|
@@ -11,52 +11,52 @@ cert_chain:
|
|
11
11
|
- |
|
12
12
|
-----BEGIN CERTIFICATE-----
|
13
13
|
MIIEXjCCAsagAwIBAgIBATANBgkqhkiG9w0BAQsFADAsMSowKAYDVQQDDCFnZW1t
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
14
|
+
YXN0ZXIvREM9amFtZXNyb2JlcnRzb24vREM9ZXUwHhcNMjAwODAzMjEwMTE2WhcN
|
15
|
+
MjEwODAzMjEwMTE2WjAsMSowKAYDVQQDDCFnZW1tYXN0ZXIvREM9amFtZXNyb2Jl
|
16
|
+
cnRzb24vREM9ZXUwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDdnN5k
|
17
|
+
jxSdi5vMTqhRRJ8yFAV3MYUs/W6NU+rMUk1uqTC/J+fFox8tg7t4VzQoQq539Onk
|
18
|
+
5iPFJhwo3jAYKIyc+LX4e3CtQMdrheyY/XkaTSu1yPR+c4lT8nhKUf4gI28hNXcx
|
19
|
+
lNekrvzzXBVZMGs5F+gKzOYUFS+i0jGPaVaison+iaGVLcjc2/t3AGIQeOHo7QYv
|
20
|
+
6Sswkj9Er4jkwJKHGAvLvh+KTcm/aImIfV4v5FUm6xJMi42lsDWbaRyACtAtNgKF
|
21
|
+
cl/pMlb3d5Si3vIcbKlktHR2K5KLtBuFqu/vWdlKygYzpnr3rWjAls8wr2fv6WiS
|
22
|
+
BqX3XVtZB4Fh/xriLY3Fj3Qo9RWuGf9S3BIGGxcnsr53bsO1zKXvbNr5UzP9d0+8
|
23
|
+
dF5lJ4bu8rw524nYypy39Is9PlHI7L6nxQjy82AmzloxEc31nYM7FF2pjkStv5H5
|
24
|
+
zfb4PVsCblnUstufNJWKrg32E2wbzXhzq8+E/U7Y7FM4zABWcyeHtt4Mv0MCAwEA
|
25
|
+
AaOBijCBhzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQU+hzRpZTx
|
26
|
+
g/xK+Tn1+5WHz7CK1y8wJgYDVR0RBB8wHYEbZ2VtbWFzdGVyQGphbWVzcm9iZXJ0
|
27
27
|
c29uLmV1MCYGA1UdEgQfMB2BG2dlbW1hc3RlckBqYW1lc3JvYmVydHNvbi5ldTAN
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
28
|
+
BgkqhkiG9w0BAQsFAAOCAYEAXg+9mfaTHPcbez7P0XnwGL0C4zdPwHtBUXioPwjP
|
29
|
+
okklRkFzMQnxArm8U9557g8cXY//vKp/cFBdCExE8lLxAOB9gHswJju0g99Yx7gY
|
30
|
+
iEV3TWv5qkLQ+n8zdTd8TxEyjWB6ZXOJcE+/cTq9Ly1j4bhf9QTdul02TBukEp6d
|
31
|
+
opP5/Yvt1GX0ED98olew/pm+KoKKNkcJzSgEH44O+pCfTi4h2STOFRvXVYaB3AH8
|
32
|
+
hpuUFcbxzPhBxOChGPAGZLaf7zurw8B2Q1vP75RV9gJ1M6NKU6Hlb6vMJQ3jz8qv
|
33
|
+
GIVFoKMCcJH2EImrG47doRVhQPIlxfz7d3f+FdYgnp7wtuFi/byklu1D2G8ZE8Ax
|
34
|
+
1s9Njv/guBMNkw+RV6IlpMtT0yeVvHeYTt0tPKeyrtBrSfsexA3393T7To6Ms+1s
|
35
|
+
B//EUqLB2VKtMmcrYMc2FfbLwllNLY4TRq+/lgVLoDb7oLHsiggYSLZS6lPBIQj/
|
36
|
+
YnYsDKwN5siDA79sB0fqq2ml
|
37
37
|
-----END CERTIFICATE-----
|
38
|
-
date:
|
38
|
+
date: 2020-08-27 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: c32
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
|
-
- - "~>"
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: '0.1'
|
47
44
|
- - ">="
|
48
45
|
- !ruby/object:Gem::Version
|
49
|
-
version: 0.
|
46
|
+
version: 0.2.0
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0.2'
|
50
50
|
type: :runtime
|
51
51
|
prerelease: false
|
52
52
|
version_requirements: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
|
-
- - "~>"
|
55
|
-
- !ruby/object:Gem::Version
|
56
|
-
version: '0.1'
|
57
54
|
- - ">="
|
58
55
|
- !ruby/object:Gem::Version
|
59
|
-
version: 0.
|
56
|
+
version: 0.2.0
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0.2'
|
60
60
|
description:
|
61
61
|
email: james@jamesrobertson.eu
|
62
62
|
executables: []
|
@@ -83,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
83
|
- !ruby/object:Gem::Version
|
84
84
|
version: '0'
|
85
85
|
requirements: []
|
86
|
-
rubygems_version: 3.0.
|
86
|
+
rubygems_version: 3.0.3
|
87
87
|
signing_key:
|
88
88
|
specification_version: 4
|
89
89
|
summary: A wrapper for the Linux utility xinput.
|
metadata.gz.sig
CHANGED
Binary file
|