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
4 changes: 4 additions & 0 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ If you forgot your credentials to the web UI, try this.
### Unusual Mouse Behavior
If you experience unusual mouse behavior, try attaching a physical mouse to the Sunshine host.

For absolute-positioning problems, set **Configuration > Advanced > Log Level** to **Debug** and reproduce the issue.
Sunshine forwards libvirtualhid diagnostics with a `[libvirtualhid]` prefix, including the streamed display viewport and
full desktop bounds used for mouse coordinate mapping.

### Web UI Access
Can't access the web UI?

Expand Down
80 changes: 55 additions & 25 deletions src/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,38 @@ namespace input {
}
}

/**
* @brief Consume the latest streamed-display geometry for an input session.
*
* @param input Input session receiving the geometry.
*/
void refresh_touch_port(std::shared_ptr<input_t> &input) {
auto &touch_port_event = input->touch_port_event;
if (touch_port_event->peek()) {
input->touch_port = *touch_port_event->pop();
}
}

/**
* @brief Convert stream touch geometry to platform pointer bounds.
*
* @param touch_port Stream touch geometry.
* @return Platform pointer bounds for the full desktop and streamed display.
*/
platf::touch_port_t platform_touch_port(const input::touch_port_t &touch_port) {
const bool has_logical_environment = touch_port.env_logical_width != 0 && touch_port.env_logical_height != 0;
return {
touch_port.offset_x,
touch_port.offset_y,
has_logical_environment ? touch_port.env_logical_width : touch_port.env_width,
has_logical_environment ? touch_port.env_logical_height : touch_port.env_height,
touch_port.logical_width,
touch_port.logical_height,
touch_port.env_offset_x,
touch_port.env_offset_y,
};
}

