Skip to content
Closed
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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,7 @@ if(NOT DEFINED CMAKE_CUDA_STANDARD)
endif()

if(APPLE)
target_link_libraries(sunshine "-framework IOKit")
target_link_options(sunshine PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,${APPLE_PLIST_FILE})
# Tell linker to dynamically load these symbols at runtime, in case they're unavailable:
target_link_options(sunshine PRIVATE -Wl,-U,_CGPreflightScreenCaptureAccess -Wl,-U,_CGRequestScreenCaptureAccess)
Expand Down
1 change: 0 additions & 1 deletion docs/source/about/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,6 @@ Uninstall:

macOS
-----
Sunshine on macOS is experimental. Gamepads do not work. Other features may not work as expected.

pkg
^^^
Expand Down
4 changes: 2 additions & 2 deletions docs/source/about/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,14 @@ Sunshine needs access to `uinput` to create mouse and gamepad events.

macOS
^^^^^
For gamepad support, install `VirtualHID <https://github.com/kotleni/VirtualHID-macOS/releases/latest>`_

Sunshine can only access microphones on macOS due to system limitations. To stream system audio use
`Soundflower <https://github.com/mattingalls/Soundflower>`_ or
`BlackHole <https://github.com/ExistentialAudio/BlackHole>`_.

.. Note:: Command Keys are not forwarded by Moonlight. Right Option-Key is mapped to CMD-Key.

.. Caution:: Gamepads are not currently supported.

Configure autostart service
**MacPorts**
.. code-block:: bash
Expand Down
11 changes: 11 additions & 0 deletions docs/source/troubleshooting/macos.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,14 @@ If you get this error:
.. code-block:: bash

launchctl load -w /Library/LaunchAgents/org.freedesktop.dbus-session.plist

No gamepad detected
-------------------
#. Verify that you've installed `VirtualHID <https://github.com/kotleni/VirtualHID-macOS/releases/latest>`_.

Unable to create HID device
---------------------------
If you get this error:
`Gamepad: Unable to create HID device. May be fine if created previously.`

