From e4dfb615098c73e2edf1872771438612538d87c9 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Mon, 20 Apr 2026 18:09:13 +0800 Subject: [PATCH] [fix] fix wayland cursor mapping --- .../mouse/linux/mouse_controller.h | 10 +- .../mouse/linux/mouse_controller_wayland.cpp | 252 +++++++++--- .../linux/screen_capturer_linux.cpp | 7 +- .../linux/screen_capturer_wayland.cpp | 35 +- .../linux/screen_capturer_wayland.h | 3 + .../screen_capturer_wayland_pipewire.cpp | 358 +++++++++++------- .../linux/screen_capturer_wayland_portal.cpp | 86 +++-- 7 files changed, 499 insertions(+), 252 deletions(-) diff --git a/src/device_controller/mouse/linux/mouse_controller.h b/src/device_controller/mouse/linux/mouse_controller.h index 0f1574c..4d78fa0 100644 --- a/src/device_controller/mouse/linux/mouse_controller.h +++ b/src/device_controller/mouse/linux/mouse_controller.h @@ -11,8 +11,8 @@ #include #include -#include #include +#include #include #include @@ -47,9 +47,9 @@ class MouseController : public DeviceController { bool NotifyWaylandPointerMotionAbsolute(uint32_t stream, double x, double y); bool NotifyWaylandPointerButton(int button, uint32_t state); bool NotifyWaylandPointerAxisDiscrete(uint32_t axis, int32_t steps); - bool SendWaylandPortalVoidCall(const char* method_name, - const std::function& - append_args); + bool SendWaylandPortalVoidCall( + const char* method_name, + const std::function& append_args); enum class WaylandAbsoluteMode { kUnknown, kPixels, kNormalized, kDisabled }; @@ -72,6 +72,8 @@ class MouseController : public DeviceController { WaylandAbsoluteMode wayland_absolute_mode_ = WaylandAbsoluteMode::kUnknown; bool wayland_absolute_disabled_logged_ = false; uint32_t wayland_absolute_stream_id_ = 0; + int wayland_portal_space_width_ = 0; + int wayland_portal_space_height_ = 0; bool using_shared_wayland_session_ = false; }; } // namespace crossdesk diff --git a/src/device_controller/mouse/linux/mouse_controller_wayland.cpp b/src/device_controller/mouse/linux/mouse_controller_wayland.cpp index 05fe690..387b146 100644 --- a/src/device_controller/mouse/linux/mouse_controller_wayland.cpp +++ b/src/device_controller/mouse/linux/mouse_controller_wayland.cpp @@ -1,5 +1,3 @@ -#include "mouse_controller.h" - #include #include #include @@ -7,6 +5,8 @@ #include #include +#include "mouse_controller.h" + #if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER #include #endif @@ -22,7 +22,8 @@ void MouseController::OnWaylandDisplayInfoListUpdated() { display_info_list_.empty() ? 0 : reinterpret_cast(display_info_list_[0].handle); - const int width0 = display_info_list_.empty() ? 0 : display_info_list_[0].width; + const int width0 = + display_info_list_.empty() ? 0 : display_info_list_[0].width; const int height0 = display_info_list_.empty() ? 0 : display_info_list_[0].height; const bool should_log = !logged_wayland_display_info_ || @@ -43,8 +44,7 @@ void MouseController::OnWaylandDisplayInfoListUpdated() { const auto& display = display_info_list_[i]; LOG_INFO( "Wayland mouse display info [{}]: name={}, rect=({},{})->({},{}) " - "size={}x{}, stream={}" - , + "size={}x{}, stream={}", i, display.name, display.left, display.top, display.right, display.bottom, display.width, display.height, reinterpret_cast(display.handle)); @@ -88,6 +88,13 @@ std::string MakeToken(const char* prefix) { } void LogDbusError(const char* action, DBusError* error) { + if (action && error && dbus_error_is_set(error) && + strcmp(action, "NotifyPointerMotionAbsolute") == 0 && error->name && + strcmp(error->name, "org.freedesktop.DBus.Error.Failed") == 0 && + error->message && strcmp(error->message, "Invalid position") == 0) { + return; + } + if (error && dbus_error_is_set(error)) { LOG_ERROR("{} failed: {} ({})", action, error->message ? error->message : "unknown", @@ -190,6 +197,27 @@ bool ReadUint32Like(DBusMessageIter* iter, uint32_t* value) { return false; } +bool ReadIntLike(DBusMessageIter* iter, int* value) { + if (!iter || !value) { + return false; + } + + if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_INT32) { + int32_t temp = 0; + dbus_message_iter_get_basic(iter, &temp); + *value = static_cast(temp); + return true; + } + + uint32_t temp = 0; + if (ReadUint32Like(iter, &temp)) { + *value = static_cast(temp); + return true; + } + + return false; +} + bool ReadFirstStreamId(DBusMessageIter* variant, uint32_t* stream_id) { if (!variant || !stream_id) { return false; @@ -215,6 +243,85 @@ bool ReadFirstStreamId(DBusMessageIter* variant, uint32_t* stream_id) { return false; } +bool ReadFirstStreamGeometry(DBusMessageIter* variant, int* width, + int* height) { + if (!variant || !width || !height) { + return false; + } + if (dbus_message_iter_get_arg_type(variant) != DBUS_TYPE_ARRAY) { + return false; + } + + int parsed_width = 0; + int parsed_height = 0; + DBusMessageIter streams; + dbus_message_iter_recurse(variant, &streams); + while (dbus_message_iter_get_arg_type(&streams) != DBUS_TYPE_INVALID) { + if (dbus_message_iter_get_arg_type(&streams) == DBUS_TYPE_STRUCT) { + DBusMessageIter stream; + dbus_message_iter_recurse(&streams, &stream); + + if (dbus_message_iter_get_arg_type(&stream) == DBUS_TYPE_UINT32) { + dbus_message_iter_next(&stream); + } + + if (dbus_message_iter_get_arg_type(&stream) == DBUS_TYPE_ARRAY) { + int stream_width = 0; + int stream_height = 0; + int logical_width = 0; + int logical_height = 0; + DBusMessageIter props; + dbus_message_iter_recurse(&stream, &props); + while (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_INVALID) { + if (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter prop_entry; + dbus_message_iter_recurse(&props, &prop_entry); + + const char* prop_key = nullptr; + dbus_message_iter_get_basic(&prop_entry, &prop_key); + if (prop_key && dbus_message_iter_next(&prop_entry) && + dbus_message_iter_get_arg_type(&prop_entry) == + DBUS_TYPE_VARIANT) { + DBusMessageIter prop_variant; + dbus_message_iter_recurse(&prop_entry, &prop_variant); + if (dbus_message_iter_get_arg_type(&prop_variant) == + DBUS_TYPE_STRUCT) { + DBusMessageIter size_iter; + int candidate_width = 0; + int candidate_height = 0; + dbus_message_iter_recurse(&prop_variant, &size_iter); + if (ReadIntLike(&size_iter, &candidate_width) && + dbus_message_iter_next(&size_iter) && + ReadIntLike(&size_iter, &candidate_height)) { + if (strcmp(prop_key, "logical_size") == 0) { + logical_width = candidate_width; + logical_height = candidate_height; + } else if (strcmp(prop_key, "size") == 0) { + stream_width = candidate_width; + stream_height = candidate_height; + } + } + } + } + } + dbus_message_iter_next(&props); + } + + parsed_width = logical_width > 0 ? logical_width : stream_width; + parsed_height = logical_height > 0 ? logical_height : stream_height; + if (parsed_width > 0 && parsed_height > 0) { + *width = parsed_width; + *height = parsed_height; + return true; + } + } + } + dbus_message_iter_next(&streams); + } + + return false; +} + std::string BuildSessionHandleFromRequestPath( const std::string& request_path, const std::string& session_handle_token) { if (request_path.rfind(kPortalRequestPathPrefix, 0) != 0 || @@ -361,8 +468,7 @@ bool ExtractPortalResponse(DBusMessage* message, uint32_t* response_code, bool SendPortalRequestAndHandleResponse( DBusConnection* connection, const char* interface_name, - const char* method_name, - const char* action_name, + const char* method_name, const char* action_name, const std::function& append_message_args, const std::function& handle_results, std::string* request_path_out = nullptr) { @@ -386,8 +492,8 @@ bool SendPortalRequestAndHandleResponse( DBusError error; dbus_error_init(&error); - DBusMessage* reply = - dbus_connection_send_with_reply_and_block(connection, message, -1, &error); + DBusMessage* reply = dbus_connection_send_with_reply_and_block( + connection, message, -1, &error); dbus_message_unref(message); if (!reply) { LogDbusError(action_name ? action_name : method_name, &error); @@ -438,6 +544,8 @@ bool MouseController::InitWaylandPortal() { dbus_connection_ = shared_session.connection; wayland_session_handle_ = shared_session.session_handle; wayland_absolute_stream_id_ = shared_session.stream_id; + wayland_portal_space_width_ = shared_session.width; + wayland_portal_space_height_ = shared_session.height; last_display_index_ = -1; last_norm_x_ = -1.0; last_norm_y_ = -1.0; @@ -448,9 +556,11 @@ bool MouseController::InitWaylandPortal() { wayland_absolute_mode_ = WaylandAbsoluteMode::kUnknown; wayland_absolute_disabled_logged_ = false; using_shared_wayland_session_ = true; - LOG_INFO("Mouse controller attached to shared Wayland portal session, " - "stream_id={}", - wayland_absolute_stream_id_); + LOG_INFO( + "Mouse controller attached to shared Wayland portal session, " + "stream_id={}, portal_space={}x{}", + wayland_absolute_stream_id_, wayland_portal_space_width_, + wayland_portal_space_height_); return true; }; @@ -469,16 +579,18 @@ bool MouseController::InitWaylandPortal() { if (!waiting_logged) { waiting_logged = true; - LOG_INFO("Waiting for shared Wayland portal session from screen " - "capturer before creating a standalone mouse session"); + LOG_INFO( + "Waiting for shared Wayland portal session from screen " + "capturer before creating a standalone mouse session"); } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } if (waiting_logged) { - LOG_WARN("Shared Wayland portal session did not appear in time; falling " - "back to standalone mouse portal session"); + LOG_WARN( + "Shared Wayland portal session did not appear in time; falling " + "back to standalone mouse portal session"); } if (AcquireSharedWaylandPortalSession(true, &shared_session)) { @@ -677,6 +789,8 @@ bool MouseController::InitWaylandPortal() { uint32_t granted_devices = 0; uint32_t absolute_stream_id = 0; + int absolute_space_width = 0; + int absolute_space_height = 0; DBusMessageIter dict; dbus_message_iter_recurse(results, &dict); while (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_INVALID) { @@ -694,6 +808,8 @@ bool MouseController::InitWaylandPortal() { ReadUint32Like(&variant, &granted_devices); } else if (strcmp(key, "streams") == 0) { ReadFirstStreamId(&variant, &absolute_stream_id); + ReadFirstStreamGeometry(&variant, &absolute_space_width, + &absolute_space_height); } } } @@ -703,19 +819,24 @@ bool MouseController::InitWaylandPortal() { pointer_granted = (granted_devices & kRemoteDesktopDevicePointer) != 0; if (!pointer_granted) { LOG_ERROR( - "RemoteDesktop.Start granted devices mask={}, pointer not allowed", + "RemoteDesktop.Start granted devices mask={}, pointer not " + "allowed", granted_devices); return false; } if (absolute_stream_id == 0) { - LOG_ERROR("RemoteDesktop.Start did not return a screencast stream id"); + LOG_ERROR( + "RemoteDesktop.Start did not return a screencast stream id"); return false; } wayland_absolute_stream_id_ = absolute_stream_id; + wayland_portal_space_width_ = absolute_space_width; + wayland_portal_space_height_ = absolute_space_height; wayland_absolute_mode_ = WaylandAbsoluteMode::kUnknown; wayland_absolute_disabled_logged_ = false; - LOG_INFO("Wayland mouse absolute stream id={}", - wayland_absolute_stream_id_); + LOG_INFO("Wayland mouse absolute stream id={}, portal_space={}x{}", + wayland_absolute_stream_id_, wayland_portal_space_width_, + wayland_portal_space_height_); return true; }); @@ -782,6 +903,8 @@ void MouseController::CleanupWaylandPortal() { wayland_absolute_mode_ = WaylandAbsoluteMode::kUnknown; wayland_absolute_disabled_logged_ = false; wayland_absolute_stream_id_ = 0; + wayland_portal_space_width_ = 0; + wayland_portal_space_height_ = 0; using_shared_wayland_session_ = false; } @@ -835,49 +958,80 @@ int MouseController::SendWaylandMouseCommand(RemoteAction remote_action, } const uint32_t stream = wayland_absolute_stream_id_; - const double abs_x = norm_x * std::max(width - 1, 1); - const double abs_y = norm_y * std::max(height - 1, 1); + const int portal_width = + wayland_portal_space_width_ > 0 ? wayland_portal_space_width_ : width; + const int portal_height = wayland_portal_space_height_ > 0 + ? wayland_portal_space_height_ + : height; + const double abs_x = norm_x * static_cast(width); + const double abs_y = norm_y * static_cast(height); + const double max_x = std::nextafter(static_cast(width), 0.0); + const double max_y = std::nextafter(static_cast(height), 0.0); + const double send_x = std::clamp(abs_x, 0.0, std::max(max_x, 0.0)); + const double send_y = std::clamp(abs_y, 0.0, std::max(max_y, 0.0)); + const bool can_use_relative = last_display_index_ == display_index && + last_norm_x_ >= 0.0 && last_norm_y_ >= 0.0; + const double rel_dx = + (norm_x - last_norm_x_) * static_cast(portal_width); + const double rel_dy = + (norm_y - last_norm_y_) * static_cast(portal_height); - auto accept_absolute = [&]() { + auto accept_motion = [&]() { last_display_index_ = display_index; last_norm_x_ = norm_x; last_norm_y_ = norm_y; + wayland_absolute_disabled_logged_ = false; return 0; }; + auto try_relative_fallback = [&]() -> bool { + if (!can_use_relative) { + return false; + } + if (std::abs(rel_dx) < 1e-6 && std::abs(rel_dy) < 1e-6) { + return false; + } + if (NotifyWaylandPointerMotion(rel_dx, rel_dy)) { + return true; + } + return false; + }; + + if (wayland_absolute_mode_ == WaylandAbsoluteMode::kDisabled) { + if (try_relative_fallback()) { + return accept_motion(); + } + if (!wayland_absolute_disabled_logged_) { + wayland_absolute_disabled_logged_ = true; + LOG_ERROR("NotifyPointerMotionAbsolute rejected by portal backend"); + } + return -3; + } + if (wayland_absolute_mode_ == WaylandAbsoluteMode::kPixels) { - if (NotifyWaylandPointerMotionAbsolute(stream, abs_x, abs_y)) { - return accept_absolute(); + if (NotifyWaylandPointerMotionAbsolute(stream, send_x, send_y)) { + return accept_motion(); } - wayland_absolute_mode_ = WaylandAbsoluteMode::kDisabled; - } else if (wayland_absolute_mode_ == WaylandAbsoluteMode::kNormalized) { - if (NotifyWaylandPointerMotionAbsolute(stream, norm_x, norm_y)) { - return accept_absolute(); + if (try_relative_fallback()) { + return accept_motion(); } - wayland_absolute_mode_ = WaylandAbsoluteMode::kDisabled; - } else { - if (NotifyWaylandPointerMotionAbsolute(stream, abs_x, abs_y)) { + } else if (wayland_absolute_mode_ == WaylandAbsoluteMode::kUnknown) { + if (NotifyWaylandPointerMotionAbsolute(stream, send_x, send_y)) { wayland_absolute_mode_ = WaylandAbsoluteMode::kPixels; - LOG_INFO("Wayland absolute pointer mode selected: pixel coordinates"); - return accept_absolute(); - } - - if (NotifyWaylandPointerMotionAbsolute(stream, norm_x, norm_y)) { - wayland_absolute_mode_ = WaylandAbsoluteMode::kNormalized; LOG_INFO( - "Wayland absolute pointer mode selected: normalized " - "coordinates"); - return accept_absolute(); + "Wayland absolute pointer mode selected: pixel coordinates " + "(pointer space {}x{})", + width, height); + return accept_motion(); + } + if (try_relative_fallback()) { + return accept_motion(); } - - wayland_absolute_mode_ = WaylandAbsoluteMode::kDisabled; } if (!wayland_absolute_disabled_logged_) { wayland_absolute_disabled_logged_ = true; - LOG_ERROR( - "NotifyPointerMotionAbsolute rejected by portal backend in both " - "pixel and normalized modes"); + LOG_ERROR("NotifyPointerMotionAbsolute rejected by portal backend"); } return -3; } @@ -1030,9 +1184,9 @@ bool MouseController::SendWaylandPortalVoidCall( return false; } - DBusMessage* message = dbus_message_new_method_call( - kPortalBusName, kPortalObjectPath, kPortalRemoteDesktopInterface, - method_name); + DBusMessage* message = + dbus_message_new_method_call(kPortalBusName, kPortalObjectPath, + kPortalRemoteDesktopInterface, method_name); if (!message) { LOG_ERROR("Failed to allocate {} message", method_name); return false; diff --git a/src/screen_capturer/linux/screen_capturer_linux.cpp b/src/screen_capturer/linux/screen_capturer_linux.cpp index 87e51dc..1641caa 100644 --- a/src/screen_capturer/linux/screen_capturer_linux.cpp +++ b/src/screen_capturer/linux/screen_capturer_linux.cpp @@ -177,8 +177,8 @@ int ScreenCapturerLinux::Start(bool show_cursor) { backend_name = "Wayland"; } - LOG_WARN("Linux screen capturer backend {} start failed: {}", - backend_name, ret); + LOG_WARN("Linux screen capturer backend {} start failed: {}", backend_name, + ret); if (backend_ == BackendType::kX11 && kDrmBuildEnabled && TryFallbackToDrm(show_cursor)) { @@ -484,7 +484,8 @@ void ScreenCapturerLinux::UpdateAliasesFromBackend(ScreenCapturer* backend) { } } -std::string ScreenCapturerLinux::MapDisplayName(const char* display_name) const { +std::string ScreenCapturerLinux::MapDisplayName( + const char* display_name) const { std::string input_name = display_name ? display_name : ""; if (input_name.empty()) { return input_name; diff --git a/src/screen_capturer/linux/screen_capturer_wayland.cpp b/src/screen_capturer/linux/screen_capturer_wayland.cpp index e43b08a..a8068d6 100644 --- a/src/screen_capturer/linux/screen_capturer_wayland.cpp +++ b/src/screen_capturer/linux/screen_capturer_wayland.cpp @@ -3,13 +3,13 @@ #include "screen_capturer_wayland_build.h" #if !CROSSDESK_WAYLAND_BUILD_ENABLED -#error "Wayland capturer requires USE_WAYLAND=true and Wayland development headers" +#error \ + "Wayland capturer requires USE_WAYLAND=true and Wayland development headers" #endif +#include #include #include - -#include #include #include "platform.h" @@ -69,6 +69,9 @@ int ScreenCapturerWayland::Init(const int fps, cb_desktop_data cb) { frame_width_ = kFallbackWidth; frame_height_ = kFallbackHeight; frame_stride_ = kFallbackWidth * 4; + portal_has_logical_size_ = false; + portal_stream_width_ = 0; + portal_stream_height_ = 0; logical_width_ = kFallbackWidth; logical_height_ = kFallbackHeight; y_plane_.resize(kFallbackWidth * kFallbackHeight); @@ -94,9 +97,9 @@ int ScreenCapturerWayland::Start(bool show_cursor) { show_cursor_ = show_cursor; paused_ = false; pipewire_node_id_ = 0; - UpdateDisplayGeometry(logical_width_ > 0 ? logical_width_ : kFallbackWidth, - logical_height_ > 0 ? logical_height_ - : kFallbackHeight); + UpdateDisplayGeometry( + logical_width_ > 0 ? logical_width_ : kFallbackWidth, + logical_height_ > 0 ? logical_height_ : kFallbackHeight); pipewire_format_ready_.store(false); pipewire_stream_start_ms_.store(0); pipewire_last_frame_ms_.store(0); @@ -111,9 +114,9 @@ int ScreenCapturerWayland::Stop() { thread_.join(); } pipewire_node_id_ = 0; - UpdateDisplayGeometry(logical_width_ > 0 ? logical_width_ : kFallbackWidth, - logical_height_ > 0 ? logical_height_ - : kFallbackHeight); + UpdateDisplayGeometry( + logical_width_ > 0 ? logical_width_ : kFallbackWidth, + logical_height_ > 0 ? logical_height_ : kFallbackHeight); return 0; } @@ -182,9 +185,9 @@ void ScreenCapturerWayland::Run() { const bool format_timeout = stream_start > 0 && !format_ready && (now - stream_start) > 1200; - const bool first_frame_timeout = - stream_start > 0 && format_ready && last_frame == 0 && - (now - stream_start) > 4000; + const bool first_frame_timeout = stream_start > 0 && format_ready && + last_frame == 0 && + (now - stream_start) > 4000; const bool frame_stall = last_frame > 0 && (now - last_frame) > 5000; if (format_timeout || first_frame_timeout || frame_stall) { @@ -200,10 +203,10 @@ void ScreenCapturerWayland::Run() { } ++recovery_index; - const char* reason = format_timeout - ? "format-timeout" - : (first_frame_timeout ? "first-frame-timeout" - : "frame-stall"); + const char* reason = + format_timeout + ? "format-timeout" + : (first_frame_timeout ? "first-frame-timeout" : "frame-stall"); const auto& config = kRecoveryConfigs[recovery_index]; LOG_WARN( "Wayland capture stalled ({}) - retrying PipeWire only, " diff --git a/src/screen_capturer/linux/screen_capturer_wayland.h b/src/screen_capturer/linux/screen_capturer_wayland.h index 58f8f16..e958752 100644 --- a/src/screen_capturer/linux/screen_capturer_wayland.h +++ b/src/screen_capturer/linux/screen_capturer_wayland.h @@ -94,10 +94,13 @@ class ScreenCapturerWayland : public ScreenCapturer { bool pipewire_thread_loop_started_ = false; bool pointer_granted_ = false; bool shared_session_registered_ = false; + bool portal_has_logical_size_ = false; uint32_t spa_video_format_ = 0; int frame_width_ = 0; int frame_height_ = 0; int frame_stride_ = 0; + int portal_stream_width_ = 0; + int portal_stream_height_ = 0; int logical_width_ = 0; int logical_height_ = 0; diff --git a/src/screen_capturer/linux/screen_capturer_wayland_pipewire.cpp b/src/screen_capturer/linux/screen_capturer_wayland_pipewire.cpp index 6fe69b6..7092480 100644 --- a/src/screen_capturer/linux/screen_capturer_wayland_pipewire.cpp +++ b/src/screen_capturer/linux/screen_capturer_wayland_pipewire.cpp @@ -1,14 +1,16 @@ #include "screen_capturer_wayland.h" - #include "screen_capturer_wayland_build.h" #if CROSSDESK_WAYLAND_BUILD_ENABLED -#include -#include -#include #include +#include +#include +#include +#include +#include +#include #include #include "libyuv.h" @@ -57,6 +59,22 @@ int64_t NowMs() { .count(); } +double SnapLikelyFractionalScale(double observed_scale) { + static constexpr double kCandidates[] = { + 1.0, 1.25, 1.3333333333, 1.5, 1.6666666667, 1.75, 2.0, 2.25, 2.5, 3.0}; + double best = observed_scale; + double best_error = std::numeric_limits::max(); + for (double candidate : kCandidates) { + const double error = std::abs(candidate - observed_scale); + if (error < best_error) { + best = candidate; + best_error = error; + } + } + + return best_error <= 0.08 ? best : observed_scale; +} + struct PipeWireTargetLookupState { pw_thread_loop* loop = nullptr; uint32_t target_node_id = 0; @@ -87,30 +105,30 @@ std::string LookupPipeWireTargetObjectSerial(pw_core* core, pw_registry_events registry_events{}; registry_events.version = PW_VERSION_REGISTRY_EVENTS; - registry_events.global = - [](void* userdata, uint32_t id, uint32_t permissions, const char* type, - uint32_t version, const spa_dict* props) { - (void)permissions; - (void)version; - auto* state = static_cast(userdata); - if (!state || !props || id != state->target_node_id || !type) { - return; - } - if (strcmp(type, PW_TYPE_INTERFACE_Node) != 0) { - return; - } + registry_events.global = [](void* userdata, uint32_t id, uint32_t permissions, + const char* type, uint32_t version, + const spa_dict* props) { + (void)permissions; + (void)version; + auto* state = static_cast(userdata); + if (!state || !props || id != state->target_node_id || !type) { + return; + } + if (strcmp(type, PW_TYPE_INTERFACE_Node) != 0) { + return; + } - const char* object_serial = spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL); - if (!object_serial || object_serial[0] == '\0') { - object_serial = spa_dict_lookup(props, "object.serial"); - } - if (!object_serial || object_serial[0] == '\0') { - return; - } + const char* object_serial = spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL); + if (!object_serial || object_serial[0] == '\0') { + object_serial = spa_dict_lookup(props, "object.serial"); + } + if (!object_serial || object_serial[0] == '\0') { + return; + } - state->object_serial = object_serial; - state->found = true; - }; + state->object_serial = object_serial; + state->found = true; + }; pw_core_events core_events{}; core_events.version = PW_VERSION_CORE_EVENTS; @@ -226,23 +244,23 @@ bool ScreenCapturerWayland::SetupPipeWireStream(bool relaxed_connect, std::string target_object_serial; if (mode == PipeWireConnectMode::kTargetObject) { - target_object_serial = - LookupPipeWireTargetObjectSerial(pw_core_, pw_thread_loop_, - pipewire_node_id_); + target_object_serial = LookupPipeWireTargetObjectSerial( + pw_core_, pw_thread_loop_, pipewire_node_id_); if (!target_object_serial.empty()) { pw_properties_set(stream_props, PW_KEY_TARGET_OBJECT, target_object_serial.c_str()); LOG_INFO("PipeWire target object serial for node {} is {}", pipewire_node_id_, target_object_serial); } else { - LOG_WARN("PipeWire target object serial lookup failed for node {}, " - "falling back to direct target id in target-object mode", - pipewire_node_id_); + LOG_WARN( + "PipeWire target object serial lookup failed for node {}, " + "falling back to direct target id in target-object mode", + pipewire_node_id_); } } - pw_stream_ = pw_stream_new(pw_core_, "CrossDesk Wayland Capture", - stream_props); + pw_stream_ = + pw_stream_new(pw_core_, "CrossDesk Wayland Capture", stream_props); if (!pw_stream_) { LOG_ERROR("Failed to create PipeWire stream"); pw_thread_loop_unlock(pw_thread_loop_); @@ -256,123 +274,180 @@ bool ScreenCapturerWayland::SetupPipeWireStream(bool relaxed_connect, static const pw_stream_events stream_events = [] { pw_stream_events events{}; events.version = PW_VERSION_STREAM_EVENTS; - events.state_changed = - [](void* userdata, enum pw_stream_state old_state, - enum pw_stream_state state, const char* error_message) { - auto* self = static_cast(userdata); - if (!self) { - return; - } + events.state_changed = [](void* userdata, enum pw_stream_state old_state, + enum pw_stream_state state, + const char* error_message) { + auto* self = static_cast(userdata); + if (!self) { + return; + } - if (state == PW_STREAM_STATE_ERROR) { - LOG_ERROR("PipeWire stream error: {}", - error_message ? error_message : "unknown"); - self->running_ = false; - return; - } + if (state == PW_STREAM_STATE_ERROR) { + LOG_ERROR("PipeWire stream error: {}", + error_message ? error_message : "unknown"); + self->running_ = false; + return; + } - LOG_INFO("PipeWire stream state: {} -> {}", - pw_stream_state_as_string(old_state), - pw_stream_state_as_string(state)); + LOG_INFO("PipeWire stream state: {} -> {}", + pw_stream_state_as_string(old_state), + pw_stream_state_as_string(state)); + }; + events.param_changed = [](void* userdata, uint32_t id, + const struct spa_pod* param) { + auto* self = static_cast(userdata); + if (!self || id != SPA_PARAM_Format || !param) { + return; + } - }; - events.param_changed = - [](void* userdata, uint32_t id, const struct spa_pod* param) { - auto* self = static_cast(userdata); - if (!self || id != SPA_PARAM_Format || !param) { - return; - } + spa_video_info_raw info{}; + if (spa_format_video_raw_parse(param, &info) < 0) { + LOG_ERROR("Failed to parse PipeWire video format"); + return; + } - spa_video_info_raw info{}; - if (spa_format_video_raw_parse(param, &info) < 0) { - LOG_ERROR("Failed to parse PipeWire video format"); - return; - } + self->spa_video_format_ = info.format; + self->frame_width_ = static_cast(info.size.width); + self->frame_height_ = static_cast(info.size.height); + self->frame_stride_ = static_cast(info.size.width) * 4; - self->spa_video_format_ = info.format; - self->frame_width_ = static_cast(info.size.width); - self->frame_height_ = static_cast(info.size.height); - self->frame_stride_ = static_cast(info.size.width) * 4; - - bool supported_format = - (self->spa_video_format_ == SPA_VIDEO_FORMAT_BGRx) || - (self->spa_video_format_ == SPA_VIDEO_FORMAT_BGRA); + bool supported_format = + (self->spa_video_format_ == SPA_VIDEO_FORMAT_BGRx) || + (self->spa_video_format_ == SPA_VIDEO_FORMAT_BGRA); #ifdef SPA_VIDEO_FORMAT_RGBx - supported_format = - supported_format || - (self->spa_video_format_ == SPA_VIDEO_FORMAT_RGBx); + supported_format = supported_format || + (self->spa_video_format_ == SPA_VIDEO_FORMAT_RGBx); #endif #ifdef SPA_VIDEO_FORMAT_RGBA - supported_format = - supported_format || - (self->spa_video_format_ == SPA_VIDEO_FORMAT_RGBA); + supported_format = supported_format || + (self->spa_video_format_ == SPA_VIDEO_FORMAT_RGBA); #endif - if (!supported_format) { - LOG_ERROR("Unsupported PipeWire pixel format: {}", - PipeWireFormatName(self->spa_video_format_)); - self->running_ = false; - return; - } + if (!supported_format) { + LOG_ERROR("Unsupported PipeWire pixel format: {}", + PipeWireFormatName(self->spa_video_format_)); + self->running_ = false; + return; + } - const int bytes_per_pixel = BytesPerPixel(self->spa_video_format_); - if (bytes_per_pixel <= 0 || self->frame_width_ <= 0 || - self->frame_height_ <= 0) { - LOG_ERROR("Invalid PipeWire frame layout: format={}, size={}x{}", - PipeWireFormatName(self->spa_video_format_), - self->frame_width_, self->frame_height_); - self->running_ = false; - return; - } + const int bytes_per_pixel = BytesPerPixel(self->spa_video_format_); + if (bytes_per_pixel <= 0 || self->frame_width_ <= 0 || + self->frame_height_ <= 0) { + LOG_ERROR("Invalid PipeWire frame layout: format={}, size={}x{}", + PipeWireFormatName(self->spa_video_format_), + self->frame_width_, self->frame_height_); + self->running_ = false; + return; + } - self->frame_stride_ = self->frame_width_ * bytes_per_pixel; + self->frame_stride_ = self->frame_width_ * bytes_per_pixel; - uint8_t buffer[1024]; - spa_pod_builder builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - const spa_pod* params[2]; - uint32_t param_count = 0; + uint8_t buffer[1024]; + spa_pod_builder builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + const spa_pod* params[2]; + uint32_t param_count = 0; - params[param_count++] = reinterpret_cast( - spa_pod_builder_add_object( - &builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, - CROSSDESK_SPA_PARAM_BUFFERS_BUFFERS, - SPA_POD_CHOICE_RANGE_Int(8, 4, 16), - CROSSDESK_SPA_PARAM_BUFFERS_BLOCKS, SPA_POD_Int(1), - CROSSDESK_SPA_PARAM_BUFFERS_SIZE, - SPA_POD_CHOICE_RANGE_Int(self->frame_stride_ * - self->frame_height_, - self->frame_stride_ * - self->frame_height_, - self->frame_stride_ * - self->frame_height_), - CROSSDESK_SPA_PARAM_BUFFERS_STRIDE, - SPA_POD_CHOICE_RANGE_Int(self->frame_stride_, - self->frame_stride_, - self->frame_stride_))); + params[param_count++] = + reinterpret_cast(spa_pod_builder_add_object( + &builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + CROSSDESK_SPA_PARAM_BUFFERS_BUFFERS, + SPA_POD_CHOICE_RANGE_Int(8, 4, 16), + CROSSDESK_SPA_PARAM_BUFFERS_BLOCKS, SPA_POD_Int(1), + CROSSDESK_SPA_PARAM_BUFFERS_SIZE, + SPA_POD_CHOICE_RANGE_Int( + self->frame_stride_ * self->frame_height_, + self->frame_stride_ * self->frame_height_, + self->frame_stride_ * self->frame_height_), + CROSSDESK_SPA_PARAM_BUFFERS_STRIDE, + SPA_POD_CHOICE_RANGE_Int(self->frame_stride_, self->frame_stride_, + self->frame_stride_))); - params[param_count++] = reinterpret_cast( - spa_pod_builder_add_object( - &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, - CROSSDESK_SPA_PARAM_META_TYPE, SPA_POD_Id(SPA_META_Header), - CROSSDESK_SPA_PARAM_META_SIZE, - SPA_POD_Int(sizeof(struct spa_meta_header)))); + params[param_count++] = + reinterpret_cast(spa_pod_builder_add_object( + &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, + CROSSDESK_SPA_PARAM_META_TYPE, SPA_POD_Id(SPA_META_Header), + CROSSDESK_SPA_PARAM_META_SIZE, + SPA_POD_Int(sizeof(struct spa_meta_header)))); - if (self->pw_stream_) { - pw_stream_update_params(self->pw_stream_, params, param_count); - } - self->pipewire_format_ready_.store(true); + if (self->pw_stream_) { + pw_stream_update_params(self->pw_stream_, params, param_count); + } + self->pipewire_format_ready_.store(true); - const int pointer_width = - self->logical_width_ > 0 ? self->logical_width_ : self->frame_width_; - const int pointer_height = self->logical_height_ > 0 - ? self->logical_height_ - : self->frame_height_; - self->UpdateDisplayGeometry(pointer_width, pointer_height); - LOG_INFO( - "PipeWire video format: {}, {}x{} stride={} (pointer space {}x{})", - PipeWireFormatName(self->spa_video_format_), - self->frame_width_, self->frame_height_, self->frame_stride_, - pointer_width, pointer_height); - }; + int pointer_width = + self->logical_width_ > 0 ? self->logical_width_ : self->frame_width_; + int pointer_height = self->logical_height_ > 0 ? self->logical_height_ + : self->frame_height_; + double observed_scale_x = pointer_width > 0 + ? static_cast(self->frame_width_) / + static_cast(pointer_width) + : 1.0; + double observed_scale_y = pointer_height > 0 + ? static_cast(self->frame_height_) / + static_cast(pointer_height) + : 1.0; + double snapped_scale = 1.0; + bool derived_pointer_space = false; + + if (!self->portal_has_logical_size_ && self->portal_stream_width_ > 0 && + self->portal_stream_height_ > 0 && self->frame_width_ > 0 && + self->frame_height_ > 0) { + const double raw_scale_x = + static_cast(self->frame_width_) / + static_cast(self->portal_stream_width_); + const double raw_scale_y = + static_cast(self->frame_height_) / + static_cast(self->portal_stream_height_); + const double average_scale = (raw_scale_x + raw_scale_y) * 0.5; + snapped_scale = SnapLikelyFractionalScale(average_scale); + + const bool scales_are_consistent = + std::abs(raw_scale_x - raw_scale_y) <= 0.05; + const bool scale_was_snapped = + std::abs(snapped_scale - average_scale) <= 0.08; + if (scales_are_consistent && scale_was_snapped && + snapped_scale > 1.05) { + pointer_width = + std::max(1, static_cast(std::floor( + static_cast(self->portal_stream_width_) * + snapped_scale + + 1e-6))); + pointer_height = + std::max(1, static_cast(std::floor( + static_cast(self->portal_stream_height_) * + snapped_scale + + 1e-6))); + observed_scale_x = pointer_width > 0 + ? static_cast(self->frame_width_) / + static_cast(pointer_width) + : 1.0; + observed_scale_y = pointer_height > 0 + ? static_cast(self->frame_height_) / + static_cast(pointer_height) + : 1.0; + derived_pointer_space = true; + } + } + + self->UpdateDisplayGeometry(pointer_width, pointer_height); + if (derived_pointer_space) { + LOG_INFO( + "PipeWire video format: {}, {}x{} stride={} (pointer space {}x{}, " + "derived from portal stream {}x{} with compositor scale {:.4f}, " + "effective scale {:.4f}x{:.4f})", + PipeWireFormatName(self->spa_video_format_), self->frame_width_, + self->frame_height_, self->frame_stride_, pointer_width, + pointer_height, self->portal_stream_width_, + self->portal_stream_height_, snapped_scale, observed_scale_x, + observed_scale_y); + } else { + LOG_INFO( + "PipeWire video format: {}, {}x{} stride={} (pointer space {}x{}, " + "scale {:.4f}x{:.4f})", + PipeWireFormatName(self->spa_video_format_), self->frame_width_, + self->frame_height_, self->frame_stride_, pointer_width, + pointer_height, observed_scale_x, observed_scale_y); + } + }; events.process = [](void* userdata) { auto* self = static_cast(userdata); if (self) { @@ -392,7 +467,8 @@ bool ScreenCapturerWayland::SetupPipeWireStream(bool relaxed_connect, const spa_pod* params[8]; int param_count = 0; const spa_rectangle fixed_size{ - static_cast(logical_width_ > 0 ? logical_width_ : kFallbackWidth), + static_cast(logical_width_ > 0 ? logical_width_ + : kFallbackWidth), static_cast(logical_height_ > 0 ? logical_height_ : kFallbackHeight)}; const spa_rectangle min_size{1u, 1u}; @@ -463,9 +539,9 @@ bool ScreenCapturerWayland::SetupPipeWireStream(bool relaxed_connect, } void ScreenCapturerWayland::CleanupPipeWire() { - const bool need_lock = pw_thread_loop_ && - (pw_stream_ != nullptr || pw_core_ != nullptr || - pw_context_ != nullptr); + const bool need_lock = + pw_thread_loop_ && + (pw_stream_ != nullptr || pw_core_ != nullptr || pw_context_ != nullptr); if (need_lock) { pw_thread_loop_lock(pw_thread_loop_); } @@ -584,8 +660,8 @@ void ScreenCapturerWayland::HandlePipeWireBuffer() { uv_plane_.resize(uv_size); } - libyuv::ARGBToNV12(src, stride, y_plane_.data(), even_width, - uv_plane_.data(), even_width, even_width, even_height); + libyuv::ARGBToNV12(src, stride, y_plane_.data(), even_width, uv_plane_.data(), + even_width, even_width, even_height); std::vector nv12; nv12.reserve(y_plane_.size() + uv_plane_.size()); diff --git a/src/screen_capturer/linux/screen_capturer_wayland_portal.cpp b/src/screen_capturer/linux/screen_capturer_wayland_portal.cpp index d958697..2157bf6 100644 --- a/src/screen_capturer/linux/screen_capturer_wayland_portal.cpp +++ b/src/screen_capturer/linux/screen_capturer_wayland_portal.cpp @@ -1,15 +1,15 @@ #include "screen_capturer_wayland.h" - #include "screen_capturer_wayland_build.h" #include "wayland_portal_shared.h" #if CROSSDESK_WAYLAND_BUILD_ENABLED +#include + #include #include #include #include -#include #include "rd_log.h" @@ -149,8 +149,8 @@ std::string BuildSessionHandleFromRequestPath( return ""; } - const std::string sender = request_path.substr(sender_start, - token_sep - sender_start); + const std::string sender = + request_path.substr(sender_start, token_sep - sender_start); if (sender.empty()) { return ""; } @@ -284,8 +284,7 @@ bool ExtractPortalResponse(DBusMessage* message, uint32_t* response_code, bool SendPortalRequestAndHandleResponse( DBusConnection* connection, const char* interface_name, - const char* method_name, - const char* action_name, + const char* method_name, const char* action_name, const std::function& append_message_args, const std::atomic& running, const std::function& handle_results, @@ -295,9 +294,8 @@ bool SendPortalRequestAndHandleResponse( return false; } - DBusMessage* message = - dbus_message_new_method_call(kPortalBusName, kPortalObjectPath, - interface_name, method_name); + DBusMessage* message = dbus_message_new_method_call( + kPortalBusName, kPortalObjectPath, interface_name, method_name); if (!message) { LOG_ERROR("Failed to allocate {} message", method_name); return false; @@ -311,8 +309,8 @@ bool SendPortalRequestAndHandleResponse( DBusError error; dbus_error_init(&error); - DBusMessage* reply = - dbus_connection_send_with_reply_and_block(connection, message, -1, &error); + DBusMessage* reply = dbus_connection_send_with_reply_and_block( + connection, message, -1, &error); dbus_message_unref(message); if (!reply) { LogDbusError(action_name ? action_name : method_name, &error); @@ -365,8 +363,8 @@ bool ScreenCapturerWayland::CheckPortalAvailability() const { return false; } - const dbus_bool_t has_owner = dbus_bus_name_has_owner( - connection, kPortalBusName, &error); + const dbus_bool_t has_owner = + dbus_bus_name_has_owner(connection, kPortalBusName, &error); if (dbus_error_is_set(&error)) { LogDbusError("dbus_bus_name_has_owner", &error); dbus_error_free(&error); @@ -415,7 +413,8 @@ bool ScreenCapturerWayland::CreatePortalSession() { &options); AppendDictEntryString(&options, "session_handle_token", session_handle_token); - AppendDictEntryString(&options, "handle_token", MakeToken("crossdesk_req")); + AppendDictEntryString(&options, "handle_token", + MakeToken("crossdesk_req")); dbus_message_iter_close_container(&iter, &options); return true; }, @@ -459,8 +458,8 @@ bool ScreenCapturerWayland::CreatePortalSession() { } if (session_handle_.empty()) { - const std::string fallback_handle = BuildSessionHandleFromRequestPath( - request_path, session_handle_token); + const std::string fallback_handle = + BuildSessionHandleFromRequestPath(request_path, session_handle_token); if (!fallback_handle.empty()) { LOG_WARN( "CreateSession response missing session_handle, using derived handle " @@ -505,7 +504,8 @@ bool ScreenCapturerWayland::SelectPortalSource() { dbus_message_iter_close_container(&iter, &options); return true; }, - running_, [](uint32_t response_code, DBusMessageIter*) { + running_, + [](uint32_t response_code, DBusMessageIter*) { if (response_code != 0) { LOG_ERROR("SelectSources was denied or malformed, response={}", response_code); @@ -538,7 +538,8 @@ bool ScreenCapturerWayland::SelectPortalDevices() { dbus_message_iter_close_container(&iter, &options); return true; }, - running_, [](uint32_t response_code, DBusMessageIter*) { + running_, + [](uint32_t response_code, DBusMessageIter*) { if (response_code != 0) { LOG_ERROR("SelectDevices was denied or malformed, response={}", response_code); @@ -567,14 +568,16 @@ bool ScreenCapturerWayland::StartPortalSession() { dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &parent_window); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &options); - AppendDictEntryString(&options, "handle_token", MakeToken("crossdesk_req")); + AppendDictEntryString(&options, "handle_token", + MakeToken("crossdesk_req")); dbus_message_iter_close_container(&iter, &options); return true; }, running_, [&](uint32_t response_code, DBusMessageIter* results) { if (response_code != 0) { - LOG_ERROR("Start was denied or malformed, response={}", response_code); + LOG_ERROR("Start was denied or malformed, response={}", + response_code); return false; } @@ -602,16 +605,19 @@ bool ScreenCapturerWayland::StartPortalSession() { DBusMessageIter streams; dbus_message_iter_recurse(&variant, &streams); - if (dbus_message_iter_get_arg_type(&streams) == DBUS_TYPE_STRUCT) { + if (dbus_message_iter_get_arg_type(&streams) == + DBUS_TYPE_STRUCT) { DBusMessageIter stream; dbus_message_iter_recurse(&streams, &stream); - if (dbus_message_iter_get_arg_type(&stream) == DBUS_TYPE_UINT32) { + if (dbus_message_iter_get_arg_type(&stream) == + DBUS_TYPE_UINT32) { dbus_message_iter_get_basic(&stream, &pipewire_node_id_); } if (dbus_message_iter_next(&stream) && - dbus_message_iter_get_arg_type(&stream) == DBUS_TYPE_ARRAY) { + dbus_message_iter_get_arg_type(&stream) == + DBUS_TYPE_ARRAY) { DBusMessageIter props; int stream_width = 0; int stream_height = 0; @@ -637,7 +643,8 @@ bool ScreenCapturerWayland::StartPortalSession() { DBusMessageIter size_iter; int width = 0; int height = 0; - dbus_message_iter_recurse(&prop_variant, &size_iter); + dbus_message_iter_recurse(&prop_variant, + &size_iter); if (ReadIntLike(&size_iter, &width) && dbus_message_iter_next(&size_iter) && ReadIntLike(&size_iter, &height)) { @@ -665,6 +672,11 @@ bool ScreenCapturerWayland::StartPortalSession() { stream_width, stream_height, logical_width, logical_height, picked_width, picked_height); + portal_stream_width_ = stream_width; + portal_stream_height_ = stream_height; + portal_has_logical_size_ = + logical_width > 0 && logical_height > 0; + if (logical_width > 0 && logical_height > 0) { logical_width_ = logical_width; logical_height_ = logical_height; @@ -682,8 +694,7 @@ bool ScreenCapturerWayland::StartPortalSession() { dbus_message_iter_next(&dict); } - pointer_granted_ = - (granted_devices & kRemoteDesktopDevicePointer) != 0; + pointer_granted_ = (granted_devices & kRemoteDesktopDevicePointer) != 0; return true; }); if (!ok) { @@ -699,8 +710,8 @@ bool ScreenCapturerWayland::StartPortalSession() { return false; } - shared_session_registered_ = PublishSharedWaylandPortalSession( - SharedWaylandPortalSessionInfo{ + shared_session_registered_ = + PublishSharedWaylandPortalSession(SharedWaylandPortalSessionInfo{ dbus_connection_, session_handle_, pipewire_node_id_, logical_width_, logical_height_, pointer_granted_}); if (!shared_session_registered_) { @@ -728,16 +739,14 @@ bool ScreenCapturerWayland::OpenPipeWireRemote() { DBusMessageIter options; const char* session_handle = session_handle_.c_str(); dbus_message_iter_init_append(message, &iter); - dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, - &session_handle); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &session_handle); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &options); dbus_message_iter_close_container(&iter, &options); DBusError error; dbus_error_init(&error); - DBusMessage* reply = - dbus_connection_send_with_reply_and_block(dbus_connection_, message, -1, - &error); + DBusMessage* reply = dbus_connection_send_with_reply_and_block( + dbus_connection_, message, -1, &error); dbus_message_unref(message); if (!reply) { LogDbusError("OpenPipeWireRemote", &error); @@ -792,9 +801,8 @@ void ScreenCapturerWayland::ClosePortalSession() { ReleaseSharedWaylandPortalSession(&close_connection, &close_session_handle); shared_session_registered_ = false; if (close_connection) { - CloseWaylandPortalSessionAndConnection(close_connection, - close_session_handle, - "Session.Close"); + CloseWaylandPortalSessionAndConnection( + close_connection, close_session_handle, "Session.Close"); } dbus_connection_ = nullptr; } else if (dbus_connection_ && !session_handle_.empty()) { @@ -805,9 +813,9 @@ void ScreenCapturerWayland::ClosePortalSession() { session_handle_.clear(); pipewire_node_id_ = 0; - UpdateDisplayGeometry(logical_width_ > 0 ? logical_width_ : kFallbackWidth, - logical_height_ > 0 ? logical_height_ - : kFallbackHeight); + UpdateDisplayGeometry( + logical_width_ > 0 ? logical_width_ : kFallbackWidth, + logical_height_ > 0 ? logical_height_ : kFallbackHeight); pointer_granted_ = false; }