diff --git a/src/config_center/config_center.cpp b/src/config_center/config_center.cpp index d025bbf..ced799d 100644 --- a/src/config_center/config_center.cpp +++ b/src/config_center/config_center.cpp @@ -34,6 +34,8 @@ int ConfigCenter::Load() { persist_config_migration |= ini_.Delete(section_, "screen_content"); persist_config_migration |= ini_.Delete(section_, "enable_desktop_quality_optimization"); + persist_config_migration |= + ini_.Delete(section_, "enable_minimize_to_tray"); const long language_value = ini_.GetLongValue(section_, "language", static_cast(language_)); @@ -109,8 +111,6 @@ int ConfigCenter::Load() { enable_autostart_ = ini_.GetBoolValue(section_, "enable_autostart", enable_autostart_); enable_daemon_ = ini_.GetBoolValue(section_, "enable_daemon", enable_daemon_); - enable_minimize_to_tray_ = ini_.GetBoolValue( - section_, "enable_minimize_to_tray", enable_minimize_to_tray_); portable_service_prompt_suppressed_ = ini_.GetBoolValue(section_, "portable_service_prompt_suppressed", portable_service_prompt_suppressed_); @@ -157,8 +157,6 @@ int ConfigCenter::Save() { ini_.SetBoolValue(section_, "enable_autostart", enable_autostart_); ini_.SetBoolValue(section_, "enable_daemon", enable_daemon_); - ini_.SetBoolValue(section_, "enable_minimize_to_tray", - enable_minimize_to_tray_); ini_.SetBoolValue(section_, "portable_service_prompt_suppressed", portable_service_prompt_suppressed_); @@ -338,17 +336,6 @@ int ConfigCenter::SetSelfHosted(bool enable_self_hosted) { return 0; } -int ConfigCenter::SetMinimizeToTray(bool enable_minimize_to_tray) { - enable_minimize_to_tray_ = enable_minimize_to_tray; - ini_.SetBoolValue(section_, "enable_minimize_to_tray", - enable_minimize_to_tray_); - SI_Error rc = ini_.SaveFile(config_path_.c_str()); - if (rc < 0) { - return -1; - } - return 0; -} - int ConfigCenter::SetAutostart(bool enable_autostart) { enable_autostart_ = enable_autostart; bool success = false; @@ -448,8 +435,6 @@ int ConfigCenter::GetDefaultCoturnServerPort() const { bool ConfigCenter::IsSelfHosted() const { return enable_self_hosted_; } -bool ConfigCenter::IsMinimizeToTray() const { return enable_minimize_to_tray_; } - bool ConfigCenter::IsEnableAutostart() const { return enable_autostart_; } bool ConfigCenter::IsEnableDaemon() const { return enable_daemon_; } diff --git a/src/config_center/config_center.h b/src/config_center/config_center.h index d0725cf..920dd8d 100644 --- a/src/config_center/config_center.h +++ b/src/config_center/config_center.h @@ -43,7 +43,6 @@ class ConfigCenter { int SetServerPort(int signal_server_port); int SetCoturnServerPort(int coturn_server_port); int SetSelfHosted(bool enable_self_hosted); - int SetMinimizeToTray(bool enable_minimize_to_tray); int SetAutostart(bool enable_autostart); int SetDaemon(bool enable_daemon); int SetPortableServicePromptSuppressed(bool suppressed); @@ -66,7 +65,6 @@ class ConfigCenter { int GetDefaultSignalServerPort() const; int GetDefaultCoturnServerPort() const; bool IsSelfHosted() const; - bool IsMinimizeToTray() const; bool IsEnableAutostart() const; bool IsEnableDaemon() const; bool IsPortableServicePromptSuppressed() const; @@ -94,7 +92,6 @@ class ConfigCenter { int coturn_server_port_ = 0; int coturn_server_port_default_ = 3478; bool enable_self_hosted_ = false; - bool enable_minimize_to_tray_ = false; bool enable_autostart_ = false; bool enable_daemon_ = false; bool portable_service_prompt_suppressed_ = false; diff --git a/src/gui/application/gui_application.cpp b/src/gui/application/gui_application.cpp index 2404965..0018a67 100644 --- a/src/gui/application/gui_application.cpp +++ b/src/gui/application/gui_application.cpp @@ -1163,22 +1163,24 @@ void GuiApplication::InitializeSystemTray() { }; #if _WIN32 ui_->tray = std::make_unique( - std::move(show_window), std::move(hide_window), std::move(exit_app), - LoadSlintTrayIcon(), L"CrossDesk", localization_language_index_); + std::move(show_window), std::move(hide_window), + std::move(open_settings), std::move(exit_app), LoadSlintTrayIcon(), + L"CrossDesk", localization_language_index_); #elif defined(__APPLE__) ui_->tray = std::make_unique( std::move(show_window), std::move(hide_window), std::move(open_settings), std::move(exit_app), "CrossDesk", localization_language_index_); #elif defined(__linux__) ui_->tray = std::make_unique( - std::move(show_window), std::move(hide_window), std::move(exit_app), - "CrossDesk", localization_language_index_); + std::move(show_window), std::move(hide_window), + std::move(open_settings), std::move(exit_app), "CrossDesk", + localization_language_index_); #endif #endif } bool GuiApplication::MinimizeMainWindowToTray() { - if (!enable_minimize_to_tray_ || !ui_) { + if (!ui_) { return false; } #if _WIN32 || defined(__APPLE__) @@ -1208,7 +1210,6 @@ void GuiApplication::ResetSettingsUi() { ui_->main->set_self_hosted_enabled(enable_self_hosted_); ui_->main->set_autostart_enabled(enable_autostart_); ui_->main->set_daemon_enabled(enable_daemon_); - ui_->main->set_minimize_to_tray_enabled(enable_minimize_to_tray_); ui_->main->set_file_save_path(file_transfer_save_path_buf_); ui_->main->set_server_host(signal_server_ip_self_); ui_->main->set_server_port(signal_server_port_self_); @@ -1218,12 +1219,10 @@ void GuiApplication::ResetSettingsUi() { void GuiApplication::BindMainCallbacks() { auto& main = ui_->main; main->window().on_close_requested([this] { - if (MinimizeMainWindowToTray()) { - // MinimizeMainWindowToTray() ensures the tray icon exists. Let Slint - // complete the close request by hiding the main window as well. - return slint::CloseRequestResponse::HideWindow; - } - exit_ = true; + // Keep the application and all active sessions alive. The tray helper + // ensures that supported platforms still provide a way to restore the + // main window; Slint hides it regardless of tray availability. + MinimizeMainWindowToTray(); return slint::CloseRequestResponse::HideWindow; }); main->on_main_title_drag([this](int phase, float x, float y) { @@ -1240,11 +1239,7 @@ void GuiApplication::BindMainCallbacks() { if (!ui_) { return; } - if (MinimizeMainWindowToTray()) { - ui_->main->hide(); - return; - } - exit_ = true; + MinimizeMainWindowToTray(); ui_->main->hide(); }); main->on_copy_local_id([this] { @@ -2756,7 +2751,6 @@ void GuiApplication::SaveSettingsFromUi() { enable_self_hosted_ = main->get_self_hosted_enabled(); enable_autostart_ = main->get_autostart_enabled(); enable_daemon_ = main->get_daemon_enabled(); - enable_minimize_to_tray_ = main->get_minimize_to_tray_enabled(); localization_language_ = static_cast(language_button_value_); @@ -2775,7 +2769,6 @@ void GuiApplication::SaveSettingsFromUi() { config_center_->SetSelfHosted(enable_self_hosted_); config_center_->SetAutostart(enable_autostart_); config_center_->SetDaemon(enable_daemon_); - config_center_->SetMinimizeToTray(enable_minimize_to_tray_); const std::string path(main->get_file_save_path()); std::memset(file_transfer_save_path_buf_, 0, @@ -2805,7 +2798,6 @@ void GuiApplication::SaveSettingsFromUi() { enable_self_hosted_last_ = enable_self_hosted_; enable_autostart_last_ = enable_autostart_; enable_daemon_last_ = enable_daemon_; - enable_minimize_to_tray_last_ = enable_minimize_to_tray_; file_transfer_save_path_last_ = path; ui_->localized_language = -1; diff --git a/src/gui/application/sdl_event_dispatch.cpp b/src/gui/application/sdl_event_dispatch.cpp index ece1e20..710f7f8 100644 --- a/src/gui/application/sdl_event_dispatch.cpp +++ b/src/gui/application/sdl_event_dispatch.cpp @@ -134,8 +134,10 @@ void GuiApplication::ProcessSdlEvent(const SDL_Event &event) { } if (main_window_ && - event.window.windowID == SDL_GetWindowID(main_window_) && - MinimizeMainWindowToTray()) { + event.window.windowID == SDL_GetWindowID(main_window_)) { + if (!MinimizeMainWindowToTray()) { + SDL_HideWindow(main_window_); + } break; } diff --git a/src/gui/application/window_rendering.cpp b/src/gui/application/window_rendering.cpp index 0ea7216..eb25a07 100644 --- a/src/gui/application/window_rendering.cpp +++ b/src/gui/application/window_rendering.cpp @@ -150,7 +150,7 @@ int GuiApplication::DrawServerWindow() { } bool GuiApplication::MinimizeMainWindowToTray() { - if (!enable_minimize_to_tray_ || !main_window_) { + if (!main_window_) { return false; } diff --git a/src/gui/assets/localization/localization.h b/src/gui/assets/localization/localization.h index e116baa..820e11c 100644 --- a/src/gui/assets/localization/localization.h +++ b/src/gui/assets/localization/localization.h @@ -135,35 +135,58 @@ inline std::string FormatDisplayLabel(size_t display_index, CROSSDESK_LOCALIZATION_ALL(CROSSDESK_DECLARE_LOCALIZED_STRING) #undef CROSSDESK_DECLARE_LOCALIZED_STRING -#if _WIN32 -inline const wchar_t* GetExitProgramLabel(int language_index) { - static std::vector cache(GetSupportedLanguages().size()); +#if _WIN32 +inline const wchar_t* GetWindowsLocalizedLabel( + int language_index, const char* key, const wchar_t* fallback, + std::vector& cache) { const int normalized_index = detail::ClampLanguageIndex(language_index); - std::wstring& cached_text = cache[normalized_index]; - if (!cached_text.empty()) { - return cached_text.c_str(); - } - - const std::string& utf8_text = - detail::GetTranslatedText("exit_program", normalized_index); - if (utf8_text.empty()) { - cached_text = L"Exit"; - return cached_text.c_str(); - } - - int wide_length = - MultiByteToWideChar(CP_UTF8, 0, utf8_text.c_str(), -1, nullptr, 0); - if (wide_length <= 0) { - cached_text = L"Exit"; - return cached_text.c_str(); - } - - cached_text.resize(static_cast(wide_length - 1)); - MultiByteToWideChar(CP_UTF8, 0, utf8_text.c_str(), -1, cached_text.data(), - wide_length); - return cached_text.c_str(); -} -#endif + std::wstring& cached_text = cache[normalized_index]; + if (!cached_text.empty()) { + return cached_text.c_str(); + } + + const std::string& utf8_text = + detail::GetTranslatedText(key, normalized_index); + if (utf8_text.empty()) { + cached_text = fallback; + return cached_text.c_str(); + } + + int wide_length = + MultiByteToWideChar(CP_UTF8, 0, utf8_text.c_str(), -1, nullptr, 0); + if (wide_length <= 0) { + cached_text = fallback; + return cached_text.c_str(); + } + + cached_text.resize(static_cast(wide_length)); + if (MultiByteToWideChar(CP_UTF8, 0, utf8_text.c_str(), -1, + cached_text.data(), wide_length) <= 0) { + cached_text = fallback; + return cached_text.c_str(); + } + cached_text.pop_back(); + return cached_text.c_str(); +} + +inline const wchar_t* GetShowMainWindowLabel(int language_index) { + static std::vector cache(GetSupportedLanguages().size()); + return GetWindowsLocalizedLabel(language_index, "show_main_window", + L"Show Main Window", cache); +} + +inline const wchar_t* GetSettingsLabel(int language_index) { + static std::vector cache(GetSupportedLanguages().size()); + return GetWindowsLocalizedLabel(language_index, "settings", L"Settings", + cache); +} + +inline const wchar_t* GetExitProgramLabel(int language_index) { + static std::vector cache(GetSupportedLanguages().size()); + return GetWindowsLocalizedLabel(language_index, "exit_program", L"Exit", + cache); +} +#endif } // namespace localization } // namespace crossdesk diff --git a/src/gui/assets/localization/localization_data.h b/src/gui/assets/localization/localization_data.h index cb6db90..a3c34e7 100644 --- a/src/gui/assets/localization/localization_data.h +++ b/src/gui/assets/localization/localization_data.h @@ -215,8 +215,6 @@ struct TranslationRow { X(file_transfer_save_path, u8"文件接收保存路径:", \ "File Transfer Save Path:", u8"Путь сохранения файлов:") \ X(default_desktop, u8"桌面", "Desktop", u8"Рабочий стол") \ - X(minimize_to_tray, u8"退出时最小化到系统托盘:", \ - "Minimize on Exit:", u8"Сворачивать в трей при выходе:") \ X(resolution, u8"分辨率", "Res", u8"Разрешение") \ X(connection_mode, u8"连接模式", "Mode", u8"Режим") \ X(connection_mode_direct, u8"直连", "Direct", u8"Прямой") \ @@ -233,6 +231,8 @@ struct TranslationRow { X(permission_required_message, u8"该应用需要授权以下权限:", \ "The application requires the following permissions:", \ u8"Для работы приложения требуются следующие разрешения:") \ + X(show_main_window, u8"显示主界面", "Show Main Window", \ + u8"Показать главное окно") \ X(exit_program, u8"退出", "Exit", u8"Выход") inline constexpr TranslationRow kTranslationRows[] = { diff --git a/src/gui/features/settings/settings_manager.cpp b/src/gui/features/settings/settings_manager.cpp index b41e795..83ed7e8 100644 --- a/src/gui/features/settings/settings_manager.cpp +++ b/src/gui/features/settings/settings_manager.cpp @@ -310,7 +310,6 @@ int SettingsManager::Load() { owner_.enable_self_hosted_ = owner_.config_center_->IsSelfHosted(); owner_.enable_autostart_ = owner_.config_center_->IsEnableAutostart(); owner_.enable_daemon_ = owner_.config_center_->IsEnableDaemon(); - owner_.enable_minimize_to_tray_ = owner_.config_center_->IsMinimizeToTray(); #if _WIN32 && CROSSDESK_PORTABLE owner_.portable_service_prompt_suppressed_ = owner_.config_center_->IsPortableServicePromptSuppressed(); @@ -333,7 +332,6 @@ int SettingsManager::Load() { owner_.enable_srtp_last_ = owner_.enable_srtp_; owner_.enable_self_hosted_last_ = owner_.enable_self_hosted_; owner_.enable_autostart_last_ = owner_.enable_autostart_; - owner_.enable_minimize_to_tray_last_ = owner_.enable_minimize_to_tray_; LOG_INFO("Load settings from cache file"); return 0; diff --git a/src/gui/platform/tray/linux_tray.cpp b/src/gui/platform/tray/linux_tray.cpp index cb9e647..bb9b543 100644 --- a/src/gui/platform/tray/linux_tray.cpp +++ b/src/gui/platform/tray/linux_tray.cpp @@ -32,7 +32,9 @@ namespace crossdesk { namespace { constexpr int kTrayIconSize = 24; -constexpr int kMenuHeight = 28; +constexpr int kMenuItemHeight = 28; +constexpr int kMenuItemCount = 3; +constexpr int kMenuHeight = kMenuItemHeight * kMenuItemCount; constexpr int kMenuHorizontalPadding = 12; constexpr int kDockTimeoutMs = 800; constexpr int kMaxTrayEventsPerTick = 32; @@ -50,7 +52,21 @@ bool IsAsciiPrintable(const std::string& text) { return true; } -std::string GetMenuLabel(int language_index) { +std::string GetShowMainWindowMenuLabel(int language_index) { + const int normalized_index = localization::detail::ClampLanguageIndex( + language_index); + const std::string& label = localization::show_main_window[normalized_index]; + return label.empty() ? "Show Main Window" : label; +} + +std::string GetSettingsMenuLabel(int language_index) { + const int normalized_index = localization::detail::ClampLanguageIndex( + language_index); + const std::string& label = localization::settings[normalized_index]; + return label.empty() ? "Settings" : label; +} + +std::string GetExitMenuLabel(int language_index) { const int normalized_index = localization::detail::ClampLanguageIndex( language_index); const std::string& label = localization::exit_program[normalized_index]; @@ -187,20 +203,42 @@ struct LinuxTrayImpl { tooltip(std::move(tray_tooltip)), language_index(language_index_value), exit_event_type(tray_exit_event_type), - menu_label(GetMenuLabel(language_index_value)), - menu_ascii_label(IsAsciiPrintable(menu_label) ? menu_label : "Exit") {} + show_menu_label(GetShowMainWindowMenuLabel(language_index_value)), + show_menu_ascii_label(IsAsciiPrintable(show_menu_label) + ? show_menu_label + : "Show Main Window"), + settings_menu_label(GetSettingsMenuLabel(language_index_value)), + settings_menu_ascii_label(IsAsciiPrintable(settings_menu_label) + ? settings_menu_label + : "Settings"), + exit_menu_label(GetExitMenuLabel(language_index_value)), + exit_menu_ascii_label(IsAsciiPrintable(exit_menu_label) + ? exit_menu_label + : "Exit") {} explicit LinuxTrayImpl(std::function show_window_callback, std::function hide_window_callback, + std::function open_settings_callback, std::function exit_callback, std::string tray_tooltip, int language_index_value) : show_window(std::move(show_window_callback)), hide_window(std::move(hide_window_callback)), + open_settings(std::move(open_settings_callback)), exit_app(std::move(exit_callback)), tooltip(std::move(tray_tooltip)), language_index(language_index_value), - menu_label(GetMenuLabel(language_index_value)), - menu_ascii_label(IsAsciiPrintable(menu_label) ? menu_label : "Exit") {} + show_menu_label(GetShowMainWindowMenuLabel(language_index_value)), + show_menu_ascii_label(IsAsciiPrintable(show_menu_label) + ? show_menu_label + : "Show Main Window"), + settings_menu_label(GetSettingsMenuLabel(language_index_value)), + settings_menu_ascii_label(IsAsciiPrintable(settings_menu_label) + ? settings_menu_label + : "Settings"), + exit_menu_label(GetExitMenuLabel(language_index_value)), + exit_menu_ascii_label(IsAsciiPrintable(exit_menu_label) + ? exit_menu_label + : "Exit") {} ~LinuxTrayImpl() { RemoveTrayIcon(); } @@ -540,6 +578,20 @@ struct LinuxTrayImpl { HandleButtonRelease(event.xbutton); break; + case MotionNotify: + if (event.xmotion.window == menu_window) { + const int hovered_item = + event.xmotion.x >= 0 && event.xmotion.x < menu_width && + event.xmotion.y >= 0 && event.xmotion.y < kMenuHeight + ? event.xmotion.y / kMenuItemHeight + : -1; + if (hovered_item != hovered_menu_item) { + hovered_menu_item = hovered_item; + DrawMenu(); + } + } + break; + case LeaveNotify: if (event.xcrossing.window == menu_window) { HideMenu(); @@ -556,9 +608,15 @@ struct LinuxTrayImpl { const bool inside_menu = event.x >= 0 && event.y >= 0 && event.x < menu_width && event.y < kMenuHeight; - const bool activate_exit = inside_menu && event.button == Button1; + const int selected_item = inside_menu && event.button == Button1 + ? event.y / kMenuItemHeight + : -1; HideMenu(); - if (activate_exit) { + if (selected_item == 0) { + ShowWindow(); + } else if (selected_item == 1) { + OpenSettings(); + } else if (selected_item == 2) { RequestExit(); } return; @@ -571,9 +629,7 @@ struct LinuxTrayImpl { return; } - if (event.button == Button1) { - ShowWindow(); - } else if (event.button == Button3) { + if (event.button == Button1 || event.button == Button3) { ShowMenu(event.x_root, event.y_root); } } @@ -824,29 +880,30 @@ struct LinuxTrayImpl { return false; } - int MenuTextWidth() const { + int MenuTextWidth(const std::string& label, + const std::string& ascii_label) const { if (menu_font) { XGlyphInfo extents{}; XftTextExtentsUtf8(display, menu_font, - reinterpret_cast(menu_label.c_str()), - static_cast(menu_label.size()), &extents); + reinterpret_cast(label.c_str()), + static_cast(label.size()), &extents); return extents.xOff; } if (font_set) { XRectangle ink{}; XRectangle logical{}; - Xutf8TextExtents(font_set, menu_label.c_str(), - static_cast(menu_label.size()), &ink, &logical); + Xutf8TextExtents(font_set, label.c_str(), static_cast(label.size()), + &ink, &logical); return logical.width; } if (fallback_font) { - return XTextWidth(fallback_font, menu_ascii_label.c_str(), - static_cast(menu_ascii_label.size())); + return XTextWidth(fallback_font, ascii_label.c_str(), + static_cast(ascii_label.size())); } - return static_cast(menu_ascii_label.size()) * 8; + return static_cast(ascii_label.size()) * 8; } void ShowMenu(int root_x, int root_y) { @@ -856,7 +913,15 @@ struct LinuxTrayImpl { HideMenu(); - menu_width = std::max(72, MenuTextWidth() + kMenuHorizontalPadding * 2); + menu_width = + std::max({72, + MenuTextWidth(show_menu_label, show_menu_ascii_label) + + kMenuHorizontalPadding * 2, + MenuTextWidth(settings_menu_label, + settings_menu_ascii_label) + + kMenuHorizontalPadding * 2, + MenuTextWidth(exit_menu_label, exit_menu_ascii_label) + + kMenuHorizontalPadding * 2}); const int display_width = DisplayWidth(display, screen); const int display_height = DisplayHeight(display, screen); const int x = std::clamp(root_x, 0, std::max(0, display_width - menu_width)); @@ -868,7 +933,7 @@ struct LinuxTrayImpl { attrs.background_pixel = white_pixel; attrs.border_pixel = black_pixel; attrs.event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask | - LeaveWindowMask; + PointerMotionMask | LeaveWindowMask; menu_window = XCreateWindow( display, root_window, x, y, menu_width, kMenuHeight, 1, CopyFromParent, InputOutput, CopyFromParent, @@ -878,10 +943,11 @@ struct LinuxTrayImpl { } menu_visible = true; + hovered_menu_item = -1; XMapRaised(display, menu_window); XGrabPointer(display, menu_window, False, - ButtonPressMask | ButtonReleaseMask, GrabModeAsync, - GrabModeAsync, None, None, CurrentTime); + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, None, None, CurrentTime); DrawMenu(); XFlush(display); } @@ -889,6 +955,7 @@ struct LinuxTrayImpl { void HideMenu() { if (!display || !menu_window) { menu_visible = false; + hovered_menu_item = -1; return; } @@ -896,6 +963,7 @@ struct LinuxTrayImpl { XDestroyWindow(display, menu_window); menu_window = 0; menu_visible = false; + hovered_menu_item = -1; XFlush(display); } @@ -907,45 +975,66 @@ struct LinuxTrayImpl { GC gc = XCreateGC(display, menu_window, 0, nullptr); XSetForeground(display, gc, white_pixel); XFillRectangle(display, menu_window, gc, 0, 0, menu_width, kMenuHeight); + if (hovered_menu_item >= 0 && hovered_menu_item < kMenuItemCount) { + XSetForeground(display, gc, hover_pixel); + XFillRectangle(display, menu_window, gc, 1, + hovered_menu_item * kMenuItemHeight + 1, menu_width - 2, + kMenuItemHeight - 2); + } XSetForeground(display, gc, hover_pixel); - XFillRectangle(display, menu_window, gc, 1, 1, menu_width - 2, - kMenuHeight - 2); + XDrawLine(display, menu_window, gc, 1, kMenuItemHeight * 2, + menu_width - 2, kMenuItemHeight * 2); XSetForeground(display, gc, black_pixel); - int baseline = kMenuHeight / 2 + 5; - if (menu_font && menu_text_color_allocated) { - XftDraw* draw = XftDrawCreate(display, menu_window, - DefaultVisual(display, screen), - DefaultColormap(display, screen)); - if (draw) { - baseline = (kMenuHeight - (menu_font->ascent + menu_font->descent)) / + XftDraw* xft_draw = menu_font && menu_text_color_allocated + ? XftDrawCreate(display, menu_window, + DefaultVisual(display, screen), + DefaultColormap(display, screen)) + : nullptr; + auto draw_label = [&](int item_index, const std::string& label, + const std::string& ascii_label) { + const int item_y = item_index * kMenuItemHeight; + int baseline = item_y + kMenuItemHeight / 2 + 5; + if (xft_draw) { + baseline = item_y + + (kMenuItemHeight - + (menu_font->ascent + menu_font->descent)) / 2 + menu_font->ascent; XftDrawStringUtf8( - draw, &menu_text_color, menu_font, kMenuHorizontalPadding, baseline, - reinterpret_cast(menu_label.c_str()), - static_cast(menu_label.size())); - XftDrawDestroy(draw); + xft_draw, &menu_text_color, menu_font, kMenuHorizontalPadding, + baseline, reinterpret_cast(label.c_str()), + static_cast(label.size())); + } else if (font_set) { + XFontSetExtents* extents = XExtentsOfFontSet(font_set); + if (extents) { + baseline = item_y + + (kMenuItemHeight - + extents->max_logical_extent.height) / + 2 - + extents->max_logical_extent.y; + } + Xutf8DrawString(display, menu_window, font_set, gc, + kMenuHorizontalPadding, baseline, label.c_str(), + static_cast(label.size())); + } else { + if (fallback_font) { + XSetFont(display, gc, fallback_font->fid); + baseline = item_y + + (kMenuItemHeight + fallback_font->ascent - + fallback_font->descent) / + 2; + } + XDrawString(display, menu_window, gc, kMenuHorizontalPadding, baseline, + ascii_label.c_str(), + static_cast(ascii_label.size())); } - } else if (font_set) { - XFontSetExtents* extents = XExtentsOfFontSet(font_set); - if (extents) { - baseline = (kMenuHeight - extents->max_logical_extent.height) / 2 - - extents->max_logical_extent.y; - } - Xutf8DrawString(display, menu_window, font_set, gc, - kMenuHorizontalPadding, baseline, menu_label.c_str(), - static_cast(menu_label.size())); - } else { - if (fallback_font) { - XSetFont(display, gc, fallback_font->fid); - baseline = (kMenuHeight + fallback_font->ascent - - fallback_font->descent) / - 2; - } - XDrawString(display, menu_window, gc, kMenuHorizontalPadding, baseline, - menu_ascii_label.c_str(), - static_cast(menu_ascii_label.size())); + }; + draw_label(0, show_menu_label, show_menu_ascii_label); + draw_label(1, settings_menu_label, settings_menu_ascii_label); + draw_label(2, exit_menu_label, exit_menu_ascii_label); + if (xft_draw) { + XftDrawDestroy(xft_draw); } XFreeGC(display, gc); @@ -962,6 +1051,14 @@ struct LinuxTrayImpl { } } + void OpenSettings() { + if (open_settings) { + open_settings(); + } else { + ShowWindow(); + } + } + void RequestExit() { if (exit_app) { exit_app(); @@ -978,12 +1075,17 @@ struct LinuxTrayImpl { ::SDL_Window* app_window = nullptr; std::function show_window; std::function hide_window; + std::function open_settings; std::function exit_app; std::string tooltip; int language_index = 0; uint32_t exit_event_type = 0; - std::string menu_label; - std::string menu_ascii_label; + std::string show_menu_label; + std::string show_menu_ascii_label; + std::string settings_menu_label; + std::string settings_menu_ascii_label; + std::string exit_menu_label; + std::string exit_menu_ascii_label; Display* display = nullptr; int screen = 0; ::Window root_window = 0; @@ -1014,6 +1116,7 @@ struct LinuxTrayImpl { int icon_width = 0; int icon_height = 0; int menu_width = 72; + int hovered_menu_item = -1; bool docked = false; bool embedded = false; bool menu_visible = false; @@ -1028,11 +1131,13 @@ LinuxTray::LinuxTray(::SDL_Window* app_window, const std::string& tooltip, LinuxTray::LinuxTray(std::function show_window, std::function hide_window, + std::function open_settings, std::function exit_app, const std::string& tooltip, int language_index) : impl_(std::make_unique( - std::move(show_window), std::move(hide_window), std::move(exit_app), - tooltip, language_index)) {} + std::move(show_window), std::move(hide_window), + std::move(open_settings), std::move(exit_app), tooltip, + language_index)) {} LinuxTray::~LinuxTray() = default; diff --git a/src/gui/platform/tray/linux_tray.h b/src/gui/platform/tray/linux_tray.h index d2d840b..a822cb7 100644 --- a/src/gui/platform/tray/linux_tray.h +++ b/src/gui/platform/tray/linux_tray.h @@ -25,7 +25,9 @@ class LinuxTray { LinuxTray(::SDL_Window* app_window, const std::string& tooltip, int language_index, uint32_t exit_event_type); LinuxTray(std::function show_window, - std::function hide_window, std::function exit_app, + std::function hide_window, + std::function open_settings, + std::function exit_app, const std::string& tooltip, int language_index); ~LinuxTray(); diff --git a/src/gui/platform/tray/mac_tray.mm b/src/gui/platform/tray/mac_tray.mm index 3cb9f32..1956b0c 100644 --- a/src/gui/platform/tray/mac_tray.mm +++ b/src/gui/platform/tray/mac_tray.mm @@ -13,6 +13,7 @@ @interface CrossDeskMacTrayTarget : NSObject - (instancetype)initWithOwner:(crossdesk::MacTrayImpl *)owner; - (void)statusItemClicked:(id)sender; +- (void)showMainWindow:(id)sender; - (void)openSettings:(id)sender; - (void)exitApplication:(id)sender; @end @@ -84,6 +85,19 @@ struct MacTrayImpl { } NSMenu *menu = [[NSMenu alloc] initWithTitle:@"CrossDesk"]; + [menu setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameAqua]]; + + NSString *show_main_window_title = + NSStringFromUtf8(localization::show_main_window + [localization::detail::ClampLanguageIndex( + language_index)]); + NSMenuItem *show_main_window_item = + [[NSMenuItem alloc] initWithTitle:show_main_window_title + action:@selector(showMainWindow:) + keyEquivalent:@""]; + [show_main_window_item setTarget:target]; + [menu addItem:show_main_window_item]; + NSString *settings_title = NSStringFromUtf8(localization::settings [localization::detail::ClampLanguageIndex( @@ -287,14 +301,7 @@ void MacTray::RemoveTrayIcon() { impl_->RemoveTrayIcon(); } if (!owner_) { return; } - - NSEvent *event = [NSApp currentEvent]; - if (event && [event type] == NSEventTypeRightMouseUp) { - owner_->ShowMenu(); - return; - } - - owner_->ShowWindow(); + owner_->ShowMenu(); } - (void)exitApplication:(id)sender { @@ -304,6 +311,13 @@ void MacTray::RemoveTrayIcon() { impl_->RemoveTrayIcon(); } } } +- (void)showMainWindow:(id)sender { + (void)sender; + if (owner_) { + owner_->ShowWindow(); + } +} + - (void)openSettings:(id)sender { (void)sender; if (owner_) { diff --git a/src/gui/platform/tray/win_tray.cpp b/src/gui/platform/tray/win_tray.cpp index e0b1249..5592e8c 100644 --- a/src/gui/platform/tray/win_tray.cpp +++ b/src/gui/platform/tray/win_tray.cpp @@ -64,11 +64,13 @@ WinTray::WinTray(HWND app_hwnd, HICON icon, const std::wstring& tooltip, WinTray::WinTray(std::function show_window, std::function hide_window, + std::function open_settings, std::function exit_app, HICON icon, const std::wstring& tooltip, int language_index) : WinTray(nullptr, icon, tooltip, language_index) { show_window_ = std::move(show_window); hide_window_ = std::move(hide_window); + open_settings_ = std::move(open_settings); exit_app_ = std::move(exit_app); } @@ -97,6 +99,14 @@ void WinTray::ShowApplicationWindow() { } } +void WinTray::OpenSettings() { + if (open_settings_) { + open_settings_(); + } else { + ShowApplicationWindow(); + } +} + bool WinTray::HandleTrayMessage(MSG* msg) { if (!msg || msg->message != WM_TRAY_CALLBACK) return false; @@ -111,6 +121,20 @@ bool WinTray::HandleTrayMessage(MSG* msg) { POINT pt; GetCursorPos(&pt); HMENU menu = CreatePopupMenu(); + HBRUSH menu_background = CreateSolidBrush(RGB(255, 255, 255)); + if (menu_background) { + MENUINFO menu_info{}; + menu_info.cbSize = sizeof(menu_info); + menu_info.fMask = MIM_BACKGROUND | MIM_APPLYTOSUBMENUS; + menu_info.hbrBack = menu_background; + SetMenuInfo(menu, &menu_info); + } + + AppendMenuW(menu, MF_STRING, 1002, + localization::GetShowMainWindowLabel(language_index_)); + AppendMenuW(menu, MF_STRING, 1003, + localization::GetSettingsLabel(language_index_)); + AppendMenuW(menu, MF_SEPARATOR, 0, nullptr); AppendMenuW(menu, MF_STRING, 1001, localization::GetExitProgramLabel(language_index_)); @@ -119,6 +143,9 @@ bool WinTray::HandleTrayMessage(MSG* msg) { TrackPopupMenu(menu, TPM_RETURNCMD | TPM_NONOTIFY | TPM_LEFTALIGN, pt.x, pt.y, 0, hwnd_message_only_, nullptr); DestroyMenu(menu); + if (menu_background) { + DeleteObject(menu_background); + } // handle menu command if (cmd == 1001) { @@ -131,6 +158,8 @@ bool WinTray::HandleTrayMessage(MSG* msg) { } } else if (cmd == 1002) { ShowApplicationWindow(); + } else if (cmd == 1003) { + OpenSettings(); } break; } diff --git a/src/gui/platform/tray/win_tray.h b/src/gui/platform/tray/win_tray.h index 17deed8..014e748 100644 --- a/src/gui/platform/tray/win_tray.h +++ b/src/gui/platform/tray/win_tray.h @@ -22,7 +22,9 @@ class WinTray { WinTray(HWND app_hwnd, HICON icon, const std::wstring& tooltip, int language_index); WinTray(std::function show_window, - std::function hide_window, std::function exit_app, + std::function hide_window, + std::function open_settings, + std::function exit_app, HICON icon, const std::wstring& tooltip, int language_index); ~WinTray(); @@ -32,6 +34,7 @@ class WinTray { private: void ShowApplicationWindow(); + void OpenSettings(); HWND app_hwnd_; HWND hwnd_message_only_; @@ -41,6 +44,7 @@ class WinTray { NOTIFYICONDATA nid_; std::function show_window_; std::function hide_window_; + std::function open_settings_; std::function exit_app_; }; } // namespace crossdesk diff --git a/src/gui/runtime/runtime_state.h b/src/gui/runtime/runtime_state.h index 53faad0..ed2c3fc 100644 --- a/src/gui/runtime/runtime_state.h +++ b/src/gui/runtime/runtime_state.h @@ -140,8 +140,6 @@ struct UserSettingsState { bool enable_autostart_last_ = false; bool enable_daemon_ = false; bool enable_daemon_last_ = false; - bool enable_minimize_to_tray_ = false; - bool enable_minimize_to_tray_last_ = false; char file_transfer_save_path_buf_[512] = ""; std::string file_transfer_save_path_last_; char signal_server_ip_self_[256] = ""; diff --git a/src/gui/ui/main_window.slint b/src/gui/ui/main_window.slint index 0d91ad0..7809ffd 100644 --- a/src/gui/ui/main_window.slint +++ b/src/gui/ui/main_window.slint @@ -46,7 +46,6 @@ export global UiStrings { in-out property self-hosted: "Self-hosted server"; in-out property autostart: "Start with system"; in-out property daemon: "Run in background"; - in-out property minimize-to-tray: "Minimize to tray"; in-out property file-save-path: "File save path"; in-out property default-desktop: "Desktop"; in-out property server-host: "Server host"; @@ -242,7 +241,6 @@ export component MainWindow inherits Window { in-out property self-hosted-enabled: false; in-out property autostart-enabled: false; in-out property daemon-enabled: false; - in-out property minimize-to-tray-enabled: false; in-out property file-save-path: ""; in-out property server-host: ""; in-out property server-port: ""; @@ -1488,11 +1486,6 @@ export component MainWindow inherits Window { Text { x: 8px; y: 3px; width: root.compact-language ? 145px : 212px; height: 24px; text: UiStrings.daemon; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; } ImGuiCheckBox { x: root.compact-language ? 157px : 224px; y: 3px; checked <=> root.daemon-enabled; enabled: !root.settings-session-active; } } - Rectangle { - height: 30px; - Text { x: 8px; y: 3px; width: root.compact-language ? 145px : 212px; height: 24px; text: UiStrings.minimize-to-tray; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; } - ImGuiCheckBox { x: root.compact-language ? 157px : 224px; y: 3px; checked <=> root.minimize-to-tray-enabled; enabled: !root.settings-session-active; } - } Rectangle { height: 30px; Text { x: 8px; y: 3px; width: root.compact-language ? 94px : 154px; height: 24px; text: UiStrings.file-save-path; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; } diff --git a/src/gui/ui/ui_localization.h b/src/gui/ui/ui_localization.h index 799a87f..09dee78 100644 --- a/src/gui/ui/ui_localization.h +++ b/src/gui/ui/ui_localization.h @@ -57,8 +57,6 @@ inline int ApplyMainWindowStrings( Text(localization::self_hosted_server_config[language])); strings.set_autostart(Text(localization::enable_autostart[language])); strings.set_daemon(Text(localization::enable_daemon[language])); - strings.set_minimize_to_tray( - Text(localization::minimize_to_tray[language])); strings.set_file_save_path( Text(localization::file_transfer_save_path[language])); strings.set_default_desktop(Text(localization::default_desktop[language])); diff --git a/src/gui/views/toolbars/title_bar.cpp b/src/gui/views/toolbars/title_bar.cpp index 8a12295..ca9c97a 100644 --- a/src/gui/views/toolbars/title_bar.cpp +++ b/src/gui/views/toolbars/title_bar.cpp @@ -299,8 +299,11 @@ int GuiApplication::TitleBar(bool main_window) { } if (close_button_clicked) { - const bool minimized_to_tray = main_window && MinimizeMainWindowToTray(); - if (!minimized_to_tray) { + if (main_window) { + if (!MinimizeMainWindowToTray()) { + SDL_HideWindow(main_window_); + } + } else { SDL_Event event; event.type = SDL_EVENT_QUIT; SDL_PushEvent(&event); diff --git a/src/gui/views/windows/main_settings_window.cpp b/src/gui/views/windows/main_settings_window.cpp index 5647fd8..c304493 100644 --- a/src/gui/views/windows/main_settings_window.cpp +++ b/src/gui/views/windows/main_settings_window.cpp @@ -355,26 +355,6 @@ int GuiApplication::SettingWindow() { ImGui::Separator(); - { - settings_items_offset += settings_items_padding; - ImGui::SetCursorPosY(settings_items_offset); - ImGui::AlignTextToFramePadding(); - ImGui::Text("%s", - localization::minimize_to_tray[localization_language_index_] - .c_str()); - ImGui::SameLine(); - if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { - ImGui::SetCursorPosX(title_bar_button_width_ * 4.275f); - } else { - ImGui::SetCursorPosX(title_bar_button_width_ * 5.955f); - } - - ImGui::Checkbox("##enable_minimize_to_tray_", - &enable_minimize_to_tray_); - } - - ImGui::Separator(); - { settings_items_offset += settings_items_padding; ImGui::SetCursorPosY(settings_items_offset); @@ -618,13 +598,6 @@ int GuiApplication::SettingWindow() { } enable_daemon_last_ = enable_daemon_; - if (enable_minimize_to_tray_) { - config_center_->SetMinimizeToTray(true); - } else { - config_center_->SetMinimizeToTray(false); - } - enable_minimize_to_tray_last_ = enable_minimize_to_tray_; - // File transfer save path config_center_->SetFileTransferSavePath(file_transfer_save_path_buf_); file_transfer_save_path_last_ = file_transfer_save_path_buf_;