aboutsummaryrefslogblamecommitdiff
path: root/platform/darwin.c
blob: 3189a5d49c13d0b67f85380ad71c76c68efa5fb9 (plain) (tree)
1
2
3
4
5
6




                                   
                                   







                                                                          

                                          
                             
 

                                        



                                                               

                                             



































































                                                                                                
                                                                   


































































                                                                                                
#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 */
}