aboutsummaryrefslogblamecommitdiffstats
path: root/test-visual.c
blob: 6e2b46680ad719c87e9c9e2de924597f73cf13cd (plain) (tree)
1
2
3
4
5
6
7




                                                         

                   














                       



















                                                   


                                                                 

                                                 



                                                   
                                                                                      


                           



                                                                   


                                                                              

                                                 
                                                                            










                                                           

                                   

                            



                                     


                                      
                                        
                  

                                





                                                                                                                
                                                                                         



                                                 














                                                 


                         



















                                                         









                                                                               
                                                                                               














                                                                                                    
                                                                                                        






























                                                                                                            
                                                                                                             




                                                                         
                                                                                         





                                                                  















                                              
                                                               


                                                 

                                            






















                                                                                                                    




                                          

                 
/* See LICENSE file for copyright and license details. */
#include "libgamepad.h"

#include <sys/ioctl.h>
#include <assert.h>
#include <poll.h>
#include <signal.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 volatile sig_atomic_t sigwinch_received = 0;
static volatile sig_atomic_t sigint_received = 0;


static void
sigwinch_handler(int signo)
{
	(void) signo;
	sigwinch_received = 1;
}


static void
sigint_handler(int signo)
{
	(void) signo;
	sigint_received = 1;
}


static const char *
draw_axis(char buffer[], const struct abs_axis *axis, size_t len)
{
	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 ? 0 : /* this actually happens */
	             axis->value > axis->max ? axis->max - axis->min :
	             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 + 1 - 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;
	size_t max_button_name_len;
	size_t len, x, y, i;
	struct winsize size;
	size_t button_columns;
	size_t button_rows;
	size_t max_abs_axis_name_len;
	struct abs_axis *abs_axes;
	char *buffer;
	ssize_t saxis_len;
	size_t axis_len, req_axis_len;
	size_t max_max_len, max_min_len;
	ssize_t r;
	struct pollfd pfd;
	struct sigaction action;

	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 | O_NONBLOCK)) {
		perror("libgamepad_open_device");
		return 1;
	}

	pfd.fd = gamepad.fd;
	pfd.events = POLLIN;
	pfd.revents = 0;

	memset(&action, 0, sizeof(action));
	action.sa_handler = sigwinch_handler;
	if (sigaction(SIGWINCH, &action, NULL)) {
		perror("sigaction");
		return 1;
	}

	memset(&action, 0, sizeof(action));
	action.sa_handler = sigint_handler;
	if (sigaction(SIGINT, &action, NULL)) {
		perror("sigaction");
		return 1;
	}

redraw:
	button_names = NULL;
	max_button_name_len = 0;
	button_columns = 0;
	button_rows = 0;
	max_abs_axis_name_len = 0;
	abs_axes = NULL;
	max_max_len = 0;
	max_min_len = 0;

	while (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size)) {
		if (errno != EINTR) {
			perror("TIOCGWINSZ");
			return 1;
		}
		sigwinch_received = 0;
		if (sigint_received)
			goto out;
	}

	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(NULL, 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(NULL, 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(NULL, gamepad.absolute_axes[i]),
		       draw_axis(buffer, &abs_axes[i], axis_len));
	}

	fflush(stdout);

	for (;;) {
		if (sigwinch_received) {
			sigwinch_received = 0;
			free(button_names);
			free(abs_axes);
			free(buffer);
			goto redraw;
		}
		if (sigint_received)
			break;

		if (poll(&pfd, 1, -1) < 0) {
			if (errno == EINTR)
				continue;
			break;
		}

		r = libgamepad_next_event(&gamepad, &event, 1);
		if (r <= 0) {
			if (!r || errno == EINTR)
				continue;
			if (errno == EAGAIN)
				break;
			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 */
	}

out:
	free(buffer);
	free(button_names);
	free(abs_axes);
	libgamepad_close_device(&gamepad);
	return 0;
}