Skip to content
Draft
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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ LizardByte has the full documentation hosted on [Read the Docs](https://docs.liz
<td>✅<sup>2</sup></td>
<td>✅<sup>3</sup></td>
</tr>
<tr>
<td>Steam Controller (2nd generation)</td>
<td>🟡<sup>1</sup></td>
<td>✅</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>Xbox 360</td>
<td>🟡<sup>1</sup></td>
Expand Down
6 changes: 5 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ supported on the current platform.
@endcode</td>
</tr>
<tr>
<td rowspan="7">Choices</td>
<td rowspan="8">Choices</td>
<td>generic</td>
<td>Generic HID gamepad</td>
</tr>
Expand All @@ -399,6 +399,10 @@ supported on the current platform.
<td>switch</td>
<td>Switch Pro controller</td>
</tr>
<tr>
<td>steam_triton</td>
<td>Steam Controller (2nd generation)</td>
</tr>
<tr>
<td>x360</td>
<td>Xbox 360 controller</td>
Expand Down
19 changes: 15 additions & 4 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,12 @@ keyboard or mouse input. Sunshine’s menu bar **Virtual HID Broker** submenu sh
for license management and downloads. Sunshine’s DMG does not contain the broker. Keyboard and mouse input use the
standard macOS synthetic-input permission path.

The `steam_triton` option requires a Virtual HID Broker build with support for the Steam Controller
(2nd generation). It creates a native Steam Controller profile on macOS;
Sunshine forwards its pads, rear buttons, motion, battery state, and feedback
through the same client protocol used on Windows and Linux. The macOS path
still needs a signed-broker, physical-controller test.

#### DMG

##### Install
Expand Down Expand Up @@ -587,10 +593,15 @@ gamepad support. ViGEmBus remains available as a limited alternative for Xbox 36

When Virtual HID Broker is used, Sunshine requires libvirtualhid version `2026.914.1218.10` or newer.

Compared with the ViGEmBus fallback, Virtual HID Broker can create Xbox One, Xbox Series, DualSense, Nintendo Switch
Pro, and Generic gamepads in addition to Xbox 360 and DualShock 4. It can also expose controller-specific features such
as motion, touchpads, LEDs, and adaptive triggers when supported. Virtual HID Broker is actively developed and
supported by the LizardByte team.
Compared with the ViGEmBus fallback, Virtual HID Broker can create the Steam Controller (2nd generation), Xbox One,
Xbox Series, DualSense, Nintendo Switch Pro, and Generic gamepads in addition to Xbox 360 and DualShock 4. The
`steam_triton` selection specifically emulates the Steam Controller (2nd generation), not the original Steam Controller. It exposes the standard controls,
Home and miscellaneous buttons, four rear buttons, motion, battery state, both trackpads, and separate clicks for both
trackpads when the Moonlight client reports those capabilities. Standard rumble and the controller's independently
addressable trackpad haptics are returned to capable Moonlight clients. Older clients retain pressure- and
trigger-position-based click inference and do not receive addressable haptic effects. Virtual HID Broker
also exposes controller-specific features such as LEDs and adaptive triggers for other profiles when supported, and is
actively developed and supported by the LizardByte team.

With a compatible driver and active license, normal key transitions are exposed through a real HID keyboard so
applications using Raw Input can receive them. Unicode text input and keys outside the supported HID keyboard page
Expand Down
16 changes: 13 additions & 3 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,12 @@ and the broker or license is unavailable. Choose **None** under **Configuration
virtual gamepads and that notice without affecting keyboard or mouse input. Reconnect the Moonlight session after choosing
a different emulated gamepad profile.

For `steam_triton`, use a broker build that includes the Steam Controller (2nd generation) profile.
Older macOS broker builds reject that profile during creation. The
new macOS path still needs validation with a signed broker and physical
controller; if it fails, check the broker version and permission before
changing Moonlight's gamepad mapping.

### Dynamic session lookup failed
If you get this error:

Expand All @@ -340,9 +346,13 @@ gamepad support. ViGEmBus is a limited alternative for Xbox 360 and DualShock 4

When Virtual HID Broker is used, Sunshine requires libvirtualhid version `2026.914.1218.10` or newer.

Virtual HID Broker adds Xbox One, Xbox Series, DualSense, Nintendo Switch Pro, and Generic gamepads, plus advanced
controller features such as motion, touchpads, LEDs, and adaptive triggers when supported. Unlike the discontinued
ViGEmBus project, Virtual HID Broker is actively developed and supported by the LizardByte team.
Virtual HID Broker adds the Steam Controller (2nd generation), Xbox One, Xbox Series, DualSense, Nintendo Switch Pro, and Generic
gamepads, plus advanced controller features such as motion, touchpads, LEDs, and adaptive triggers when supported. The
`steam_triton` option is exclusively for the Steam Controller (2nd generation), not the original Steam Controller. It requires a
compatible Virtual HID Broker and cannot fall back to ViGEmBus. Native trackpad clicks, trigger clicks, stick and grip
touch sensors, exact battery reporting, and addressable trackpad haptics also require a capable Moonlight client; older
clients use Sunshine's pressure and trigger-position click fallback. Unlike the discontinued ViGEmBus project, Virtual
HID Broker is actively developed and supported by the LizardByte team.

An active paid Virtual HID Broker machine license is required before Sunshine can create driver-backed libvirtualhid
devices, including gamepads and the Raw Input keyboard and mouse. Use the message on the Web UI home page, the startup
Expand Down
4 changes: 2 additions & 2 deletions gh-pages-template/_data/features.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
- title: "Control"
icon_fa: "fas fa-gamepad"
description: >
Sunshine emulates Xbox 360|One|Series, PlayStation DualShock 4|DualSense, or Nintendo Switch Pro controllers.
Use nearly any controller on your Moonlight client!
Sunshine emulates Steam Controller (2nd Generation), Xbox 360|One|Series, PlayStation DualShock 4|DualSense,
or Nintendo Switch Pro controllers. Use nearly any controller on your Moonlight client!

- title: "Configurable"
icon_fa: "fas fa-gear"
Expand Down
23 changes: 23 additions & 0 deletions src/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1505,6 +1505,7 @@ namespace input {
from_clamped_netfloat(packet->x, 0.0f, 1.0f),
from_clamped_netfloat(packet->y, 0.0f, 1.0f),
from_clamped_netfloat(packet->pressure, 0.0f, 1.0f),
packet->touchpadIndex,
};

platf::gamepad_touch(platf_input, touch);
Expand Down Expand Up @@ -2474,6 +2475,28 @@ namespace input {
::input::passthrough(input, &packet);
}

void send_controller_touch_packet(
std::shared_ptr<input_t> &input,
std::uint8_t controller_number,
std::uint8_t event_type,
std::uint8_t touchpad_index,
std::uint32_t pointer_id,
float x,
float y,
float pressure
) {
SS_CONTROLLER_TOUCH_PACKET packet {};
packet.controllerNumber = controller_number;
packet.eventType = event_type;
packet.touchpadIndex = touchpad_index;
packet.pointerId = util::endian::little(pointer_id);
boost::endian::endian_store<float, sizeof(float), boost::endian::order::little>(packet.x, x);
boost::endian::endian_store<float, sizeof(float), boost::endian::order::little>(packet.y, y);
boost::endian::endian_store<float, sizeof(float), boost::endian::order::little>(packet.pressure, pressure);

::input::passthrough(input, &packet);
}

void reset_keyboard_state() {
task_pool.cancel(key_press_repeat_id);
key_press_repeat_id = nullptr;
Expand Down
23 changes: 23 additions & 0 deletions src/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,29 @@ namespace input {
*/
void send_keyboard_packet(std::shared_ptr<input_t> &input, std::uint16_t key_code, std::uint8_t modifiers, std::uint8_t flags, bool release);

/**
* @brief Process one client controller-touch packet on the calling thread.
*
* @param input Retained input state.
* @param controller_number Client-relative controller index.
* @param event_type Moonlight touch event type.
* @param touchpad_index Zero-based touchpad index carried by the packet.
* @param pointer_id Client-provided contact identifier.
* @param x Normalized horizontal coordinate.
* @param y Normalized vertical coordinate.
* @param pressure Normalized contact pressure.
*/
void send_controller_touch_packet(
std::shared_ptr<input_t> &input,
std::uint8_t controller_number,
std::uint8_t event_type,
std::uint8_t touchpad_index,
std::uint32_t pointer_id,
float x,
float y,
float pressure
);

/**
* @brief Forget every key Sunshine tracks as pressed and cancel any pending key repeat.
*/
Expand Down
46 changes: 46 additions & 0 deletions src/platform/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ namespace platf {
constexpr std::uint32_t PADDLE4 = 0x080000; ///< Moonlight gamepad button mask bit for paddle 4.
constexpr std::uint32_t TOUCHPAD_BUTTON = 0x100000; ///< Moonlight gamepad button mask bit for touchpad click.
constexpr std::uint32_t MISC_BUTTON = 0x200000; ///< Moonlight gamepad button mask bit for the miscellaneous button.
constexpr std::uint32_t STEAM_LEFT_TOUCHPAD_BUTTON = STEAM_LEFT_TOUCHPAD_FLAG; ///< Moonlight bit for the Steam Controller (2nd generation) left trackpad click.
constexpr std::uint32_t STEAM_RIGHT_TOUCHPAD_BUTTON = STEAM_RIGHT_TOUCHPAD_FLAG; ///< Moonlight bit for the Steam Controller (2nd generation) right trackpad click.
constexpr std::uint32_t STEAM_LEFT_TRIGGER_CLICK = STEAM_LEFT_TRIGGER_CLICK_FLAG; ///< Moonlight bit for the Steam Controller (2nd generation) left trigger click.
constexpr std::uint32_t STEAM_RIGHT_TRIGGER_CLICK = STEAM_RIGHT_TRIGGER_CLICK_FLAG; ///< Moonlight bit for the Steam Controller (2nd generation) right trigger click.
constexpr std::uint32_t STEAM_LEFT_STICK_TOUCH = STEAM_LEFT_STICK_TOUCH_FLAG; ///< Moonlight bit for the Steam Controller (2nd generation) left stick touch sensor.
constexpr std::uint32_t STEAM_RIGHT_STICK_TOUCH = STEAM_RIGHT_STICK_TOUCH_FLAG; ///< Moonlight bit for the Steam Controller (2nd generation) right stick touch sensor.
constexpr std::uint32_t STEAM_LEFT_GRIP_TOUCH = STEAM_LEFT_GRIP_TOUCH_FLAG; ///< Moonlight bit for the Steam Controller (2nd generation) left grip touch sensor.
constexpr std::uint32_t STEAM_RIGHT_GRIP_TOUCH = STEAM_RIGHT_GRIP_TOUCH_FLAG; ///< Moonlight bit for the Steam Controller (2nd generation) right grip touch sensor.

/**
* @brief Gamepad type exposed to clients and why it may be disabled.
Expand All @@ -120,6 +128,26 @@ namespace platf {
set_rgb_led, ///< Set RGB LED
set_player_leds, ///< Set player indicator LEDs
set_adaptive_triggers, ///< Set adaptive triggers
set_haptics, ///< Play an addressable haptic effect
};

/**
* @brief Profile-neutral addressable gamepad haptic effect.
*/
struct gamepad_haptic_effect_t {
std::uint8_t target; ///< Target actuator selection.
std::uint8_t kind; ///< Haptic effect category.
std::int8_t gain_db; ///< Signed gain in decibels.
std::uint16_t intensity; ///< Profile-defined intensity value.
std::uint16_t frequency_hz; ///< Primary tone frequency in hertz.
std::int32_t duration_us; ///< Effect duration in microseconds; negative means indefinite.
std::uint32_t interval_us; ///< Off interval between pulses in microseconds.
std::uint16_t repeat_count; ///< Pulse repeat count.
std::uint16_t lfo_frequency_hz; ///< Low-frequency oscillator frequency in hertz.
std::uint8_t lfo_depth_percent; ///< Low-frequency oscillator depth in percent.
std::uint16_t start_frequency_hz; ///< Sweep start frequency in hertz.
std::uint16_t end_frequency_hz; ///< Sweep end frequency in hertz.
std::uint8_t script_id; ///< Controller-defined scripted effect identifier.
};

/**
Expand Down Expand Up @@ -227,6 +255,21 @@ namespace platf {
return msg;
}

/**
* @brief Create an addressable haptic effect command.
*
* @param id Identifier for the controller.
* @param effect Profile-neutral haptic effect parameters.
* @return Constructed haptic effect command.
*/
static gamepad_feedback_msg_t make_haptics(std::uint16_t id, const gamepad_haptic_effect_t &effect) {
gamepad_feedback_msg_t msg;
msg.type = gamepad_feedback_e::set_haptics;
msg.id = id;
msg.data.haptics = effect;
return msg;
}

gamepad_feedback_e type; ///< Feedback command type stored in the union payload.
std::uint16_t id; ///< Controller identifier associated with this message.

Expand Down Expand Up @@ -264,6 +307,8 @@ namespace platf {
std::array<uint8_t, 10> left; ///< Left adaptive-trigger effect parameters.
std::array<uint8_t, 10> right; ///< Right adaptive-trigger effect parameters.
} adaptive_triggers; ///< Adaptive-trigger effect payload.

gamepad_haptic_effect_t haptics; ///< Addressable haptic effect payload.
} data; ///< Controller feedback payload for the selected feedback type.
};

Expand Down Expand Up @@ -454,6 +499,7 @@ namespace platf {
float x; ///< Horizontal coordinate or vector component.
float y; ///< Vertical coordinate or vector component.
float pressure; ///< Contact pressure reported by the client.
std::uint8_t touchpadIndex = 0; ///< Zero-based Moonlight touchpad index.
};

/**
Expand Down
Loading
Loading