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?
There was a problem hiding this comment.
both point at &data[1] but they do also match two different bt report layouts one w/ the crc tail, one w/out. i kept them seperate for readability
| 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?
There was a problem hiding this comment.
this is debug info that i relied on a lot. dualsense was cable connected to my mac but i still used bt. will probably also help when (if) an unhandled report comes your way you get to know if they used usb or 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?
There was a problem hiding this comment.
in my testing macos always stripped the report id, but i kept the generic branch so a build where hidapi doesn't strip still works. i'd rather not hard-assume iokit behavior here lol
| 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.
There was a problem hiding this comment.
on success yes, but isnt the return value not checked? when failure the buffer is undefined and the zero init keeps strlen safe and dropped to the fallback serial.
| 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)? |
well most of the macos stuff is a couple lines inside shared functions, splitting those out would need either duplicating them or a short shim, do we want that?. the monitor code is self contained though, i can move that plus the serial helper into macos.c and leave the small ifdefs where they are. would that work? |
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