#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; #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); }