diff --git a/src/gui/application/application_state.h b/src/gui/application/application_state.h index 5369bf9..ab5bbf1 100644 --- a/src/gui/application/application_state.h +++ b/src/gui/application/application_state.h @@ -1,6 +1,7 @@ #ifndef CROSSDESK_GUI_APPLICATION_STATE_H_ #define CROSSDESK_GUI_APPLICATION_STATE_H_ +#include #include #include #include @@ -119,8 +120,11 @@ struct StreamWindowState { }; struct ServerWindowState { - bool need_to_create_server_window_ = false; - bool need_to_destroy_server_window_ = false; + // Transport callbacks run outside the UI thread. Keep the two lifecycle + // requests atomic so a disconnect cannot be lost while the Slint timer is + // deciding whether to create or destroy the controlled-side window. + std::atomic need_to_create_server_window_{false}; + std::atomic need_to_destroy_server_window_{false}; bool server_window_created_ = false; bool server_window_inited_ = false; int server_window_width_default_ = 250; diff --git a/src/gui/application/gui_application.cpp b/src/gui/application/gui_application.cpp index 3dccecb..4fbd62c 100644 --- a/src/gui/application/gui_application.cpp +++ b/src/gui/application/gui_application.cpp @@ -56,7 +56,7 @@ bool HasNonEmptyEnvironmentVariable(const char* name) { } class ScopedUnsetEnvironmentVariable { -public: + public: explicit ScopedUnsetEnvironmentVariable(const char* name) : name_(name) { if (const char* value = std::getenv(name_)) { original_value_ = value; @@ -77,7 +77,7 @@ public: ScopedUnsetEnvironmentVariable& operator=( const ScopedUnsetEnvironmentVariable&) = delete; -private: + private: const char* name_; std::optional original_value_; }; @@ -101,8 +101,7 @@ private: &bytes_after, &data) == Success && actual_type == XA_WINDOW && actual_format == 32 && item_count == 1 && data) { - const ::Window active_window = - *reinterpret_cast(data); + const ::Window active_window = *reinterpret_cast(data); XFree(data); return active_window; } @@ -165,9 +164,8 @@ bool X11WindowMatches(Display* display, ::Window x11_window, int root_x = 0; int root_y = 0; ::Window child = 0; - if (!XTranslateCoordinates(display, x11_window, - DefaultRootWindow(display), 0, 0, &root_x, - &root_y, &child)) { + if (!XTranslateCoordinates(display, x11_window, DefaultRootWindow(display), 0, + 0, &root_x, &root_y, &child)) { return false; } @@ -269,17 +267,15 @@ std::vector ParseReleaseNotesMarkdownForSlint( size_t line_start = 0; while (line_start < markdown.size()) { const size_t newline = markdown.find('\n', line_start); - const size_t line_end = newline == std::string_view::npos - ? markdown.size() - : newline; + const size_t line_end = + newline == std::string_view::npos ? markdown.size() : newline; std::string_view line = markdown.substr(line_start, line_end - line_start); if (!line.empty() && line.back() == '\r') { line.remove_suffix(1); } - if (std::all_of(line.begin(), line.end(), [](unsigned char ch) { - return std::isspace(ch) != 0; - })) { + if (std::all_of(line.begin(), line.end(), + [](unsigned char ch) { return std::isspace(ch) != 0; })) { section_gap = !blocks.empty(); if (newline == std::string_view::npos) { break; @@ -306,11 +302,11 @@ std::vector ParseReleaseNotesMarkdownForSlint( const std::string block_markdown(line); ParsedReleaseNoteBlock block; - if (const auto parsed = - slint::StyledText::from_markdown(block_markdown)) { + if (const auto parsed = slint::StyledText::from_markdown(block_markdown)) { block.content = *parsed; } else { - LOG_WARN("Failed to parse a release-note Markdown block; using plain text"); + LOG_WARN( + "Failed to parse a release-note Markdown block; using plain text"); block.content = slint::StyledText::from_plain_text(block_markdown); } block.section_gap = section_gap; @@ -958,13 +954,14 @@ bool GuiApplication::InitializeSDL() { const bool video_driver_allows_x11 = requested_video_driver == nullptr || requested_video_driver[0] == '\0' || std::strcmp(requested_video_driver, "x11") == 0; - use_xwayland_gui_ = - IsWaylandSession() && HasNonEmptyEnvironmentVariable("DISPLAY") && - video_driver_allows_x11; + use_xwayland_gui_ = IsWaylandSession() && + HasNonEmptyEnvironmentVariable("DISPLAY") && + video_driver_allows_x11; if (use_xwayland_gui_) { setenv("SDL_VIDEODRIVER", "x11", 1); - LOG_INFO("Wayland session detected; using XWayland for positioned GUI " - "windows while retaining Wayland portal capture and input"); + LOG_INFO( + "Wayland session detected; using XWayland for positioned GUI " + "windows while retaining Wayland portal capture and input"); } #endif if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) { @@ -1011,8 +1008,7 @@ void GuiApplication::InitializeUi() { } #endif std::vector release_note_blocks; - for (const auto& parsed : - ParseReleaseNotesMarkdownForSlint(release_notes_)) { + for (const auto& parsed : ParseReleaseNotesMarkdownForSlint(release_notes_)) { ui::ReleaseNoteBlock block; block.content = parsed.content; block.section_gap = parsed.section_gap; @@ -1159,10 +1155,10 @@ void GuiApplication::InitializeUi() { #endif BindStreamCallbacks(); (*ui_->stream)->show(); - PositionWindowAtCenter((*ui_->stream)->window(), ui_->main->window(), - kStreamWindowLogicalWidth, - StreamWindowLogicalHeight( - (*ui_->stream)->get_custom_titlebar())); + PositionWindowAtCenter( + (*ui_->stream)->window(), ui_->main->window(), + kStreamWindowLogicalWidth, + StreamWindowLogicalHeight((*ui_->stream)->get_custom_titlebar())); ui_->main->hide(); } else if (ui_->capture_mode && ui_->capture_page == "server") { ui_->server.emplace(ui::ServerWindow::create()); @@ -2235,8 +2231,7 @@ void GuiApplication::SyncXWaylandWindowActivation() { return; } - if (const auto process_id = - X11GetWindowProcessId(display, active_window); + if (const auto process_id = X11GetWindowProcessId(display, active_window); process_id && *process_id != static_cast(getpid())) { ui_->main->set_window_active(false); if (ui_->stream) { @@ -2249,8 +2244,8 @@ void GuiApplication::SyncXWaylandWindowActivation() { X11WindowMatches(display, active_window, ui_->main->window())); if (ui_->stream) { (*ui_->stream) - ->set_window_active(X11WindowMatches( - display, active_window, (*ui_->stream)->window())); + ->set_window_active( + X11WindowMatches(display, active_window, (*ui_->stream)->window())); } } #endif @@ -2289,8 +2284,7 @@ void GuiApplication::SyncStreamWindow() { if (!ui_->stream) { return; } - (*ui_->stream) - ->set_window_maximized((*ui_->stream)->window().is_maximized()); + (*ui_->stream)->set_window_maximized((*ui_->stream)->window().is_maximized()); (*ui_->stream)->set_srtp_enabled(enable_srtp_); #if defined(__APPLE__) if (ui_->stream_live_resize_configuration_attempts > 0) { @@ -2302,10 +2296,10 @@ void GuiApplication::SyncStreamWindow() { } #endif if (ui_->stream_initial_position_attempts > 0) { - if (PositionWindowAtCenter((*ui_->stream)->window(), ui_->main->window(), - kStreamWindowLogicalWidth, - StreamWindowLogicalHeight( - (*ui_->stream)->get_custom_titlebar()))) { + if (PositionWindowAtCenter( + (*ui_->stream)->window(), ui_->main->window(), + kStreamWindowLogicalWidth, + StreamWindowLogicalHeight((*ui_->stream)->get_custom_titlebar()))) { --ui_->stream_initial_position_attempts; } else { ui_->stream_initial_position_attempts = 0; @@ -2539,7 +2533,22 @@ void GuiApplication::SyncStreamWindow() { } void GuiApplication::SyncServerWindow() { - if (need_to_create_server_window_ && !ui_->server) { + // The connection map is authoritative. Lifecycle flags record callback + // intent, but callbacks for different controllers can cross each other, so + // the final decision must reflect the current connected-controller set. + need_to_create_server_window_.exchange(false, std::memory_order_acq_rel); + need_to_destroy_server_window_.exchange(false, std::memory_order_acq_rel); + bool has_connected_controller = false; + { + std::shared_lock lock(connection_status_mutex_); + has_connected_controller = + std::any_of(connection_status_.begin(), connection_status_.end(), + [](const auto& entry) { + return entry.second == ConnectionStatus::Connected; + }); + } + + if (has_connected_controller && !ui_->server) { ui_->server.emplace(ui::ServerWindow::create()); RegisterFontAwesome((*ui_->server)->window()); (*ui_->server)->set_controllers(ui_->controller_model); @@ -2559,15 +2568,13 @@ void GuiApplication::SyncServerWindow() { ui_->server_initial_position_attempts = 2; server_window_created_ = true; server_window_inited_ = true; - need_to_create_server_window_ = false; } - if (need_to_destroy_server_window_ && ui_->server) { + if (!has_connected_controller && ui_->server) { (*ui_->server)->hide(); ui_->server.reset(); server_window_created_ = false; server_window_inited_ = false; ui_->server_initial_position_attempts = 0; - need_to_destroy_server_window_ = false; } if (!ui_->server) { return; @@ -2868,9 +2875,8 @@ void GuiApplication::SendPointerInput(int button, int kind, float x, float y) { : 0.0f; const float available_height = size.height / scale - titlebar_height - - (fullscreen_button_pressed_ - ? 0.0f - : (ui_->tab_ids.size() > 1 ? 30.0f : 0.0f)); + (fullscreen_button_pressed_ ? 0.0f + : (ui_->tab_ids.size() > 1 ? 30.0f : 0.0f)); float render_width = available_width; float render_height = available_height; float offset_x = 0; @@ -2944,9 +2950,8 @@ void GuiApplication::SendScrollInput(float delta_x, float delta_y, float x, : 0.0f; const float height = size.height / scale - titlebar_height - - (fullscreen_button_pressed_ - ? 0.0f - : (ui_->tab_ids.size() > 1 ? 30.0f : 0.0f)); + (fullscreen_button_pressed_ ? 0.0f + : (ui_->tab_ids.size() > 1 ? 30.0f : 0.0f)); if (width <= 0 || height <= 0) { return; } diff --git a/src/gui/runtime/connection_runtime.cpp b/src/gui/runtime/connection_runtime.cpp index 02e82a1..47e0f80 100644 --- a/src/gui/runtime/connection_runtime.cpp +++ b/src/gui/runtime/connection_runtime.cpp @@ -1,5 +1,3 @@ -#include "runtime/gui_runtime.h" - #include #include #include @@ -12,6 +10,7 @@ #include "localization.h" #include "rd_log.h" +#include "runtime/gui_runtime.h" namespace crossdesk { @@ -22,7 +21,7 @@ bool IsConnectionAttemptPending(ConnectionStatus status) { return status == ConnectionStatus::Connecting || status == ConnectionStatus::Gathering; } -} // namespace +} // namespace void GuiRuntime::HandleConnectionStatusChange() { if (signal_connected_ && peer_ && need_to_send_recent_connections_) { @@ -31,7 +30,7 @@ void GuiRuntime::HandleConnectionStatusChange() { j["type"] = "recent_connections_presence"; j["user_id"] = client_id_; j["devices"] = nlohmann::json::array(); - for (const auto &id : recent_connection_ids_) { + for (const auto& id : recent_connection_ids_) { std::string pure_id = id; size_t pos_y = pure_id.find('Y'); size_t pos_n = pure_id.find('N'); @@ -123,7 +122,7 @@ void GuiRuntime::HandleConnectionTimeouts() { } bool rejoin_state_changed = false; - for (auto &[_, props] : remote_sessions_) { + for (auto& [_, props] : remote_sessions_) { if (!props || !props->connection_attempt_active_.load()) { continue; } @@ -152,7 +151,7 @@ void GuiRuntime::HandleConnectionTimeouts() { if (rejoin_state_changed) { need_to_rejoin_ = false; - for (const auto &[_, props] : remote_sessions_) { + for (const auto& [_, props] : remote_sessions_) { if (props && props->rejoin_) { need_to_rejoin_ = true; break; @@ -161,8 +160,65 @@ void GuiRuntime::HandleConnectionTimeouts() { } } -int GuiRuntime::RequestSingleDevicePresence(const std::string &remote_id, - const char *password, +void GuiRuntime::HandleServerControllerDisconnected( + const std::string& remote_id, const char* reason) { + keyboard_.ReleaseRemotePressedKeys(remote_id, reason); + + bool has_connected_controller = false; + bool has_web_controller = false; + std::string remaining_controller_id; + { + std::unique_lock lock(connection_status_mutex_); + connection_status_.erase(remote_id); + connection_host_names_.erase(remote_id); + for (const auto& [id, status] : connection_status_) { + if (status != ConnectionStatus::Connected) { + continue; + } + has_connected_controller = true; + has_web_controller = + has_web_controller || id.find("web") != std::string::npos; + if (remaining_controller_id.empty()) { + remaining_controller_id = id; + } + } + } + show_cursor_ = has_web_controller; + if (has_connected_controller) { + remote_client_id_ = remaining_controller_id; + return; + } + + need_to_create_server_window_.store(false, std::memory_order_release); + need_to_destroy_server_window_.store(true, std::memory_order_release); + is_server_mode_ = false; +#if defined(__linux__) && !defined(__APPLE__) + if (IsWaylandSession()) { + // Keep Wayland capture session warm to avoid black screen on subsequent + // reconnects. + start_screen_capturer_ = true; + LOG_INFO( + "Keeping Wayland screen capturer running after disconnect to " + "preserve reconnect stability"); + } else { + start_screen_capturer_ = false; + } +#else + start_screen_capturer_ = false; +#endif + start_speaker_capturer_ = false; + start_mouse_controller_ = false; + start_keyboard_capturer_ = false; + remote_client_id_.clear(); + if (audio_capture_) { + devices_.StopSpeakerCapturer(); + audio_capture_ = false; + } + devices_.ResetToInitialDisplay(); +} + +int GuiRuntime::RequestSingleDevicePresence(const std::string& remote_id, + const char* password, bool remember_password) { if (!signal_connected_ || !peer_) { return -1; @@ -221,7 +277,7 @@ void GuiRuntime::CloseRemoteSession(std::shared_ptr props) { std::thread save_thread([buffer_copy, video_width, video_height, remote_id, remote_host_name, password, thumbnail = thumbnail_]() { - thumbnail->SaveToThumbnail((char *)buffer_copy.data(), video_width, + thumbnail->SaveToThumbnail((char*)buffer_copy.data(), video_width, video_height, remote_id, remote_host_name, password); }); @@ -255,7 +311,7 @@ void GuiRuntime::CloseAllRemoteSessions() { { // std::shared_lock lock(remote_sessions_mutex_); - for (auto &it : remote_sessions_) { + for (auto& it : remote_sessions_) { auto props = it.second; CloseRemoteSession(props); } @@ -279,7 +335,7 @@ void GuiRuntime::WaitForThumbnailSaveTasks() { return; } - for (auto &thread : threads_to_join) { + for (auto& thread : threads_to_join) { if (thread.joinable()) { thread.join(); } @@ -300,9 +356,8 @@ void GuiRuntime::ResetRemoteSessionResources( } } -std::shared_ptr -GuiRuntime::FindRemoteSession( - const std::string &remote_id) { +std::shared_ptr GuiRuntime::FindRemoteSession( + const std::string& remote_id) { if (remote_id.empty()) { return nullptr; } @@ -315,8 +370,7 @@ GuiRuntime::FindRemoteSession( return it->second; } - -int GuiRuntime::ConnectTo(const std::string &remote_id, const char *password, +int GuiRuntime::ConnectTo(const std::string& remote_id, const char* password, bool remember_password, bool bypass_presence_check) { if (!bypass_presence_check && !device_presence_cache_.IsOnline(remote_id)) { int ret = @@ -336,19 +390,17 @@ int GuiRuntime::ConnectTo(const std::string &remote_id, const char *password, focused_remote_id_ = remote_id; // std::shared_lock shared_lock(remote_sessions_mutex_); - bool exists = - (remote_sessions_.find(remote_id) != remote_sessions_.end()); + bool exists = (remote_sessions_.find(remote_id) != remote_sessions_.end()); // shared_lock.unlock(); if (!exists) { - PeerPtr *peer_to_init = nullptr; + PeerPtr* peer_to_init = nullptr; std::string local_id; { // std::unique_lock unique_lock(remote_sessions_mutex_); if (remote_sessions_.find(remote_id) == remote_sessions_.end()) { - remote_sessions_[remote_id] = - std::make_shared(); + remote_sessions_[remote_id] = std::make_shared(); auto props = remote_sessions_[remote_id]; props->local_id_ = "C-" + std::string(client_id_); props->remote_id_ = remote_id; @@ -371,7 +423,7 @@ int GuiRuntime::ConnectTo(const std::string &remote_id, const char *password, return -1; } - for (const auto &display_info : devices_.display_info_list()) { + for (const auto& display_info : devices_.display_info_list()) { AddVideoStream(props->peer_, display_info.name.c_str()); } AddAudioStream(props->peer_, props->audio_label_.c_str()); @@ -429,4 +481,4 @@ int GuiRuntime::ConnectTo(const std::string &remote_id, const char *password, return 0; } -} // namespace crossdesk +} // namespace crossdesk diff --git a/src/gui/runtime/gui_runtime.h b/src/gui/runtime/gui_runtime.h index 781d803..442b4b4 100644 --- a/src/gui/runtime/gui_runtime.h +++ b/src/gui/runtime/gui_runtime.h @@ -8,16 +8,16 @@ #include "features/devices/session_device_manager.h" #include "features/file_transfer/file_transfer_manager.h" #include "features/input/keyboard_controller.h" +#include "features/settings/settings_manager.h" #include "runtime/gui_state.h" #include "runtime/peer_event_handler.h" -#include "features/settings/settings_manager.h" namespace crossdesk { // Shared GUI runtime. It owns subsystem controllers and cross-cutting session // state, but no window lifecycle, ImGui view, or transport callback methods. class GuiRuntime : protected gui_detail::GuiState { -protected: + protected: using FileTransferState = gui_detail::FileTransferState; using RemoteSession = gui_detail::RemoteSession; @@ -32,35 +32,35 @@ protected: GuiRuntime(); ~GuiRuntime(); - static void SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len); - static void SdlCaptureAudioOut(void *userdata, Uint8 *stream, int len); + static void SdlCaptureAudioIn(void* userdata, Uint8* stream, int len); + static void SdlCaptureAudioOut(void* userdata, Uint8* stream, int len); int CreateConnectionPeer(); - int ConnectTo(const std::string &remote_id, const char *password, + int ConnectTo(const std::string& remote_id, const char* password, bool remember_password, bool bypass_presence_check = false); - int RequestSingleDevicePresence(const std::string &remote_id, - const char *password, bool remember_password); + int RequestSingleDevicePresence(const std::string& remote_id, + const char* password, bool remember_password); void UpdateLabels(); void HandleRecentConnections(); void HandleConnectionStatusChange(); void HandlePendingPresenceProbe(); void HandleConnectionTimeouts(); + void HandleServerControllerDisconnected(const std::string& remote_id, + const char* reason); void HandleWindowsServiceIntegration(); void CloseRemoteSession(std::shared_ptr props); void CloseAllRemoteSessions(); - void ResetRemoteSessionResources( - std::shared_ptr props); + void ResetRemoteSessionResources(std::shared_ptr props); void WaitForThumbnailSaveTasks(); - std::shared_ptr - FindRemoteSession(const std::string &remote_id); + std::shared_ptr FindRemoteSession( + const std::string& remote_id); - void ResetRemoteServiceStatus(RemoteSession &props); - void ApplyRemoteServiceStatus(RemoteSession &props, - const ServiceStatus &status); - RemoteUnlockState - GetRemoteUnlockState(const RemoteSession &props) const; + void ResetRemoteServiceStatus(RemoteSession& props); + void ApplyRemoteServiceStatus(RemoteSession& props, + const ServiceStatus& status); + RemoteUnlockState GetRemoteUnlockState(const RemoteSession& props) const; #if _WIN32 void ResetLocalWindowsServiceState(bool clear_pending_sas); #endif @@ -82,7 +82,7 @@ protected: KeyboardController keyboard_; PeerEventHandler peer_events_; -private: + private: friend class ClipboardController; friend class SessionDeviceManager; friend class FileTransferManager; @@ -91,6 +91,6 @@ private: friend class PeerEventHandler; }; -} // namespace crossdesk +} // namespace crossdesk -#endif // CROSSDESK_GUI_RUNTIME_H_ +#endif // CROSSDESK_GUI_RUNTIME_H_ diff --git a/src/gui/runtime/peer_event_handler.cpp b/src/gui/runtime/peer_event_handler.cpp index 98a3d1f..b87fa92 100644 --- a/src/gui/runtime/peer_event_handler.cpp +++ b/src/gui/runtime/peer_event_handler.cpp @@ -26,12 +26,12 @@ namespace crossdesk { -PeerEventHandler::PeerEventHandler(GuiRuntime &owner) : owner_(owner) {} +PeerEventHandler::PeerEventHandler(GuiRuntime& owner) : owner_(owner) {} -void PeerEventHandler::OnSignalMessage(const char *message, size_t size, - void *user_data) { - auto *handler = static_cast(user_data); - GuiRuntime *runtime = handler ? &handler->owner_ : nullptr; +void PeerEventHandler::OnSignalMessage(const char* message, size_t size, + void* user_data) { + auto* handler = static_cast(user_data); + GuiRuntime* runtime = handler ? &handler->owner_ : nullptr; if (!runtime || !message || size == 0) { return; } @@ -43,7 +43,7 @@ void PeerEventHandler::OnSignalMessage(const char *message, size_t size, std::string type = j["type"].get(); if (type == "presence") { if (j.contains("devices") && j["devices"].is_array()) { - for (auto &dev : j["devices"]) { + for (auto& dev : j["devices"]) { if (!dev.is_object()) { continue; } @@ -88,11 +88,10 @@ void PeerEventHandler::OnSignalMessage(const char *message, size_t size, } } - -void PeerEventHandler::OnSignalStatus(SignalStatus status, const char *user_id, - size_t user_id_size, void *user_data) { - auto *handler = static_cast(user_data); - GuiRuntime *runtime = handler ? &handler->owner_ : nullptr; +void PeerEventHandler::OnSignalStatus(SignalStatus status, const char* user_id, + size_t user_id_size, void* user_data) { + auto* handler = static_cast(user_data); + GuiRuntime* runtime = handler ? &handler->owner_ : nullptr; if (!runtime) { return; } @@ -150,13 +149,12 @@ void PeerEventHandler::OnSignalStatus(SignalStatus status, const char *user_id, } void PeerEventHandler::OnConnectionStatus(ConnectionStatus status, - const char *user_id, + const char* user_id, const size_t user_id_size, - void *user_data) { - auto *handler = static_cast(user_data); - GuiRuntime *runtime = handler ? &handler->owner_ : nullptr; - if (!runtime) - return; + void* user_data) { + auto* handler = static_cast(user_data); + GuiRuntime* runtime = handler ? &handler->owner_ : nullptr; + if (!runtime) return; std::string remote_id(user_id, user_id_size); std::shared_ptr props; @@ -178,107 +176,110 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status, } switch (status) { - case ConnectionStatus::Connected: { - runtime->ResetRemoteServiceStatus(*props); - { - RemoteAction remote_action; - remote_action.i.display_num = - runtime->devices_.display_info_list().size(); - remote_action.i.display_list = - (char **)malloc(remote_action.i.display_num * sizeof(char *)); - remote_action.i.left = - (int *)malloc(remote_action.i.display_num * sizeof(int)); - remote_action.i.top = - (int *)malloc(remote_action.i.display_num * sizeof(int)); - remote_action.i.right = - (int *)malloc(remote_action.i.display_num * sizeof(int)); - remote_action.i.bottom = - (int *)malloc(remote_action.i.display_num * sizeof(int)); - for (int i = 0; i < remote_action.i.display_num; i++) { - LOG_INFO("Local display [{}:{}]", i + 1, - runtime->devices_.display_info_list()[i].name); - remote_action.i.display_list[i] = (char *)malloc( - runtime->devices_.display_info_list()[i].name.length() + 1); - strncpy(remote_action.i.display_list[i], - runtime->devices_.display_info_list()[i].name.c_str(), - runtime->devices_.display_info_list()[i].name.length()); - remote_action.i.display_list - [i][runtime->devices_.display_info_list()[i].name.length()] = '\0'; - remote_action.i.left[i] = - runtime->devices_.display_info_list()[i].left; - remote_action.i.top[i] = runtime->devices_.display_info_list()[i].top; - remote_action.i.right[i] = - runtime->devices_.display_info_list()[i].right; - remote_action.i.bottom[i] = - runtime->devices_.display_info_list()[i].bottom; + case ConnectionStatus::Connected: { + runtime->ResetRemoteServiceStatus(*props); + { + RemoteAction remote_action; + remote_action.i.display_num = + runtime->devices_.display_info_list().size(); + remote_action.i.display_list = + (char**)malloc(remote_action.i.display_num * sizeof(char*)); + remote_action.i.left = + (int*)malloc(remote_action.i.display_num * sizeof(int)); + remote_action.i.top = + (int*)malloc(remote_action.i.display_num * sizeof(int)); + remote_action.i.right = + (int*)malloc(remote_action.i.display_num * sizeof(int)); + remote_action.i.bottom = + (int*)malloc(remote_action.i.display_num * sizeof(int)); + for (int i = 0; i < remote_action.i.display_num; i++) { + LOG_INFO("Local display [{}:{}]", i + 1, + runtime->devices_.display_info_list()[i].name); + remote_action.i.display_list[i] = (char*)malloc( + runtime->devices_.display_info_list()[i].name.length() + 1); + strncpy(remote_action.i.display_list[i], + runtime->devices_.display_info_list()[i].name.c_str(), + runtime->devices_.display_info_list()[i].name.length()); + remote_action.i + .display_list[i][runtime->devices_.display_info_list()[i] + .name.length()] = '\0'; + remote_action.i.left[i] = + runtime->devices_.display_info_list()[i].left; + remote_action.i.top[i] = + runtime->devices_.display_info_list()[i].top; + remote_action.i.right[i] = + runtime->devices_.display_info_list()[i].right; + remote_action.i.bottom[i] = + runtime->devices_.display_info_list()[i].bottom; + } + + std::string host_name = GetHostName(); + remote_action.type = ControlType::host_infomation; + memcpy(&remote_action.i.host_name, host_name.data(), + host_name.size()); + remote_action.i.host_name[host_name.size()] = '\0'; + remote_action.i.host_name_size = host_name.size(); + + std::string msg = remote_action.to_json(); + int ret = SendReliableDataFrame(props->peer_, msg.data(), msg.size(), + runtime->control_data_label_.c_str()); + remote_action_codec::Free(remote_action); } - std::string host_name = GetHostName(); - remote_action.type = ControlType::host_infomation; - memcpy(&remote_action.i.host_name, host_name.data(), host_name.size()); - remote_action.i.host_name[host_name.size()] = '\0'; - remote_action.i.host_name_size = host_name.size(); - - std::string msg = remote_action.to_json(); - int ret = SendReliableDataFrame(props->peer_, msg.data(), msg.size(), - runtime->control_data_label_.c_str()); - remote_action_codec::Free(remote_action); + if (!runtime->need_to_create_stream_window_ && + !runtime->remote_sessions_.empty()) { + runtime->need_to_create_stream_window_ = true; + } + props->connection_established_ = true; + runtime->start_keyboard_capturer_ = true; + break; } - - if (!runtime->need_to_create_stream_window_ && - !runtime->remote_sessions_.empty()) { - runtime->need_to_create_stream_window_ = true; - } - props->connection_established_ = true; - runtime->start_keyboard_capturer_ = true; - break; - } - case ConnectionStatus::Disconnected: - case ConnectionStatus::Failed: - case ConnectionStatus::Closed: { - runtime->keyboard_.ReleaseRemotePressedKeys(remote_id, - "connection_closed"); - props->connection_established_ = false; - props->enable_mouse_control_ = false; - runtime->ResetRemoteServiceStatus(*props); - - { - std::lock_guard lock(props->video_frame_mutex_); - props->front_frame_.reset(); - props->back_frame_.reset(); - props->video_width_ = 0; - props->video_height_ = 0; - props->video_size_ = 0; - props->render_rect_dirty_ = true; - props->stream_cleanup_pending_ = true; - } - - runtime->focus_on_stream_window_ = false; - - break; - } - case ConnectionStatus::IncorrectPassword: { - runtime->password_validating_ = false; - runtime->password_validating_time_++; - if (runtime->connect_button_pressed_) { - runtime->connect_button_pressed_ = false; + case ConnectionStatus::Disconnected: + case ConnectionStatus::Failed: + case ConnectionStatus::Closed: { + runtime->keyboard_.ReleaseRemotePressedKeys(remote_id, + "connection_closed"); props->connection_established_ = false; - runtime->connect_button_label_ = - localization::connect[runtime->localization_language_index_]; + props->enable_mouse_control_ = false; + runtime->ResetRemoteServiceStatus(*props); + + { + std::lock_guard lock(props->video_frame_mutex_); + props->front_frame_.reset(); + props->back_frame_.reset(); + props->video_width_ = 0; + props->video_height_ = 0; + props->video_size_ = 0; + props->render_rect_dirty_ = true; + props->stream_cleanup_pending_ = true; + } + + runtime->focus_on_stream_window_ = false; + + break; } - break; - } - case ConnectionStatus::NoSuchTransmissionId: - case ConnectionStatus::RemoteUnavailable: { - if (runtime->connect_button_pressed_) { - props->connection_established_ = false; - runtime->connect_button_label_ = - localization::connect[runtime->localization_language_index_]; + case ConnectionStatus::IncorrectPassword: { + runtime->password_validating_ = false; + runtime->password_validating_time_++; + if (runtime->connect_button_pressed_) { + runtime->connect_button_pressed_ = false; + props->connection_established_ = false; + runtime->connect_button_label_ = + localization::connect[runtime->localization_language_index_]; + } + break; } - break; - } - default: - break; + case ConnectionStatus::NoSuchTransmissionId: + case ConnectionStatus::RemoteUnavailable: { + if (runtime->connect_button_pressed_) { + props->connection_established_ = false; + runtime->connect_button_label_ = + localization::connect[runtime->localization_language_index_]; + } + break; + } + default: + break; } } else { runtime->is_client_mode_ = false; @@ -289,155 +290,107 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status, } switch (status) { - case ConnectionStatus::Connected: { + case ConnectionStatus::Connected: { #if _WIN32 - runtime->last_windows_service_status_tick_ = 0; + runtime->last_windows_service_status_tick_ = 0; #endif - { - RemoteAction remote_action; - remote_action.i.display_num = - runtime->devices_.display_info_list().size(); - remote_action.i.display_list = - (char **)malloc(remote_action.i.display_num * sizeof(char *)); - remote_action.i.left = - (int *)malloc(remote_action.i.display_num * sizeof(int)); - remote_action.i.top = - (int *)malloc(remote_action.i.display_num * sizeof(int)); - remote_action.i.right = - (int *)malloc(remote_action.i.display_num * sizeof(int)); - remote_action.i.bottom = - (int *)malloc(remote_action.i.display_num * sizeof(int)); - for (int i = 0; i < remote_action.i.display_num; i++) { - LOG_INFO("Local display [{}:{}]", i + 1, - runtime->devices_.display_info_list()[i].name); - remote_action.i.display_list[i] = (char *)malloc( - runtime->devices_.display_info_list()[i].name.length() + 1); - strncpy(remote_action.i.display_list[i], - runtime->devices_.display_info_list()[i].name.c_str(), - runtime->devices_.display_info_list()[i].name.length()); - remote_action.i.display_list - [i][runtime->devices_.display_info_list()[i].name.length()] = '\0'; - remote_action.i.left[i] = - runtime->devices_.display_info_list()[i].left; - remote_action.i.top[i] = runtime->devices_.display_info_list()[i].top; - remote_action.i.right[i] = - runtime->devices_.display_info_list()[i].right; - remote_action.i.bottom[i] = - runtime->devices_.display_info_list()[i].bottom; - } - - std::string host_name = GetHostName(); - remote_action.type = ControlType::host_infomation; - memcpy(&remote_action.i.host_name, host_name.data(), host_name.size()); - remote_action.i.host_name[host_name.size()] = '\0'; - remote_action.i.host_name_size = host_name.size(); - - std::string msg = remote_action.to_json(); - int ret = SendReliableDataFrame(runtime->peer_, msg.data(), msg.size(), - runtime->control_data_label_.c_str()); - remote_action_codec::Free(remote_action); - } - - runtime->need_to_create_server_window_ = true; - runtime->is_server_mode_ = true; - runtime->start_screen_capturer_ = true; - runtime->start_speaker_capturer_ = true; - runtime->remote_client_id_ = remote_id; - runtime->start_mouse_controller_ = true; - { - std::shared_lock lock(runtime->connection_status_mutex_); - if (std::all_of(runtime->connection_status_.begin(), - runtime->connection_status_.end(), [](const auto &kv) { - return kv.first.find("web") != std::string::npos; - })) { - runtime->show_cursor_ = true; - } - } - - break; - } - case ConnectionStatus::Disconnected: - case ConnectionStatus::Failed: - case ConnectionStatus::Closed: { - runtime->keyboard_.ReleaseRemotePressedKeys(remote_id, - "connection_closed"); - bool all_disconnected = false; - { - std::shared_lock lock(runtime->connection_status_mutex_); - all_disconnected = - std::all_of(runtime->connection_status_.begin(), - runtime->connection_status_.end(), [](const auto &kv) { - return kv.second == ConnectionStatus::Closed || - kv.second == ConnectionStatus::Failed || - kv.second == ConnectionStatus::Disconnected; - }); - } - if (all_disconnected) { - runtime->need_to_destroy_server_window_ = true; - runtime->is_server_mode_ = false; -#if defined(__linux__) && !defined(__APPLE__) - if (IsWaylandSession()) { - // Keep Wayland capture session warm to avoid black screen on - // subsequent reconnects. - runtime->start_screen_capturer_ = true; - LOG_INFO("Keeping Wayland screen capturer running after " - "disconnect to preserve reconnect stability"); - } else { - runtime->start_screen_capturer_ = false; - } -#else - runtime->start_screen_capturer_ = false; -#endif - runtime->start_speaker_capturer_ = false; - runtime->start_mouse_controller_ = false; - runtime->start_keyboard_capturer_ = false; - runtime->remote_client_id_ = ""; - if (props) - props->connection_established_ = false; - if (runtime->audio_capture_) { - runtime->devices_.StopSpeakerCapturer(); - runtime->audio_capture_ = false; - } - { - std::unique_lock lock(runtime->connection_status_mutex_); - runtime->connection_status_.erase(remote_id); - runtime->connection_host_names_.erase(remote_id); - } - runtime->devices_.ResetToInitialDisplay(); - } + RemoteAction remote_action; + remote_action.i.display_num = + runtime->devices_.display_info_list().size(); + remote_action.i.display_list = + (char**)malloc(remote_action.i.display_num * sizeof(char*)); + remote_action.i.left = + (int*)malloc(remote_action.i.display_num * sizeof(int)); + remote_action.i.top = + (int*)malloc(remote_action.i.display_num * sizeof(int)); + remote_action.i.right = + (int*)malloc(remote_action.i.display_num * sizeof(int)); + remote_action.i.bottom = + (int*)malloc(remote_action.i.display_num * sizeof(int)); + for (int i = 0; i < remote_action.i.display_num; i++) { + LOG_INFO("Local display [{}:{}]", i + 1, + runtime->devices_.display_info_list()[i].name); + remote_action.i.display_list[i] = (char*)malloc( + runtime->devices_.display_info_list()[i].name.length() + 1); + strncpy(remote_action.i.display_list[i], + runtime->devices_.display_info_list()[i].name.c_str(), + runtime->devices_.display_info_list()[i].name.length()); + remote_action.i + .display_list[i][runtime->devices_.display_info_list()[i] + .name.length()] = '\0'; + remote_action.i.left[i] = + runtime->devices_.display_info_list()[i].left; + remote_action.i.top[i] = + runtime->devices_.display_info_list()[i].top; + remote_action.i.right[i] = + runtime->devices_.display_info_list()[i].right; + remote_action.i.bottom[i] = + runtime->devices_.display_info_list()[i].bottom; + } - { - std::shared_lock lock(runtime->connection_status_mutex_); - if (std::all_of(runtime->connection_status_.begin(), - runtime->connection_status_.end(), [](const auto &kv) { - return kv.first.find("web") == std::string::npos; - })) { - runtime->show_cursor_ = false; - } - } + std::string host_name = GetHostName(); + remote_action.type = ControlType::host_infomation; + memcpy(&remote_action.i.host_name, host_name.data(), + host_name.size()); + remote_action.i.host_name[host_name.size()] = '\0'; + remote_action.i.host_name_size = host_name.size(); - break; - } - default: - break; + std::string msg = remote_action.to_json(); + int ret = + SendReliableDataFrame(runtime->peer_, msg.data(), msg.size(), + runtime->control_data_label_.c_str()); + remote_action_codec::Free(remote_action); + } + + runtime->need_to_destroy_server_window_.store( + false, std::memory_order_release); + runtime->need_to_create_server_window_.store(true, + std::memory_order_release); + runtime->is_server_mode_ = true; + runtime->start_screen_capturer_ = true; + runtime->start_speaker_capturer_ = true; + runtime->remote_client_id_ = remote_id; + runtime->start_mouse_controller_ = true; + { + std::shared_lock lock(runtime->connection_status_mutex_); + if (std::all_of(runtime->connection_status_.begin(), + runtime->connection_status_.end(), + [](const auto& kv) { + return kv.first.find("web") != std::string::npos; + })) { + runtime->show_cursor_ = true; + } + } + + break; + } + case ConnectionStatus::Disconnected: + case ConnectionStatus::Failed: + case ConnectionStatus::Closed: { + runtime->HandleServerControllerDisconnected(remote_id, + "connection_closed"); + break; + } + default: + break; } } } void PeerEventHandler::OnNetStatusReport( - const char *client_id, size_t client_id_size, TraversalMode mode, - const XNetTrafficStats *net_traffic_stats, const char *user_id, - const size_t user_id_size, void *user_data) { - auto *handler = static_cast(user_data); - GuiRuntime *runtime = handler ? &handler->owner_ : nullptr; + const char* client_id, size_t client_id_size, TraversalMode mode, + const XNetTrafficStats* net_traffic_stats, const char* user_id, + const size_t user_id_size, void* user_data) { + auto* handler = static_cast(user_data); + GuiRuntime* runtime = handler ? &handler->owner_ : nullptr; if (!runtime) { return; } if (strchr(client_id, '@') != nullptr && strchr(user_id, '-') == nullptr) { std::string id, password; - const char *at_pos = strchr(client_id, '@'); + const char* at_pos = strchr(client_id, '@'); if (at_pos == nullptr) { id = client_id; password.clear(); @@ -510,4 +463,4 @@ void PeerEventHandler::OnNetStatusReport( props->net_traffic_stats_ = *net_traffic_stats; } } -} // namespace crossdesk +} // namespace crossdesk diff --git a/submodules/minirtc b/submodules/minirtc index da4b0f2..e6d796d 160000 --- a/submodules/minirtc +++ b/submodules/minirtc @@ -1 +1 @@ -Subproject commit da4b0f2e9a0b59b7dde4f0901b16763d63d1cfd1 +Subproject commit e6d796d7b7f59a1b454d18b909d9b91143f0a6f3 diff --git a/tests/connection_status_protocol_test.cpp b/tests/connection_status_protocol_test.cpp index eaf14f8..874b8f7 100644 --- a/tests/connection_status_protocol_test.cpp +++ b/tests/connection_status_protocol_test.cpp @@ -19,7 +19,7 @@ std::filesystem::path FindRepoRoot() { return {}; } -std::string ReadFile(const std::filesystem::path &path) { +std::string ReadFile(const std::filesystem::path& path) { std::ifstream file(path, std::ios::binary); if (!file) { return {}; @@ -30,8 +30,8 @@ std::string ReadFile(const std::filesystem::path &path) { return stream.str(); } -bool ExpectContains(const char *name, const std::string &value, - const std::string &expected) { +bool ExpectContains(const char* name, const std::string& value, + const std::string& expected) { if (value.find(expected) != std::string::npos) { return true; } @@ -40,8 +40,8 @@ bool ExpectContains(const char *name, const std::string &value, return false; } -bool ExpectNotContains(const char *name, const std::string &value, - const std::string &unexpected) { +bool ExpectNotContains(const char* name, const std::string& value, + const std::string& unexpected) { if (value.find(unexpected) == std::string::npos) { return true; } @@ -73,6 +73,10 @@ int main() { ReadFile(repo_root / "src/gui/runtime/remote_session.h"); const std::string connection_runtime_cpp = ReadFile(repo_root / "src/gui/runtime/connection_runtime.cpp"); + const std::string peer_event_handler_cpp = + ReadFile(repo_root / "src/gui/runtime/peer_event_handler.cpp"); + const std::string application_state_h = + ReadFile(repo_root / "src/gui/application/application_state.h"); bool ok = true; ok &= ExpectContains("minirtc.h", minirtc_api, "RemoteUnavailable"); @@ -81,6 +85,11 @@ int main() { "\"Remote unavailable\""); ok &= ExpectContains("peer_connection.cpp", peer_connection, "ConnectionStatus::RemoteUnavailable"); + ok &= ExpectContains("peer_connection.cpp", peer_connection, + "IsCurrentPeerConnection"); + ok &= ExpectContains( + "peer_connection.cpp", peer_connection, + "on_connection_status_(ConnectionStatus::Closed, remote_user_id.data()"); ok &= ExpectNotContains("peer_connection.cpp", peer_connection, "\"Device offline\""); ok &= ExpectNotContains("peer_connection.cpp", peer_connection, @@ -105,6 +114,14 @@ int main() { "kConnectionAttemptTimeout"); ok &= ExpectContains("connection_runtime.cpp", connection_runtime_cpp, "connection_attempt_started_at_"); + ok &= ExpectContains("connection_runtime.cpp", connection_runtime_cpp, + "HandleServerControllerDisconnected"); + ok &= ExpectContains("peer_event_handler.cpp", peer_event_handler_cpp, + "HandleServerControllerDisconnected(remote_id"); + ok &= ExpectNotContains("connection_runtime.cpp", connection_runtime_cpp, + "ControllerHeartbeat"); + ok &= ExpectContains("application_state.h", application_state_h, + "std::atomic need_to_destroy_server_window_"); return ok ? 0 : 1; }