/**
* @brief Forward a client input packet directly to the platform backend.
*
Expand All @@ -719,7 +751,17 @@ namespace input {
}

input->mouse_left_button_timeout = DISABLE_LEFT_BUTTON_DELAY;
platf::move_mouse(platf_input, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY));
refresh_touch_port(input);
if (input->touch_port) {
platf::move_mouse(
platf_input,
platform_touch_port(input->touch_port),
util::endian::big(packet->deltaX),
util::endian::big(packet->deltaY)
);
} else {
platf::move_mouse(platf_input, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY));
}
}

/**
Expand All @@ -730,11 +772,8 @@ namespace input {
* @return The host-relative coordinate pair if a touchport is available.
*/
std::optional<std::pair<float, float>> client_to_touchport(std::shared_ptr<input_t> &input, const std::pair<float, float> &val, const std::pair<float, float> &size) {
auto &touch_port_event = input->touch_port_event;
refresh_touch_port(input);
auto &touch_port = input->touch_port;
if (touch_port_event->peek()) {
touch_port = *touch_port_event->pop();
}
if (!touch_port) {
BOOST_LOG(verbose) << "Ignoring early absolute input without a touch port"sv;
return std::nullopt;
Expand Down Expand Up @@ -846,26 +885,7 @@ namespace input {

auto &touch_port = input->touch_port;

int touch_port_dim_x;
int touch_port_dim_y;
if (touch_port.env_logical_width != 0 && touch_port.env_logical_height != 0) {
touch_port_dim_x = touch_port.env_logical_width;
touch_port_dim_y = touch_port.env_logical_height;
} else {
touch_port_dim_x = touch_port.env_width;
touch_port_dim_y = touch_port.env_height;
}

platf::touch_port_t abs_port {
touch_port.offset_x,
touch_port.offset_y,
touch_port_dim_x,
touch_port_dim_y,
touch_port.logical_width,
touch_port.logical_height,
};

platf::abs_mouse(platf_input, abs_port, tpcoords->first, tpcoords->second);
platf::abs_mouse(platf_input, platform_touch_port(touch_port), tpcoords->first, tpcoords->second);
}

/**
Expand Down Expand Up @@ -2378,6 +2398,16 @@ namespace input {
::input::passthrough(input, &packet);
}

void send_relative_mouse_packet(std::shared_ptr<input_t> &input, std::int16_t delta_x, std::int16_t delta_y) {
NV_REL_MOUSE_MOVE_PACKET packet {};
packet.header.size = util::endian::big<std::uint32_t>(sizeof(packet) - sizeof(packet.header.size));
packet.header.magic = util::endian::little<std::uint32_t>(MOUSE_MOVE_REL_MAGIC_GEN5);
packet.deltaX = util::endian::big(delta_x);
packet.deltaY = util::endian::big(delta_y);

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

void reset_keyboard_state() {
task_pool.cancel(key_press_repeat_id);
key_press_repeat_id = nullptr;
Expand Down
9 changes: 9 additions & 0 deletions src/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,15 @@ 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 relative-mouse packet on the calling thread.
*
* @param input Retained input state.
* @param delta_x Horizontal client mouse delta.
* @param delta_y Vertical client mouse delta.
*/
void send_relative_mouse_packet(std::shared_ptr<input_t> &input, std::int16_t delta_x, std::int16_t delta_y);

/**
* @brief Forget every key Sunshine tracks as pressed and cancel any pending key repeat.
*/
Expand Down
13 changes: 13 additions & 0 deletions src/platform/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,8 @@ namespace platf {
int height; ///< Frame or display height in pixels.
int logical_width; ///< Logical width after display scaling.
int logical_height; ///< Logical height after display scaling.
int env_offset_x {}; ///< Horizontal origin of the full input environment in native desktop coordinates.
int env_offset_y {}; ///< Vertical origin of the full input environment in native desktop coordinates.
};

// These values must match Limelight-internal.h's SS_FF_* constants!
Expand Down Expand Up @@ -785,6 +787,8 @@ namespace platf {
// Offsets for when streaming a specific monitor. By default, they are 0.
int offset_x {0}; ///< Horizontal capture offset in physical pixels.
int offset_y {0}; ///< Vertical capture offset in physical pixels.
int env_offset_x {0}; ///< Horizontal origin of the full capture environment in native desktop coordinates.
int env_offset_y {0}; ///< Vertical origin of the full capture environment in native desktop coordinates.
int env_width {0}; ///< Width of the full capture environment in physical pixels.
int env_height {0}; ///< Height of the full capture environment in physical pixels.
int env_logical_width {0}; ///< Width of the full capture environment after display scaling.
Expand Down Expand Up @@ -1154,6 +1158,15 @@ namespace platf {
* @param deltaY Delta y.
*/
void move_mouse(input_t &input, int deltaX, int deltaY);
/**
* @brief Retarget the virtual mouse and move it relatively.
*
* @param input Platform input backend that receives the event.
* @param touch_port Desktop and streamed-display bounds for the pointer.
* @param deltaX Delta x.
* @param deltaY Delta y.
*/
void move_mouse(input_t &input, const touch_port_t &touch_port, int deltaX, int deltaY);
/**
* @brief Move the pointer to an absolute client-provided touch coordinate.
*
Expand Down
47 changes: 44 additions & 3 deletions src/platform/macos/display.mm
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
// standard includes
#include <charconv>
#include <chrono>
#include <cstdint>
#include <optional>
#include <string_view>
#include <vector>

// local includes
#include "src/config.h"
Expand Down Expand Up @@ -48,6 +50,30 @@
return display_id;
}

/**
* @brief Return the union of active CoreGraphics display bounds.
*
* @param fallback_display Display whose bounds are used if enumeration fails.
* @return Full active desktop bounds in CoreGraphics coordinates.
*/
CGRect active_desktop_bounds(CGDirectDisplayID fallback_display) {
std::uint32_t display_count {};
if (CGGetActiveDisplayList(0, nullptr, &display_count) != kCGErrorSuccess || display_count == 0) {
return CGDisplayBounds(fallback_display);
}

std::vector<CGDirectDisplayID> displays(display_count);
if (CGGetActiveDisplayList(display_count, displays.data(), &display_count) != kCGErrorSuccess || display_count == 0) {
return CGDisplayBounds(fallback_display);
}

auto bounds = CGDisplayBounds(displays.front());
for (std::uint32_t index = 1; index < display_count; ++index) {
bounds = CGRectUnion(bounds, CGDisplayBounds(displays[index]));
}
return bounds;
}

OSType videotoolbox_pixel_format(const video::config_t &config) {
const auto colorspace {video::colorspace_from_client_config(config, false)};
return colorspace.bit_depth == 10 ? kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange : kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
Expand Down Expand Up @@ -258,6 +284,24 @@ static void setPixelFormat(void *display, OSType pixelFormat) {

BOOST_LOG(info) << "Configuring selected display ("sv << display->display_id << ") to stream"sv;

const auto selected_display_bounds = CGDisplayBounds(display->display_id);
const auto desktop_bounds = active_desktop_bounds(display->display_id);
display->offset_x = static_cast<int>(selected_display_bounds.origin.x);
display->offset_y = static_cast<int>(selected_display_bounds.origin.y);
display->logical_width = static_cast<int>(selected_display_bounds.size.width);
display->logical_height = static_cast<int>(selected_display_bounds.size.height);
display->env_offset_x = static_cast<int>(desktop_bounds.origin.x);
display->env_offset_y = static_cast<int>(desktop_bounds.origin.y);
display->env_logical_width = static_cast<int>(desktop_bounds.size.width);
display->env_logical_height = static_cast<int>(desktop_bounds.size.height);
display->env_width = display->env_logical_width;
display->env_height = display->env_logical_height;

BOOST_LOG(debug) << "Selected display bounds: "sv << display->offset_x << 'x' << display->offset_y << ' '
<< display->logical_width << 'x' << display->logical_height;
BOOST_LOG(debug) << "Active desktop bounds: "sv << display->env_offset_x << 'x' << display->env_offset_y << ' '
<< display->env_logical_width << 'x' << display->env_logical_height;

display->av_capture = [[AVVideo alloc] initWithDisplay:display->display_id frameRate:config.framerate];

if (!display->av_capture) {
Expand All @@ -267,9 +311,6 @@ static void setPixelFormat(void *display, OSType pixelFormat) {

display->width = display->av_capture.frameWidth;
display->height = display->av_capture.frameHeight;
// We also need set env_width and env_height for absolute mouse coordinates
display->env_width = display->width;
display->env_height = display->height;

if (hwdevice_type == platf::mem_type_e::videotoolbox) {
const auto pixel_format {videotoolbox_pixel_format(config)};
Expand Down
Loading
Loading