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
1 change: 1 addition & 0 deletions cmake/compile_definitions/windows.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ set(PLATFORM_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/platform/windows/publish.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/windows/misc.h"
"${CMAKE_SOURCE_DIR}/src/platform/windows/misc.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/windows/input_utils.h"
"${CMAKE_SOURCE_DIR}/src/platform/windows/input.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/windows/display.h"
"${CMAKE_SOURCE_DIR}/src/platform/windows/display_base.cpp"
Expand Down
59 changes: 59 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,65 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
</table>

### touch_send_to_primary_display

<table>
<tr>
<td>Description</td>
<td colspan="2">
When enabled on Windows, Sunshine maps native touch input sent by the streaming client to the current
primary display instead of the display shown in the stream. Relative touch positions are preserved, and
physical display bounds are used so the mapping covers the full primary display even when display
resolutions or Windows scaling settings differ.
<br>
This setting does not affect pen, absolute mouse, or relative mouse input. If Sunshine cannot determine
valid primary display dimensions, touch input falls back to the streamed display.
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}
disabled
@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
touch_send_to_primary_display = enabled
@endcode</td>
</tr>
</table>

### touch_primary_display_rotation

<table>
<tr>
<td>Description</td>
<td colspan="2">
On Windows, rotates native touch coordinates clockwise before mapping them to the primary display.
This option only applies when @code{touch_send_to_primary_display} is enabled and does not affect pen
or mouse input. Select the rotation that compensates for applications that interpret touchscreen
coordinates in a different display orientation.
</td>
</tr>
<tr>
<td>Allowed values</td>
<td colspan="2">@code{0}, @code{90}, @code{180}, or @code{270}</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}
0
@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
touch_primary_display_rotation = 90
@endcode</td>
</tr>
</table>

### keybindings

<table>
Expand Down
10 changes: 10 additions & 0 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -853,11 +853,14 @@ namespace config {
true, // ds5_inputtino_randomize_mac

true, // keyboard enabled
false, // map right Alt to the Windows key
true, // mouse enabled
true, // controller enabled
true, // always send scancodes
true, // high resolution scrolling
true, // native pen/touch support
false, // send touch input to the primary display
"0", // primary display touch map rotation
};

