diff --git a/Makefile b/Makefile index 578194f..601c70e 100644 --- a/Makefile +++ b/Makefile @@ -12,16 +12,17 @@ DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra -Wdeclaration-after-statement \ -Wfloat-conversion # CFLAGS / LDFLAGS -PKGS = wayland-server xkbcommon libinput $(XLIBS) +PKGS = wayland-server xkbcommon libinput libevdev libudev $(XLIBS) DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(WLR_INCS) $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(WLR_LIBS) -lm $(LIBS) all: dwl -dwl: dwl.o util.o - $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ -dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ +dwl: dwl.o gamepad.o util.o + $(CC) dwl.o gamepad.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ +dwl.o: dwl.c client.h gamepad.h config.h config.mk cursor-shape-v1-protocol.h \ pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h +gamepad.o: gamepad.c gamepad.h util.o: util.c util.h # wayland-scanner is a tool which generates C headers and rigging for Wayland @@ -54,7 +55,7 @@ clean: dist: clean mkdir -p dwl-$(VERSION) cp -R LICENSE* Makefile CHANGELOG.md README.md client.h config.def.h \ - config.mk protocols dwl.1 dwl.c util.c util.h dwl.desktop \ + config.mk protocols dwl.1 dwl.c gamepad.c gamepad.h util.c util.h dwl.desktop \ dwl-$(VERSION) tar -caf dwl-$(VERSION).tar.gz dwl-$(VERSION) rm -rf dwl-$(VERSION) diff --git a/config.def.h b/config.def.h index 8a6eda0..48270f6 100644 --- a/config.def.h +++ b/config.def.h @@ -165,6 +165,22 @@ static const Key keys[] = { CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), }; +static const GamepadButton gamepadbuttons[] = { + { BTN_DPAD_DOWN, focusstack, {.i = +1} }, + { BTN_DPAD_UP, focusstack, {.i = -1} }, + { BTN_DPAD_LEFT, setmfact, {.f = -0.05f} }, + { BTN_DPAD_RIGHT, setmfact, {.f = +0.05f} }, + { BTN_SOUTH, zoom, {0} }, + { BTN_EAST, setlayout, {.v = &layouts[0]} }, + { BTN_NORTH, setlayout, {.v = &layouts[1]} }, + { BTN_WEST, setlayout, {.v = &layouts[2]} }, + { ABS_Z, focusmon, {.i = WLR_DIRECTION_LEFT} }, + { ABS_RZ, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { BTN_START, spawn, SHCMD("wmenu-run") }, + { BTN_SELECT, winview, {0} }, + { BTN_MODE, spawn, { "steam", NULL }}, +}; + static const Button buttons[] = { { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, { MODKEY, BTN_MIDDLE, togglefloating, {0} }, diff --git a/dwl.c b/dwl.c index 101a45f..96e1c01 100644 --- a/dwl.c +++ b/dwl.c @@ -63,6 +63,7 @@ #include #include #include +#include "gamepad.h" #ifdef XWAYLAND #include #include @@ -149,6 +150,12 @@ typedef struct { const Arg arg; } Key; +typedef struct { + unsigned int button; + void (*func)(const Arg *); + const Arg arg; +} GamepadButton; + typedef struct { struct wlr_keyboard_group *wlr_group; @@ -289,6 +296,7 @@ static void focusstack(const Arg *arg); static Client *focustop(Monitor *m); static void fullscreennotify(struct wl_listener *listener, void *data); static void gpureset(struct wl_listener *listener, void *data); +static void gamepadbutton(unsigned int button, int state); static void handlesig(int signo); static void incnmaster(const Arg *arg); static void inputdevice(struct wl_listener *listener, void *data); @@ -398,6 +406,7 @@ static struct wlr_session_lock_v1 *cur_lock; static struct wlr_seat *seat; static KeyboardGroup *kb_group; +static struct gamepad *gamepad; static unsigned int cursor_mode; static Client *grabc; static int grabcx, grabcy; /* client-relative */ @@ -713,6 +722,7 @@ cleanup(void) wlr_xcursor_manager_destroy(cursor_mgr); destroykeyboardgroup(&kb_group->destroy, NULL); + gamepad_destroy(gamepad); /* If it's not destroyed manually, it will cause a use-after-free of wlr_seat. * Destroy it until it's fixed on the wlroots side */ @@ -1558,6 +1568,21 @@ gpureset(struct wl_listener *listener, void *data) wlr_renderer_destroy(old_drw); } +void +gamepadbutton(unsigned int button, int state) +{ + const GamepadButton *b; + if (!state || locked) + return; + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + for (b = gamepadbuttons; b < END(gamepadbuttons); b++) { + if (button == b->button && b->func) { + b->func(&b->arg); + break; + } + } +} + void handlesig(int signo) { @@ -2643,6 +2668,7 @@ setup(void) kb_group = createkeyboardgroup(); wl_list_init(&kb_group->destroy.link); + gamepad = gamepad_create(event_loop, session, gamepadbutton); output_mgr = wlr_output_manager_v1_create(dpy); wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); diff --git a/gamepad.c b/gamepad.c new file mode 100644 index 0000000..6bad2f6 --- /dev/null +++ b/gamepad.c @@ -0,0 +1,255 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gamepad.h" + +struct pad { + struct wl_list link; + char *path; + int fd; + struct libevdev *dev; + struct wl_event_source *src; + unsigned char key[KEY_CNT]; + unsigned char abs[ABS_CNT]; + int enabled; + GamepadHandler fn; +}; + +struct gamepad { + struct udev *udev; + struct udev_monitor *mon; + struct wl_event_source *msrc; + struct wl_event_loop *loop; + struct wl_list pads; + GamepadHandler fn; +}; + +static int +ispad(struct udev_device *d) +{ + const char *p; + + p = udev_device_get_property_value(d, "ID_INPUT_JOYSTICK"); + return !strcmp(udev_device_get_subsystem(d), "input") + && !strncmp(udev_device_get_sysname(d), "event", 5) + && p && !strcmp(p, "1"); +} + +static void +padstop(struct pad *p) +{ + if (p->src) + wl_event_source_remove(p->src); + if (p->dev) + libevdev_free(p->dev); + if (p->fd >= 0) + close(p->fd); + p->src = NULL; + p->dev = NULL; + p->fd = -1; +} + +static void +padclose(struct pad *p) +{ + padstop(p); + wl_list_remove(&p->link); + free(p->path); + free(p); +} + +static int +padin(int fd, uint32_t mask, void *data) +{ + struct pad *p = data; + struct input_event e; + int r, v; + + if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { + padclose(p); + return 0; + } + while ((r = libevdev_next_event(p->dev, LIBEVDEV_READ_FLAG_NORMAL, &e)) == 0 + || r == LIBEVDEV_READ_STATUS_SYNC) { + if (e.type == EV_KEY) { + v = !!e.value; + if (p->key[e.code] != v) { + p->key[e.code] = v; + if (p->enabled) + p->fn(e.code, v); + if ((e.code == BTN_TL || e.code == BTN_TR) + && p->key[BTN_TL] && p->key[BTN_TR] && v) + p->enabled ^= 1; + } + } else if (e.type == EV_ABS && (e.code == ABS_Z || e.code == ABS_RZ + || e.code == ABS_GAS || e.code == ABS_BRAKE)) { + const struct input_absinfo *a = libevdev_get_abs_info(p->dev, e.code); + + v = e.value > (a->minimum + a->maximum) / 2; + if (p->abs[e.code] != v) { + p->abs[e.code] = v; + if (p->enabled) + p->fn(e.code, v); + } + } else if (e.type == EV_ABS && e.code == ABS_HAT0X) { + v = e.value + 1; + if (p->abs[e.code] != v) { + if (p->enabled) { + p->fn(BTN_DPAD_LEFT, 0); + p->fn(BTN_DPAD_RIGHT, 0); + } + p->abs[e.code] = v; + if (p->enabled) { + p->fn(BTN_DPAD_LEFT, e.value < 0); + p->fn(BTN_DPAD_RIGHT, e.value > 0); + } + } + } else if (e.type == EV_ABS && e.code == ABS_HAT0Y) { + v = e.value + 1; + if (p->abs[e.code] != v) { + if (p->enabled) { + p->fn(BTN_DPAD_UP, 0); + p->fn(BTN_DPAD_DOWN, 0); + } + p->abs[e.code] = v; + if (p->enabled) { + p->fn(BTN_DPAD_UP, e.value < 0); + p->fn(BTN_DPAD_DOWN, e.value > 0); + } + } + } + } + return 0; +} + +static void +padstart(struct pad *p, struct wl_event_loop *loop) +{ + if (p->fd >= 0 || (p->fd = open(p->path, O_RDONLY | O_NONBLOCK | O_CLOEXEC)) < 0) + return; + if (libevdev_new_from_fd(p->fd, &p->dev)) { + close(p->fd); + p->fd = -1; + return; + } + if (!(p->src = wl_event_loop_add_fd(loop, p->fd, WL_EVENT_READABLE, padin, p))) + padstop(p); +} + +static void +padadd(struct gamepad *g, struct udev_device *d) +{ + struct pad *p; + struct pad *q; + const char *path; + + if (!ispad(d) || !(path = udev_device_get_devnode(d))) + return; + wl_list_for_each(q, &g->pads, link) { + if (!strcmp(q->path, path)) + return; + } + if (!(p = calloc(1, sizeof(*p)))) + return; + if (!(p->path = strdup(path))) { + free(p); + return; + } + p->fd = -1; + p->enabled = 1; + p->fn = g->fn; + wl_list_insert(&g->pads, &p->link); + padstart(p, g->loop); +} + +static void +padremove(struct gamepad *g, const char *path) +{ + struct pad *p, *t; + + wl_list_for_each_safe(p, t, &g->pads, link) { + if (!strcmp(p->path, path)) { + padclose(p); + break; + } + } +} + +static int +monin(int fd, uint32_t mask, void *data) +{ + struct gamepad *g = data; + struct udev_device *d; + const char *a; + const char *path; + + if (!(d = udev_monitor_receive_device(g->mon))) + return 0; + a = udev_device_get_action(d); + path = udev_device_get_devnode(d); + if (a && !strcmp(a, "add")) + padadd(g, d); + else if (a && !strcmp(a, "remove") && path) + padremove(g, path); + udev_device_unref(d); + return 0; +} + +struct gamepad * +gamepad_create(struct wl_event_loop *loop, struct wlr_session *s, GamepadHandler fn) +{ + struct gamepad *g; + struct udev_enumerate *e; + struct udev_list_entry *l; + struct udev_device *d; + + if (!s || !(g = calloc(1, sizeof(*g))) || !(g->udev = udev_new())) + return NULL; + g->loop = loop; + g->fn = fn; + wl_list_init(&g->pads); + if ((e = udev_enumerate_new(g->udev))) { + udev_enumerate_add_match_subsystem(e, "input"); + udev_enumerate_scan_devices(e); + udev_list_entry_foreach(l, udev_enumerate_get_list_entry(e)) { + d = udev_device_new_from_syspath(g->udev, udev_list_entry_get_name(l)); + if (d) { + padadd(g, d); + udev_device_unref(d); + } + } + udev_enumerate_unref(e); + } + if (!(g->mon = udev_monitor_new_from_netlink(g->udev, "udev")) + || udev_monitor_filter_add_match_subsystem_devtype(g->mon, "input", NULL) + || udev_monitor_enable_receiving(g->mon) + || !(g->msrc = wl_event_loop_add_fd(loop, udev_monitor_get_fd(g->mon), WL_EVENT_READABLE, monin, g))) { + gamepad_destroy(g); + return NULL; + } + return g; +} + +void +gamepad_destroy(struct gamepad *g) +{ + struct pad *p, *t; + + if (!g) + return; + wl_list_for_each_safe(p, t, &g->pads, link) + padclose(p); + if (g->msrc) + wl_event_source_remove(g->msrc); + if (g->mon) + udev_monitor_unref(g->mon); + if (g->udev) + udev_unref(g->udev); + free(g); +} diff --git a/gamepad.h b/gamepad.h new file mode 100644 index 0000000..5d45d28 --- /dev/null +++ b/gamepad.h @@ -0,0 +1,15 @@ +#ifndef GAMEPAD_H +#define GAMEPAD_H + +#include + +struct gamepad; +struct wl_event_loop; +struct wlr_session; + +typedef void (*GamepadHandler)(unsigned int, int); + +struct gamepad *gamepad_create(struct wl_event_loop *, struct wlr_session *, GamepadHandler); +void gamepad_destroy(struct gamepad *); + +#endif