device_input 0.0.1.1 → 0.0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|