It's okay. It's just a warning.
1 change: 1 addition & 0 deletions packaging/macos/Portfile
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@ pre-build {
notes-append "Run @PROJECT_NAME@ by executing 'sunshine <path to user config>', e.g. 'sunshine ~/sunshine.conf' "
notes-append "The config file will be created if it doesn't exist."
notes-append "It is recommended to set a location for the apps file in the config."
notes-append "For gamepad support, install VirtualHID from 'https://github.com/kotleni/VirtualHID-macOS/releases/latest'."
notes-append "See our documentation at 'https://docs.lizardbyte.dev/projects/sunshine/en/v@PROJECT_VERSION@/' for further info."
175 changes: 171 additions & 4 deletions src/platform/macos/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
#include <mach/mach.h>
#include <mach/mach_time.h>

// For gamepad emulation
#include <IOKit/IOKitLib.h>
#include <string.h>

#include "src/main.h"
#include "src/platform/common.h"
#include "src/utility.h"
Expand All @@ -10,6 +14,86 @@
// FIXME: we probably want to make this configurable
#define MULTICLICK_DELAY_NS 500000000

// For gamepad emulation
// https://github.com/kotleni/foohid
#define VIRTGAMEPAD_NAME "DualShock 4" // gamepad name
#define VIRTGAMEPAD_SN "CUH-ZCT1x" // serial number
#define VIRTGAMEPAD_INPUT_COUNT 8 // device arguments count
#define SERVICE_NAME "it_unbit_foohid" // virthid service id
#define FOOHID_CREATE 0 // create selector
#define FOOHID_SEND 2 // send selector

// Gamepad buttons
#define GAMEPAD_ZEROBTNS 0
#define GAMEPAD_BTN_A (1 << 0)
#define GAMEPAD_BTN_B (1 << 1)
#define GAMEPAD_BTN_X (1 << 2)
#define GAMEPAD_BTN_Y (1 << 3)
#define GAMEPAD_BTN_L (1 << 4)
#define GAMEPAD_BTN_R (1 << 5)
#define GAMEPAD_BTN_LT (1 << 6)
#define GAMEPAD_BTN_RT (1 << 7)
#define GAMEPAD_BTN_BACK (1 << 8)
#define GAMEPAD_BTN_START (1 << 9)
#define GAMEPAD_BTN_LS (1 << 10)
#define GAMEPAD_BTN_RS (1 << 11)
#define GAMEPAD_BTN_UP (1 << 12)
#define GAMEPAD_BTN_DOWN (1 << 13)
#define GAMEPAD_BTN_LEFT (1 << 14)
#define GAMEPAD_BTN_RIGHT (1 << 15)

// http://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/
// Used HIDTool to generate the data
unsigned char gamepad_report_descriptor[] = {
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x05, // USAGE (Game Pad)
0xa1, 0x01, // COLLECTION (Application)
0xa1, 0x00, // COLLECTION (Physical)
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x09, 0x33, // USAGE (Rx)
0x09, 0x34, // USAGE (Ry)
0x16, 0x00, 0x80, // LOGICAL_MINIMUM (-32768)
0x26, 0xff, 0x7f, // LOGICAL_MAXIMUM (32767)
0x75, 0x10, // REPORT_SIZE (16)
0x95, 0x04, // REPORT_COUNT (4)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x32, // USAGE (Z)
0x09, 0x35, // USAGE (Rz)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x46, 0xff, 0x00, // PHYSICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x02, // REPORT_COUNT (2)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x10, // USAGE_MAXIMUM (Button 16)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x10, // REPORT_COUNT (16)
0x81, 0x02, // INPUT (Data,Var,Abs)
0xc0, // END_COLLECTION
0xc0 // END_COLLECTION
};

struct gamepad_report_t {
int16_t x;
int16_t y;
int16_t rx;
int16_t ry;
uint8_t z;
uint8_t rz;
uint16_t buttons;
};

io_iterator_t virtgamepad_iterator;
io_service_t virtgamepad_service;
io_connect_t virtgamepad_connect;
uint64_t virtgamepad_input[VIRTGAMEPAD_INPUT_COUNT];

namespace platf {
using namespace std::literals;

Expand Down Expand Up @@ -282,16 +366,99 @@ void unicode(input_t &input, char *utf8, int size) {
}

int alloc_gamepad(input_t &input, int nr, rumble_queue_t rumble_queue) {
BOOST_LOG(info) << "alloc_gamepad: Gamepad not yet implemented for MacOS."sv;
return -1;
// Get a reference to the IOService
kern_return_t ret = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching(SERVICE_NAME), &virtgamepad_iterator);

if(ret != KERN_SUCCESS) {
BOOST_LOG(info) << "Gamepad: Unable to access IOService."sv;
return 1;
}

// Iterate till success
int found = 0;
while((virtgamepad_service = IOIteratorNext(virtgamepad_iterator)) != IO_OBJECT_NULL) {
ret = IOServiceOpen(virtgamepad_service, mach_task_self(), 0, &virtgamepad_connect);
if(ret == KERN_SUCCESS) {
found = 1;
break;
}

IOObjectRelease(virtgamepad_service);
}

IOObjectRelease(virtgamepad_iterator);

if(!found) {
BOOST_LOG(info) << "Gamepad: Unable to open IOService."sv;
return 1;
}

// Fill up the input arguments.
virtgamepad_input[0] = (uint64_t)strdup(VIRTGAMEPAD_NAME); // device name
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like your driver is identifying the gamepads by name. Can we concatenate the nr parameter to the hardcoded name or something to support multiple gamepads?

Copy link
Author

@kotleni kotleni Jan 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name can be changed, it's dynamic. (It's not a inditifier)

virtgamepad_input[1] = strlen((char *)virtgamepad_input[0]); // name length
virtgamepad_input[2] = (uint64_t)gamepad_report_descriptor; // report descriptor
virtgamepad_input[3] = sizeof(gamepad_report_descriptor); // report descriptor len
virtgamepad_input[4] = (uint64_t)strdup(VIRTGAMEPAD_SN); // serial number
virtgamepad_input[5] = strlen((char *)virtgamepad_input[4]); // serial number len
virtgamepad_input[6] = (uint64_t)0x054c; // vendor ID
virtgamepad_input[7] = (uint64_t)0x05c4; // device ID

ret = IOConnectCallScalarMethod(virtgamepad_connect, FOOHID_CREATE, virtgamepad_input, VIRTGAMEPAD_INPUT_COUNT, NULL, 0);
if(ret != KERN_SUCCESS) {
BOOST_LOG(info) << "Gamepad: Unable to create HID device. May be fine if created previously."sv;
}
return 0;
}

