From a44a71ab0f0567ff0d9639c9ccdea709f65a9a42 Mon Sep 17 00:00:00 2001 From: Dhruva Sambrani <44899822+DhruvaSambrani@users.noreply.github.com> Date: Sat, 18 Jul 2026 23:04:54 +0200 Subject: [PATCH] add gamepad-bindings patch --- patches/gamepad-bindings/README.md | 8 + .../gamepad-bindings/gamepad-bindings.patch | 423 ++++++++++++++++++ 2 files changed, 431 insertions(+) create mode 100644 patches/gamepad-bindings/README.md create mode 100644 patches/gamepad-bindings/gamepad-bindings.patch diff --git a/patches/gamepad-bindings/README.md b/patches/gamepad-bindings/README.md new file mode 100644 index 0000000..1b5f882 --- /dev/null +++ b/patches/gamepad-bindings/README.md @@ -0,0 +1,8 @@ +### Description +Gamepad bindings to dwl. Press `LB + RB` toggle gamepad bindings. + +### Download +- [2026-07-18](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/gamepad-bindings/gamepad-bindings.patch) +### Authors +- [dhruva_sambrani](https://codeberg.org/dhruva_sambrani) + diff --git a/patches/gamepad-bindings/gamepad-bindings.patch b/patches/gamepad-bindings/gamepad-bindings.patch new file mode 100644 index 0000000..28b20aa --- /dev/null +++ b/patches/gamepad-bindings/gamepad-bindings.patch @@ -0,0 +1,423 @@ +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