aboutsummaryrefslogblamecommitdiff
path: root/platform/linux.c
blob: da8d64c3b2dc8da46cb88a278e328775793b68fb (plain) (tree)



























                                                                                       










                                                                 






                                                                       
                                                                                           



                                                                     




                                                                                                 



                                                                        



























                                                                                                                     


                                                               



                                                            
         
 
                                         
                                      

 
  



                                                     
                                                                            














                                                                                                             

                                                                 









                                                                                          
                                                           


























                                                                                  


                                                      


























                                                                                              
 










                                                                                     
#include <stdio.h>
#include <stdbool.h>
#include <signal.h>
#include <dlfcn.h>

/* Audio with SDL for now (would prefer something more direct but it's proving hard) */
#include <SDL2/SDL.h>
#include <SDL2/SDL_mixer.h>

/* Capture input with XInput Extensions 2 */
#include <X11/Xlib.h>
#include <X11/extensions/XInput2.h>

#include "platform.h"
#include "../board/board.h"

#define MIXER_FREQ 44100
#define MIXER_FORMAT AUDIO_S16LSB
#define MIXER_CHANNELS 2
#define MIXER_CHUNKSIZE 64

extern void keyboard_on_down(void);
extern void keyboard_on_up(void);

static bool running = true;
static Display *display;
static int xi_opcode;

#define AUDIO_CACHE_INITIAL 10

struct mixer_cache {
	Mix_Chunk *chunk;
	unsigned char *buffer;
};
static struct mixer_cache *cached_chunks = NULL;
static unsigned int cached_len = 0;
/* TODO: Add a limit to the capacity? Do we panic if it's hit? */
static unsigned int cached_capacity = AUDIO_CACHE_INITIAL;

bool sound_init(float volume)
{
	if (SDL_Init(SDL_INIT_AUDIO) < 0) {
		printf("ERROR: Could not initialise SDL for audio.\n");
		return false;
	}

	if (Mix_OpenAudio(MIXER_FREQ, MIXER_FORMAT, MIXER_CHANNELS, MIXER_CHUNKSIZE) < 0) {
		printf("ERROR: SDL mixer audio device open fail.\n");
		return false;
	}

	if ((cached_chunks = malloc(AUDIO_CACHE_INITIAL * sizeof(struct mixer_cache))) == NULL) {
		printf("ERROR: Could not allocate for audio cache.\n");
		return false;
	}

	Mix_Volume(-1, volume * MIX_MAX_VOLUME); // set for all channels
	return true;
}

/* Find sample in cache, otherwise load into it */
static Mix_Chunk *sound_cached(unsigned char *buffer, unsigned int buffer_len) {
	/* Linear scan for now */
	for (unsigned int i = 0; i < cached_len; ++i) {
		struct mixer_cache cached = cached_chunks[i];
		if (cached.buffer == buffer)
			return cached.chunk;
	}

	/* Not in cache; grow if required as well */
	if (cached_len >= cached_capacity) {
		cached_capacity += 10;
		if ((cached_chunks = realloc(cached_chunks, cached_capacity * sizeof(struct mixer_cache))) == NULL) {
			printf("ERROR: Could not allocate for audio cache!\n");
			return NULL;
		}
	}

	SDL_RWops *sample_rw = SDL_RWFromMem(buffer, buffer_len);
	Mix_Chunk *chunk = Mix_LoadWAV_RW(sample_rw, 1);
	if (chunk == NULL) {
		printf("ERROR: Audio sample could not be loaded - %s\n", Mix_GetError());
		return NULL;
	}

	cached_chunks[cached_len++] = (struct mixer_cache){ .chunk = chunk, .buffer = buffer };
	return chunk;
}

void sound_play(unsigned char *buffer, unsigned int buffer_len)
{
	Mix_Chunk *chunk = sound_cached(buffer, buffer_len);
	if (chunk == NULL) {
		running = false;
		return;
	}

	/* -1 means not-in-use channel */
	Mix_PlayChannel(-1, chunk, 0);
}

/*
 * Uses XInput2 extensions to hook into the keyboard.
 * Noticed this was possible when I used:
 *   $ xinput test-xi2 --root
 * Resources used:
 *   - https://github.com/freedesktop/xorg-xinput/blob/master/src/test_xi2.c
 *   - https://cgit.freedesktop.org/xorg/proto/inputproto/commit/?id=f4f09d40e0fd94d267b280f2a82385dca1141347
 */
bool keyboard_hook(void)
{
	fprintf(stderr, "Setting up keyboard hook...\n");

	/* Connect to X, then use XI2 to grab events */
	display = XOpenDisplay(NULL);
	if (display == NULL) {
		printf("ERROR: Could not open X display.\n");
		return false;
	}
	Window root_window = DefaultRootWindow(display);

	int xi_event, xi_error;
	if (!XQueryExtension(display, "XInputExtension",
	                     &xi_opcode, &xi_event, &xi_error)) {
		printf("ERROR: Could not find XInputExtension.\n");
		return false;
	}

	fprintf(stderr, "Setting up XInput2 filter...\n");
	unsigned char mask[XIMaskLen(XI_RawKeyPress) + XIMaskLen(XI_RawKeyRelease)] = {0};
	XISetMask(mask, XI_RawKeyPress);
	XISetMask(mask, XI_RawKeyRelease);
	XIEventMask event_mask = {
		.deviceid = XIAllMasterDevices,
		.mask_len = sizeof(mask) / sizeof(mask[0]),
		.mask = mask,
	};
	XISelectEvents(display, root_window, &event_mask, 1);
	return true;
}

void keyboard_unhook(void)
{
	fprintf(stderr, "Cleaning up audio system and hooks...\n");
	Mix_Quit();
	SDL_Quit();
	XCloseDisplay(display);
}

void handle_signal(int)
{
	running = false;
}
void enter_idle(void)
{
	struct sigaction sa = { .sa_handler = handle_signal };
	sigaction(SIGINT, &sa, NULL);

	XGenericEventCookie event;
	while (running) {
		XNextEvent(display, (XEvent *) &event);
		/* XGenEventData fails if it isn't a XGenericEventCookie anyway */
		if (XGetEventData(display, &event)
		    && event.type == GenericEvent
		    && event.extension == xi_opcode) {
			/* char *ur = event.evtype == XI_RawKeyPress ? "press" : "release"; */
			/* unsigned int kc = ((XIDeviceEvent *) event.data)->detail; */

			if (event.evtype == XI_RawKeyPress) {
				keyboard_on_down();
			} else if (event.evtype == XI_RawKeyRelease) {
				keyboard_on_up();
			}
		}
		XFreeEventData(display, &event);
	}
}

struct board *load_board(char *board_name)
{
	char so_name[100];
	int r = snprintf(so_name, 100, "./board/%s.so", board_name);
	if (r < 0 || r >= 100) {
		printf("ERROR: Invalid board name.\n");
		return false;
	}

	void *board_module = dlopen(so_name, RTLD_LAZY);
	if (board_module == NULL) {
		printf("ERROR: Could not load board module.\n");
		return false;
	}

	fn_board_init board_init = dlsym(board_module, "board_init");
	if (board_init == NULL) {
		printf("ERROR: No board initialisation function could be loaded.\n");
		return false;
	}

	struct board_data data = {
		.sound_play = &sound_play
	};
	return board_init(data);
}