-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathevdev.go
executable file
·112 lines (93 loc) · 3.17 KB
/
evdev.go
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
// Read input events from evdev (Linux input device subsystem)
package evdev
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"os"
"golang.org/x/sys/unix"
)
// https://github.com/gvalkov/golang-evdev implements what we do, but requires Cgo
/* Hunting the numeric code for EVIOCGRAB was an absolute pain (none of these specify it legibly):
- https://github.com/gvalkov/golang-evdev/search?q=EVIOCGRAB&unscoped_q=EVIOCGRAB
- https://github.com/pfirpfel/node-exclusive-keyboard/blob/master/lib/eviocgrab.cpp
- https://docs.rs/ioctl/0.3.1/src/ioctl/.cargo/registry/src/github.com-1ecc6299db9ec823/ioctl-0.3.1/src/platform/linux.rs.html#396
- https://github.com/torvalds/linux/blob/4a3033ef6e6bb4c566bd1d556de69b494d76976c/include/uapi/linux/input.h#L183
*/
const (
EVIOCGRAB = 1074021776 // found out with help of evdev.EVIOCGRAB (github.com/gvalkov/golang-evdev)
)
// Sidenote: code for enumerating input devices: https://github.com/MarinX/keylogger/blob/master/keylogger.go
func NewChan() chan InputEvent {
return make(chan InputEvent)
}
// ScanInput() may close the given channel
func ScanInput(ctx context.Context, inputDevicePath string, ch chan InputEvent) error {
return scanInput(ctx, inputDevicePath, ch, false)
}
// same as ScanInput() but grabbed means exclusive access (we'll be the only one receiving the events)
// https://stackoverflow.com/a/1698686
// https://stackoverflow.com/a/1550320
func ScanInputGrabbed(ctx context.Context, inputDevicePath string, ch chan InputEvent) error {
return scanInput(ctx, inputDevicePath, ch, true)
}
func scanInput(ctx context.Context, inputDevicePath string, ch chan InputEvent, grab bool) error {
inputDevice, err := os.Open(inputDevicePath)
if err != nil {
return err
}
defer inputDevice.Close()
// other programs won't receive input while we have this file handle open
if err := grabExclusiveInputDeviceAccess(inputDevice); err != nil {
return err
}
readingErrored := make(chan error, 1)
go func() {
for {
e, err := readOneInputEvent(inputDevice)
if err != nil {
readingErrored <- fmt.Errorf("readOneInputEvent: %w", err)
close(ch)
break
}
if e != nil {
ch <- *e
}
}
}()
for {
select {
case <-ctx.Done():
// this triggers close of inputDevice which will result in return of readOneInputEvent()
// whose error will be returned to readingErrored, but that's ok because it's buffered
// and will now not be read because we just exited gracefully
return nil
case err := <-readingErrored:
return err
}
}
}
func readOneInputEvent(inputDevice *os.File) (*InputEvent, error) {
buffer := make([]byte, eventsize)
n, err := inputDevice.Read(buffer)
if err != nil {
return nil, err
}
// no input, dont send error
if n <= 0 {
return nil, nil
}
return eventFromBuffer(buffer)
}
func grabExclusiveInputDeviceAccess(inputDevice *os.File) error {
if err := unix.IoctlSetInt(int(inputDevice.Fd()), EVIOCGRAB, 1); err != nil {
return fmt.Errorf("grabExclusiveInputDeviceAccess: IOCTL(EVIOCGRAB): %w", err)
}
return nil
}
func eventFromBuffer(buffer []byte) (*InputEvent, error) {
event := &InputEvent{}
err := binary.Read(bytes.NewBuffer(buffer), binary.LittleEndian, event)
return event, err
}