device_input 0.0.1.1 → 0.0.3.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
- data/README.md +126 -17
- data/lib/device_input/{events.rb → codes.rb} +9 -9
- data/lib/device_input.rb +27 -23
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42e1a1dde8d9b9884e97bef833d8890d9aaa3a23
|
4
|
+
data.tar.gz: 5076d4580ee666d8c82a0f9b924ed94c2890b041
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ae5856b3d005ea2b268e931d5c1ac3c73cc8fd1a31ce0e4264b16adc4f70841b3858e8fd5aa771df47631da111eba823ef0ca1f3089c6a6f602a866eb759f58
|
7
|
+
data.tar.gz: 5f4c0defc1877124d209cc424f312cd099493f84a1feab65f651210b08475f2d56f4fdfd827b172a8b6ce3527f0e4bc5a7e1ba1301f0dcf8158ec59da1d302a6
|
data/README.md
CHANGED
@@ -1,13 +1,117 @@
|
|
1
|
-
#
|
1
|
+
# Device Input
|
2
2
|
|
3
|
-
|
3
|
+
*for the Linux kernel*
|
4
|
+
|
5
|
+
We want to read events from e.g. `/dev/input/event0` in Ruby. For example,
|
6
|
+
if you want to see what's happening "on the wire" when you press a special
|
7
|
+
function key on a laptop. While this code can be used for the purpose of
|
8
|
+
malicious keystroke logging, it is not well suited for it and does not provide
|
9
|
+
the root privileges in order to read `/dev/input`. Once you've got the
|
10
|
+
privilege to read `/dev/input` it's *game over* anyway.
|
11
|
+
|
12
|
+
## Rationale
|
13
|
+
|
14
|
+
`/dev/input/eventX` is just a character device. Can't we read it with simple
|
15
|
+
Unix tooling? Yes and no. First of all, a character device just means that
|
16
|
+
it passes bytes (not necessarily characters or strings) from userspace into
|
17
|
+
the kernel. Secondarily, the messages (defined as C structs) are in fact
|
18
|
+
binary and not strings or conventional characters.
|
19
|
+
|
20
|
+
Since these are C structs (analagous to a binary message), we need to be able
|
21
|
+
to delimit individual messages and decode them. We can't simply read a byte
|
22
|
+
at a time and try to make sense of it. In fact, on my system,
|
23
|
+
`/dev/input/event0` refuses any read that is not a multiple of the struct /
|
24
|
+
message size, so we need to know the message size before even attempting a
|
25
|
+
read(), without even a decode().
|
26
|
+
|
27
|
+
To determine the message size, we need to know the data structure. For a
|
28
|
+
long time, it was pretty simple: events are 16 bytes:
|
29
|
+
|
30
|
+
* timestamp - 8 bytes
|
31
|
+
* type - 1 byte
|
32
|
+
* code - 1 byte
|
33
|
+
* value - 2 bytes
|
34
|
+
|
35
|
+
However, this is only true for 32-bit platforms. On 64-bit platforms, event
|
36
|
+
timestamps became 16 bytes, increasing events from 16 to 24 bytes. This is
|
37
|
+
because a timestamp is defined as two `long`s, and `long`s are bigger on
|
38
|
+
64-bit platforms. It's easy to remember:
|
39
|
+
|
40
|
+
* 32-bit platform: 32-bit `long` (4 bytes)
|
41
|
+
* 64-bit platform: 64-bit `long` (8 bytes)
|
42
|
+
|
43
|
+
`read(/dev/input/event0, 16)` will fail on a 64-bit machine.
|
44
|
+
|
45
|
+
Your tooling must be aware of this distinction and choose the correct
|
46
|
+
underlying data types just to be able to delimit messages and perform a
|
47
|
+
successful read. This software does that, decodes the message, maps the
|
48
|
+
encoded values to friendly strings for display, and provides both library and
|
49
|
+
executable code to assist in examining kernel input events.
|
50
|
+
|
51
|
+
# Installation
|
52
|
+
|
53
|
+
Install the gem:
|
54
|
+
|
55
|
+
```
|
56
|
+
$ gem install device_input # sudo as necessary
|
57
|
+
```
|
58
|
+
|
59
|
+
Or, if using [Bundler](http://bundler.io/), add to your `Gemfile`:
|
60
|
+
|
61
|
+
```
|
62
|
+
gem 'device_input', '~> 0.0'
|
63
|
+
```
|
64
|
+
|
65
|
+
# Usage
|
66
|
+
|
67
|
+
## Executable
|
68
|
+
|
69
|
+
```
|
70
|
+
$ sudo devsniff /dev/input/event0
|
71
|
+
```
|
72
|
+
|
73
|
+
When the `f` key is pressed:
|
74
|
+
|
75
|
+
```
|
76
|
+
Misc:ScanCode:33
|
77
|
+
Key:F:1
|
78
|
+
Sync:Sync:0
|
79
|
+
```
|
80
|
+
|
81
|
+
And released:
|
82
|
+
```
|
83
|
+
Misc:ScanCode:33
|
84
|
+
Key:F:0
|
85
|
+
Sync:Sync:0
|
86
|
+
```
|
87
|
+
|
88
|
+
## Library
|
89
|
+
|
90
|
+
```
|
91
|
+
require 'device_input'
|
92
|
+
|
93
|
+
DeviceInput.read_from('/dev/input/event0') do |event|
|
94
|
+
puts event
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
An event has:
|
99
|
+
|
100
|
+
* `#data` - a Struct of ints (class name Data)
|
101
|
+
* `#time` - a Time, accurate to usecs
|
102
|
+
* `#type` - a String, possibly UNK-X where X is the integer from `#data`
|
103
|
+
* `#code` - a String, possibly UNK-X-Y where X and Y are from `#data`
|
104
|
+
* `#value` - a Fixnum (signed)
|
105
|
+
|
106
|
+
# Research
|
4
107
|
|
5
108
|
## Kernel docs
|
6
109
|
|
7
110
|
* https://www.kernel.org/doc/Documentation/input/input.txt
|
8
111
|
* https://www.kernel.org/doc/Documentation/input/event-codes.txt
|
9
112
|
|
10
|
-
These events are defined as C structs with a fixed size in bytes.
|
113
|
+
These events are defined as C structs with a fixed size in bytes. See more
|
114
|
+
about these structs towards the end of this document.
|
11
115
|
|
12
116
|
## Kernel structs
|
13
117
|
|
@@ -34,8 +138,7 @@ struct input_event {
|
|
34
138
|
};
|
35
139
|
```
|
36
140
|
|
37
|
-
What's a `timeval
|
38
|
-
https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/time.h#n15
|
141
|
+
What's a [`timeval`](https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/time.h#n15)?
|
39
142
|
|
40
143
|
```
|
41
144
|
struct timeval {
|
@@ -44,8 +147,7 @@ struct timeval {
|
|
44
147
|
};
|
45
148
|
```
|
46
149
|
|
47
|
-
What's a `__kernel_time_t
|
48
|
-
https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/asm-generic/posix_types.h#n88
|
150
|
+
What's a [`__kernel_time_t`](https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/asm-generic/posix_types.h#n88)?
|
49
151
|
|
50
152
|
```
|
51
153
|
typedef long __kernel_long_t;
|
@@ -55,10 +157,9 @@ typedef __kernel_long_t __kernel_suseconds_t;
|
|
55
157
|
typedef __kernel_long_t __kernel_time_t;
|
56
158
|
```
|
57
159
|
|
58
|
-
What's a `__u16
|
59
|
-
|
60
|
-
|
61
|
-
https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/asm-generic/int-l64.h#n23
|
160
|
+
What's a [`__u16`](https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/asm-generic/int-l64.h#n23)?
|
161
|
+
We're pretty sure it's an unsigned 16 bit integer. Likewise `__s32` should
|
162
|
+
be a signed 32-bit integer:
|
62
163
|
|
63
164
|
```
|
64
165
|
typedef unsigned short __u16;
|
@@ -69,7 +170,7 @@ typedef __signed__ int __s32;
|
|
69
170
|
Why is the value signed? It's meant to be able to communicate an "analog"
|
70
171
|
range, say -127 to +127 as determined by the position of a joystick.
|
71
172
|
|
72
|
-
|
173
|
+
## Review
|
73
174
|
|
74
175
|
`input_event`
|
75
176
|
|
@@ -84,18 +185,26 @@ Flattened: `SEC` `USEC` `TYPE` `CODE` `VALUE`
|
|
84
185
|
|
85
186
|
How many bytes is a `long`? Well, it's platform-dependent. On a 32-bit
|
86
187
|
platform, you get 32 bits (4 bytes). On a 64-bit platform you get 64 bits
|
87
|
-
(8 bytes).
|
188
|
+
(8 bytes). This means that the event is 16 bytes on a 32-bit machine and
|
189
|
+
24 bytes on a 64-bit machine. Software will need to accommodate.
|
88
190
|
|
89
|
-
|
90
|
-
64-bit machine. Software will need to accommodate.
|
191
|
+
## Ruby tools
|
91
192
|
|
92
|
-
We can use `RbConfig` and `Array#pack` to help us read these
|
193
|
+
We can use `RbConfig` and `Array#pack`/`String#unpack` to help us read these
|
194
|
+
binary structs:
|
93
195
|
|
94
196
|
```
|
95
197
|
FIELD C RbConfig Pack
|
96
|
-
|
198
|
+
--- --- --- ---
|
199
|
+
tv_sec long long l!
|
97
200
|
tv_usec long long l!
|
98
201
|
type __u16 uint16_t S
|
99
202
|
code __u16 uint16_t S
|
100
203
|
value __s32 int32_t l
|
101
204
|
```
|
205
|
+
|
206
|
+
# Acknowledgments
|
207
|
+
|
208
|
+
* Inspired by https://github.com/prullmann/libdevinput (don't use it)
|
209
|
+
- also the source of the [event code labels](lib/device_input/codes.rb)
|
210
|
+
* Thanks to al2o3-cr from #ruby on Freenode for feedback
|
@@ -1,13 +1,13 @@
|
|
1
1
|
module DeviceInput
|
2
|
-
|
2
|
+
CODES = {}
|
3
3
|
|
4
4
|
# type = Sync
|
5
|
-
|
5
|
+
CODES[0] = {
|
6
6
|
0 => 'Sync',
|
7
7
|
}
|
8
8
|
|
9
9
|
# type = Key
|
10
|
-
|
10
|
+
CODES[1] = {
|
11
11
|
0 => 'Reserved',
|
12
12
|
1 => 'Esc',
|
13
13
|
2 => '1',
|
@@ -358,7 +358,7 @@ module DeviceInput
|
|
358
358
|
}
|
359
359
|
|
360
360
|
# type = Relative
|
361
|
-
|
361
|
+
CODES[2] = {
|
362
362
|
0 => 'X',
|
363
363
|
1 => 'Y',
|
364
364
|
2 => 'Z',
|
@@ -369,7 +369,7 @@ module DeviceInput
|
|
369
369
|
}
|
370
370
|
|
371
371
|
# type = Absolute
|
372
|
-
|
372
|
+
CODES[3] = {
|
373
373
|
0 => 'X',
|
374
374
|
1 => 'Y',
|
375
375
|
2 => 'Z',
|
@@ -399,7 +399,7 @@ module DeviceInput
|
|
399
399
|
}
|
400
400
|
|
401
401
|
# type = Misc
|
402
|
-
|
402
|
+
CODES[4] = {
|
403
403
|
0 => 'Serial',
|
404
404
|
1 => 'Pulseled',
|
405
405
|
2 => 'Gesture',
|
@@ -408,7 +408,7 @@ module DeviceInput
|
|
408
408
|
}
|
409
409
|
|
410
410
|
# type = LED
|
411
|
-
|
411
|
+
CODES[17] = {
|
412
412
|
0 => 'NumLock',
|
413
413
|
1 => 'CapsLock',
|
414
414
|
2 => 'ScrollLock',
|
@@ -421,14 +421,14 @@ module DeviceInput
|
|
421
421
|
}
|
422
422
|
|
423
423
|
# type = Sound
|
424
|
-
|
424
|
+
CODES[18] = {
|
425
425
|
0 => 'Click',
|
426
426
|
1 => 'Bell',
|
427
427
|
2 => 'Tone',
|
428
428
|
}
|
429
429
|
|
430
430
|
# type = Repeat
|
431
|
-
|
431
|
+
CODES[20] = {
|
432
432
|
0 => 'Delay',
|
433
433
|
1 => 'Period',
|
434
434
|
}
|
data/lib/device_input.rb
CHANGED
@@ -14,8 +14,24 @@ module DeviceInput
|
|
14
14
|
}
|
15
15
|
PACK = DEFINITION.values.map { |v| PACK_MAP.fetch(v) }.join
|
16
16
|
|
17
|
+
# this defines a class, i.e. class Data ...
|
17
18
|
Data = Struct.new(*DEFINITION.keys)
|
18
19
|
|
20
|
+
# these are just labels, not used internally
|
21
|
+
TYPES = {
|
22
|
+
0 => 'Sync',
|
23
|
+
1 => 'Key',
|
24
|
+
2 => 'Relative',
|
25
|
+
3 => 'Absolute',
|
26
|
+
4 => 'Misc',
|
27
|
+
17 => 'LED',
|
28
|
+
18 => 'Sound',
|
29
|
+
20 => 'Repeat',
|
30
|
+
21 => 'ForceFeedback',
|
31
|
+
22 => 'Power',
|
32
|
+
23 => 'ForceFeedbackStatus',
|
33
|
+
}
|
34
|
+
|
19
35
|
# convert Event::Data to a string
|
20
36
|
def self.encode(data)
|
21
37
|
data.values.pack(PACK)
|
@@ -31,51 +47,39 @@ module DeviceInput
|
|
31
47
|
end
|
32
48
|
|
33
49
|
def self.code_str(type_code, code_code)
|
34
|
-
require 'device_input/
|
35
|
-
DeviceInput::
|
50
|
+
require 'device_input/codes'
|
51
|
+
DeviceInput::CODES.dig(type_code, code_code) ||
|
36
52
|
"UNK-#{type_code}-#{code_code}"
|
37
53
|
end
|
38
54
|
|
39
55
|
NULL_DATA = Data.new(0, 0, 0, 0, 0)
|
40
56
|
NULL_MSG = self.encode(NULL_DATA)
|
57
|
+
BYTE_LENGTH = NULL_MSG.length
|
41
58
|
|
42
|
-
attr_reader :data, :time, :type, :code
|
59
|
+
attr_reader :data, :time, :type, :code
|
43
60
|
|
44
61
|
def initialize(data)
|
45
62
|
@data = data
|
46
63
|
@time = Time.at(data.tv_sec, data.tv_usec)
|
47
64
|
@type = self.class.type_str(data.type)
|
48
65
|
@code = self.class.code_str(data.type, data.code)
|
49
|
-
@value = data.value
|
50
66
|
end
|
51
67
|
|
52
|
-
def
|
53
|
-
|
68
|
+
def value
|
69
|
+
@data.value
|
54
70
|
end
|
55
71
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
2 => 'Relative',
|
60
|
-
3 => 'Absolute',
|
61
|
-
4 => 'Misc',
|
62
|
-
17 => 'LED',
|
63
|
-
18 => 'Sound',
|
64
|
-
20 => 'Repeat',
|
65
|
-
21 => 'ForceFeedback',
|
66
|
-
22 => 'Power',
|
67
|
-
23 => 'ForceFeedbackStatus',
|
68
|
-
}
|
69
|
-
|
70
|
-
BYTE_LENGTH = NULL_MSG.length
|
72
|
+
def to_s
|
73
|
+
[@type, @code, @data.value].join(':')
|
74
|
+
end
|
71
75
|
end
|
72
76
|
|
73
77
|
def self.read_from(filename)
|
74
78
|
File.open(filename, 'r') { |f|
|
75
79
|
loop {
|
76
80
|
bytes = f.read(Event::BYTE_LENGTH)
|
77
|
-
|
78
|
-
yield Event.new(
|
81
|
+
data = Event.decode(bytes)
|
82
|
+
yield Event.new(data)
|
79
83
|
}
|
80
84
|
}
|
81
85
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: device_input
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rick Hull
|
@@ -36,7 +36,7 @@ files:
|
|
36
36
|
- README.md
|
37
37
|
- bin/devsniff
|
38
38
|
- lib/device_input.rb
|
39
|
-
- lib/device_input/
|
39
|
+
- lib/device_input/codes.rb
|
40
40
|
homepage: https://github.com/rickhull/device_input
|
41
41
|
licenses:
|
42
42
|
- GPL-3.0
|