diff --git a/apps/desktop/src/gui/runtime/gui_runtime.cpp b/apps/desktop/src/gui/runtime/gui_runtime.cpp index 06c61f5b..206a135b 100644 --- a/apps/desktop/src/gui/runtime/gui_runtime.cpp +++ b/apps/desktop/src/gui/runtime/gui_runtime.cpp @@ -206,6 +206,35 @@ int GuiRuntime::CreateConnectionPeer() { } void GuiRuntime::HandleRecentConnections() { + if (thumbnail_ && session_cleanup_tasks_.empty()) { + std::vector> sessions; + { + std::shared_lock lock(remote_sessions_mutex_); + for (const auto& [_, props] : remote_sessions_) + if (props && !props->closing_ && props->was_connected_ && + props->recent_connection_dirty_.exchange(false)) + sessions.push_back(props); + } + for (const auto& props : sessions) { + std::string host_name; + HostPlatform platform; + { + std::lock_guard lock(props->remote_version_mutex_); + host_name = props->remote_host_name_; + platform = props->recent_platform_; + } + // Record a successful connection before a preview or host info arrives. + // The close path still updates its final thumbnail. Remember password + // controls only credential storage, not whether this device is listed. + if (thumbnail_->SaveToThumbnail( + nullptr, 0, 0, props->remote_id_, host_name, + props->remember_password_ ? props->remote_password_ : "", + platform) == 0) + reload_recent_connections_ = true; + else + LOG_ERROR("[{}] Failed to save recent connection", props->remote_id_); + } + } if (session_cleanup_tasks_.empty() && reload_recent_connections_ && thumbnail_) { uint32_t now_time = SDL_GetTicks(); if (now_time - recent_connection_image_save_time_ >= 50) { diff --git a/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp b/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp index e2d5a120..4b64d930 100644 --- a/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp +++ b/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp @@ -357,17 +357,21 @@ void PeerEventHandler::OnReceiveDataBuffer( } } // client mode - if (props && props->remote_host_name_.empty()) { - props->remote_host_name_ = std::string(remote_action.i.host_name, - remote_action.i.host_name_size); - LOG_INFO("Remote hostname: [{}]", props->remote_host_name_); + if (props) { + std::lock_guard lock(props->remote_version_mutex_); + if (props->remote_host_name_.empty()) { + props->remote_host_name_ = std::string(remote_action.i.host_name, + remote_action.i.host_name_size); + LOG_INFO("Remote hostname: [{}]", props->remote_host_name_); - for (int i = 0; i < remote_action.i.display_num; i++) { - props->display_info_list_.push_back( - DisplayInfo(remote_action.i.display_list[i], - remote_action.i.left[i], remote_action.i.top[i], - remote_action.i.right[i], remote_action.i.bottom[i])); + for (int i = 0; i < remote_action.i.display_num; i++) { + props->display_info_list_.push_back( + DisplayInfo(remote_action.i.display_list[i], + remote_action.i.left[i], remote_action.i.top[i], + remote_action.i.right[i], remote_action.i.bottom[i])); + } } + props->recent_connection_dirty_ = true; } FreeRemoteAction(remote_action); } else { diff --git a/apps/desktop/src/gui/runtime/peer_event_handler.cpp b/apps/desktop/src/gui/runtime/peer_event_handler.cpp index 1e8a75a9..79233932 100644 --- a/apps/desktop/src/gui/runtime/peer_event_handler.cpp +++ b/apps/desktop/src/gui/runtime/peer_event_handler.cpp @@ -308,6 +308,7 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status, } props->connection_established_ = true; props->was_connected_ = true; + props->recent_connection_dirty_ = true; runtime->start_keyboard_capturer_ = true; break; } diff --git a/apps/desktop/src/gui/runtime/remote_session.h b/apps/desktop/src/gui/runtime/remote_session.h index 73ebe66f..14715e89 100644 --- a/apps/desktop/src/gui/runtime/remote_session.h +++ b/apps/desktop/src/gui/runtime/remote_session.h @@ -91,6 +91,8 @@ struct RemoteSession { SignalStatus signal_status_ = SignalStatus::SignalClosed; bool connection_established_ = false; std::atomic was_connected_{false}; + // Transport callbacks publish changes; the UI thread persists history. + std::atomic recent_connection_dirty_{false}; bool rejoin_ = false; bool net_traffic_stats_button_pressed_ = false; bool enable_mouse_control_ = true; diff --git a/apps/desktop/src/thumbnail/thumbnail.cpp b/apps/desktop/src/thumbnail/thumbnail.cpp index 36a607d4..3593872c 100644 --- a/apps/desktop/src/thumbnail/thumbnail.cpp +++ b/apps/desktop/src/thumbnail/thumbnail.cpp @@ -203,16 +203,15 @@ int Thumbnail::SaveToThumbnail(const char* nv12, int width, int height, const std::string& password, HostPlatform platform) { std::lock_guard lock(mutex_); - // Preserve the existing opt-in via Remember password for recent connections. - if (password.empty()) return 0; + // Successful connections are history even without saved credentials. if (!IsRemoteId(remote_id)) return -1; RecentConnection connection; ReadRecord(std::filesystem::path(save_path_) / (remote_id + ".json"), &connection); connection.remote_id = remote_id; - connection.remote_host_name = host_name; + if (!host_name.empty()) connection.remote_host_name = host_name; connection.password = password; - connection.remember_password = true; + connection.remember_password = !password.empty(); if (platform != HostPlatform::Unknown) connection.platform = platform; if (!WriteRecord(connection)) return -1; diff --git a/apps/desktop/src/thumbnail/thumbnail.h b/apps/desktop/src/thumbnail/thumbnail.h index 29fc3344..e6f24a32 100644 --- a/apps/desktop/src/thumbnail/thumbnail.h +++ b/apps/desktop/src/thumbnail/thumbnail.h @@ -41,6 +41,8 @@ class Thumbnail { // Serialized with writes so disabling also clears any in-flight preview. int SetSavePreviews(bool enabled); + // Also records metadata without an image. Empty password means do not store + // credentials (and clears any remembered password from a previous session). int SaveToThumbnail(const char* nv12, int width, int height, const std::string& remote_id, const std::string& host_name,