swaystick/swaystick.c
2024-09-17 17:57:12 +12:00

246 lines
4 KiB
C

/*
* swaystick: control Sway with a DualSense gamepad
* Copyright (c) 2024 Jeremy Baxter.
*/
#include <sys/wait.h>
#include <linux/joystick.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/* 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;
}