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