aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicholas Tay <nick@windblume.net>2022-11-03 00:56:38 +1100
committerNicholas Tay <nick@windblume.net>2022-11-03 01:00:06 +1100
commit0f5a18991f320a2df370994326f882a144c45b02 (patch)
tree685ad7abb3883311c085c203038a9a1566acb847
parent7fe48a5a3f65c759adf7ff4b778bcf8a18334c7b (diff)
downloadclak-0f5a18991f320a2df370994326f882a144c45b02.tar.gz
clak-0f5a18991f320a2df370994326f882a144c45b02.tar.bz2
clak-0f5a18991f320a2df370994326f882a144c45b02.zip
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.
-rw-r--r--.gitignore3
-rw-r--r--Makefile5
-rw-r--r--board/Makefile8
-rw-r--r--platform/darwin.c167
4 files changed, 180 insertions, 3 deletions
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 <stdio.h>
+#include <stdbool.h>
+#include <dlfcn.h>
+#include <IOKit/hid/IOHIDManager.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 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 */
+}