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