commit 62b60b6

shrub  ·  2026-02-03 23:55:27 +0000 UTC
parent deb6d25
Add an evdev backend
7 files changed,  +671, -128
+0, -122
  1@@ -1,122 +0,0 @@
  2-# swc: Makefile
  3-
  4-.PHONY: all
  5-all: build
  6-
  7-# Defaults for config.mk
  8-PREFIX          ?= /usr/local
  9-BINDIR          ?= $(PREFIX)/bin
 10-LIBDIR          ?= $(PREFIX)/lib
 11-INCLUDEDIR      ?= $(PREFIX)/include
 12-DATADIR         ?= $(PREFIX)/share
 13-PKGCONFIGDIR    ?= $(LIBDIR)/pkgconfig
 14-
 15-OBJCOPY         ?= objcopy
 16-PKG_CONFIG      ?= pkg-config
 17-WAYLAND_SCANNER ?= wayland-scanner
 18-
 19-VERSION_MAJOR   := 0
 20-VERSION_MINOR   := 0
 21-VERSION         := $(VERSION_MAJOR).$(VERSION_MINOR)
 22-
 23-TARGETS         := swc.pc
 24-SUBDIRS         := launch libswc protocol cursor example
 25-CLEAN_FILES     := $(TARGETS)
 26-
 27-include config.mk
 28-
 29-# Dependencies
 30-PACKAGES :=           \
 31-    libdrm            \
 32-    pixman-1          \
 33-    wayland-server    \
 34-    wayland-protocols \
 35-    wld               \
 36-    xkbcommon
 37-
 38-ifeq ($(ENABLE_XWAYLAND),1)
 39-PACKAGES +=         \
 40-    xcb             \
 41-    xcb-composite   \
 42-    xcb-ewmh        \
 43-    xcb-icccm
 44-endif
 45-
 46-ifneq ($(shell uname),NetBSD)
 47-    PACKAGES += libinput
 48-    ifeq ($(ENABLE_LIBUDEV),1)
 49-        PACKAGES += libudev
 50-    endif
 51-endif
 52-
 53-libinput_CONSTRAINTS        := --atleast-version=0.4
 54-wayland-server_CONSTRAINTS  := --atleast-version=1.6.0
 55-
 56-define check
 57-    ifeq ($$(origin $(1)_EXISTS),undefined)
 58-        $(1)_EXISTS = $$(shell $$(PKG_CONFIG) --exists $$($(1)_CONSTRAINTS) $(1) && echo yes)
 59-    endif
 60-    ifneq ($$($(1)_EXISTS),yes)
 61-        $$(error Could not find package $(1) $$($(1)_CONSTRAINTS))
 62-    endif
 63-endef
 64-
 65-$(foreach pkg,$(PACKAGES),$(eval $(call check,$(pkg))))
 66-
 67-FINAL_CFLAGS = $(CFLAGS) -fvisibility=hidden -std=c11
 68-FINAL_CPPFLAGS = $(CPPFLAGS) -D_GNU_SOURCE # Required for mkostemp
 69-
 70-# Warning/error flags
 71-FINAL_CFLAGS += -Werror=implicit-function-declaration -Werror=implicit-int \
 72-                -Werror=pointer-sign -Werror=pointer-arith \
 73-                -Wall -Wno-missing-braces
 74-
 75-ifeq ($(ENABLE_DEBUG),1)
 76-    FINAL_CPPFLAGS += -DENABLE_DEBUG=1
 77-    FINAL_CFLAGS += -g
 78-endif
 79-
 80-ifeq ($(if $(V),$(V),0),0)
 81-    quiet = @echo '  $1 $@';
 82-endif
 83-
 84-Q_AR      = $(call quiet,AR     )
 85-Q_CC      = $(call quiet,CC     )
 86-Q_CCLD    = $(call quiet,CCLD   )
 87-Q_GEN     = $(call quiet,GEN    )
 88-Q_OBJCOPY = $(call quiet,OBJCOPY)
 89-Q_SYM     = $(call quiet,SYM    )
 90-
 91-compile   = $(Q_CC)$(CC) $(FINAL_CPPFLAGS) $(FINAL_CFLAGS) -I . -c -o $@ $< \
 92-            -MMD -MP -MF .deps/$(basename $<).d -MT $(basename $@).o -MT $(basename $@).lo
 93-link      = $(Q_CCLD)$(CC) $(LDFLAGS) -o $@ $^
 94-pkgconfig = $(foreach pkg,$(1),$(if $($(pkg)_$(3)),$($(pkg)_$(3)), \
 95-                                    $(shell $(PKG_CONFIG) --$(2) $(pkg))))
 96-
 97-include $(SUBDIRS:%=%/local.mk)
 98-
 99-$(foreach dir,BIN LIB INCLUDE PKGCONFIG,$(DESTDIR)$($(dir)DIR)) $(DESTDIR)$(DATADIR)/swc:
