#include #include #include #include #include #include #include #include #include #include "display_stream_id.h" #include "localization.h" #include "platform.h" #include "platform/video_renderer.h" #include "rd_log.h" #include "runtime/gui_runtime.h" namespace crossdesk { namespace { constexpr auto kPresenceProbeTimeout = std::chrono::seconds(5); } // namespace void GuiRuntime::HandleConnectionStatusChange() { if (signal_connected_ && peer_ && need_to_send_recent_connections_) { if (!recent_connection_ids_.empty()) { nlohmann::json j; j["type"] = "recent_connections_presence"; j["user_id"] = client_id_; j["devices"] = nlohmann::json::array(); 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'); size_t pos = std::string::npos; if (pos_y != std::string::npos && (pos_n == std::string::npos || pos_y < pos_n)) { pos = pos_y; } else if (pos_n != std::string::npos) { pos = pos_n; } if (pos != std::string::npos) { pure_id = pure_id.substr(0, pos); } j["devices"].push_back(pure_id); } auto s = j.dump(); SendSignalMessage(peer_, s.data(), s.size()); } } need_to_send_recent_connections_ = false; } void GuiRuntime::HandlePendingPresenceProbe() { bool has_action = false; bool should_connect = false; bool remember_password = false; std::string remote_id; std::string password; { std::lock_guard lock(pending_presence_probe_mutex_); if (!pending_presence_probe_ || !pending_presence_result_ready_) { return; } has_action = true; should_connect = pending_presence_online_; remote_id = pending_presence_remote_id_; password = pending_presence_password_; remember_password = pending_presence_remember_password_; pending_presence_probe_ = false; pending_presence_result_ready_ = false; pending_presence_online_ = false; pending_presence_remote_id_.clear(); pending_presence_password_.clear(); pending_presence_remember_password_ = false; } if (!has_action) { return; } if (should_connect) { ConnectTo(remote_id, password.c_str(), remember_password, true); return; } offline_warning_text_ = localization::device_offline[localization_language_index_]; show_offline_warning_window_ = true; } void GuiRuntime::HandlePresenceProbeTimeout() { const auto now = std::chrono::steady_clock::now(); bool presence_probe_timed_out = false; std::string presence_remote_id; { std::lock_guard lock(pending_presence_probe_mutex_); if (pending_presence_probe_ && !pending_presence_result_ready_ && now - pending_presence_probe_started_at_ >= kPresenceProbeTimeout) { presence_probe_timed_out = true; presence_remote_id = pending_presence_remote_id_; pending_presence_probe_ = false; pending_presence_result_ready_ = false; pending_presence_online_ = false; pending_presence_remote_id_.clear(); pending_presence_password_.clear(); pending_presence_remember_password_ = false; } } if (presence_probe_timed_out) { offline_warning_text_ = localization::device_offline[localization_language_index_]; show_offline_warning_window_ = true; LOG_WARN("Presence probe timed out for [{}]", presence_remote_id); } } 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; } { std::lock_guard lock(pending_presence_probe_mutex_); pending_presence_probe_ = true; pending_presence_result_ready_ = false; pending_presence_online_ = false; pending_presence_probe_started_at_ = std::chrono::steady_clock::now(); pending_presence_remote_id_ = remote_id; pending_presence_password_ = password ? password : ""; pending_presence_remember_password_ = remember_password; } nlohmann::json j; j["type"] = "recent_connections_presence"; j["user_id"] = client_id_; j["devices"] = nlohmann::json::array({remote_id}); auto s = j.dump(); int ret = SendSignalMessage(peer_, s.data(), s.size()); if (ret != 0) { std::lock_guard lock(pending_presence_probe_mutex_); pending_presence_probe_ = false; pending_presence_result_ready_ = false; pending_presence_online_ = false; pending_presence_remote_id_.clear(); pending_presence_password_.clear(); pending_presence_remember_password_ = false; } return ret; } void GuiRuntime::CloseRemoteSession(std::shared_ptr props) { std::shared_ptr> frame_snapshot; int video_width = 0; int video_height = 0; { std::lock_guard lock(props->video_frame_mutex_); frame_snapshot = props->front_frame_; video_width = props->video_width_; video_height = props->video_height_; if ((!frame_snapshot || frame_snapshot->empty()) && props->thumbnail_frame_ && !props->thumbnail_frame_->empty()) { frame_snapshot = props->thumbnail_frame_; video_width = props->thumbnail_width_; video_height = props->thumbnail_height_; } } auto* native_renderer = video_renderer_.get(); if ((!frame_snapshot || frame_snapshot->empty()) && native_renderer) { auto native_snapshot = std::make_shared>(); if (native_renderer->CopyLatestNv12(props->remote_id_, native_snapshot.get(), &video_width, &video_height)) { frame_snapshot = std::move(native_snapshot); } } if (frame_snapshot && !frame_snapshot->empty() && video_width > 0 && video_height > 0) { std::vector buffer_copy(*frame_snapshot); std::string remote_id = props->remote_id_; std::string remote_host_name = props->remote_host_name_; std::string password = props->remember_password_ ? props->remote_password_ : ""; 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, video_height, remote_id, remote_host_name, password); }); { std::lock_guard lock(thumbnail_save_threads_mutex_); thumbnail_save_threads_.emplace_back(std::move(save_thread)); } } if (native_renderer) { native_renderer->DiscardStream(props->remote_id_); video_frame_dirty_.store(true, std::memory_order_release); } if (props->peer_) { LOG_INFO("[{}] Leave connection [{}]", props->local_id_, props->remote_id_); LeaveConnection(props->peer_, props->remote_id_.c_str()); LOG_INFO("Destroy peer [{}]", props->local_id_); DestroyPeer(&props->peer_); } } void GuiRuntime::CloseAllRemoteSessions() { if (peer_) { LOG_INFO("[{}] Leave connection [{}]", client_id_, client_id_); LeaveConnection(peer_, client_id_); is_client_mode_ = false; devices_.StopMouseController(); devices_.StopScreenCapturer(); devices_.StopSpeakerCapturer(); devices_.StopKeyboardCapturer(); LOG_INFO("Destroy peer [{}]", client_id_); DestroyPeer(&peer_); } { // std::shared_lock lock(remote_sessions_mutex_); for (auto& it : remote_sessions_) { auto props = it.second; CloseRemoteSession(props); } } { // std::unique_lock lock(remote_sessions_mutex_); remote_sessions_.clear(); } } void GuiRuntime::WaitForThumbnailSaveTasks() { std::vector threads_to_join; { std::lock_guard lock(thumbnail_save_threads_mutex_); threads_to_join.swap(thumbnail_save_threads_); } if (threads_to_join.empty()) { return; } for (auto& thread : threads_to_join) { if (thread.joinable()) { thread.join(); } } } void GuiRuntime::ResetRemoteSessionResources( std::shared_ptr props) { { std::lock_guard lock(props->video_frame_mutex_); props->front_frame_.reset(); props->back_frame_.reset(); props->thumbnail_frame_.reset(); props->thumbnail_width_ = 0; props->thumbnail_height_ = 0; props->background_snapshot_time_ = {}; props->video_width_ = 0; props->video_height_ = 0; props->video_size_ = 0; props->render_rect_dirty_ = true; props->stream_cleanup_pending_ = false; } { std::lock_guard lock(props->remote_cursor_state_mutex_); props->remote_cursor_state_ = {}; props->remote_cursor_state_received_ = false; } } std::shared_ptr GuiRuntime::FindRemoteSession( const std::string& remote_id) { if (remote_id.empty()) { return nullptr; } std::shared_lock lock(remote_sessions_mutex_); auto it = remote_sessions_.find(remote_id); if (it == remote_sessions_.end()) { return nullptr; } return it->second; } 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 = RequestSingleDevicePresence(remote_id, password, remember_password); if (ret != 0) { offline_warning_text_ = localization::device_offline[localization_language_index_]; show_offline_warning_window_ = true; LOG_WARN("Presence probe failed for [{}], ret={}", remote_id, ret); } else { LOG_INFO("Presence probe requested for [{}] before connect", remote_id); } return -1; } LOG_INFO("Connect to [{}]", remote_id); focused_remote_id_ = remote_id; // std::shared_lock shared_lock(remote_sessions_mutex_); bool exists = (remote_sessions_.find(remote_id) != remote_sessions_.end()); // shared_lock.unlock(); if (!exists) { 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(); auto props = remote_sessions_[remote_id]; props->local_id_ = "C-" + std::string(client_id_); props->remote_id_ = remote_id; memcpy(&props->params_, ¶ms_, sizeof(Params)); props->params_.user_id = props->local_id_.c_str(); props->peer_ = CreatePeer(&props->params_); props->control_window_width_ = title_bar_height_ * 10.0f; props->control_window_height_ = title_bar_height_ * 1.3f; props->control_window_min_width_ = title_bar_height_ * 0.65f; props->control_window_min_height_ = title_bar_height_ * 1.3f; props->control_window_max_width_ = title_bar_height_ * 10.0f; props->control_window_max_height_ = title_bar_height_ * 7.0f; props->connection_status_.store(ConnectionStatus::Connecting); show_connection_status_window_ = true; if (!props->peer_) { LOG_INFO("Create peer [{}] instance failed", props->local_id_); return -1; } const auto& displays = devices_.display_info_list(); for (size_t index = 0; index < displays.size(); ++index) { const std::string stream_id = MakeDisplayStreamId(index); AddVideoStream(props->peer_, stream_id.c_str()); } AddAudioStream(props->peer_, props->audio_label_.c_str()); AddDataStream(props->peer_, props->data_label_.c_str(), false); AddDataStream(props->peer_, props->mouse_label_.c_str(), false); AddDataStream(props->peer_, props->keyboard_label_.c_str(), true); AddDataStream(props->peer_, props->control_data_label_.c_str(), true); AddDataStream(props->peer_, props->file_label_.c_str(), true); AddDataStream(props->peer_, props->file_feedback_label_.c_str(), true); AddDataStream(props->peer_, props->clipboard_label_.c_str(), true); props->connection_status_.store(ConnectionStatus::Connecting); peer_to_init = props->peer_; local_id = props->local_id_; } } if (peer_to_init) { LOG_INFO("[{}] Create peer instance successful", local_id); Init(peer_to_init); LOG_INFO("[{}] Peer init finish", local_id); } } int ret = -1; // std::shared_lock read_lock(remote_sessions_mutex_); auto props = remote_sessions_[remote_id]; if (!props->connection_established_) { props->connection_status_.store(ConnectionStatus::Connecting); show_connection_status_window_ = true; props->remember_password_ = remember_password; if (strcmp(password, "") != 0 && strcmp(password, props->remote_password_) != 0) { strncpy(props->remote_password_, password, sizeof(props->remote_password_) - 1); props->remote_password_[sizeof(props->remote_password_) - 1] = '\0'; } std::string remote_id_with_pwd = remote_id + "@" + password; if (props->peer_) { ret = JoinConnection(props->peer_, remote_id_with_pwd.c_str()); if (0 == ret) { props->rejoin_ = false; } else { props->rejoin_ = true; need_to_rejoin_ = true; } } } // read_lock.unlock(); return 0; } } // namespace crossdesk