aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicholas Tay <nick@windblume.net>2022-05-17 18:57:48 +1000
committerNicholas Tay <nick@windblume.net>2022-05-17 19:06:25 +1000
commitd3ea90f183a1742fe205f06ec6543f65869a6799 (patch)
tree5d38f0e9e2dd794fcdfaaeae9895c0bd101dd175
parent2fee408d6fe0964e245dc0bae90027baa13b159a (diff)
downloadclak-d3ea90f183a1742fe205f06ec6543f65869a6799.tar.gz
clak-d3ea90f183a1742fe205f06ec6543f65869a6799.tar.bz2
clak-d3ea90f183a1742fe205f06ec6543f65869a6799.zip
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.
-rw-r--r--Makefile7
-rw-r--r--board/Makefile3
-rw-r--r--board/board.h4
-rw-r--r--board/simple_board.h9
-rw-r--r--clak.c4
-rw-r--r--platform/linux.c165
-rw-r--r--platform/platform.h4
-rw-r--r--platform/win32.c4
8 files changed, 187 insertions, 13 deletions
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 <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);
+}
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
+}