/* See LICENSE file for copyright and license details. */
#include "libgamepad.h"
#include <sys/ioctl.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
struct abs_axis {
intmax_t min;
intmax_t max;
intmax_t value;
size_t max_len;
size_t min_len;
};
static const char *
draw_axis(char buffer[], const struct abs_axis *axis, size_t len)
{
/* TODO axis->value can actually be out of bounds */
char value_buf[sizeof(intmax_t) * 3 + 2];
size_t width, x, value_len;
#ifdef __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wformat-overflow"
#endif
sprintf(buffer, "%-*ji%ji", (int)(len - axis->max_len), axis->min, axis->max);
#ifdef __GNUC__
# pragma GCC diagnostic pop
#endif
width = len - axis->max_len - axis->min_len;
value_len = (size_t)sprintf(value_buf, "%ji", axis->value);
x = axis->max_len + (width - value_len) / 2;
memcpy(&buffer[x], value_buf, value_len);
x = (size_t)(axis->value - axis->min) * len;
x += (size_t)(axis->max - axis->min) / 2;
x /= (size_t)(axis->max - axis->min);
memmove(&buffer[x + sizeof("\033[m") - 1], &buffer[x], len - x);
memcpy(&buffer[x], "\033[m", sizeof("\033[m") - 1);
return buffer;
}
int
main(int argc, char *argv[])
{
const struct input_absinfo *absinfo;
struct libgamepad_input_event event;
struct libgamepad_device gamepad;
const char **button_names = NULL;
size_t max_button_name_len = 0;
size_t len, x, y, i;
struct winsize size;
size_t button_columns = 0;
size_t button_rows = 0;
size_t max_abs_axis_name_len = 0;
struct abs_axis *abs_axes = NULL;
char *buffer;
ssize_t saxis_len;
size_t axis_len, req_axis_len;
size_t max_max_len = 0, max_min_len = 0;
int r;
if (argc != 2) {
fprintf(stderr, "Please provide the path to the subdevice as the only command line argument\n");
return 1;
}
if (libgamepad_open_device(&gamepad, AT_FDCWD, argv[1], O_RDONLY)) {
perror("libgamepad_open_device");
return 1;
}
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size)) {
perror("TIOCGWINSZ");
return 1;
}
size.ws_row = size.ws_row < 1 ? 1 : size.ws_row;
size.ws_col = size.ws_col < 1 ? 1 : size.ws_col;
if (gamepad.nbuttons) {
button_names = calloc(gamepad.nbuttons, sizeof(*button_names));
if (!button_names) {
perror("calloc");
return 1;
}
for (i = 0; i < gamepad.nbuttons; i++) {
button_names[i] = libgamepad_get_button_name(gamepad.buttons[i]);
len = strlen(button_names[i]);
max_button_name_len = max_button_name_len > len ? max_button_name_len : len;
}
button_columns = (size.ws_col + 1) / (max_button_name_len + 3);
button_columns = button_columns < 1 ? 1 : button_columns;
button_rows = (gamepad.nbuttons + button_columns - 1) / button_columns;
}
if (gamepad.nabsolute_axes) {
abs_axes = calloc(gamepad.nabsolute_axes, sizeof(*abs_axes));
if (!abs_axes) {
perror("calloc");
return 1;
}
for (i = 0; i < gamepad.nabsolute_axes; i++) {
len = strlen(libgamepad_get_absolute_axis_name(gamepad.absolute_axes[i]));
max_abs_axis_name_len = max_abs_axis_name_len > len ? max_abs_axis_name_len : len;
absinfo = libgamepad_get_absolute_axis_info(&gamepad, gamepad.absolute_axes[i]);
assert(absinfo);
abs_axes[i].min = (intmax_t)absinfo->minimum;
abs_axes[i].max = (intmax_t)absinfo->maximum;
abs_axes[i].value = (intmax_t)absinfo->value;
abs_axes[i].min_len = (size_t)snprintf(NULL, 0, "%ji", abs_axes[i].min);
max_min_len = max_min_len > abs_axes[i].min_len ? max_min_len : abs_axes[i].min_len;
abs_axes[i].max_len = (size_t)snprintf(NULL, 0, "%ji", abs_axes[i].max);
max_max_len = max_max_len > abs_axes[i].max_len ? max_max_len : abs_axes[i].max_len;
}
}
req_axis_len = max_max_len + max_min_len + 2;
req_axis_len += max_max_len > max_min_len ? max_max_len : max_min_len;
saxis_len = (ssize_t)size.ws_col - (ssize_t)max_abs_axis_name_len - 3;
if (saxis_len < (ssize_t)req_axis_len)
saxis_len = (ssize_t)req_axis_len;
axis_len = (size_t)saxis_len;
buffer = malloc(axis_len + sizeof("\033[m"));
if (!buffer) {
perror("malloc");
return 1;
}
printf("\033[m\033[H\033[J\033[?25l");
for (i = 0; i < gamepad.nbuttons; i++) {
x = i % button_columns;
y = i / button_columns;
printf("\033[%zu;%zuH%s¿%s?\033[m", y + 1, x * (max_button_name_len + 3) + 1,
libgamepad_get_button_is_pressed(&gamepad, gamepad.buttons[i]) ? "\033[7m" : "\033[m",
button_names[i]);
}
printf("\033[%zuH", button_rows + 1);
for (i = 0; i < gamepad.nabsolute_axes; i++) {
printf("\n%-*s ¿\033[7m%s?", (int)max_abs_axis_name_len,
libgamepad_get_absolute_axis_name(gamepad.absolute_axes[i]),
draw_axis(buffer, &abs_axes[i], axis_len));
}
fflush(stdout);
for (;;) {
/* TODO use nonblocking; listen for window resize */
r = libgamepad_next_event(&gamepad, &event);
if (r <= 0) {
if (!r || errno == EINTR)
continue;
perror("libgamepad_next_event");
return 1;
}
if (event.type == LIBGAMEPAD_BUTTON) {
i = (size_t)gamepad.button_map[event.code];
x = i % button_columns;
y = i / button_columns;
printf("\033[%zu;%zuH%s[%s]\033[m", y + 1, x * (max_button_name_len + 3) + 1,
event.value ? "\033[7m" : "\033[m", button_names[i]);
fflush(stdout);
} else if (event.type == LIBGAMEPAD_ABSOLUTE_AXIS) {
i = (size_t)gamepad.absolute_axis_map[event.code];
y = button_rows + 1 + i;
x = max_abs_axis_name_len + 1;
abs_axes[i].value = (intmax_t)event.value;
printf("\033[%zu;%zuH[\033[7m%s]", y + 1, x + 1, draw_axis(buffer, &abs_axes[i], axis_len));
fflush(stdout);
}
/* TODO add relative axis */
}
return 0;
}