From 0f5a18991f320a2df370994326f882a144c45b02 Mon Sep 17 00:00:00 2001 From: Nicholas Tay Date: Thu, 3 Nov 2022 00:56:38 +1100 Subject: Initial macOS support (key hooking only, no sound yet) Thought I'd commit this first, since it'll be some Objective-C stuff coming (tested it out in another mini probe). Mostly has stuff similar to Linux so probably should abstract some out to *nix common. But uses IOKit to get into the typing events. Will use Obj-C for now for sound, since it'll probably be a NSSound thing, hooked up with NSCache (like Linux) to reduce loading into NS format multiple times. Also probably would need to free the sound object on finish sound, and not sure how I'd do Obj-C delegates from C... it would be fun to figure it out though eventually Also switched the Makefile to use clang, it's warnings do seem to be nicer :) and is what `gcc` is aliased to on a Mac by default anyway. --- .gitignore | 3 +- Makefile | 5 +- board/Makefile | 8 ++- platform/darwin.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 platform/darwin.c diff --git a/.gitignore b/.gitignore index 5768e2e..c9120e7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ clak.exe board/boards.h board/*/sound.h board/*.dll -board/*.so \ No newline at end of file +board/*.so +board/*.dylib diff --git a/Makefile b/Makefile index 1fdf54f..e21da87 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ NAME = clak PREFIX = $(HOME)/.local -CC = gcc +CC = clang CFLAGS += -std=c99 -Wall -Wextra -Wshadow -Werror ifeq ($(OS),Windows_NT) @@ -14,6 +14,9 @@ else PKG_CONF_LIBS = sdl2 SDL2_mixer x11 xi CFLAGS += `pkg-config --cflags $(PKG_CONF_LIBS)` LDLIBS += `pkg-config --libs $(PKG_CONF_LIBS)` + else ifeq ($(UNAME_S),Darwin) + PLATFORM = darwin + CFLAGS += -framework CoreFoundation -framework IOKit -framework AppKit endif endif diff --git a/board/Makefile b/board/Makefile index ae3d691..ba53910 100644 --- a/board/Makefile +++ b/board/Makefile @@ -6,8 +6,14 @@ CFLAGS += -std=c99 -Wall -Wextra -Wshadow -Werror -pedantic -shared ifeq ($(OS),Windows_NT) OUTEXT = dll else - OUTEXT = so CFLAGS += -fPIC + + UNAME_S := $(shell uname) + ifeq ($(UNAME_S),Linux) + OUTEXT = so + else ifeq ($(UNAME_S),Darwin) + OUTEXT = dylib + endif endif default: all diff --git a/platform/darwin.c b/platform/darwin.c new file mode 100644 index 0000000..820c012 --- /dev/null +++ b/platform/darwin.c @@ -0,0 +1,167 @@ +#include +#include +#include +#include + +#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 float volume = 0; +static IOHIDManagerRef hid_manager = NULL; + +bool sound_init(float _volume) +{ + volume = _volume; + + /* TODO: Init sound, NSCache probably too via bridge? */ + + return true; +} + +void sound_play(unsigned char *buffer, unsigned int buffer_len) +{ + (void) buffer; + (void) buffer_len; + + /* TODO: Play the sound via obj-c for now */ +} + +/* 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 */ +} -- cgit