aboutsummaryrefslogtreecommitdiff
path: root/platform/linux.c
blob: 6793abcaac41ca73fbc08ec97b5a8e12d44bcca5 (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
#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);
}