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. --- Makefile | 7 ++- board/Makefile | 3 +- board/board.h | 4 +- board/simple_board.h | 9 ++- clak.c | 4 +- platform/linux.c | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++ platform/platform.h | 4 +- platform/win32.c | 4 +- 8 files changed, 187 insertions(+), 13 deletions(-) create mode 100644 platform/linux.c diff --git a/Makefile b/Makefile index cf64742..bc2e86a 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ NAME = clak PREFIX = $(HOME)/.local CC = gcc -CFLAGS += -std=c99 -Wall -Wextra -Wshadow -Werror -pedantic +CFLAGS += -std=c99 -Wall -Wextra -Wshadow -Werror ifeq ($(OS),Windows_NT) LDLIBS = -lWinmm @@ -11,6 +11,9 @@ else UNAME_S := $(shell uname) ifeq ($(UNAME_S),Linux) PLATFORM = linux + PKG_CONF_LIBS = sdl2 SDL2_mixer x11 xi + CFLAGS += `pkg-config --cflags $(PKG_CONF_LIBS)` + LDLIBS += `pkg-config --libs $(PKG_CONF_LIBS)` endif endif @@ -21,4 +24,4 @@ $(NAME): $(NAME).c platform/$(PLATFORM).c clean: rm -f *.o - rm -f $(NAME) \ No newline at end of file + rm -f $(NAME) diff --git a/board/Makefile b/board/Makefile index 24cb10c..03449cc 100644 --- a/board/Makefile +++ b/board/Makefile @@ -7,6 +7,7 @@ ifeq ($(OS),Windows_NT) OUTEXT = dll else OUTEXT = so + CFLAGS += -fPIC endif default: all @@ -29,4 +30,4 @@ clean: rm -f *.o rm -f *.dll rm -f *.so - rm -f */sound.h \ No newline at end of file + rm -f */sound.h diff --git a/board/board.h b/board/board.h index c7dfc1f..36d39fb 100644 --- a/board/board.h +++ b/board/board.h @@ -8,9 +8,9 @@ struct board { }; struct board_data { - void (*sound_play)(unsigned char *buffer); + void (*sound_play)(unsigned char *buffer, unsigned int buffer_len); }; typedef struct board *(*fn_board_init)(struct board_data data); -#endif /* CLAK_BOARD_H_ */ \ No newline at end of file +#endif /* CLAK_BOARD_H_ */ diff --git a/board/simple_board.h b/board/simple_board.h index 9d9836b..f0997c7 100644 --- a/board/simple_board.h +++ b/board/simple_board.h @@ -2,17 +2,20 @@ static struct board_data board_data; +#define WAV_LEN(W) PREPROC_CONCAT(W,_len) +#define PREPROC_CONCAT(A, B) A ## B + void board_on_down(void) { #ifdef BOARD_DOWN_WAV - board_data.sound_play(BOARD_DOWN_WAV); + board_data.sound_play(BOARD_DOWN_WAV, WAV_LEN(BOARD_DOWN_WAV)); #endif } void board_on_up(void) { #ifdef BOARD_UP_WAV - board_data.sound_play(BOARD_UP_WAV); + board_data.sound_play(BOARD_UP_WAV, WAV_LEN(BOARD_UP_WAV)); #endif } @@ -30,4 +33,4 @@ struct board *board_init(struct board_data data) { board_data = data; return &board; -} \ No newline at end of file +} diff --git a/clak.c b/clak.c index 444165f..f7208e7 100644 --- a/clak.c +++ b/clak.c @@ -40,12 +40,14 @@ int main(int argc, char **argv) // TODO: List valid boards. return 1; } + fprintf(stderr, "Loading board...\n"); if ((board = load_board(argv[1])) == NULL) { printf("Failed to load board, exiting.\n"); return 1; } fprintf(stderr, "Loaded board: %s\n", board->name); + fprintf(stderr, "Initialisating sound system...\n"); if (!sound_init(VOLUME)) { printf("ERROR: Could not initialise sound system.\n"); return 1; @@ -60,4 +62,4 @@ int main(int argc, char **argv) enter_idle(); return 0; -} \ No newline at end of file +} 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