/* * swaystick: control Sway with a DualSense gamepad * Copyright (c) 2024 Jeremy Baxter. */ #include #include #include #include #include #include /* DualSense button mapping */ enum DSButton { DS_CROSS = 0, DS_CIRCLE = 1, DS_TRIANGLE = 2, DS_SQUARE = 3, DS_L1 = 4, DS_R1 = 5, DS_L2 = 6, DS_R2 = 7, DS_CREATE = 8, DS_OPTIONS = 9, DS_PLAYSTATION = 10, DS_LSTICK = 11, DS_RSTICK = 12 }; int read_event(int fd, struct js_event *event) { ssize_t bytes; bytes = read(fd, event, sizeof(*event)); if (bytes == sizeof(*event)) return 0; return -1; } size_t get_axis_count(int fd) { __u8 axes; if (ioctl(fd, JSIOCGAXES, &axes) == -1) return 0; return axes; } size_t get_button_count(int fd) { __u8 buttons; if (ioctl(fd, JSIOCGBUTTONS, &buttons) == -1) return 0; return buttons; } char * make_move_command(int pos, int x) { char *buffer; size_t len; len = 32; buffer = malloc(len * sizeof(char)); snprintf(buffer, len, "move %s %d", x ? "right" : "down", pos / 2000); return buffer; } char * make_resize_command(int pos, int x) { char *buffer; size_t len; len = 32; buffer = malloc(len * sizeof(char)); snprintf(buffer, len, "resize %s %s %d", pos > 0 ? "grow" : "shrink", x ? "width" : "height", pos / 2000); return buffer; } void swaymsg(const char *msg) { const char *prog = "swaymsg"; pid_t child; int wstatus; child = fork(); switch (child) { case -1: perror("fork"); break; case 0: if (execlp(prog, prog, msg, NULL) == -1) perror("execlp"); break; default: if (wait(&wstatus) == -1) perror("wait"); } } struct axis_state { short x, y; }; /** * Keeps track of the current axis state. * * NOTE: This function assumes that axes are numbered starting from 0, * and that the X axis is an even number, and the Y axis is an odd number. * However, this is usually a safe assumption. * * Returns the axis that the event indicated. */ size_t get_axis_state(struct js_event *event, struct axis_state axes[3]) { size_t axis = event->number / 2; if (axis < 3) { if (event->number % 2 == 0) axes[axis].x = event->value; else axes[axis].y = event->value; } return axis; } int main(int argc, char *argv[]) { struct axis_state axes[3] = {0}; struct js_event event; const char *device; size_t axis; int js; int moving; moving = 0; if (argc > 1) device = argv[1]; else device = "/dev/input/js0"; js = open(device, O_RDONLY); if (js == -1) perror(device); /* This loop will exit if the controller is unplugged. */ while (read_event(js, &event) == 0) { if (moving) swaymsg("move position cursor"); switch (event.type) { case JS_EVENT_BUTTON: if (event.value) { /* pressed */ switch (event.number) { case DS_CROSS: swaymsg("kill"); break; case DS_TRIANGLE: moving = 1; break; case DS_LSTICK: swaymsg("focus prev"); break; case DS_RSTICK: swaymsg("focus next"); break; default: break; } break; } /* released */ switch (event.number) { case DS_CIRCLE: swaymsg("focus mode_toggle"); break; case DS_TRIANGLE: moving = 0; break; case DS_SQUARE: swaymsg("floating toggle"); break; case DS_L1: swaymsg("workspace prev"); break; case DS_R1: swaymsg("workspace next"); break; case DS_CREATE: swaymsg("layout tabbed"); break; case DS_OPTIONS: swaymsg("layout toggle split"); break; case DS_PLAYSTATION: swaymsg("exec 'emacsclient -c'"); break; default: break; } break; case JS_EVENT_AXIS: axis = get_axis_state(&event, axes); if (axis > 2) break; /* no activity? */ if (axes[axis].x > -2000 && axes[axis].x < 2000 && axes[axis].y > -2000 && axes[axis].y < 2000) break; if (axis == 0) { /* left X/Y */ swaymsg(make_move_command(axes[axis].x, 1)); swaymsg(make_move_command(axes[axis].y, 0)); } default: /* ignore init events */ break; } } close(js); return 0; }