From 3d280053a7fa01bb125c15a52db57cfaed5fdc2e Mon Sep 17 00:00:00 2001 From: dijunkun Date: Wed, 17 Jun 2026 17:48:17 +0800 Subject: [PATCH] [feat] add custom names for recent connection devices and improve panel display --- .../assets/localization/localization_data.h | 13 +- src/gui/panels/recent_connections_panel.cpp | 467 ++++++++++++++---- src/gui/render.cpp | 108 ++++ src/gui/render.h | 11 + src/thumbnail/thumbnail.cpp | 27 +- 5 files changed, 517 insertions(+), 109 deletions(-) diff --git a/src/gui/assets/localization/localization_data.h b/src/gui/assets/localization/localization_data.h index 04fce4b..de88b94 100644 --- a/src/gui/assets/localization/localization_data.h +++ b/src/gui/assets/localization/localization_data.h @@ -87,7 +87,7 @@ struct TranslationRow { X(install_windows_service, u8"安装", "Install", u8"Установить") \ X(windows_service_settings_label, u8"锁屏控制服务:", \ "Lock Screen Service:", u8"Служба блокировки экрана:") \ - X(windows_service_installed, u8"已安装", "Installed", u8"Установлена") \ + X(windows_service_installed, u8"已安装", "Installed", u8"Установлена") \ X(do_not_remind_again, u8"不再提醒", "Do not remind again", \ u8"Больше не напоминать") \ X(windows_service_prompt_suppressed_message, \ @@ -159,7 +159,8 @@ struct TranslationRow { X(signal_disconnected, u8"未连接服务器", "Disconnected", \ u8"Нет подключения к серверу") \ X(signal_tls_cert_error, u8"证书验证失败,请重新安装自托管根证书", \ - "Certificate verification failed. Reinstall the self-hosted root certificate.", \ + "Certificate verification failed. Reinstall the self-hosted root " \ + "certificate.", \ u8"Ошибка проверки сертификата. Переустановите корневой сертификат.") \ X(p2p_connected, u8"对等连接已建立", "P2P Connected", u8"P2P подключено") \ X(p2p_disconnected, u8"对等连接已断开", "P2P Disconnected", \ @@ -180,6 +181,14 @@ struct TranslationRow { X(access_website, u8"访问官网: ", \ "Access Website: ", u8"Официальный сайт: ") \ X(update, u8"更新", "Update", u8"Обновить") \ + X(connection_alias, u8"修改名称", "Edit Alias", \ + u8"Изменить имя подключения") \ + X(delete_connection, u8"删除连接", "Delete Connection", \ + u8"Удалить подключение") \ + X(connect_to_this_connection, u8"发起连接", "Connect to this connection", \ + u8"Подключиться") \ + X(input_connection_alias, u8"请输入连接名称:", \ + "Please input connection name:", u8"Введите имя подключения:") \ X(confirm_delete_connection, u8"确认删除此连接", \ "Confirm to delete this connection", u8"Удалить это подключение?") \ X(enable_autostart, u8"开机自启:", "Auto Start:", u8"Автозапуск:") \ diff --git a/src/gui/panels/recent_connections_panel.cpp b/src/gui/panels/recent_connections_panel.cpp index 4831722..851015e 100644 --- a/src/gui/panels/recent_connections_panel.cpp +++ b/src/gui/panels/recent_connections_panel.cpp @@ -1,9 +1,37 @@ +#include +#include + #include "layout_relative.h" #include "localization.h" #include "rd_log.h" #include "render.h" namespace crossdesk { +namespace { + +std::string TrimConnectionAlias(const char* value) { + std::string alias = value ? value : ""; + + auto not_space = [](unsigned char ch) { return !std::isspace(ch); }; + alias.erase(alias.begin(), + std::find_if(alias.begin(), alias.end(), not_space)); + alias.erase(std::find_if(alias.rbegin(), alias.rend(), not_space).base(), + alias.end()); + + return alias; +} + +void SetDarkTextTooltip(const char* text) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.05f, 0.05f, 0.05f, 1.0f)); + ImGui::BeginTooltip(); + ImGui::SetWindowFontScale(0.5f); + ImGui::Text("%s", text); + ImGui::SetWindowFontScale(1.0f); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); +} + +} // namespace int Render::RecentConnectionsWindow() { ImGuiIO& io = ImGui::GetIO(); @@ -51,8 +79,9 @@ int Render::ShowRecentConnections() { float recent_connection_button_width = recent_connection_image_width * 0.15f; float recent_connection_button_height = recent_connection_image_height * 0.25f; - float recent_connection_dummy_button_width = - recent_connection_image_width - 2 * recent_connection_button_width; + float recent_connection_footer_height = + recent_connection_button_height * 1.18f; + float recent_connection_name_width = recent_connection_image_width; ImGui::SetCursorPos( ImVec2(io.DisplaySize.x * 0.045f, io.DisplaySize.y * 0.1f)); @@ -90,149 +119,307 @@ int Render::ShowRecentConnections() { // password length is 6 // connection_info -> remote_id + 'Y' + host_name + '@' + password // -> remote_id + 'N' + host_name - if ('Y' == connection_info[9] && connection_info.size() >= 16) { + bool invalid_connection_info = false; + if (connection_info.size() > 9 && 'Y' == connection_info[9] && + connection_info.size() >= 16) { size_t pos_y = connection_info.find('Y'); size_t pos_at = connection_info.find('@'); if (pos_y == std::string::npos || pos_at == std::string::npos || pos_y >= pos_at) { LOG_ERROR("Invalid filename"); - continue; + invalid_connection_info = true; + } else { + it.second.remote_id = connection_info.substr(0, pos_y); + it.second.remote_host_name = + connection_info.substr(pos_y + 1, pos_at - pos_y - 1); + it.second.password = connection_info.substr(pos_at + 1); + it.second.remember_password = true; } - - it.second.remote_id = connection_info.substr(0, pos_y); - it.second.remote_host_name = - connection_info.substr(pos_y + 1, pos_at - pos_y - 1); - it.second.password = connection_info.substr(pos_at + 1); - it.second.remember_password = true; - } else if ('N' == connection_info[9] && connection_info.size() >= 10) { + } else if (connection_info.size() > 9 && 'N' == connection_info[9] && + connection_info.size() >= 10) { size_t pos_n = connection_info.find('N'); - size_t pos_at = connection_info.find('@'); if (pos_n == std::string::npos) { LOG_ERROR("Invalid filename"); - continue; + invalid_connection_info = true; + } else { + it.second.remote_id = connection_info.substr(0, pos_n); + it.second.remote_host_name = connection_info.substr(pos_n + 1); + it.second.password = ""; + it.second.remember_password = false; } - - it.second.remote_id = connection_info.substr(0, pos_n); - it.second.remote_host_name = connection_info.substr(pos_n + 1); - it.second.password = ""; - it.second.remember_password = false; } else { - it.second.remote_host_name = "unknown"; + invalid_connection_info = true; } + if (invalid_connection_info) { + it.second.remote_id = connection_info.substr( + 0, std::min(connection_info.size(), 9)); + it.second.remote_host_name = "unknown"; + it.second.password = ""; + it.second.remember_password = false; + } + + std::string display_name = GetRecentConnectionDisplayName(it.second); bool online = device_presence_.IsOnline(it.second.remote_id); ImVec2 image_screen_pos = ImVec2( ImGui::GetCursorScreenPos().x + recent_connection_image_width * 0.04f, ImGui::GetCursorScreenPos().y + recent_connection_image_height * 0.08f); + ImVec2 image_pos = ImVec2(ImGui::GetCursorPosX() + recent_connection_image_width * 0.05f, ImGui::GetCursorPosY() + recent_connection_image_height * 0.08f); + ImGui::SetCursorPos(image_pos); ImGui::Image( (ImTextureID)(intptr_t)it.second.texture, ImVec2(recent_connection_image_width, recent_connection_image_height)); - if (ImGui::IsItemHovered()) { + + // 必须在 ImGui::Image 后立刻保存 hovered 状态 + const bool image_item_hovered = ImGui::IsItemHovered(); + + ImVec2 card_screen_min = image_screen_pos; + ImVec2 card_screen_max = + ImVec2(image_screen_pos.x + recent_connection_image_width, + image_screen_pos.y + recent_connection_image_height + + recent_connection_footer_height); + + const bool card_hovered = + ImGui::IsMouseHoveringRect(card_screen_min, card_screen_max, true); + + // 预先计算 toolbar 区域,即三个按钮所在区域 + const float recent_connection_toolbar_width = + 3.0f * recent_connection_button_width; + + const float recent_connection_toolbar_padding = + recent_connection_image_width * 0.025f; + + const ImVec2 toolbar_pos = ImVec2( + image_pos.x + recent_connection_image_width - + recent_connection_toolbar_width - recent_connection_toolbar_padding, + image_pos.y + recent_connection_toolbar_padding); + + const ImVec2 toolbar_screen_pos = ImVec2( + image_screen_pos.x + recent_connection_image_width - + recent_connection_toolbar_width - recent_connection_toolbar_padding, + image_screen_pos.y + recent_connection_toolbar_padding); + + const ImVec2 toolbar_screen_end = + ImVec2(toolbar_screen_pos.x + recent_connection_toolbar_width, + toolbar_screen_pos.y + recent_connection_button_height); + + const bool toolbar_hovered = + card_hovered && ImGui::IsMouseHoveringRect(toolbar_screen_pos, + toolbar_screen_end, true); + + // 关键:鼠标在三个按钮区域时,不显示背景图 tooltip + const bool show_image_tooltip = image_item_hovered && !toolbar_hovered; + + if (show_image_tooltip) { ImGui::BeginTooltip(); + ImGui::SetWindowFontScale(0.5f); - std::string display_host_name_with_presence = - it.second.remote_host_name + " " + - (online ? localization::online[localization_language_index_] - : localization::offline[localization_language_index_]); - ImGui::Text("%s", display_host_name_with_presence.c_str()); + + ImGui::Text("%s", display_name.c_str()); + + if (!it.second.remote_host_name.empty() && + it.second.remote_host_name != display_name) { + ImGui::Text("%s", it.second.remote_host_name.c_str()); + } + + ImGui::Text("%s: %s", + localization::remote_id[localization_language_index_].c_str(), + it.second.remote_id.c_str()); + + ImGui::Text("%s", + (online ? localization::online[localization_language_index_] + : localization::offline[localization_language_index_]) + .c_str()); + ImGui::SetWindowFontScale(1.0f); + ImGui::EndTooltip(); } ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 circle_pos = - ImVec2(image_screen_pos.x + recent_connection_image_width * 0.07f, - image_screen_pos.y + recent_connection_image_height * 0.12f); + ImU32 fill_color = online ? IM_COL32(0, 255, 0, 255) : IM_COL32(140, 140, 140, 255); + ImU32 border_color = IM_COL32(255, 255, 255, 255); - float dot_radius = recent_connection_image_height * 0.06f; - draw_list->AddCircleFilled(circle_pos, dot_radius * 1.25f, border_color, - 100); - draw_list->AddCircleFilled(circle_pos, dot_radius, fill_color, 100); - // remote id display button + // connection name footer { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0.2f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0.2f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0, 0, 0, 0.2f)); - - ImVec2 dummy_button_pos = + ImVec2 footer_pos = ImVec2(image_pos.x, image_pos.y + recent_connection_image_height); - std::string dummy_button_name = "##DummyButton" + it.second.remote_id; - ImGui::SetCursorPos(dummy_button_pos); - ImGui::SetWindowFontScale(0.6f); - ImGui::Button(dummy_button_name.c_str(), - ImVec2(recent_connection_dummy_button_width, - recent_connection_button_height)); + + ImVec2 footer_screen_pos = + ImVec2(image_screen_pos.x, + image_screen_pos.y + recent_connection_image_height); + + ImVec2 footer_screen_end = + ImVec2(footer_screen_pos.x + recent_connection_name_width, + footer_screen_pos.y + recent_connection_footer_height); + + float footer_rounding = recent_connection_footer_height * 0.16f; + + draw_list->AddRectFilled(footer_screen_pos, footer_screen_end, + IM_COL32(0, 0, 0, 40), footer_rounding, + ImDrawFlags_RoundCornersBottom); + + float dot_radius = recent_connection_footer_height * 0.16f; + + ImVec2 footer_dot_pos = + ImVec2(footer_screen_pos.x + recent_connection_footer_height * 0.45f, + footer_screen_pos.y + recent_connection_footer_height * 0.5f); + + draw_list->AddCircleFilled(footer_dot_pos, dot_radius * 1.45f, + border_color, 100); + + draw_list->AddCircleFilled(footer_dot_pos, dot_radius, fill_color, 100); + + ImVec2 text_min = + ImVec2(footer_dot_pos.x + dot_radius * 2.2f, footer_screen_pos.y); + + ImVec2 text_max = + ImVec2(footer_screen_end.x - recent_connection_name_width * 0.05f, + footer_screen_end.y); + + ImGui::SetWindowFontScale(0.52f); + + ImGui::RenderTextClipped(text_min, text_max, display_name.c_str(), + nullptr, nullptr, ImVec2(0.0f, 0.5f)); + ImGui::SetWindowFontScale(1.0f); - ImGui::SetCursorPos(ImVec2( - dummy_button_pos.x + recent_connection_dummy_button_width * 0.05f, - dummy_button_pos.y + recent_connection_button_height * 0.05f)); - ImGui::SetWindowFontScale(0.65f); - ImGui::Text("%s", it.second.remote_id.c_str()); - ImGui::SetWindowFontScale(1.0f); - ImGui::PopStyleColor(3); } - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0.2f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, - ImVec4(0.1f, 0.4f, 0.8f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, - ImVec4(1.0f, 1.0f, 1.0f, 0.7f)); - ImGui::SetWindowFontScale(0.5f); - // trash button - { - ImVec2 trash_can_button_pos = - ImVec2(image_pos.x + recent_connection_image_width - - 2 * recent_connection_button_width, - image_pos.y + recent_connection_image_height); - ImGui::SetCursorPos(trash_can_button_pos); - std::string trash_can = ICON_FA_TRASH_CAN; - std::string recent_connection_delete_button_name = - trash_can + "##RecentConnectionDelete" + - std::to_string(trash_can_button_pos.x); - if (ImGui::Button(recent_connection_delete_button_name.c_str(), - ImVec2(recent_connection_button_width, - recent_connection_button_height))) { - show_confirm_delete_connection_ = true; - delete_connection_name_ = it.first; - } + // toolbar / three buttons + if (card_hovered) { + float toolbar_rounding = recent_connection_button_height * 0.22f; - if (delete_connection_ && delete_connection_name_ == it.first) { - if (!thumbnail_->DeleteThumbnail(it.first)) { - reload_recent_connections_ = true; - delete_connection_ = false; + draw_list->AddRectFilled( + ImVec2(toolbar_screen_pos.x, toolbar_screen_pos.y + 1.0f), + ImVec2(toolbar_screen_end.x, toolbar_screen_end.y + 1.0f), + IM_COL32(0, 0, 0, 70), toolbar_rounding); + + draw_list->AddRectFilled(toolbar_screen_pos, toolbar_screen_end, + IM_COL32(20, 24, 30, 170), toolbar_rounding); + + draw_list->AddRect(toolbar_screen_pos, toolbar_screen_end, + IM_COL32(255, 255, 255, 48), toolbar_rounding); + + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, toolbar_rounding); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(1.0f, 1.0f, 1.0f, 0.18f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, + ImVec4(0.35f, 0.55f, 0.95f, 0.45f)); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 0.95f)); + + ImGui::SetWindowFontScale(0.5f); + + // edit alias button + { + ImGui::SetCursorPos(toolbar_pos); + + std::string edit = ICON_FA_PEN; + std::string recent_connection_edit_button_name = + edit + "##RecentConnectionAlias" + it.first; + + if (ImGui::Button(recent_connection_edit_button_name.c_str(), + ImVec2(recent_connection_button_width, + recent_connection_button_height))) { + BeginEditRecentConnectionAlias(it.second); + } + + if (ImGui::IsItemHovered()) { + SetDarkTextTooltip( + localization::connection_alias[localization_language_index_] + .c_str()); } } + + // trash button + { + ImVec2 trash_can_button_pos = ImVec2( + toolbar_pos.x + recent_connection_button_width, toolbar_pos.y); + + ImGui::SetCursorPos(trash_can_button_pos); + + std::string trash_can = ICON_FA_TRASH_CAN; + std::string recent_connection_delete_button_name = + trash_can + "##RecentConnectionDelete" + it.first; + + if (ImGui::Button(recent_connection_delete_button_name.c_str(), + ImVec2(recent_connection_button_width, + recent_connection_button_height))) { + show_confirm_delete_connection_ = true; + delete_connection_name_ = it.first; + } + if (ImGui::IsItemHovered()) { + SetDarkTextTooltip( + localization::delete_connection[localization_language_index_] + .c_str()); + } + } + + // connect button + { + ImVec2 connect_button_pos = ImVec2( + toolbar_pos.x + 2 * recent_connection_button_width, toolbar_pos.y); + + ImGui::SetCursorPos(connect_button_pos); + + std::string connect = ICON_FA_ARROW_RIGHT_LONG; + std::string connect_to_this_connection_button_name = + connect + "##ConnectionTo" + it.first; + + if (ImGui::Button(connect_to_this_connection_button_name.c_str(), + ImVec2(recent_connection_button_width, + recent_connection_button_height))) { + ConnectTo(it.second.remote_id, it.second.password.c_str(), + it.second.remember_password); + } + if (ImGui::IsItemHovered()) { + SetDarkTextTooltip(localization::connect_to_this_connection + [localization_language_index_] + .c_str()); + } + } + + ImGui::SetWindowFontScale(1.0f); + + ImGui::PopStyleColor(4); + ImGui::PopStyleVar(3); } - // connect button - { - ImVec2 connect_button_pos = - ImVec2(image_pos.x + recent_connection_image_width - - recent_connection_button_width, - image_pos.y + recent_connection_image_height); - ImGui::SetCursorPos(connect_button_pos); - std::string connect = ICON_FA_ARROW_RIGHT_LONG; - std::string connect_to_this_connection_button_name = - connect + "##ConnectionTo" + it.first; - if (ImGui::Button(connect_to_this_connection_button_name.c_str(), - ImVec2(recent_connection_button_width, - recent_connection_button_height))) { - ConnectTo(it.second.remote_id, it.second.password.c_str(), - it.second.remember_password); + if (count != recent_connections_count - 1) { + ImVec2 line_start = + ImVec2(image_screen_pos.x + recent_connection_image_width * 1.19f, + image_screen_pos.y); + + ImVec2 line_end = + ImVec2(image_screen_pos.x + recent_connection_image_width * 1.19f, + image_screen_pos.y + recent_connection_image_height + + recent_connection_footer_height); + + ImGui::GetWindowDrawList()->AddLine(line_start, line_end, + IM_COL32(0, 0, 0, 122), 1.0f); + } + + if (delete_connection_ && delete_connection_name_ == it.first) { + if (!thumbnail_->DeleteThumbnail(it.first)) { + recent_connection_aliases_.erase(it.second.remote_id); + SaveRecentConnectionAliases(); + reload_recent_connections_ = true; + delete_connection_ = false; } } - ImGui::SetWindowFontScale(1.0f); - ImGui::PopStyleColor(3); ImGui::EndChild(); @@ -243,7 +430,7 @@ int Render::ShowRecentConnections() { ImVec2 line_end = ImVec2(image_screen_pos.x + recent_connection_image_width * 1.19f, image_screen_pos.y + recent_connection_image_height + - recent_connection_button_height); + recent_connection_footer_height); ImGui::GetWindowDrawList()->AddLine(line_start, line_end, IM_COL32(0, 0, 0, 122), 1.0f); } @@ -259,6 +446,9 @@ int Render::ShowRecentConnections() { if (show_confirm_delete_connection_) { ConfirmDeleteConnection(); } + if (show_edit_connection_alias_window_) { + EditRecentConnectionAliasWindow(); + } if (show_offline_warning_window_) { OfflineWarningWindow(); } @@ -320,6 +510,89 @@ int Render::ConfirmDeleteConnection() { return 0; } +int Render::EditRecentConnectionAliasWindow() { + ImGuiIO& io = ImGui::GetIO(); + ImGui::SetNextWindowPos( + ImVec2(io.DisplaySize.x * 0.33f, io.DisplaySize.y * 0.33f)); + ImGui::SetNextWindowSize( + ImVec2(io.DisplaySize.x * 0.33f, io.DisplaySize.y * 0.33f)); + + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_); + + ImGui::Begin("EditRecentConnectionAliasWindow", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoSavedSettings); + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(); + + auto window_width = ImGui::GetWindowSize().x; + auto window_height = ImGui::GetWindowSize().y; + std::string text = + localization::input_connection_alias[localization_language_index_]; + + ImGui::SetWindowFontScale(0.5f); + auto text_width = ImGui::CalcTextSize(text.c_str()).x; + ImGui::SetCursorPosX((window_width - text_width) * 0.5f); + ImGui::SetCursorPosY(window_height * 0.2f); + ImGui::Text("%s", text.c_str()); + + ImGui::SetCursorPosX(window_width * 0.2f); + ImGui::SetCursorPosY(window_height * 0.4f); + ImGui::SetNextItemWidth(window_width * 0.6f); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); + + if (focus_on_input_widget_) { + ImGui::SetKeyboardFocusHere(); + focus_on_input_widget_ = false; + } + + bool enter_pressed = + ImGui::InputText("##recent_connection_alias", edit_connection_alias_, + IM_ARRAYSIZE(edit_connection_alias_), + ImGuiInputTextFlags_EnterReturnsTrue); + + ImGui::PopStyleVar(); + + ImGui::SetCursorPosX(window_width * 0.315f); + ImGui::SetCursorPosY(window_height * 0.75f); + + if (ImGui::Button(localization::ok[localization_language_index_].c_str()) || + enter_pressed) { + std::string alias = TrimConnectionAlias(edit_connection_alias_); + if (alias.empty()) { + recent_connection_aliases_.erase(edit_connection_alias_remote_id_); + } else { + recent_connection_aliases_[edit_connection_alias_remote_id_] = alias; + } + + SaveRecentConnectionAliases(); + show_edit_connection_alias_window_ = false; + focus_on_input_widget_ = true; + memset(edit_connection_alias_, 0, sizeof(edit_connection_alias_)); + edit_connection_alias_remote_id_.clear(); + } + + ImGui::SameLine(); + + if (ImGui::Button( + localization::cancel[localization_language_index_].c_str()) || + ImGui::IsKeyPressed(ImGuiKey_Escape)) { + show_edit_connection_alias_window_ = false; + focus_on_input_widget_ = true; + memset(edit_connection_alias_, 0, sizeof(edit_connection_alias_)); + edit_connection_alias_remote_id_.clear(); + } + + ImGui::SetWindowFontScale(1.0f); + + ImGui::End(); + ImGui::PopStyleVar(); + return 0; +} + int Render::OfflineWarningWindow() { ImGuiIO& io = ImGui::GetIO(); ImGui::SetNextWindowPos( @@ -360,4 +633,4 @@ int Render::OfflineWarningWindow() { ImGui::PopStyleVar(); return 0; } -} // namespace crossdesk +} // namespace crossdesk diff --git a/src/gui/render.cpp b/src/gui/render.cpp index efb2deb..a8d84d9 100644 --- a/src/gui/render.cpp +++ b/src/gui/render.cpp @@ -640,6 +640,113 @@ int Render::LoadSettingsFromCacheFile() { return 0; } +int Render::LoadRecentConnectionAliases() { + recent_connection_aliases_.clear(); + + std::ifstream alias_file(cache_path_ + "/recent_connection_aliases.json"); + if (!alias_file.good()) { + return 0; + } + + try { + nlohmann::json alias_json; + alias_file >> alias_json; + + const nlohmann::json* aliases = &alias_json; + if (alias_json.contains("aliases") && alias_json["aliases"].is_object()) { + aliases = &alias_json["aliases"]; + } + + if (!aliases->is_object()) { + LOG_WARN("Invalid recent connection alias file"); + return -1; + } + + for (auto it = aliases->begin(); it != aliases->end(); ++it) { + if (!it.value().is_string()) { + continue; + } + + std::string remote_id = it.key(); + std::string alias = it.value().get(); + if (!remote_id.empty() && !alias.empty()) { + recent_connection_aliases_[remote_id] = alias; + } + } + } catch (const std::exception& e) { + LOG_WARN("Load recent connection aliases failed: {}", e.what()); + return -1; + } + + return 0; +} + +int Render::SaveRecentConnectionAliases() const { + std::error_code ec; + std::filesystem::create_directories(cache_path_, ec); + if (ec) { + LOG_WARN("Create cache directory failed while saving aliases: {}", + ec.message()); + return -1; + } + + nlohmann::json alias_json; + alias_json["aliases"] = nlohmann::json::object(); + + for (const auto& [remote_id, alias] : recent_connection_aliases_) { + if (!remote_id.empty() && !alias.empty()) { + alias_json["aliases"][remote_id] = alias; + } + } + + std::ofstream alias_file(cache_path_ + "/recent_connection_aliases.json", + std::ios::trunc); + if (!alias_file.good()) { + LOG_WARN("Open recent connection alias file failed"); + return -1; + } + + alias_file << alias_json.dump(2); + return 0; +} + +std::string Render::GetRecentConnectionDisplayName( + const Thumbnail::RecentConnection& connection) const { + const auto alias_it = recent_connection_aliases_.find(connection.remote_id); + if (alias_it != recent_connection_aliases_.end() && + !alias_it->second.empty()) { + return alias_it->second; + } + + if (!connection.remote_host_name.empty() && + connection.remote_host_name != "unknown") { + return connection.remote_host_name; + } + + return connection.remote_id; +} + +void Render::BeginEditRecentConnectionAlias( + const Thumbnail::RecentConnection& connection) { + edit_connection_alias_remote_id_ = connection.remote_id; + memset(edit_connection_alias_, 0, sizeof(edit_connection_alias_)); + + const auto alias_it = recent_connection_aliases_.find(connection.remote_id); + std::string alias = + alias_it != recent_connection_aliases_.end() + ? alias_it->second + : GetRecentConnectionDisplayName(connection); + + if (!alias.empty()) { + strncpy(edit_connection_alias_, alias.c_str(), + sizeof(edit_connection_alias_) - 1); + edit_connection_alias_[sizeof(edit_connection_alias_) - 1] = '\0'; + } + + focus_on_input_widget_ = true; + show_edit_connection_alias_window_ = true; +} + int Render::ScreenCapturerInit() { #ifdef __APPLE__ if (!EnsureMacScreenRecordingPermission()) { @@ -1811,6 +1918,7 @@ void Render::InitializeLogger() { InitLogger(exec_log_path_); } void Render::InitializeSettings() { LoadSettingsFromCacheFile(); + LoadRecentConnectionAliases(); localization_language_index_ = localization::detail::ClampLanguageIndex(language_button_value_); diff --git a/src/gui/render.h b/src/gui/render.h index cfae904..036f5ab 100644 --- a/src/gui/render.h +++ b/src/gui/render.h @@ -278,6 +278,7 @@ class Render { int DrawStreamWindow(); int DrawServerWindow(); int ConfirmDeleteConnection(); + int EditRecentConnectionAliasWindow(); int OfflineWarningWindow(); int NetTrafficStats(std::shared_ptr& props); void DrawConnectionStatusText( @@ -381,6 +382,12 @@ class Render { private: int SaveSettingsIntoCacheFile(); int LoadSettingsFromCacheFile(); + int LoadRecentConnectionAliases(); + int SaveRecentConnectionAliases() const; + std::string GetRecentConnectionDisplayName( + const Thumbnail::RecentConnection& connection) const; + void BeginEditRecentConnectionAlias( + const Thumbnail::RecentConnection& connection); int ScreenCapturerInit(); int StartScreenCapturer(); @@ -683,10 +690,14 @@ class Render { bool is_server_mode_ = false; bool reload_recent_connections_ = true; bool show_confirm_delete_connection_ = false; + bool show_edit_connection_alias_window_ = false; bool show_offline_warning_window_ = false; bool delete_connection_ = false; bool is_tab_bar_hovered_ = false; std::string delete_connection_name_ = ""; + std::unordered_map recent_connection_aliases_; + std::string edit_connection_alias_remote_id_ = ""; + char edit_connection_alias_[128] = ""; std::string offline_warning_text_ = ""; bool re_enter_remote_id_ = false; double copy_start_time_ = 0; diff --git a/src/thumbnail/thumbnail.cpp b/src/thumbnail/thumbnail.cpp index 362add5..b254e85 100644 --- a/src/thumbnail/thumbnail.cpp +++ b/src/thumbnail/thumbnail.cpp @@ -225,9 +225,12 @@ int Thumbnail::LoadThumbnail( std::string remote_id; std::string cipher_password; std::string remote_host_name; + std::string password; std::string original_image_name; + bool remember_password = false; - if ('Y' == cipher_image_name[9] && cipher_image_name.size() >= 16) { + if (cipher_image_name.size() > 9 && 'Y' == cipher_image_name[9] && + cipher_image_name.size() >= 16) { size_t pos_y = cipher_image_name.find('Y'); size_t pos_at = cipher_image_name.find('@'); @@ -241,10 +244,11 @@ int Thumbnail::LoadThumbnail( remote_host_name = cipher_image_name.substr(pos_y + 1, pos_at - pos_y - 1); cipher_password = cipher_image_name.substr(pos_at + 1); + password = AES_decrypt(cipher_password, aes128_key_, aes128_iv_); + remember_password = true; - original_image_name = - remote_id + 'Y' + remote_host_name + "@" + - AES_decrypt(cipher_password, aes128_key_, aes128_iv_); + original_image_name = remote_id + 'Y' + remote_host_name + "@" + + password; } else { size_t pos_n = cipher_image_name.find('N'); // size_t pos_at = cipher_image_name.find('@'); @@ -257,16 +261,19 @@ int Thumbnail::LoadThumbnail( remote_id = cipher_image_name.substr(0, pos_n); remote_host_name = cipher_image_name.substr(pos_n + 1); - original_image_name = - remote_id + 'N' + remote_host_name + "@" + - AES_decrypt(cipher_password, aes128_key_, aes128_iv_); + original_image_name = remote_id + 'N' + remote_host_name; } std::string image_path = save_path_ + cipher_image_name; + Thumbnail::RecentConnection recent_connection; + recent_connection.remote_id = remote_id; + recent_connection.remote_host_name = remote_host_name; + recent_connection.password = password; + recent_connection.remember_password = remember_password; recent_connections.emplace_back( - std::make_pair(original_image_name, Thumbnail::RecentConnection())); + std::make_pair(original_image_name, recent_connection)); LoadTextureFromFile(image_path.c_str(), renderer, - &(recent_connections[i].second.texture), width, + &(recent_connections.back().second.texture), width, height); } return 0; @@ -436,4 +443,4 @@ std::string Thumbnail::AES_decrypt(const std::string& ciphertext, return std::string(reinterpret_cast(plaintext), plaintext_len); } -} // namespace crossdesk \ No newline at end of file +} // namespace crossdesk