From d3ea90f183a1742fe205f06ec6543f65869a6799 Mon Sep 17 00:00:00 2001 From: Nicholas Tay Date: Tue, 17 May 2022 18:57:48 +1000 Subject: Add Linux support Oh boy this was a bit of a hassle lol - dynamic loading was the easiest part... but then came both sound and x11 Using SDL for now but I'd really like to change it for even lower layer, but then I might have to make my own mixer... oh no. --- platform/linux.c | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++++ platform/platform.h | 4 +- platform/win32.c | 4 +- 3 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 platform/linux.c (limited to 'platform') diff --git a/platform/linux.c b/platform/linux.c new file mode 100644 index 0000000..6793abc --- /dev/null +++ b/platform/linux.c @@ -0,0 +1,165 @@ +#include +#include +#include +#include + +/* Audio with SDL for now (would prefer something more direct but it's proving hard) */ +#include +#include + +/* Capture input with XInput Extensions 2 */ +#include +#include + +#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); +} diff --git a/platform/platform.h b/platform/platform.h index a5e5db4..ca07789 100644 --- a/platform/platform.h +++ b/platform/platform.h @@ -8,11 +8,11 @@ struct board *load_board(char *board_name); bool sound_init(float volume); -void sound_play(unsigned char *buffer); +void sound_play(unsigned char *buffer, unsigned int buffer_len); void keyboard_unhook(void); bool keyboard_hook(void); void enter_idle(void); -#endif /* CLAK_PLATFORM_H_ */ \ No newline at end of file +#endif /* CLAK_PLATFORM_H_ */ diff --git a/platform/win32.c b/platform/win32.c index 1dccc7b..0b1da80 100644 --- a/platform/win32.c +++ b/platform/win32.c @@ -19,7 +19,7 @@ bool sound_init(float volume) return true; } -void sound_play(unsigned char *buffer) +void sound_play(unsigned char *buffer, unsigned int) { PlaySound((const char *) buffer, NULL, SND_MEMORY | SND_ASYNC | SND_NODEFAULT); } @@ -107,4 +107,4 @@ struct board *load_board(char *board_name) .sound_play = &sound_play }; return board_init(data); -} \ No newline at end of file +} -- cgit