add macOS support - #57
Conversation
replace libudev with IOKit/CoreFoundation for device monitoring, swap hidapi-hidraw for the generic hidapi (IOKit backend), handle macOS-specific HID report ID stripping, and add nanosleep compat for thrd_sleep. all changes behind #ifdef __APPLE__ — linux code is untouched. tested on macOS sequoia (arm64) over both USB and bluetooth.
There was a problem hiding this comment.
Pull request overview
This PR adds macOS support to dualsensectl by introducing macOS-specific HID handling and device monitoring while keeping Linux behavior intact via platform conditionals.
Changes:
- Adds platform-conditional Meson dependencies to switch between Linux (
hidapi-hidraw+libudev) and macOS (hidapi+ IOKit/CoreFoundation frameworks). - Introduces macOS-specific handling for HID input report layout differences (report ID stripping).
- Implements a macOS
monitorcommand backend usingIOHIDManagercallbacks.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| meson.build | Adds OS-conditional dependencies and linker args for macOS vs Linux. |
| main.c | Adds macOS-specific includes/shims, input report parsing adjustments, and an IOKit-based monitor implementation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- fix potential NULL dereference in serial number fprintf - handle macOS report ID stripping in update command (not just battery) - fix CF object leak on IOHIDManagerOpen error path - use bracket syntax for meson array appends
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
hello! |
|
bump |
egormanga
left a comment
There was a problem hiding this comment.
My two cents; never used a Mac.
| } else if (ds->bt && res == DS_INPUT_REPORT_BT_SIZE - 4 - 1) { | ||
| /* macOS IOKit strips report ID on BT; skip 1 byte (tag area) */ | ||
| ds_report = (struct dualsense_input_report *)&data[1]; | ||
| } else if (ds->bt && res == DS_INPUT_REPORT_BT_SIZE - 1) { | ||
| /* macOS IOKit strips report ID on BT, but keeps CRC */ | ||
| ds_report = (struct dualsense_input_report *)&data[1]; |
There was a problem hiding this comment.
What's the point in having these two identical branches separately?
| return 2; | ||
| } | ||
| #endif | ||
| fprintf(stderr, "Unhandled report ID %d (size %d, bt=%d)\n", (int)data[0], res, ds->bt); |
There was a problem hiding this comment.
What's the purpose of %d'ing ds->bt?
| ds_report = (struct dualsense_input_report *)&data[1]; | ||
| #ifdef __APPLE__ | ||
| } else if (res == DS_INPUT_REPORT_USB_SIZE - 1) { | ||
| /* macOS IOKit strips report ID */ |
There was a problem hiding this comment.
Maybe replace the entire branch on Darwin instead of falling back?
| char buf[64] = {0}; | ||
| CFStringGetCString(serial, buf, sizeof(buf), kCFStringEncodingUTF8); |
There was a problem hiding this comment.
Doesn't CFStringGetCString() write the \0 into buf? If so, initializing is not required.
| size_t len = strlen(buf); | ||
| if (len == 17) { | ||
| /* Replace dashes with colons if needed, uppercase */ | ||
| for (int i = 0; i < 17; i++) { |
There was a problem hiding this comment.
Trap for future refactoring!
| for (int i = 0; i < 17; i++) { | |
| for (int i = 0; i < len; i++) { |
| if (buf[i] == '-') buf[i] = ':'; | ||
| serial_number[i] = toupper(buf[i]); | ||
| } | ||
| serial_number[17] = '\0'; |
There was a problem hiding this comment.
| serial_number[17] = '\0'; | |
| serial_number[len] = '\0'; |
| static void iokit_device_added(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) | ||
| { | ||
| (void)context; (void)result; (void)sender; | ||
| char serial_number[18] = "00:00:00:00:00:00"; |
There was a problem hiding this comment.
| char serial_number[18] = "00:00:00:00:00:00"; | |
| char serial_number[] = "00:00:00:00:00:00"; |
| static void iokit_device_removed(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) | ||
| { | ||
| (void)context; (void)result; (void)sender; | ||
| char serial_number[18] = "00:00:00:00:00:00"; |
There was a problem hiding this comment.
| char serial_number[18] = "00:00:00:00:00:00"; | |
| char serial_number[] = "00:00:00:00:00:00"; |
|
|
||
| IOReturn ret = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone); | ||
| if (ret != kIOReturnSuccess) { | ||
| fprintf(stderr, "Failed to open IOHIDManager: 0x%x\n", ret); |
There was a problem hiding this comment.
| fprintf(stderr, "Failed to open IOHIDManager: 0x%x\n", ret); | |
| fprintf(stderr, "Failed to open IOHIDManager: %#04x\n", ret); |
|
Can you please put all the macos specific code to a new file (eg. macos.c)? |
summary
adds macOS support to dualsensectl. all changes are behind
#ifdef __APPLE__— linux code is untouched.libudevwithIOKit/CoreFoundationfor device monitoring (IOHIDManagercallbacks for hotplug)hidapi-hidrawfor the generichidapi(uses IOKit backend on macOS)nanosleepcompat shim forthrd_sleepmeson.build(auto-detects macOS vs Linux dependencies)tested on
build on macOS