100-	mkdir -p $@
101-
102-.PHONY: build
103-build: $(SUBDIRS:%=build-%) $(TARGETS)
104-
105-REQUIRES          := wayland-server
106-REQUIRES_PRIVATE  := $(filter-out $(REQUIRES),$(libswc_PACKAGES))
107-SWC_PC_VARS       := VERSION PREFIX LIBDIR INCLUDEDIR DATADIR REQUIRES REQUIRES_PRIVATE
108-
109-swc.pc: swc.pc.in
110-	$(Q_GEN)sed $(foreach var,$(SWC_PC_VARS),-e 's:@$(var)@:$($(var)):') $< >$@
111-
112-.PHONY: install-swc.pc
113-install-swc.pc: swc.pc | $(DESTDIR)$(PKGCONFIGDIR)
114-	install -m 644 $< $(DESTDIR)$(PKGCONFIGDIR)
115-
116-.PHONY: install
117-install: $(SUBDIRS:%=install-%) $(TARGETS:%=install-%)
118-
119-.PHONY: clean
120-clean:
121-	rm -f $(CLEAN_FILES)
122-
123--include .deps/*/*.d
+22, -3
 1@@ -41,13 +41,21 @@ AR=ar
 2 
 3 UNAME!= uname
 4 
 5+.if !defined(INPUT_BACKEND)
 6+. if ${UNAME} == "NetBSD"
 7+INPUT_BACKEND=wscons
 8+. else
 9+INPUT_BACKEND=libinput
10+. endif
11+.endif
12+
13 VERSION_MAJOR=0
14 VERSION_MINOR=0
15 VERSION=${VERSION_MAJOR}.${VERSION_MINOR}
16 
17 PACKAGES=libdrm pixman-1 wayland-server wayland-protocols wld xkbcommon
18 
19-.if ${UNAME} != "NetBSD"
20+.if ${INPUT_BACKEND} == "libinput"
21 PACKAGES+= libinput
22 .if defined(ENABLE_LIBUDEV) && ${ENABLE_LIBUDEV} == 1
23 PACKAGES+= libudev
24@@ -78,6 +86,13 @@ CPPFLAGS+= -DENABLE_LIBUDEV
25 CPPFLAGS+= -DENABLE_XWAYLAND
26 .endif
27 
28+.if defined(EVDEV_KBD_DEVICE)
29+CPPFLAGS+= -DEVDEV_KBD_DEVICE=\\\"${EVDEV_KBD_DEVICE}\\\"
30+.endif
31+.if defined(EVDEV_POINTER_DEVICE)
32+CPPFLAGS+= -DEVDEV_POINTER_DEVICE=\\\"${EVDEV_POINTER_DEVICE}\\\"
33+.endif
34+
35 PROTO_EXTENSIONS= \
36 	protocol/server-decoration.xml \
37 	protocol/swc.xml \
38@@ -200,10 +215,14 @@ SWC_SOURCES= \
39 	protocol/xdg-decoration-unstable-v1-protocol.c \
40 	protocol/xdg-shell-protocol.c
41 
42-.if ${UNAME} == "NetBSD"
43+.if ${INPUT_BACKEND} == "wscons"
44 SWC_SOURCES+= libswc/seat-ws.c
45-.else
46+.elif ${INPUT_BACKEND} == "evdev"
47+SWC_SOURCES+= libswc/seat-evdev.c
48+.elif ${INPUT_BACKEND} == "libinput"
49 SWC_SOURCES+= libswc/seat.c
50+.else
51+.error Unknown INPUT_BACKEND '${INPUT_BACKEND}'. Use libinput, evdev, or wscons.
52 .endif
53 
54 .if defined(ENABLE_XWAYLAND) && ${ENABLE_XWAYLAND} == 1
+2, -0
1@@ -15,4 +15,6 @@ features
2 - fullscreen
3 - double window borders
4 - wallpapers
5+- screenshots
6+- evdev-only input backend
7 - probably more i forgot about
+5, -1
 1@@ -2,7 +2,7 @@
 2 
 3 # The commented out options are defaults
 4 
 5-# PREFIX          = /usr/local
 6+PREFIX          = /usr
 7 # BINDIR          = $(PREFIX)/bin
 8 # LIBDIR          = $(PREFIX)/lib
 9 # INCLUDEDIR      = $(PREFIX)/include
10@@ -18,3 +18,7 @@ ENABLE_STATIC   = 1
11 ENABLE_SHARED   = 0
12 ENABLE_LIBUDEV  = 1
13 ENABLE_XWAYLAND = 1
14+
15+#INPUT_BACKEND  = libinput
16+#   available: libinput, evdev, wscons
17+#   default: libinput on linux, wscons on NetBSD
+20, -2
 1@@ -65,15 +65,33 @@ SWC_SOURCES =                       \
 2     protocol/xdg-decoration-unstable-v1-protocol.c \
 3     protocol/xdg-shell-protocol.c
 4 
 5-ifeq ($(shell uname),NetBSD)
 6+ifeq ($(strip $(INPUT_BACKEND)),)
 7+    ifeq ($(shell uname),NetBSD)
 8+        INPUT_BACKEND = wscons
 9+    else
10+        INPUT_BACKEND = libinput
11+    endif
12+endif
13+
14+ifeq ($(INPUT_BACKEND),wscons)
15     SWC_SOURCES += libswc/seat-ws.c
16-else
17+else ifeq ($(INPUT_BACKEND),evdev)
18+    SWC_SOURCES += libswc/seat-evdev.c
19+    ifneq ($(EVDEV_KBD_DEVICE),)
20+        $(dir)_CFLAGS += -DEVDEV_KBD_DEVICE=\"$(EVDEV_KBD_DEVICE)\"
21+    endif
22+    ifneq ($(EVDEV_POINTER_DEVICE),)
23+        $(dir)_CFLAGS += -DEVDEV_POINTER_DEVICE=\"$(EVDEV_POINTER_DEVICE)\"
24+    endif
25+else ifeq ($(INPUT_BACKEND),libinput)
26     SWC_SOURCES += libswc/seat.c
27     $(dir)_PACKAGES += libinput
28     ifeq ($(ENABLE_LIBUDEV),1)
29         $(dir)_CFLAGS += -DENABLE_LIBUDEV
30         $(dir)_PACKAGES += libudev
31     endif
32+else
33+    $(error Unknown INPUT_BACKEND '$(INPUT_BACKEND)'. Use libinput, evdev, or wscons.)
34 endif
35 
36 ifeq ($(ENABLE_XWAYLAND),1)
+621, -0
  1@@ -0,0 +1,621 @@
  2+#include "seat.h"
  3+#include "compositor.h"
  4+#include "data_device.h"
  5+#include "event.h"
  6+#include "internal.h"
  7+#include "keyboard.h"
  8+#include "launch.h"
  9+#include "pointer.h"
 10+#include "screen.h"
 11+#include "surface.h"
 12+#include "util.h"
 13+
 14+#include <dirent.h>
 15+#include <errno.h>
 16+#include <fcntl.h>
 17+#include <limits.h>
 18+#include <stdbool.h>
 19+#include <stdint.h>
 20+#include <stdio.h>
 21+#include <stdlib.h>
 22+#include <string.h>
 23+#include <unistd.h>
 24+#include <ctype.h>
 25+#include <sys/ioctl.h>
 26+
 27+#include <linux/input.h>
 28+
 29+#ifndef EVDEV_KBD_DEVICE
 30+#define EVDEV_KBD_DEVICE "/dev/input/event0"
 31+#endif
 32+
 33+#ifndef EVDEV_POINTER_DEVICE
 34+#define EVDEV_POINTER_DEVICE "/dev/input/event1"
 35+#endif
 36+
 37+struct seat {
 38+	struct swc_seat base;
 39+
 40+	char *name;
 41+	uint32_t capabilities;
 42+
 43+	int mouse_fd;
 44+	int kbd_fd;
 45+	bool ignore;
 46+
 47+	struct xkb_rule_names names;
 48+
 49+	struct wl_event_source *mouse_source;
 50+	struct wl_event_source *kbd_source;
 51+
 52+	struct wl_listener swc_listener;
 53+
 54+	struct wl_listener keyboard_focus_listener;
 55+	struct pointer pointer;
 56+	struct wl_listener data_device_listener;
 57+
 58+	struct wl_global *global;
 59+	struct wl_list resources;
 60+
 61+	wl_fixed_t abs_x;
 62+	wl_fixed_t abs_y;
 63+	bool abs_initialized;
 64+
 65+	bool shared_fd;
 66+};
 67+
 68+static void
 69+handle_keyboard_focus_event(struct wl_listener *listener, void *data)
 70+{
 71+	struct seat *seat = wl_container_of(listener, seat, keyboard_focus_listener);
 72+	struct event *ev = data;
 73+	struct input_focus_event_data *event_data = ev->data;
 74+
 75+	if (ev->type != INPUT_FOCUS_EVENT_CHANGED)
 76+		return;
 77+
 78+	if (event_data->new) {
 79+		struct wl_client *client = wl_resource_get_client(event_data->new->surface->resource);
 80+
 81+		/* offer the selection to the new focus */
 82+		data_device_offer_selection(seat->base.data_device, client);
 83+	}
 84+}
 85+
 86+static void
 87+handle_data_device_event(struct wl_listener *listener, void *data)
 88+{
 89+	struct seat *seat = wl_container_of(listener, seat, data_device_listener);
 90+	struct event *ev = data;
 91+
 92+	if (ev->type != DATA_DEVICE_EVENT_SELECTION_CHANGED)
 93+		return;
 94+
 95+	if (seat->base.keyboard->focus.client)
 96+		data_device_offer_selection(seat->base.data_device, seat->base.keyboard->focus.client);
 97+}
 98+
 99+static void
