Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion cmake/compile_definitions/macos.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
${CORE_MEDIA_LIBRARY}
${CORE_VIDEO_LIBRARY}
${FOUNDATION_LIBRARY}
${IO_KIT_LIBRARY}
${VIDEO_TOOLBOX_LIBRARY})

set(APPLE_PLIST_TEMPLATE "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/build/Info.plist.in")
Expand All @@ -48,7 +49,9 @@ set(PLATFORM_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/platform/macos/av_video.h"
"${CMAKE_SOURCE_DIR}/src/platform/macos/av_video.m"
"${CMAKE_SOURCE_DIR}/src/platform/macos/display.mm"
"${CMAKE_SOURCE_DIR}/src/platform/macos/input.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/macos/hid_gamepad.h"
"${CMAKE_SOURCE_DIR}/src/platform/macos/hid_gamepad.m"
"${CMAKE_SOURCE_DIR}/src/platform/macos/input.mm"
"${CMAKE_SOURCE_DIR}/src/platform/macos/microphone.mm"
"${CMAKE_SOURCE_DIR}/src/platform/macos/misc.mm"
"${CMAKE_SOURCE_DIR}/src/platform/macos/misc.h"
Expand Down
6 changes: 6 additions & 0 deletions cmake/dependencies/macos.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ FIND_LIBRARY(CORE_AUDIO_LIBRARY CoreAudio)
FIND_LIBRARY(CORE_MEDIA_LIBRARY CoreMedia)
FIND_LIBRARY(CORE_VIDEO_LIBRARY CoreVideo)
FIND_LIBRARY(FOUNDATION_LIBRARY Foundation)
# IOKit is needed for IOHIDUserDevice* (virtual gamepad device — hid_gamepad.m).
# Actually creating devices at runtime requires the user to disable AMFI via
# `nvram boot-args="amfi_get_out_of_my_way=1"`, but the symbols themselves
# are unconditionally present and the host alloc_gamepad path probes
# availability before relying on them.
FIND_LIBRARY(IO_KIT_LIBRARY IOKit)
FIND_LIBRARY(VIDEO_TOOLBOX_LIBRARY VideoToolbox)

if(SUNSHINE_ENABLE_TRAY)
Expand Down
78 changes: 78 additions & 0 deletions src/platform/macos/hid_gamepad.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* @file src/platform/macos/hid_gamepad.h
* @brief Virtual HID gamepad via IOHIDUserDevice for macOS.
* @details Creates a system-wide virtual gamepad that macOS Game
* Controller framework, SDL, Steam, and raw IOKit HID
* consumers all recognize as a real USB controller. Requires
* AMFI to be bypassed at boot via
* `nvram boot-args="amfi_get_out_of_my_way=1"`. SIP can remain
* on; the relevant subsystem is AMFI, not SIP.
*/
#pragma once

#import <Foundation/Foundation.h>
#import <IOKit/hidsystem/IOHIDUserDevice.h>

/**
* HID report sent to IOHIDUserDevice. Packed to exactly 14 bytes.
* Matches the HID report descriptor defined in hid_gamepad.m.
*/
typedef struct __attribute__((packed)) {
uint8_t reportId; // Always 0x01
uint16_t buttons; // 16 button bits
uint8_t hatSwitch; // D-pad hat switch (0-7 = directions, 8 = neutral)
uint8_t leftTrigger; // 0-255
uint8_t rightTrigger; // 0-255
int16_t leftStickX; // -32768 to 32767
int16_t leftStickY; // -32768 to 32767
int16_t rightStickX; // -32768 to 32767
int16_t rightStickY; // -32768 to 32767
} HIDGamepadReport;

@interface HIDGamepad : NSObject

@property (nonatomic, assign) int gamepadIndex;
@property (nonatomic, assign) BOOL isConnected;
@property (nonatomic, assign) IOHIDUserDeviceRef hidDevice;
@property (nonatomic, strong) dispatch_queue_t hidQueue;

/**
* Probes whether IOHIDUserDevice virtual gamepads can be created.
* Returns NO when AMFI is enabled — the entitlement check refuses
* IOHIDUserDeviceCreateWithProperties without the AMFI bypass boot
* flag in place.
*/
+ (BOOL)isAvailable;

- (instancetype)initWithIndex:(int)index;

/**
* Creates the IOHIDUserDevice and sends an initial neutral-state report.
* @return YES on success, NO on failure.
*/
- (BOOL)createDevice;

/**
* Maps Sunshine's gamepad state to an HID report and sends it.
* @param buttons Sunshine's 32-bit buttonFlags (only lower 16 bits + HOME used)
* @param lsX Left stick X (-32768..32767)
* @param lsY Left stick Y (-32768..32767)
* @param rsX Right stick X (-32768..32767)
* @param rsY Right stick Y (-32768..32767)
* @param lt Left trigger (0..255)
* @param rt Right trigger (0..255)
*/
- (void)updateState:(uint32_t)buttons
leftStickX:(int16_t)lsX
leftStickY:(int16_t)lsY
rightStickX:(int16_t)rsX
rightStickY:(int16_t)rsY
leftTrigger:(uint8_t)lt
rightTrigger:(uint8_t)rt;

/**
* Destroys the IOHIDUserDevice and cleans up resources.
*/
- (void)disconnect;

@end
Loading