Skip to content

Commit 22a6dea

Browse files
authored
Merge pull request #68 from shengwen-tw/main
Support input device hot-plugging and removal via udev
2 parents efe7d49 + 4c35cd6 commit 22a6dea

File tree

3 files changed

+158
-14
lines changed

3 files changed

+158
-14
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ endif
113113

114114
ifeq ($(CONFIG_BACKEND_FBDEV), y)
115115
BACKEND = fbdev
116+
TARGET_LIBS += -ludev
116117
libtwin.a_files-y += backend/fbdev.c
117118
libtwin.a_files-y += backend/linux_input.c
118119
endif

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ For the VNC backend, please note that it has only been tested on GNU/Linux, and
8888
$ tools/build-neatvnc.sh
8989
```
9090

91+
For Linux framebuffer backend, install `libudev` and `libuuid`:
92+
* Ubuntu Linux / Debian: `sudo apt install libudev-dev uuid-dev`
93+
9194
### Configuration
9295

9396
Configure via [Kconfiglib](https://pypi.org/project/kconfiglib/), you should select either SDL video, the Linux framebuffer, or VNC as the graphics backend.

backend/linux_input.c

Lines changed: 154 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
#include <fcntl.h>
8+
#include <libudev.h>
89
#include <linux/input.h>
910
#include <poll.h>
1011
#include <pthread.h>
@@ -18,18 +19,25 @@
1819

1920
#define EVDEV_CNT_MAX 32
2021
#define EVDEV_NAME_SIZE_MAX 50
22+
#define UDEV_RESERVED_CNT 1
23+
#define UDEV_EVDEV_FD_CNT (EVDEV_CNT_MAX + UDEV_RESERVED_CNT)
24+
25+
struct evdev_info {
26+
int idx;
27+
int fd;
28+
};
2129

2230
typedef struct {
2331
twin_screen_t *screen;
2432
pthread_t evdev_thread;
33+
struct evdev_info evdevs[EVDEV_CNT_MAX];
34+
size_t evdev_cnt;
35+
size_t udev_cnt;
2536
int fd;
2637
int btns;
2738
int x, y;
2839
} twin_linux_input_t;
2940

30-
static int evdev_fd[EVDEV_CNT_MAX];
31-
static int evdev_cnt;
32-
3341
static void check_mouse_bounds(twin_linux_input_t *tm)
3442
{
3543
if (tm->x < 0)
@@ -101,37 +109,165 @@ static void twin_linux_input_events(struct input_event *ev,
101109
}
102110
}
103111

104-
static void *twin_linux_evdev_thread(void *arg)
112+
static int twin_linux_udev_init(struct udev **udev, struct udev_monitor **mon)
105113
{
106-
twin_linux_input_t *tm = arg;
114+
/* Create udev object */
115+
*udev = udev_new();
116+
if (!*udev) {
117+
log_error("Failed to create udev object");
118+
return -1;
119+
}
120+
121+
/* Create a monitor for kernel events */
122+
*mon = udev_monitor_new_from_netlink(*udev, "udev");
123+
if (!*mon) {
124+
log_error("Failed to create udev monitor");
125+
udev_unref(*udev);
126+
return -1;
127+
}
128+
129+
/* Filter for input subsystem */
130+
udev_monitor_filter_add_match_subsystem_devtype(*mon, "input", NULL);
131+
udev_monitor_enable_receiving(*mon);
132+
133+
/* File descriptor for the monitor */
134+
return udev_monitor_get_fd(*mon);
135+
}
136+
137+
static bool twin_linux_udev_update(struct udev_monitor *mon)
138+
{
139+
struct udev_device *dev = NULL;
140+
141+
/* Get the device that caused the event */
142+
dev = udev_monitor_receive_device(mon);
143+
if (dev) {
144+
const char *action = udev_device_get_action(dev);
145+
const char *dev_node = udev_device_get_devnode(dev);
146+
147+
if (action && dev_node) {
148+
const char *keyboard =
149+
udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD");
150+
const char *mouse =
151+
udev_device_get_property_value(dev, "ID_INPUT_MOUSE");
152+
153+
/* Ensure udev event is for mouse or keyboard */
154+
if (!keyboard && !mouse) {
155+
udev_device_unref(dev);
156+
return false;
157+
}
158+
159+
/* Capture only add and remove events */
160+
if (!strcmp(action, "add") || !strcmp(action, "remove")) {
161+
log_info("udev: %s: %s", action, dev_node);
162+
udev_device_unref(dev);
163+
return true;
164+
}
165+
}
166+
}
167+
168+
/* No event is caputured */
169+
return false;
170+
}
171+
172+
static void twin_linux_edev_open(struct pollfd *pfds, twin_linux_input_t *tm)
173+
{
174+
/* New event device list */
175+
struct evdev_info evdevs[EVDEV_CNT_MAX];
176+
int new_evdev_cnt = 0;
177+
memset(evdevs, 0, sizeof(evdevs));
107178

108179
/* Open all event devices */
109180
char evdev_name[EVDEV_NAME_SIZE_MAX] = {0};
110181
for (int i = 0; i < EVDEV_CNT_MAX; i++) {
182+
/* Check if the file exists */
111183
snprintf(evdev_name, EVDEV_NAME_SIZE_MAX, "/dev/input/event%d", i);
184+
if (access(evdev_name, F_OK) != 0)
185+
continue;
186+
187+
/* Match device with the old device list */
188+
bool opened = false;
189+
for (size_t j = 0; j < tm->evdev_cnt; j++) {
190+
/* Copy the fd if the device is already on the list */
191+
if (tm->evdevs[j].idx == i) {
192+
evdevs[new_evdev_cnt].idx = tm->evdevs[j].idx;
193+
evdevs[new_evdev_cnt].fd = tm->evdevs[j].fd;
194+
tm->evdevs[j].fd = -1;
195+
new_evdev_cnt++;
196+
opened = true;
197+
break;
198+
}
199+
}
200+
201+
/* Open the file if it is not on the list */
112202
int fd = open(evdev_name, O_RDWR | O_NONBLOCK);
113-
if (fd > 0) {
114-
evdev_fd[evdev_cnt] = fd;
115-
evdev_cnt++;
203+
if (fd > 0 && !opened) {
204+
evdevs[new_evdev_cnt].idx = i;
205+
evdevs[new_evdev_cnt].fd = fd;
206+
new_evdev_cnt++;
116207
}
117208
}
118209

119-
/* Initialize pollfd array */
120-
struct pollfd pfds[EVDEV_CNT_MAX];
121-
for (int i = 0; i < evdev_cnt; i++) {
122-
pfds[i].fd = evdev_fd[i];
210+
/* Close disconnected devices */
211+
for (size_t i = 0; i < tm->evdev_cnt; i++) {
212+
if (tm->evdevs[i].fd > 0)
213+
close(tm->evdevs[i].fd);
214+
}
215+
216+
/* Overwrite the evdev list */
217+
memcpy(tm->evdevs, evdevs, sizeof(tm->evdevs));
218+
tm->evdev_cnt = new_evdev_cnt;
219+
220+
/* Initialize evdev poll file descriptors */
221+
for (size_t i = tm->udev_cnt; i < tm->evdev_cnt + tm->udev_cnt; i++) {
222+
pfds[i].fd = tm->evdevs[i - 1].fd;
123223
pfds[i].events = POLLIN;
124224
}
225+
}
226+
227+
static void *twin_linux_evdev_thread(void *arg)
228+
{
229+
twin_linux_input_t *tm = arg;
230+
231+
struct udev *udev = NULL;
232+
struct udev_monitor *mon = NULL;
233+
234+
/* Open Linux udev (user space device manager) */
235+
int udev_fd = twin_linux_udev_init(&udev, &mon);
236+
if (udev_fd >= 0)
237+
tm->udev_cnt = 1;
238+
239+
/* Place the udev fd into the poll fds */
240+
struct pollfd pfds[UDEV_EVDEV_FD_CNT];
241+
pfds[0].fd = udev_fd;
242+
pfds[0].events = POLLIN;
243+
244+
/* Open event devices */
245+
twin_linux_edev_open(pfds, tm);
246+
247+
/* Accessing to input devices is impossible, terminate the thread */
248+
if (tm->evdev_cnt == 0 && tm->udev_cnt == 0) {
249+
log_error("Failed to open udev and evdev");
250+
pthread_exit(NULL);
251+
}
125252

126253
/* Event polling */
127254
struct input_event ev;
128255
while (1) {
129256
/* Wait until any event is available */
130-
if (poll(pfds, evdev_cnt, -1) <= 0)
257+
if (poll(pfds, tm->evdev_cnt + tm->udev_cnt, -1) <= 0)
131258
continue;
132259

133260
/* Iterate through all file descriptors */
134-
for (int i = 0; i < evdev_cnt; i++) {
261+
for (size_t i = 0; i < tm->evdev_cnt + tm->udev_cnt; i++) {
262+
if (i < tm->udev_cnt) {
263+
/* Check udev event */
264+
if (twin_linux_udev_update(mon)) {
265+
/* Re-open event devices */
266+
twin_linux_edev_open(pfds, tm);
267+
break;
268+
}
269+
continue;
270+
}
135271
/* Try reading events */
136272
ssize_t n = read(pfds[i].fd, &ev, sizeof(ev));
137273
if (n == sizeof(ev)) {
@@ -141,6 +277,10 @@ static void *twin_linux_evdev_thread(void *arg)
141277
}
142278
}
143279

280+
/* Clean up */
281+
udev_monitor_unref(mon);
282+
udev_unref(udev);
283+
144284
return NULL;
145285
}
146286

0 commit comments

Comments
 (0)