void free_gamepad(input_t &input, int nr) {
BOOST_LOG(info) << "free_gamepad: Gamepad not yet implemented for MacOS."sv;
IOObjectRelease(virtgamepad_connect);
}

void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
BOOST_LOG(info) << "gamepad: Gamepad not yet implemented for MacOS."sv;
// Arguments to be passed through the HID message.
struct gamepad_report_t gamepad;
uint32_t send_count = 4;
uint64_t send[send_count];
send[0] = (uint64_t)virtgamepad_input[0]; // device name
send[1] = strlen((char *)virtgamepad_input[0]); // name length
send[2] = (uint64_t)&gamepad; // mouse struct
send[3] = sizeof(struct gamepad_report_t); // mouse struct len

auto flags = gamepad_state.buttonFlags;

// reset buttons
gamepad.buttons = GAMEPAD_ZEROBTNS;

// Map buttons
if(flags & LEFT_STICK) gamepad.buttons |= GAMEPAD_BTN_LS;
if(flags & RIGHT_STICK) gamepad.buttons |= GAMEPAD_BTN_RS;
if(flags & LEFT_BUTTON) gamepad.buttons |= GAMEPAD_BTN_L;
if(flags & RIGHT_BUTTON) gamepad.buttons |= GAMEPAD_BTN_R;
if(flags & START) gamepad.buttons |= GAMEPAD_BTN_START;
if(flags & BACK) gamepad.buttons |= GAMEPAD_BTN_BACK;
if(flags & A) gamepad.buttons |= GAMEPAD_BTN_A;
if(flags & B) gamepad.buttons |= GAMEPAD_BTN_B;
if(flags & X) gamepad.buttons |= GAMEPAD_BTN_X;
if(flags & Y) gamepad.buttons |= GAMEPAD_BTN_Y;
if(flags & DPAD_UP) gamepad.buttons |= GAMEPAD_BTN_UP;
if(flags & DPAD_DOWN) gamepad.buttons |= GAMEPAD_BTN_DOWN;
if(flags & DPAD_LEFT) gamepad.buttons |= GAMEPAD_BTN_LEFT;
if(flags & DPAD_RIGHT) gamepad.buttons |= GAMEPAD_BTN_RIGHT;

// Map triggers
if(gamepad_state.lt > 0) gamepad.buttons |= GAMEPAD_BTN_LT;
if(gamepad_state.rt > 0) gamepad.buttons |= GAMEPAD_BTN_RT;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to emulate these with analog axes? Digital triggers are problematic for driving games and other games where precise trigger control is required.

Copy link
Author

@kotleni kotleni Jan 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that now I can implement analog support. Maybe I might do it in the future.

Copy link
Author

@kotleni kotleni Jan 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And, I don't have device with analog trigger, for testing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. See comments in PR.


// Map sticks
gamepad.x = gamepad_state.lsX;
gamepad.y = -gamepad_state.lsY;
gamepad.rx = gamepad_state.rsX;
gamepad.ry = -gamepad_state.rsY;

kern_return_t ret = IOConnectCallScalarMethod(virtgamepad_connect, FOOHID_SEND, send, send_count, NULL, 0);
if(ret != KERN_SUCCESS) {
BOOST_LOG(info) << "Gamepad: Unable to send message to HID device."sv;
}
}

// returns current mouse location:
Expand Down