100+handle_swc_event(struct wl_listener *listener, void *data)
101+{
102+	struct seat *seat = wl_container_of(listener, seat, swc_listener);
103+	struct event *ev = data;
104+
105+	switch (ev->type) {
106+	case SWC_EVENT_DEACTIVATED:
107+		seat->ignore = true;
108+		keyboard_reset(seat->base.keyboard);
109+		break;
110+	case SWC_EVENT_ACTIVATED:
111+		seat->ignore = false;
112+		break;
113+	}
114+}
115+
116+/* da seat */
117+static void
118+get_pointer(struct wl_client *client, struct wl_resource *resource, uint32_t id)
119+{
120+	struct seat *seat = wl_resource_get_user_data(resource);
121+
122+	pointer_bind(&seat->pointer, client, wl_resource_get_version(resource), id);
123+}
124+
125+static void
126+get_keyboard(struct wl_client *client, struct wl_resource *resource, uint32_t id)
127+{
128+	struct seat *seat = wl_resource_get_user_data(resource);
129+
130+	keyboard_bind(seat->base.keyboard, client, wl_resource_get_version(resource), id);
131+}
132+
133+static void
134+get_touch(struct wl_client *client, struct wl_resource *resource, uint32_t id)
135+{
136+}
137+
138+static struct wl_seat_interface seat_impl = {
139+	.get_pointer = get_pointer,
140+	.get_keyboard = get_keyboard,
141+	.get_touch = get_touch,
142+};
143+
144+static void
145+bind_seat(struct wl_client *client, void *data, uint32_t version, uint32_t id)
146+{
147+	struct seat *seat = data;
148+	struct wl_resource *resource;
149+
150+	if (version > 4)
151+		version = 4;
152+
153+	resource = wl_resource_create(client, &wl_seat_interface, version, id);
154+	wl_resource_set_implementation(resource, &seat_impl, seat, &remove_resource);
155+	wl_list_insert(&seat->resources, wl_resource_get_link(resource));
156+
157+	if (version >= 2)
158+		wl_seat_send_name(resource, seat->name);
159+
160+	wl_seat_send_capabilities(resource, seat->capabilities);
161+}
162+
163+static uint32_t
164+event_time_ms(const struct input_event *ev)
165+{
166+	return (uint32_t)(ev->time.tv_sec * 1000 + ev->time.tv_usec / 1000);
167+}
168+
169+static void
170+handle_evdev_key(struct seat *seat, const struct input_event *ev)
171+{
172+	uint32_t state;
173+	uint32_t time = event_time_ms(ev);
174+
175+	if (ev->value == 2)
176+		return;
177+
178+	if (ev->code >= BTN_MISC)
179+		pointer_handle_button(seat->base.pointer, time, ev->code,
180+			ev->value ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED);
181+	else {
182+		if (ev->code > 255)
183+			return;
184+		state = (ev->value ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED);
185+		keyboard_handle_key(seat->base.keyboard, time, ev->code, state);
186+	}
187+}
188+
189+static void
190+handle_evdev_rel(struct seat *seat, const struct input_event *ev)
191+{
192+	uint32_t time = event_time_ms(ev);
193+	wl_fixed_t value;
194+
195+	switch (ev->code) {
196+	case REL_X:
197+		pointer_handle_relative_motion(seat->base.pointer, time, wl_fixed_from_int(ev->value), 0);
198+		break;
199+	case REL_Y:
200+		pointer_handle_relative_motion(seat->base.pointer, time, 0, wl_fixed_from_int(ev->value));
201+		break;
202+	case REL_WHEEL:
203+		value = wl_fixed_from_int(ev->value * 10);
204+		pointer_handle_axis(seat->base.pointer, time, WL_POINTER_AXIS_VERTICAL_SCROLL, WL_POINTER_AXIS_SOURCE_WHEEL, value, ev->value * 120);
205+		break;
206+	case REL_HWHEEL:
207+		value = wl_fixed_from_int(ev->value * 10);
208+		pointer_handle_axis(seat->base.pointer, time, WL_POINTER_AXIS_HORIZONTAL_SCROLL, WL_POINTER_AXIS_SOURCE_WHEEL, value, ev->value * 120);
209+		break;
210+	default:
211+		break;
212+	}
213+}
214+
215+static void
216+handle_evdev_abs(struct seat *seat, const struct input_event *ev)
217+{
218+	uint32_t time = event_time_ms(ev);
219+
220+	switch (ev->code) {
221+	case ABS_X:
222+		seat->abs_x = wl_fixed_from_int(ev->value);
223+		seat->abs_initialized = true;
224+		break;
225+	case ABS_Y:
226+		seat->abs_y = wl_fixed_from_int(ev->value);
227+		seat->abs_initialized = true;
228+		break;
229+	default:
230+		return;
231+	}
232+
233+	if (seat->abs_initialized)
234+		pointer_handle_absolute_motion(seat->base.pointer, time, seat->abs_x, seat->abs_y);
235+}
236+
237+static int
238+handle_evdev_data(int fd, uint32_t mask, void *data)
239+{
240+	struct seat *seat = data;
241+	struct input_event ev;
242+	ssize_t n;
243+
244+	while (!seat->ignore) {
245+		n = read(fd, &ev, sizeof(ev));
246+		if (n == -1) {
247+			if (errno == EAGAIN || errno == EINTR)
248+				break;
249+			return 0;
250+		}
251+		if (n != (ssize_t)sizeof(ev))
252+			break;
253+
254+		switch (ev.type) {
255+		case EV_KEY:
256+			handle_evdev_key(seat, &ev);
257+			break;
258+		case EV_REL:
259+			handle_evdev_rel(seat, &ev);
260+			break;
261+		case EV_ABS:
262+			handle_evdev_abs(seat, &ev);
263+			break;
264+		case EV_SYN:
265+			if (ev.code == SYN_REPORT)
266+				pointer_handle_frame(seat->base.pointer);
267+			break;
268+		default:
269+			break;
270+		}
271+	}
272+
273+	return 0;
274+}
275+
276+static bool
277+test_bit(const unsigned long *bits, size_t bit)
278+{
279+	return (bits[bit / (sizeof(unsigned long) * 8)] >> (bit % (sizeof(unsigned long) * 8))) & 1;
280+}
281+
282+static bool
283+contains_ci(const char *haystack, const char *needle)
284+{
285+	size_t nlen;
286+	const char *h;
287+
288+	if (!haystack || !needle || !*needle)
289+		return false;
290+
291+	nlen = strlen(needle);
292+	for (h = haystack; *h; ++h) {
293+		size_t i;
294+		for (i = 0; i < nlen; ++i) {
295+			unsigned char hc = (unsigned char)h[i];
296+			unsigned char nc = (unsigned char)needle[i];
297+			if (!h[i] || tolower(hc) != tolower(nc))
298+				break;
299+		}
300+		if (i == nlen)
301+			return true;
302+	}
303+	return false;
304+}
305+
306+static bool
307+is_keyboard_device(int fd)
308+{
309+	unsigned long ev_bits[(EV_MAX + 8 * sizeof(unsigned long) - 1) / (8 * sizeof(unsigned long))];
310+	unsigned long key_bits[(KEY_MAX + 8 * sizeof(unsigned long) - 1) / (8 * sizeof(unsigned long))];
311+
312+	memset(ev_bits, 0, sizeof(ev_bits));
313+	memset(key_bits, 0, sizeof(key_bits));
314+
315+	if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) < 0)
316+		return false;
317+	if (!test_bit(ev_bits, EV_KEY))
318+		return false;
319+	if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), key_bits) < 0)
320+		return false;
321+
322+	return test_bit(key_bits, KEY_A) &&
323+		test_bit(key_bits, KEY_Z) &&
324+		test_bit(key_bits, KEY_ENTER) &&
325+		test_bit(key_bits, KEY_ESC) &&
326+		test_bit(key_bits, KEY_SPACE);
327+}
328+
329+static bool
330+is_pointer_device(int fd)
331+{
332+	unsigned long ev_bits[(EV_MAX + 8 * sizeof(unsigned long) - 1) / (8 * sizeof(unsigned long))];
333+	unsigned long rel_bits[(REL_MAX + 8 * sizeof(unsigned long) - 1) / (8 * sizeof(unsigned long))];
334+	unsigned long key_bits[(KEY_MAX + 8 * sizeof(unsigned long) - 1) / (8 * sizeof(unsigned long))];
335+
336+	memset(ev_bits, 0, sizeof(ev_bits));
337+	memset(rel_bits, 0, sizeof(rel_bits));
338+	memset(key_bits, 0, sizeof(key_bits));
339+
340+	if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) < 0)
341+		return false;
342+
343+	if (test_bit(ev_bits, EV_REL)) {
344+		if (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(rel_bits)), rel_bits) < 0)
345+			return false;
346+		if (test_bit(rel_bits, REL_X) && test_bit(rel_bits, REL_Y))
347+			return true;
348+	}
349+
350+	if (test_bit(ev_bits, EV_KEY)) {
351+		if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), key_bits) < 0)
352+			return false;
353+		if (test_bit(key_bits, BTN_LEFT) && test_bit(key_bits, BTN_RIGHT))
354+			return true;
355+	}
356+
357+	return false;
358+}
359+
360+static bool
361+get_ev_bits(int fd, unsigned long *ev_bits, size_t ev_bits_len)
362+{
363+	memset(ev_bits, 0, ev_bits_len);
364+	return ioctl(fd, EVIOCGBIT(0, ev_bits_len), ev_bits) >= 0;
365+}
366+
367+static int
368+score_candidate(int fd, bool want_keyboard, const char *id_name)
369+{
370+	char name[256];
371+	unsigned long ev_bits[(EV_MAX + 8 * sizeof(unsigned long) - 1) / (8 * sizeof(unsigned long))];
372+	bool is_kbd;
373+	bool is_ptr;
374+	int score = 10;
375+
376+	is_kbd = is_keyboard_device(fd);
377+	is_ptr = is_pointer_device(fd);
378+
379+	if (want_keyboard && !is_kbd)
380+		return -1;
381+	if (!want_keyboard && !is_ptr)
382+		return -1;
383+
384+	if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0)
385+		name[0] = '\0';
386+
387+	if (!get_ev_bits(fd, ev_bits, sizeof(ev_bits)))
388+		memset(ev_bits, 0, sizeof(ev_bits));
389+
390+	if (want_keyboard) {
391+		if (is_ptr)
392+			score -= 6;
393+		if (contains_ci(id_name, "mouse") || contains_ci(name, "mouse"))
394+			score -= 12;
395+		if (contains_ci(id_name, "kbd") || contains_ci(id_name, "keyboard"))
396+			score += 4;
397+		if (contains_ci(name, "keyboard"))
398+			score += 2;
399+		if (test_bit(ev_bits, EV_LED))
400+			score += 3;
401+		if (test_bit(ev_bits, EV_REP))
402+			score += 1;
403+	} else {
404+		if (contains_ci(id_name, "mouse") || contains_ci(name, "mouse"))
405+			score += 4;
406+		if (contains_ci(id_name, "kbd") || contains_ci(id_name, "keyboard"))
407+			score -= 6;
408+		if (contains_ci(name, "keyboard"))
409+			score -= 4;
410+	}
411+
412+	return score;
413+}
414+
415+static bool
416+pick_best_device(const char *dir_path, const char *name_prefix, const char *name_substr,
417+                 bool want_keyboard, char *out, size_t out_len)
418+{
419+	DIR *dir;
420+	struct dirent *ent;
421+	bool found = false;
422+	int best_score = -1;
423+	size_t prefix_len = name_prefix ? strlen(name_prefix) : 0;
424+
425+	dir = opendir(dir_path);
426+	if (!dir)
427+		return false;
428+
429+	while ((ent = readdir(dir)) != NULL) {
430+		char path[PATH_MAX];
431+		int fd;
432+		int score;
433+
434+		if (ent->d_name[0] == '.')
435+			continue;
436+		if (name_prefix && strncmp(ent->d_name, name_prefix, prefix_len) != 0)
437+			continue;
438+		if (name_substr && !strstr(ent->d_name, name_substr))
439+			continue;
440+
441+		snprintf(path, sizeof(path), "%s/%s", dir_path, ent->d_name);
442+		fd = launch_open_device(path, O_RDONLY | O_NONBLOCK);
443+		if (fd == -1)
444+			continue;
445+
446+		score = score_candidate(fd, want_keyboard, ent->d_name);
447+		if (score < 0) {
448+			close(fd);
449+			continue;
450+		}
451+
452+		if (score > best_score) {
453+			snprintf(out, out_len, "%s", path);
454+			best_score = score;
455+			found = true;
456+		}
457+
458+		close(fd);
459+	}
460+
461+	closedir(dir);
462+	return found;
463+}
464+
465+static bool
466+initialize_evdev(struct seat *seat)
467+{
468+	char kbd_path[PATH_MAX];
469+	char mouse_path[PATH_MAX];
470+	const char *kbd_dev = EVDEV_KBD_DEVICE;
471+	const char *mouse_dev = EVDEV_POINTER_DEVICE;
472+
473+	if (pick_best_device("/dev/input/by-id", NULL, "event-kbd", true, kbd_path, sizeof(kbd_path)))
474+		kbd_dev = kbd_path;
475+	else if (pick_best_device("/dev/input/by-path", NULL, "event-kbd", true, kbd_path, sizeof(kbd_path)))
476+		kbd_dev = kbd_path;
477+	else if (pick_best_device("/dev/input", "event", NULL, true, kbd_path, sizeof(kbd_path)))
478+		kbd_dev = kbd_path;
479+
480+	if (pick_best_device("/dev/input/by-id", NULL, "event-mouse", false, mouse_path, sizeof(mouse_path)))
481+		mouse_dev = mouse_path;
482+	else if (pick_best_device("/dev/input/by-path", NULL, "event-mouse", false, mouse_path, sizeof(mouse_path)))
483+		mouse_dev = mouse_path;
484+	else if (pick_best_device("/dev/input", "event", NULL, false, mouse_path, sizeof(mouse_path)))
485+		mouse_dev = mouse_path;
486+
487+	DEBUG("evdev devices: keyboard=%s pointer=%s\n", kbd_dev, mouse_dev);
488+
489+	seat->kbd_fd = launch_open_device(kbd_dev, O_RDONLY | O_NONBLOCK);
490+	if (seat->kbd_fd == -1) {
491+		ERROR("Could not open evdev keyboard device %s\n", kbd_dev);
492+		goto error0;
493+	}
494+
495+	if (strcmp(kbd_dev, mouse_dev) == 0) {
496+		seat->mouse_fd = seat->kbd_fd;
497+		seat->shared_fd = true;
498+		return true;
499+	}
500+
501+	seat->mouse_fd = launch_open_device(mouse_dev, O_RDONLY | O_NONBLOCK);
502+	if (seat->mouse_fd == -1) {
503+		ERROR("Could not open evdev pointer device %s\n", mouse_dev);
504+		goto error1;
505+	}
506+
507+	return true;
508+
509+error1:
510+	close(seat->kbd_fd);
511+error0:
512+	return false;
513+}
514+
515+struct swc_seat *
516+seat_create(struct wl_display *display, const char *seat_name)
517+{
518+	struct seat *seat;
519+
520+	seat = malloc(sizeof(*seat));
521+	if (!seat)
522+		goto error0;
523+
524+	memset(&seat->names, 0, sizeof(seat->names));
525+	seat->names.rules = "base";
526+	seat->names.model = "pc105";
527+	seat->names.layout = "us";
528+	seat->names.variant = "basic";
529+	seat->shared_fd = false;
530+
531+	seat->name = strdup(seat_name);
532+	if (!seat->name) {
533+		ERROR("Could not allocate seat name string\n");
534+		goto error1;
535+	}
536+
537+	if (!initialize_evdev(seat))
538+		goto error2;
539+
540+	seat->global = wl_global_create(display, &wl_seat_interface, 4, seat, &bind_seat);
541+	if (!seat->global)
542+		goto error2;
543+	seat->capabilities = WL_SEAT_CAPABILITY_KEYBOARD | WL_SEAT_CAPABILITY_POINTER;
544+	wl_list_init(&seat->resources);
545+
546+	seat->swc_listener.notify = &handle_swc_event;
547+	wl_signal_add(&swc.event_signal, &seat->swc_listener);
548+
549+	seat->base.data_device = data_device_create();
550+	if (!seat->base.data_device) {
551+		ERROR("could not initialize data device\n");
552+		goto error3;
553+	}
554+	seat->data_device_listener.notify = &handle_data_device_event;
555+	wl_signal_add(&seat->base.data_device->event_signal, &seat->data_device_listener);
556+
557+	seat->base.keyboard = keyboard_create(&seat->names);
558+	if (!seat->base.keyboard) {
559+		ERROR("could not initialize keyboard\n");
560+		goto error4;
561+	}
562+	seat->keyboard_focus_listener.notify = handle_keyboard_focus_event;
563+	wl_signal_add(&seat->base.keyboard->focus.event_signal, &seat->keyboard_focus_listener);
564+
565+	if (!pointer_initialize(&seat->pointer)) {
566+		ERROR("Could not initialize pointer\n");
567+		goto error5;
568+	}
569+	seat->base.pointer = &seat->pointer;
570+
571+	seat->kbd_source = wl_event_loop_add_fd
572+		(swc.event_loop, seat->kbd_fd, WL_EVENT_READABLE,
573+		 &handle_evdev_data, seat);
574+	if (!seat->shared_fd) {
575+		seat->mouse_source = wl_event_loop_add_fd
576+			(swc.event_loop, seat->mouse_fd, WL_EVENT_READABLE,
577+			 &handle_evdev_data, seat);
578+	} else {
579+		seat->mouse_source = NULL;
580+	}
581+
582+	seat->abs_initialized = false;
583+
584+	return &seat->base;
585+
586+error5:
587+	keyboard_destroy(seat->base.keyboard);
588+error4:
589+	data_device_destroy(seat->base.data_device);
590+error3:
591+	wl_global_destroy(seat->global);
592+error2:
593+	free(seat->name);
594+error1:
595+	free(seat);
596+error0:
597+	return NULL;
598+}
599+
600+void
601+seat_destroy(struct swc_seat *seat_base)
602+{
603+	struct seat *seat = wl_container_of(seat_base, seat, base);
604+
605+	if (seat->mouse_source)
606+		wl_event_source_remove(seat->mouse_source);
607+	wl_event_source_remove(seat->kbd_source);
608+	if (seat->mouse_source) {
609+		close(seat->mouse_fd);
610+		seat->mouse_fd = -1;
611+	}
612+	close(seat->kbd_fd);
613+	seat->kbd_fd = -1;
614+
615+	pointer_finalize(&seat->pointer);
616+	keyboard_destroy(seat->base.keyboard);
617+	data_device_destroy(seat->base.data_device);
618+
619+	wl_global_destroy(seat->global);
620+	free(seat->name);
621+	free(seat);
622+}
+1, -0
1@@ -361,6 +361,7 @@ seat_create(struct wl_display *display, const char *seat_name)
2 	if (!seat)
3 		goto error0;
4 
5+	memset(&seat->names, 0, sizeof(seat->names));
6 	seat->names.rules = "base";
7 	seat->names.model = "pc105";
8 	seat->names.layout = "us";