aboutsummaryrefslogtreecommitdiff
path: root/platform/linux.c
blob: da8d64c3b2dc8da46cb88a278e328775793b68fb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#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;

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