aboutsummaryrefslogtreecommitdiff
path: root/platform/darwin.c
blob: 3189a5d49c13d0b67f85380ad71c76c68efa5fb9 (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
#include <stdio.h>
#include <stdbool.h>
#include <dlfcn.h>
#include <IOKit/hid/IOHIDManager.h>

#include "darwin-native.bridging.h"
#include "platform.h"
#include "../board/board.h"

/* TODO: Try out objc_msgsend for fun in future to not even have Obj-C! */

extern void keyboard_on_down(void);
extern void keyboard_on_up(void);

static IOHIDManagerRef hid_manager = NULL;

bool sound_init(float volume)
{
	/* Call Obj-C function */
	return macos_sound_init(volume);
}

void sound_play(unsigned char *buffer, unsigned int buffer_len)
{
	/* Call Obj-C function */
	macos_sound_play(buffer, buffer_len);
}

/* TODO: Maybe merge some of this stuff with linux, lots of overlap */
void handle_signal(int signal)
{
	(void) signal;

	/* Terminate the runloop, which will kick out of enter_idle */
	CFRunLoopStop(CFRunLoopGetMain());
}
void enter_idle(void)
{
	/* Setup quit handling */
	struct sigaction sa = { .sa_handler = handle_signal };
	sigaction(SIGINT, &sa, NULL);

	/* Enter the CoreFoundation run loop and it will pump events for our callback */
	CFRunLoopRun();
}

/* TODO: probably also merge into a *nix module */
struct board *load_board(char *board_name)
{
	char so_name[100];
	int r = snprintf(so_name, 100, "./board/%s.dylib", 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);
}

/* https://developer.apple.com/documentation/iokit/iohidvaluecallback */
void hid_callback(void *context, IOReturn result, void *sender, IOHIDValueRef value)
{
	(void) context;
	(void) result;
	(void) sender;

	IOHIDElementRef elem = IOHIDValueGetElement(value);

	/* seems like 'usage' is the scancode */
	int32_t scancode = IOHIDElementGetUsage(elem);
	/* someething < 4 (usually -1) is emitted on every press, not sure what it really means.
	 * But it doesn't seem to be a valid scancode regardless...
	 * https://www.win.tue.nl/~aeb/linux/kbd/scancodes-14.html */
	if (scancode < 4)
		return;

	/* seems like getting 'int value' is if it is key up or down
	 * true(1) = down */
	bool key_down = IOHIDValueGetIntegerValue(value) == 1;
	// printf(">>> value: %d, down: %d\n", scancode, key_down);
	if (key_down)
		keyboard_on_down();
	else
		keyboard_on_up();
}

bool keyboard_hook(void)
{
	fprintf(stderr, "Setting up keyboard manager...\n");
	hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
	if (hid_manager == NULL) {
		printf("ERROR: IOKit HID manager failed to initialise.\n");
		return false;
	}

	/* TODO: handle errors on allocs below? */

	/* We want to hook keyboard, so setup the filter to match the device for IOKit.
	 * It wants a CFArray of CFDictionary, so we set that up */
	fprintf(stderr, "Setting up IOKit hook...\n");
	const void *keyboard_keys[] = {
		CFSTR(kIOHIDDeviceUsagePageKey),
		CFSTR(kIOHIDDeviceUsageKey)
	};
	int gd = kHIDPage_GenericDesktop;
	int gdk = kHIDUsage_GD_Keyboard;
	const void *keyboard_values[] = { 
		CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &gd),
		CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &gdk)
	};
	/* Usage Page = Generic Desktop, Usage = Keyboard */
	CFDictionaryRef keyboard_dict = CFDictionaryCreate(
		kCFAllocatorDefault,
		keyboard_keys,
		keyboard_values,
		2,
		&kCFTypeDictionaryKeyCallBacks,
		&kCFTypeDictionaryValueCallBacks
	);
	const void *match_values[] = { keyboard_dict };
	/* 1 element, the keyboard filter dict */
	CFArrayRef device_matches = CFArrayCreate(
		kCFAllocatorDefault,
		match_values,
		1,
		&kCFTypeArrayCallBacks
	);

	IOHIDManagerSetDeviceMatchingMultiple(hid_manager, device_matches);
	CFRelease(keyboard_dict);
	CFRelease(device_matches);

	IOHIDManagerRegisterInputValueCallback(hid_manager, hid_callback, NULL);
	IOHIDManagerOpen(hid_manager, kIOHIDOptionsTypeNone);

	/* Schedule the HID manager for run loop later */
	IOHIDManagerScheduleWithRunLoop(hid_manager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);

	return true;
}

void keyboard_unhook(void)
{
	fprintf(stderr, "Cleaning up keyboard hooks...\n");
	IOHIDManagerClose(hid_manager, kIOHIDOptionsTypeNone);
	/* TODO: Cleanup sound system */
}