-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinput_linux.go
More file actions
124 lines (114 loc) · 3.29 KB
/
input_linux.go
File metadata and controls
124 lines (114 loc) · 3.29 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
// input_linux.go watches a touchscreen input device and toggles the active
// bus stop on each tap.
package main
import (
"encoding/binary"
"fmt"
"log/slog"
"os"
"path/filepath"
"strings"
"syscall"
"time"
)
const (
evKey = 0x01
btnTouch = 0x14a
eviocgrab = 0x40044590 // EVIOCGRAB: exclusively grab the device
)
// inputEvent mirrors struct input_event from <linux/input.h> for 32-bit ARM.
// On 32-bit Linux timeval uses two uint32s, giving a 16-byte struct total.
type inputEvent struct {
Sec uint32
Usec uint32
Type uint16
Code uint16
Value int32
}
// findTouchDevice scans /sys/class/input for a known touchscreen driver name.
func findTouchDevice() string {
names, _ := filepath.Glob("/sys/class/input/event*/device/name")
for _, p := range names {
data, err := os.ReadFile(p)
if err != nil {
continue
}
lower := strings.ToLower(string(data))
if strings.Contains(lower, "ft5") || strings.Contains(lower, "touch") {
// p is .../event0/device/name — event dir is three levels up
event := filepath.Base(filepath.Dir(filepath.Dir(p)))
return "/dev/input/" + event
}
}
return ""
}
// openTouchDevice blocks until the touch device at dev (or an auto-detected
// device if dev is empty) exists and is accessible, then returns an open file.
// It uses inotify on /dev/input to avoid polling.
func openTouchDevice(dev string) (*os.File, error) {
ifd, err := syscall.InotifyInit1(syscall.IN_CLOEXEC)
if err != nil {
return nil, fmt.Errorf("inotify init: %w", err)
}
defer syscall.Close(ifd)
_, err = syscall.InotifyAddWatch(ifd, "/dev/input", syscall.IN_CREATE|syscall.IN_ATTRIB)
if err != nil {
return nil, fmt.Errorf("inotify watch /dev/input: %w", err)
}
buf := make([]byte, 4096)
for {
if dev == "" {
dev = findTouchDevice()
}
if dev != "" {
f, err := os.Open(dev)
if err == nil {
slog.Info("watching touch device", "dev", dev)
return f, nil
}
if os.IsPermission(err) {
// Node exists but udev has not yet applied group permissions;
// watch the file itself so we wake as soon as IN_ATTRIB fires.
syscall.InotifyAddWatch(ifd, dev, syscall.IN_ATTRIB)
} else if !os.IsNotExist(err) {
return nil, err
}
}
slog.Info("waiting for touch device", "dev", dev)
_, err = syscall.Read(ifd, buf)
if err != nil {
return nil, fmt.Errorf("inotify read: %w", err)
}
}
}
// watchTouch reads touch events and calls flip on each finger-down
// (BTN_TOUCH value 1) event, subject to the debounce interval.
func watchTouch(dev string, flip func(), debounce time.Duration) {
f, err := openTouchDevice(dev)
if err != nil {
slog.Error("open touch device", "err", err)
return
}
defer f.Close()
// Grab the device exclusively so the kernel doesn't also feed events to
// /dev/mice, which would cause the DRM hardware cursor to appear on screen.
if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), eviocgrab, 1); errno != 0 {
slog.Warn("EVIOCGRAB", "dev", f.Name(), "err", errno)
}
var ev inputEvent
var lastSwitch time.Time
for {
err := binary.Read(f, binary.LittleEndian, &ev)
if err != nil {
slog.Error("read touch event", "err", err)
return
}
if ev.Type == evKey && ev.Code == btnTouch && ev.Value == 1 {
if time.Since(lastSwitch) < debounce {
continue
}
lastSwitch = time.Now()
flip()
}
}
}