#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;
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)) {
printf("ERROR: SDL mixer audio device open fail.\n");
return false;
}
Mix_Volume(-1, volume * MIX_MAX_VOLUME); // set for all channels
return true;
}
// TODO: get rid of this with proper cache thing - only supports 1 sound for now
static Mix_Chunk *loaded_chunk = NULL;
void sound_play(unsigned char *buffer, unsigned int buffer_len)
{
/* Load sample into SDL memory thing then into the mixer */
// TODO: Cache these samples as loaded into SDL
if (loaded_chunk == NULL) {
SDL_RWops *sample_rw = SDL_RWFromMem(buffer, buffer_len);
loaded_chunk = Mix_LoadWAV_RW(sample_rw, 1);
if (loaded_chunk == NULL) {
printf("ERROR: Sample load fail - %s\n", Mix_GetError());
running = false;
return;
}
}
/* -1 means not-in-use channel */
Mix_PlayChannel(-1, loaded_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;
}
// HACK: Cast to void function ptr then cast back, otherwise types are incompatible to the compiler.
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);
}