246 lines
4 KiB
C
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;
|
|
}
|