diff --git a/apps/desktop/src/gui/application/gui_application.cpp b/apps/desktop/src/gui/application/gui_application.cpp index 5cc200e8..322d2e45 100644 --- a/apps/desktop/src/gui/application/gui_application.cpp +++ b/apps/desktop/src/gui/application/gui_application.cpp @@ -3112,8 +3112,10 @@ void GuiApplication::SyncStreamWindow() { } const ConnectionStatus status = props->connection_status_.load(); bool remote_update_available = false; + HostPlatform remote_platform = HostPlatform::Unknown; if (status == ConnectionStatus::Connected) { std::lock_guard lock(props->remote_version_mutex_); + remote_platform = props->remote_platform_; // Wait for host_info: a version still in transit is not a legacy peer. if (props->remote_host_info_received_ && !props->remote_update_dismissed_) { remote_update_available = props->remote_app_version_.empty() || @@ -3176,7 +3178,7 @@ void GuiApplication::SyncStreamWindow() { props->remote_cursor_state_received_; if (remote_cursor_active) { remote_cursor_shape = static_cast( - props->cursor_presentation_.Resolve(SDL_GetTicks())); + props->cursor_presentation_.Resolve(SDL_GetTicks(), remote_platform)); } } (*ui_->stream)->set_remote_cursor_active(remote_cursor_active); diff --git a/apps/desktop/src/gui/runtime/cursor_presentation.h b/apps/desktop/src/gui/runtime/cursor_presentation.h index 0f37df2b..48333b30 100644 --- a/apps/desktop/src/gui/runtime/cursor_presentation.h +++ b/apps/desktop/src/gui/runtime/cursor_presentation.h @@ -33,6 +33,15 @@ class CursorPresentation { const bool separate = state.render_mode == CursorRenderMode::separate || (state.render_mode == CursorRenderMode::legacy && state.visible); + system_hidden_ = state.render_mode == CursorRenderMode::hidden && + state.hidden_reason == CursorHiddenReason::system_hidden; + if (separate) { + mac_locator_allowed_ = state.shape != RemoteCursorShape::none; + } else if (!system_hidden_) { + // Embedded cursors and other hiding policies end the separate-cursor + // history. A later system_hidden sample must not revive that overlay. + mac_locator_allowed_ = false; + } // Old senders cannot distinguish embedded from hidden. Only explicit // metadata from a new sender can establish the no-device compatibility // case. @@ -47,9 +56,21 @@ class CursorPresentation { void NoteInput(uint64_t now) { last_input_ = now; } - RemoteCursorShape Resolve(uint64_t now) { + RemoteCursorShape Resolve(uint64_t now, HostPlatform remote_platform) { if (!stale_at_) return RemoteCursorShape::default_cursor; + // macOS can keep its system cursor hidden after typing even while remote + // mouse events move it. Keep a previously separate desktop locator across + // those heartbeats; the video has no pointer to fall back to. On a new + // connection, use an arrow until the first visible shape arrives. The + // protocol cannot distinguish typing from other system_hidden cases; + // restrict this policy to identified Mac hosts and stop on any explicit + // embedded cursor or other hiding policy. + if (remote_platform == HostPlatform::MacOS && system_hidden_ && + mac_locator_allowed_) { + return last_visible_shape_; + } + const bool recent_input = last_input_ && now - *last_input_ < kInputActivityMs; const bool can_recover = @@ -64,6 +85,8 @@ class CursorPresentation { private: bool sample_unavailable_ = false; + bool system_hidden_ = false; + bool mac_locator_allowed_ = true; bool no_device_ = false; bool fallback_engaged_ = false; std::optional stale_at_; @@ -74,4 +97,4 @@ class CursorPresentation { } // namespace crossdesk -#endif \ No newline at end of file +#endif