/**
Expand Down Expand Up @@ -1802,6 +1805,13 @@ namespace config {

bool_f(vars, "high_resolution_scrolling", input.high_resolution_scrolling);
bool_f(vars, "native_pen_touch", input.native_pen_touch);
bool_f(vars, "touch_send_to_primary_display", input.touch_send_to_primary_display);
string_restricted_f(
vars,
"touch_primary_display_rotation",
input.touch_primary_display_rotation,
{"0"sv, "90"sv, "180"sv, "270"sv}
);

bool_f(vars, "notify_pre_releases", sunshine.notify_pre_releases);
bool_f(vars, "system_tray", sunshine.system_tray);
Expand Down
2 changes: 2 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,8 @@ namespace config {

bool high_resolution_scrolling; ///< Enable high-resolution mouse-wheel events.
bool native_pen_touch; ///< Enable native pen and touch injection.
bool touch_send_to_primary_display {false}; ///< Map streaming-client touch input to the Windows primary display.
std::string touch_primary_display_rotation {"0"}; ///< Rotate primary-display touch coordinates clockwise.
};

namespace flag {
Expand Down
52 changes: 36 additions & 16 deletions src/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1086,28 +1086,43 @@ namespace input {
input->gamepads[packet->controllerNumber].id = id;
}

/**
* @brief Normalizes coordinates to monitor-local logical touch dimensions.
* @param touch_port The current touch port metadata.
* @param coords The in/out coordinate pair to normalize.
* @return The monitor-local touch port, or std::nullopt if dimensions are invalid.
*/
std::optional<platf::touch_port_t> monitor_touch_port(const input::touch_port_t &touch_port, std::pair<float, float> &coords) {
const float monitor_logical_w = (touch_port.width * touch_port.scalar_inv) / touch_port.scalar_tpcoords;
const float monitor_logical_h = (touch_port.height * touch_port.scalar_inv) / touch_port.scalar_tpcoords;
if (monitor_logical_w <= 0.0f || monitor_logical_h <= 0.0f) {
std::optional<platf::touch_port_t> monitor_touch_port(
const input::touch_port_t &touch_port,
std::pair<float, float> &coords,
bool use_effective_content
) {
// client_to_touchport() has already subtracted the leading client padding from the coordinates. Remove padding
// from both sides of the encoded extent here so effective-content coordinates still normalize to 0.0-1.0.
const float frame_logical_w = (touch_port.width * touch_port.scalar_inv) / touch_port.scalar_tpcoords;
const float frame_logical_h = (touch_port.height * touch_port.scalar_inv) / touch_port.scalar_tpcoords;
const float content_width = touch_port.width - (2.0f * touch_port.client_offsetX);
const float content_height = touch_port.height - (2.0f * touch_port.client_offsetY);
const float monitor_logical_w = (content_width * touch_port.scalar_inv) / touch_port.scalar_tpcoords;
const float monitor_logical_h = (content_height * touch_port.scalar_inv) / touch_port.scalar_tpcoords;
const float normalization_width = use_effective_content ? monitor_logical_w : frame_logical_w;
const float normalization_height = use_effective_content ? monitor_logical_h : frame_logical_h;
if (!std::isfinite(normalization_width) ||
!std::isfinite(normalization_height) ||
normalization_width <= 0.0f ||
normalization_height <= 0.0f) {
BOOST_LOG(warning) << "Ignoring touch/pen input due to invalid logical touch dimensions"sv;
return std::nullopt;
}

coords.first = (coords.first - touch_port.offset_x) / monitor_logical_w;
coords.second = (coords.second - touch_port.offset_y) / monitor_logical_h;
coords.first = (coords.first - touch_port.offset_x) / normalization_width;
coords.second = (coords.second - touch_port.offset_y) / normalization_height;
if (use_effective_content) {
// client_to_touchport() maps encoded padding to the content edges. Keep those edge coordinates inside the
// normalized target so native touch cannot escape onto an adjacent display.
coords.first = std::clamp(coords.first, 0.0f, 1.0f);
coords.second = std::clamp(coords.second, 0.0f, 1.0f);
}

return platf::touch_port_t {
touch_port.offset_x,
touch_port.offset_y,
static_cast<int>(monitor_logical_w),
static_cast<int>(monitor_logical_h)
static_cast<int>(normalization_width),
static_cast<int>(normalization_height)
};
}

Expand All @@ -1129,7 +1144,12 @@ namespace input {

auto &touch_port = input->touch_port;

auto abs_port = monitor_touch_port(touch_port, *coords);
#ifdef _WIN32
const bool use_effective_touch_content = config::input.touch_send_to_primary_display;
#else
constexpr bool use_effective_touch_content = false;
#endif
auto abs_port = monitor_touch_port(touch_port, *coords, use_effective_touch_content);
if (!abs_port) {
return;
}
Expand Down Expand Up @@ -1180,7 +1200,7 @@ namespace input {

auto &touch_port = input->touch_port;

auto abs_port = monitor_touch_port(touch_port, *coords);
auto abs_port = monitor_touch_port(touch_port, *coords, false);
if (!abs_port) {
return;
}
Expand Down
17 changes: 17 additions & 0 deletions src/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

// standard includes
#include <functional>
#include <optional>

// local includes
#include "platform/common.h"
Expand Down Expand Up @@ -79,6 +80,22 @@ namespace input {
}
};

/**
* @brief Normalize host coordinates against the encoded frame or its effective display content.
* @details When effective content is selected, symmetric letterbox or pillarbox padding is removed from the
* encoded extent and coordinates in that padding are clamped to the nearest content edge.
*
* @param touch_port Touch-port metadata for the active stream and captured display.
* @param coords Host-relative coordinates to normalize in place.
* @param use_effective_content Whether to exclude encoded letterbox or pillarbox regions from normalization.
* @return The selected logical touch port, or `std::nullopt` when dimensions are invalid.
*/
std::optional<platf::touch_port_t> monitor_touch_port(
const touch_port_t &touch_port,
std::pair<float, float> &coords,
bool use_effective_content
);

/**
* @brief Scale the ellipse axes according to the provided size.
* @param val The major and minor axis pair.
Expand Down
Loading