From 193a206b9dea470a03b7d054d26af63b0d96e25a Mon Sep 17 00:00:00 2001 From: shilyx Date: Fri, 28 Aug 2026 12:26:38 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=89=98=E7=9B=98=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E5=BD=93=E5=89=8D=E6=A1=8C=E9=9D=A2=E7=BC=96?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude --- src/core/Application.cpp | 2 + src/ui/TrayIcon.cpp | 117 ++++++++++++++++++++++++++++++++++++++- src/ui/TrayIcon.h | 10 ++++ xmake.lua | 2 +- 4 files changed, 128 insertions(+), 3 deletions(-) diff --git a/src/core/Application.cpp b/src/core/Application.cpp index 2215cee..04260db 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -410,6 +410,8 @@ void Application::SyncDesktopState() { if (m_pOverlay) { m_pOverlay->ShowTemporarily(); } } + m_pTrayIcon->UpdateTrayIcon(currentDesktop + 1); + if (m_pOverlay) { auto emptyMask = m_switcher->GetDesktopEmptyMask(); m_pOverlay->SetDesktopState(desktopCount, currentDesktop, emptyMask); diff --git a/src/ui/TrayIcon.cpp b/src/ui/TrayIcon.cpp index 8a8f70f..cb8a9a5 100644 --- a/src/ui/TrayIcon.cpp +++ b/src/ui/TrayIcon.cpp @@ -2,12 +2,16 @@ #include +#include + #include "core/IndicatorConfig.h" #include "util/AutoStart.h" #include "util/ConfigIni.h" #include "util/Lang.h" #include "util/Utils.h" +#pragma comment(lib, "gdiplus.lib") + namespace { constexpr UINT WM_TRAY_EXIT = WM_USER + 3; @@ -81,6 +85,29 @@ void DrawSwatchRect(HDC hdc, RECT rect, const std::wstring &hex) { DeleteObject(hp); } +ULONG_PTR g_gdiplusToken = 0; + +// EnsureGdiplus 初始化 GDI+(幂等),成功返回 true +bool EnsureGdiplus() { + if (g_gdiplusToken != 0) { + return true; + } + Gdiplus::GdiplusStartupInput input; + if (Gdiplus::GdiplusStartup(&g_gdiplusToken, &input, nullptr) != Gdiplus::Ok) { + g_gdiplusToken = 0; + return false; + } + return true; +} + +// ShutdownGdiplus 释放 GDI+ 资源 +void ShutdownGdiplus() { + if (g_gdiplusToken != 0) { + Gdiplus::GdiplusShutdown(g_gdiplusToken); + g_gdiplusToken = 0; + } +} + } // namespace TrayIcon::~TrayIcon() { @@ -90,6 +117,13 @@ TrayIcon::~TrayIcon() { if (m_hMenu != nullptr) { DestroyMenu(m_hMenu); } + for (int i = 0; i <= kTrayNumberMax; ++i) { + if (m_hNumberIcons[i] != nullptr) { + DestroyIcon(m_hNumberIcons[i]); + m_hNumberIcons[i] = nullptr; + } + } + ShutdownGdiplus(); } void TrayIcon::BuildMenu() { @@ -166,7 +200,9 @@ void TrayIcon::BuildMenu() { } bool TrayIcon::Initialize(HWND hwnd, HINSTANCE hInstance) { - HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(101)); + m_hInstance = hInstance; + + HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(kTrayDefaultIconResource)); if (hIcon == nullptr) { hIcon = LoadIcon(nullptr, IDI_APPLICATION); } @@ -177,6 +213,7 @@ bool TrayIcon::Initialize(HWND hwnd, HINSTANCE hInstance) { m_nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; m_nid.uCallbackMessage = WM_TRAYICON; m_nid.hIcon = hIcon; + m_nTrayNumber = -1; UpdateTooltip(Lang::Get(L"Tray.DefaultTip")); @@ -426,10 +463,86 @@ bool TrayIcon::Reinitialize() { if (m_nid.hWnd != nullptr) { Shell_NotifyIconW(NIM_DELETE, &m_nid); } - return Shell_NotifyIconW(NIM_ADD, &m_nid) != 0; + const bool ok = Shell_NotifyIconW(NIM_ADD, &m_nid) != 0; + m_nTrayNumber = -1; // 强制下一次同步重新应用桌面编号图标 + return ok; } void TrayIcon::UpdateTooltip(const std::wstring &tooltip) { wcsncpy_s(m_nid.szTip, tooltip.c_str(), _TRUNCATE); Shell_NotifyIconW(NIM_MODIFY, &m_nid); } + +void TrayIcon::UpdateTrayIcon(int displayNumber) { + if (displayNumber == m_nTrayNumber) { + return; + } + + NOTIFYICONDATAW nid = {}; + nid.cbSize = sizeof(nid); + nid.hWnd = m_nid.hWnd; + nid.uID = m_nid.uID; + nid.uFlags = NIF_ICON; + nid.hIcon = GetTrayIconForNumber(displayNumber); + Shell_NotifyIconW(NIM_MODIFY, &nid); + m_nTrayNumber = displayNumber; +} + +HICON TrayIcon::GetTrayIconForNumber(int nDisplay) { + if (nDisplay >= 1 && nDisplay <= kTrayNumberMax) { + HICON hIcon = GetNumberIcon(nDisplay); + if (hIcon != nullptr) { + return hIcon; + } + } + return LoadIcon(m_hInstance, MAKEINTRESOURCE(kTrayDefaultIconResource)); +} + +HICON TrayIcon::GetNumberIcon(int nNumber) { + if (nNumber < 1 || nNumber > kTrayNumberMax) { + return nullptr; + } + if (m_hNumberIcons[nNumber] == nullptr) { + m_hNumberIcons[nNumber] = CreateNumberIcon(nNumber); + } + return m_hNumberIcons[nNumber]; +} + +HICON TrayIcon::CreateNumberIcon(int nNumber) { + using namespace Gdiplus; + + if (!EnsureGdiplus()) { + return nullptr; + } + + int nSize = GetSystemMetrics(SM_CXSMICON); + if (nSize < 16) { + nSize = 16; + } + if (nSize > 64) { + nSize = 64; + } + + Bitmap bmp(nSize, nSize, PixelFormat32bppARGB); + Graphics graphics(&bmp); + graphics.SetSmoothingMode(SmoothingModeAntiAlias); + graphics.SetTextRenderingHint(TextRenderingHintAntiAliasGridFit); + + wchar_t szNum[8] = {}; + swprintf_s(szNum, 8, L"%d", nNumber); + + FontFamily fontFamily(L"Segoe UI"); + Font font(&fontFamily, static_cast(nSize * 0.85f), FontStyleBold, UnitPixel); + SolidBrush brushText(Color(255, 41, 151, 255)); + StringFormat format; + format.SetAlignment(StringAlignmentCenter); + format.SetLineAlignment(StringAlignmentCenter); + RectF rectText(0.0f, 0.0f, static_cast(nSize), static_cast(nSize)); + graphics.DrawString(szNum, -1, &font, rectText, &format, &brushText); + + HICON hIcon = nullptr; + if (bmp.GetHICON(&hIcon) != Ok) { + hIcon = nullptr; + } + return hIcon; +} diff --git a/src/ui/TrayIcon.h b/src/ui/TrayIcon.h index 67c0fb2..9dcc418 100644 --- a/src/ui/TrayIcon.h +++ b/src/ui/TrayIcon.h @@ -14,6 +14,9 @@ constexpr UINT WM_TRAY_LANG_CHINESE = WM_USER + 50; constexpr UINT WM_TRAY_LANG_ENGLISH = WM_USER + 51; constexpr UINT CMD_COLOR_OPTIONS_BASE = WM_USER + 100; +constexpr int kTrayDefaultIconResource = 101; +constexpr int kTrayNumberMax = 9; + class TrayIcon { public: TrayIcon(const TrayIcon &) = delete; @@ -28,6 +31,7 @@ class TrayIcon { bool Reinitialize(); void HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); void UpdateTooltip(const std::wstring &tooltip); + void UpdateTrayIcon(int displayNumber); void SetActivePositionPreset(PositionPreset preset) { m_activePositionPreset = preset; } void SetEditModeCallback(std::function cb) { m_editModeFn = std::move(cb); } @@ -43,11 +47,14 @@ class TrayIcon { private: NOTIFYICONDATAW m_nid{}; + HINSTANCE m_hInstance = nullptr; HMENU m_hMenu = nullptr; bool m_autoStartEnabled = false; PositionPreset m_activePositionPreset = PositionPreset::TopCenter; int m_menuAveWidth = 6; int m_dpi = 96; + int m_nTrayNumber = -1; + HICON m_hNumberIcons[kTrayNumberMax + 1] = {}; std::function m_colorFn; std::function m_editModeFn; @@ -63,6 +70,9 @@ class TrayIcon { void BuildMenu(); void HandleCommand(WPARAM wParam); void DrawColorSwatch(LPDRAWITEMSTRUCT dis) const; + HICON GetNumberIcon(int nNumber); + HICON GetTrayIconForNumber(int nDisplay); + HICON CreateNumberIcon(int nNumber); static void HandleRunAsAdmin(); static void HandleReset(); diff --git a/xmake.lua b/xmake.lua index 48c7526..75ac084 100644 --- a/xmake.lua +++ b/xmake.lua @@ -20,6 +20,6 @@ target("VirtualDesktopSwitcher") add_defines("NOMINMAX") add_cxflags("/GR-") add_ldflags("/SUBSYSTEM:WINDOWS") - add_syslinks("shell32", "user32", "gdi32", "advapi32", "comctl32", "ole32", "dwmapi", "shcore") + add_syslinks("shell32", "user32", "gdi32", "advapi32", "comctl32", "ole32", "dwmapi", "shcore", "gdiplus") add_packages("stb") From af5c4e5fa31eb3d0fc96807033ce224ba24471f7 Mon Sep 17 00:00:00 2001 From: chooyy Date: Sat, 29 Aug 2026 15:12:01 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/Application.cpp | 2 +- src/core/VirtualDesktopHelper.cpp | 2 +- src/core/VirtualDesktopSwitcher.cpp | 2 +- src/core/VirtualDesktopSwitcher.h | 2 +- src/ui/DesktopIndicator.cpp | 2 +- src/ui/TrayIcon.cpp | 96 ++++++++++++++--------------- src/ui/TrayIcon.h | 33 +++++----- src/util/Utils.h | 2 +- 8 files changed, 68 insertions(+), 73 deletions(-) diff --git a/src/core/Application.cpp b/src/core/Application.cpp index 04260db..cad3ed6 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -243,7 +243,7 @@ void Application::LoadConfiguration() { static_cast(ReadIniInt(L"General", L"PrevDesktopKey", VK_OEM_3))); VirtualDesktopSwitcher::SetPinAllDesktopsKey( static_cast(ReadIniInt(L"General", L"PinAllDesktopsKey", 'D'))); - for (int i = 0; i < static_cast(kMaxDesktops); ++i) { + for (int i = 0; i < kMaxDesktops; ++i) { std::wstring keyName = L"DesktopKey" + std::to_wstring(i + 1); VirtualDesktopSwitcher::SetDesktopKey(i, static_cast(ReadIniInt(L"General", keyName, '1' + i))); diff --git a/src/core/VirtualDesktopHelper.cpp b/src/core/VirtualDesktopHelper.cpp index 5cb6336..b079703 100644 --- a/src/core/VirtualDesktopHelper.cpp +++ b/src/core/VirtualDesktopHelper.cpp @@ -436,7 +436,7 @@ std::array VirtualDesktopHelper::GetDesktopEmptyMask() const } } - for (UINT i = 0; i < desktopCount && i < kMaxDesktops; ++i) { + for (UINT i = 0; i < desktopCount && i < static_cast(kMaxDesktops); ++i) { emptyMask.at(i) = (windowCounts.at(i) == 0); } return emptyMask; diff --git a/src/core/VirtualDesktopSwitcher.cpp b/src/core/VirtualDesktopSwitcher.cpp index 9561e2e..ffc25f2 100644 --- a/src/core/VirtualDesktopSwitcher.cpp +++ b/src/core/VirtualDesktopSwitcher.cpp @@ -88,7 +88,7 @@ LRESULT CALLBACK VirtualDesktopSwitcher::LowLevelKeyboardProc(int nCode, WPARAM bool match = held != 0 && held == s_modMask; if (match) { - for (int i = 0; i < static_cast(kMaxDesktops); ++i) { + for (int i = 0; i < kMaxDesktops; ++i) { if (pKeyboard->vkCode == s_desktopKeys.at(i)) { PostMessage(s_active->m_hwnd, WM_SWITCH_DESKTOP, i, 0); return 1; diff --git a/src/core/VirtualDesktopSwitcher.h b/src/core/VirtualDesktopSwitcher.h index b3c09d8..595e72d 100644 --- a/src/core/VirtualDesktopSwitcher.h +++ b/src/core/VirtualDesktopSwitcher.h @@ -43,7 +43,7 @@ class VirtualDesktopSwitcher { static void SetPrevDesktopKey(uint8_t vk) { s_prevDesktopKey = vk; } static void SetPinAllDesktopsKey(uint8_t vk) { s_pinAllDesktopsKey = vk; } static void SetDesktopKey(int index, uint8_t vk) { - if (index >= 0 && index < static_cast(kMaxDesktops)) { s_desktopKeys.at(index) = vk; } + if (index >= 0 && index < kMaxDesktops) { s_desktopKeys.at(index) = vk; } } [[nodiscard]] int GetDesktopCount() const; diff --git a/src/ui/DesktopIndicator.cpp b/src/ui/DesktopIndicator.cpp index d27dabd..6468b8e 100644 --- a/src/ui/DesktopIndicator.cpp +++ b/src/ui/DesktopIndicator.cpp @@ -563,7 +563,7 @@ void DesktopIndicator::RebuildText() { return; } std::wstring text; - for (int i = 0; i < m_desktopCount && i < static_cast(kMaxDesktops); ++i) { + for (int i = 0; i < m_desktopCount && i < kMaxDesktops; ++i) { text += (i == m_currentDesktop) ? m_pCfg->currentSymbol : m_emptyDesktops.at(i) ? m_pCfg->emptySymbol : m_pCfg->otherSymbol; diff --git a/src/ui/TrayIcon.cpp b/src/ui/TrayIcon.cpp index cb8a9a5..a9b9ed4 100644 --- a/src/ui/TrayIcon.cpp +++ b/src/ui/TrayIcon.cpp @@ -1,5 +1,6 @@ #include "TrayIcon.h" +#include #include #include @@ -10,8 +11,6 @@ #include "util/Lang.h" #include "util/Utils.h" -#pragma comment(lib, "gdiplus.lib") - namespace { constexpr UINT WM_TRAY_EXIT = WM_USER + 3; @@ -85,28 +84,30 @@ void DrawSwatchRect(HDC hdc, RECT rect, const std::wstring &hex) { DeleteObject(hp); } -ULONG_PTR g_gdiplusToken = 0; - -// EnsureGdiplus 初始化 GDI+(幂等),成功返回 true -bool EnsureGdiplus() { - if (g_gdiplusToken != 0) { - return true; +// GdiplusScope RAII:构造时初始化 GDI+,析构时释放 +class GdiplusScope { +public: + GdiplusScope() { + Gdiplus::GdiplusStartupInput input; + if (Gdiplus::GdiplusStartup(&m_token, &input, nullptr) != Gdiplus::Ok) { + m_token = 0; + } } - Gdiplus::GdiplusStartupInput input; - if (Gdiplus::GdiplusStartup(&g_gdiplusToken, &input, nullptr) != Gdiplus::Ok) { - g_gdiplusToken = 0; - return false; + ~GdiplusScope() { + if (m_token != 0) { + Gdiplus::GdiplusShutdown(m_token); + } } - return true; -} + GdiplusScope(const GdiplusScope &) = delete; + GdiplusScope &operator=(const GdiplusScope &) = delete; + GdiplusScope(GdiplusScope &&) = delete; + GdiplusScope &operator=(GdiplusScope &&) = delete; -// ShutdownGdiplus 释放 GDI+ 资源 -void ShutdownGdiplus() { - if (g_gdiplusToken != 0) { - Gdiplus::GdiplusShutdown(g_gdiplusToken); - g_gdiplusToken = 0; - } -} + [[nodiscard]] bool Ok() const { return m_token != 0; } + +private: + ULONG_PTR m_token = 0; +}; } // namespace @@ -117,13 +118,12 @@ TrayIcon::~TrayIcon() { if (m_hMenu != nullptr) { DestroyMenu(m_hMenu); } - for (int i = 0; i <= kTrayNumberMax; ++i) { + for (int i = 0; i <= kMaxDesktops; ++i) { if (m_hNumberIcons[i] != nullptr) { DestroyIcon(m_hNumberIcons[i]); m_hNumberIcons[i] = nullptr; } } - ShutdownGdiplus(); } void TrayIcon::BuildMenu() { @@ -213,7 +213,6 @@ bool TrayIcon::Initialize(HWND hwnd, HINSTANCE hInstance) { m_nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; m_nid.uCallbackMessage = WM_TRAYICON; m_nid.hIcon = hIcon; - m_nTrayNumber = -1; UpdateTooltip(Lang::Get(L"Tray.DefaultTip")); @@ -485,11 +484,12 @@ void TrayIcon::UpdateTrayIcon(int displayNumber) { nid.uFlags = NIF_ICON; nid.hIcon = GetTrayIconForNumber(displayNumber); Shell_NotifyIconW(NIM_MODIFY, &nid); + m_nid.hIcon = nid.hIcon; m_nTrayNumber = displayNumber; } HICON TrayIcon::GetTrayIconForNumber(int nDisplay) { - if (nDisplay >= 1 && nDisplay <= kTrayNumberMax) { + if (nDisplay >= 1 && nDisplay <= kMaxDesktops) { HICON hIcon = GetNumberIcon(nDisplay); if (hIcon != nullptr) { return hIcon; @@ -499,7 +499,7 @@ HICON TrayIcon::GetTrayIconForNumber(int nDisplay) { } HICON TrayIcon::GetNumberIcon(int nNumber) { - if (nNumber < 1 || nNumber > kTrayNumberMax) { + if (nNumber < 1 || nNumber > kMaxDesktops) { return nullptr; } if (m_hNumberIcons[nNumber] == nullptr) { @@ -509,39 +509,33 @@ HICON TrayIcon::GetNumberIcon(int nNumber) { } HICON TrayIcon::CreateNumberIcon(int nNumber) { - using namespace Gdiplus; - - if (!EnsureGdiplus()) { + GdiplusScope gdiplus; + if (!gdiplus.Ok()) { return nullptr; } - int nSize = GetSystemMetrics(SM_CXSMICON); - if (nSize < 16) { - nSize = 16; - } - if (nSize > 64) { - nSize = 64; - } + int nSize = std::clamp(GetSystemMetrics(SM_CXSMICON), 16, 64); - Bitmap bmp(nSize, nSize, PixelFormat32bppARGB); - Graphics graphics(&bmp); - graphics.SetSmoothingMode(SmoothingModeAntiAlias); - graphics.SetTextRenderingHint(TextRenderingHintAntiAliasGridFit); + Gdiplus::Bitmap bmp(nSize, nSize, PixelFormat32bppARGB); + Gdiplus::Graphics graphics(&bmp); + graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAliasGridFit); - wchar_t szNum[8] = {}; - swprintf_s(szNum, 8, L"%d", nNumber); + std::wstring szNum = std::to_wstring(nNumber); - FontFamily fontFamily(L"Segoe UI"); - Font font(&fontFamily, static_cast(nSize * 0.85f), FontStyleBold, UnitPixel); - SolidBrush brushText(Color(255, 41, 151, 255)); - StringFormat format; - format.SetAlignment(StringAlignmentCenter); - format.SetLineAlignment(StringAlignmentCenter); - RectF rectText(0.0f, 0.0f, static_cast(nSize), static_cast(nSize)); - graphics.DrawString(szNum, -1, &font, rectText, &format, &brushText); + Gdiplus::FontFamily fontFamily(L"Segoe UI"); + constexpr BYTE kA = 255, kR = 41, kG = 151, kB = 255; + Gdiplus::Font font(&fontFamily, static_cast(nSize) * 0.85f, + Gdiplus::FontStyleBold, Gdiplus::UnitPixel); + Gdiplus::SolidBrush brushText(Gdiplus::Color(kA, kR, kG, kB)); + Gdiplus::StringFormat format; + format.SetAlignment(Gdiplus::StringAlignmentCenter); + format.SetLineAlignment(Gdiplus::StringAlignmentCenter); + Gdiplus::RectF rectText(0.0f, 0.0f, static_cast(nSize), static_cast(nSize)); + graphics.DrawString(szNum.c_str(), -1, &font, rectText, &format, &brushText); HICON hIcon = nullptr; - if (bmp.GetHICON(&hIcon) != Ok) { + if (bmp.GetHICON(&hIcon) != Gdiplus::Ok) { hIcon = nullptr; } return hIcon; diff --git a/src/ui/TrayIcon.h b/src/ui/TrayIcon.h index 9dcc418..6e09436 100644 --- a/src/ui/TrayIcon.h +++ b/src/ui/TrayIcon.h @@ -4,10 +4,12 @@ #include +#include #include #include #include "core/IndicatorConfig.h" +#include "util/Utils.h" constexpr UINT WM_TRAYICON = WM_USER + 2; constexpr UINT WM_TRAY_LANG_CHINESE = WM_USER + 50; @@ -15,7 +17,6 @@ constexpr UINT WM_TRAY_LANG_ENGLISH = WM_USER + 51; constexpr UINT CMD_COLOR_OPTIONS_BASE = WM_USER + 100; constexpr int kTrayDefaultIconResource = 101; -constexpr int kTrayNumberMax = 9; class TrayIcon { public: @@ -46,15 +47,15 @@ class TrayIcon { void SetDragSwitchModeCallback(std::function cb) { m_dragModeFn = std::move(cb); } private: - NOTIFYICONDATAW m_nid{}; - HINSTANCE m_hInstance = nullptr; - HMENU m_hMenu = nullptr; - bool m_autoStartEnabled = false; - PositionPreset m_activePositionPreset = PositionPreset::TopCenter; - int m_menuAveWidth = 6; - int m_dpi = 96; - int m_nTrayNumber = -1; - HICON m_hNumberIcons[kTrayNumberMax + 1] = {}; + NOTIFYICONDATAW m_nid{}; + HINSTANCE m_hInstance = nullptr; + HMENU m_hMenu = nullptr; + bool m_autoStartEnabled = false; + PositionPreset m_activePositionPreset = PositionPreset::TopCenter; + int m_menuAveWidth = 6; + int m_dpi = 96; + int m_nTrayNumber = -1; + std::array m_hNumberIcons{}; std::function m_colorFn; std::function m_editModeFn; @@ -67,12 +68,12 @@ class TrayIcon { std::function m_autoFocusFn; std::function m_dragModeFn; - void BuildMenu(); - void HandleCommand(WPARAM wParam); - void DrawColorSwatch(LPDRAWITEMSTRUCT dis) const; - HICON GetNumberIcon(int nNumber); - HICON GetTrayIconForNumber(int nDisplay); - HICON CreateNumberIcon(int nNumber); + void BuildMenu(); + void HandleCommand(WPARAM wParam); + void DrawColorSwatch(LPDRAWITEMSTRUCT dis) const; + HICON GetNumberIcon(int nNumber); + HICON GetTrayIconForNumber(int nDisplay); + static HICON CreateNumberIcon(int nNumber); static void HandleRunAsAdmin(); static void HandleReset(); diff --git a/src/util/Utils.h b/src/util/Utils.h index 0556716..4f76d72 100644 --- a/src/util/Utils.h +++ b/src/util/Utils.h @@ -7,7 +7,7 @@ // --- Constants --- -constexpr size_t kMaxDesktops = 9; +constexpr int kMaxDesktops = 9; constexpr std::array kPredefinedColors = { L"#FFFFFF", From f72be0017766176958daeb092bc2cd11b6d6214c Mon Sep 17 00:00:00 2001 From: chooyy Date: Sat, 29 Aug 2026 15:51:19 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=89=98=E7=9B=98?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/TrayIcon.cpp | 69 ++++++++++++++++++++++++++++++++++++++------- src/ui/TrayIcon.h | 18 +++++++----- xmake.lua | 2 +- 3 files changed, 71 insertions(+), 18 deletions(-) diff --git a/src/ui/TrayIcon.cpp b/src/ui/TrayIcon.cpp index 8a8f70f..596e657 100644 --- a/src/ui/TrayIcon.cpp +++ b/src/ui/TrayIcon.cpp @@ -2,6 +2,8 @@ #include +#include + #include "core/IndicatorConfig.h" #include "util/AutoStart.h" #include "util/ConfigIni.h" @@ -53,19 +55,64 @@ constexpr std::array kDragModeKeys = { L"Menu.DragNever", }; -void DrawSwatchRect(HDC hdc, RECT rect, const std::wstring &hex) { +} // namespace + +// GdiplusGuard RAII:GDI+ 生命周期由 TrayIcon 持有(构造启动、析构关闭) +class GdiplusGuard { +public: + GdiplusGuard() { + Gdiplus::GdiplusStartupInput input; + if (Gdiplus::GdiplusStartup(&m_token, &input, nullptr) != Gdiplus::Ok) { + m_token = 0; + } + } + ~GdiplusGuard() { + if (m_token != 0) { + Gdiplus::GdiplusShutdown(m_token); + } + } + GdiplusGuard(const GdiplusGuard &) = delete; + GdiplusGuard &operator=(const GdiplusGuard &) = delete; + GdiplusGuard(GdiplusGuard &&) = delete; + GdiplusGuard &operator=(GdiplusGuard &&) = delete; + + [[nodiscard]] bool Ok() const { return m_token != 0; } + +private: + ULONG_PTR m_token = 0; +}; + +namespace { + +void DrawSwatchRect(HDC hdc, RECT rect, const std::wstring &hex, GdiplusGuard &gdiplus) { auto colors = ParseMultiColorString(hex); - if (colors.count >= 2) { - const int sw = rect.right - rect.left; - const int sh = rect.bottom - rect.top; - for (int sx = 0; sx < sw; ++sx) { - COLORREF col = InterpolateGradientColor(colors.colors.data(), colors.count, static_cast(sx) / static_cast(sw)); - for (int sy = 0; sy < sh; ++sy) { - SetPixel(hdc, rect.left + sx, rect.top + sy, col); + if (colors.count >= 2 && gdiplus.Ok()) { + std::array gdColors{}; + for (size_t i = 0; i < colors.count; ++i) { + gdColors[i] = Gdiplus::Color(255, GetRValue(colors.colors[i]), + GetGValue(colors.colors[i]), GetBValue(colors.colors[i])); + } + Gdiplus::LinearGradientBrush brush( + Gdiplus::RectF(static_cast(rect.left), static_cast(rect.top), + static_cast(rect.right - rect.left), + static_cast(rect.bottom - rect.top)), + gdColors[0], gdColors[colors.count - 1], Gdiplus::LinearGradientModeHorizontal); + if (colors.count > 2) { + std::array positions{}; + for (size_t i = 0; i < colors.count; ++i) { + positions[i] = static_cast(i) / static_cast(colors.count - 1); } + brush.SetInterpolationColors(gdColors.data(), positions.data(), static_cast(colors.count)); } - } else if (colors.count == 1) { + { + Gdiplus::Graphics g(hdc); + g.FillRectangle(&brush, + static_cast(rect.left), static_cast(rect.top), + static_cast(rect.right - rect.left), + static_cast(rect.bottom - rect.top)); + } + } else if (colors.count >= 1) { HBRUSH hb = CreateSolidBrush(colors.colors[0]); FillRect(hdc, &rect, hb); DeleteObject(hb); @@ -83,6 +130,8 @@ void DrawSwatchRect(HDC hdc, RECT rect, const std::wstring &hex) { } // namespace +TrayIcon::TrayIcon() : m_gdiplus(std::make_unique()) {} + TrayIcon::~TrayIcon() { if (m_nid.hWnd != nullptr) { Shell_NotifyIconW(NIM_DELETE, &m_nid); @@ -410,7 +459,7 @@ void TrayIcon::DrawColorSwatch(LPDRAWITEMSTRUCT dis) const { cr.bottom -= MulDiv(1, m_dpi, 96); cr.right = dis->rcItem.right - pad; - DrawSwatchRect(dis->hDC, cr, kPredefinedColors.at(colorIndex)); + DrawSwatchRect(dis->hDC, cr, kPredefinedColors.at(colorIndex), *m_gdiplus); // Number label const std::wstring numStr = std::to_wstring(colorIndex + 1); diff --git a/src/ui/TrayIcon.h b/src/ui/TrayIcon.h index 67c0fb2..baa7560 100644 --- a/src/ui/TrayIcon.h +++ b/src/ui/TrayIcon.h @@ -5,6 +5,7 @@ #include #include +#include #include #include "core/IndicatorConfig.h" @@ -14,6 +15,8 @@ constexpr UINT WM_TRAY_LANG_CHINESE = WM_USER + 50; constexpr UINT WM_TRAY_LANG_ENGLISH = WM_USER + 51; constexpr UINT CMD_COLOR_OPTIONS_BASE = WM_USER + 100; +class GdiplusGuard; + class TrayIcon { public: TrayIcon(const TrayIcon &) = delete; @@ -21,7 +24,7 @@ class TrayIcon { TrayIcon(TrayIcon &&) = delete; TrayIcon &operator=(TrayIcon &&) = delete; - TrayIcon() = default; + TrayIcon(); ~TrayIcon(); bool Initialize(HWND hwnd, HINSTANCE hInstance); @@ -42,12 +45,13 @@ class TrayIcon { void SetDragSwitchModeCallback(std::function cb) { m_dragModeFn = std::move(cb); } private: - NOTIFYICONDATAW m_nid{}; - HMENU m_hMenu = nullptr; - bool m_autoStartEnabled = false; - PositionPreset m_activePositionPreset = PositionPreset::TopCenter; - int m_menuAveWidth = 6; - int m_dpi = 96; + NOTIFYICONDATAW m_nid{}; + std::unique_ptr m_gdiplus; + HMENU m_hMenu = nullptr; + bool m_autoStartEnabled = false; + PositionPreset m_activePositionPreset = PositionPreset::TopCenter; + int m_menuAveWidth = 6; + int m_dpi = 96; std::function m_colorFn; std::function m_editModeFn; diff --git a/xmake.lua b/xmake.lua index 48c7526..75ac084 100644 --- a/xmake.lua +++ b/xmake.lua @@ -20,6 +20,6 @@ target("VirtualDesktopSwitcher") add_defines("NOMINMAX") add_cxflags("/GR-") add_ldflags("/SUBSYSTEM:WINDOWS") - add_syslinks("shell32", "user32", "gdi32", "advapi32", "comctl32", "ole32", "dwmapi", "shcore") + add_syslinks("shell32", "user32", "gdi32", "advapi32", "comctl32", "ole32", "dwmapi", "shcore", "gdiplus") add_packages("stb") From f14b337fa38e6e161d6e0451a4283b2140a3bb74 Mon Sep 17 00:00:00 2001 From: chooyy Date: Sat, 29 Aug 2026 16:13:28 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/IndicatorConfig.h | 6 + src/ui/TrayIcon.cpp | 226 +++++++++++++++++++++++-------------- src/ui/TrayIcon.h | 23 ++-- src/util/LangData_en.inc | 4 + src/util/LangData_zh.inc | 4 + src/util/Utils.h | 13 ++- 6 files changed, 178 insertions(+), 98 deletions(-) diff --git a/src/core/IndicatorConfig.h b/src/core/IndicatorConfig.h index 5d1a093..015dd41 100644 --- a/src/core/IndicatorConfig.h +++ b/src/core/IndicatorConfig.h @@ -30,6 +30,12 @@ enum class DragSwitchMode : std::uint8_t { Count = 4 }; +enum class TrayIconMode : std::uint8_t { + Icon = 0, + Number = 1, + Count = 2 +}; + struct WindowRatio { float x = 0.5f; float y = 0.0f; diff --git a/src/ui/TrayIcon.cpp b/src/ui/TrayIcon.cpp index 05ef989..97e89a3 100644 --- a/src/ui/TrayIcon.cpp +++ b/src/ui/TrayIcon.cpp @@ -13,22 +13,22 @@ namespace { -constexpr UINT WM_TRAY_EXIT = WM_USER + 3; -constexpr UINT WM_TRAY_TOGGLE_AUTOSTART = WM_USER + 4; -constexpr UINT WM_TRAY_SETTINGS = WM_USER + 6; -constexpr UINT WM_TRAY_ABOUT = WM_USER + 7; -constexpr UINT WM_TRAY_RUNAS_ADMIN = WM_USER + 8; -constexpr UINT WM_TRAY_ANIM_MODE = WM_USER + 9; -constexpr UINT WM_TRAY_TOGGLE_SHOW = WM_USER + 10; -constexpr UINT WM_TRAY_RESET = WM_USER + 11; -constexpr UINT WM_TRAY_AUTO_CONTRAST = WM_USER + 12; -constexpr UINT WM_TRAY_AUTO_FOCUS = WM_USER + 13; -constexpr UINT CMD_SHOW_MODE_BASE = WM_USER + 300; -constexpr UINT CMD_SHOW_MODE_CUSTOM = CMD_SHOW_MODE_BASE + static_cast(ShowMode::Count); -constexpr UINT CMD_POSITION_BASE = WM_USER + 200; -constexpr UINT CMD_POSITION_CUSTOM = CMD_POSITION_BASE + static_cast(PositionPreset::Count) + 1; -constexpr UINT CMD_DRAG_MODE_BASE = WM_USER + 400; -constexpr UINT CMD_DRAG_MODE_CUSTOM = CMD_DRAG_MODE_BASE + static_cast(DragSwitchMode::Count); +constexpr UINT CMD_TRAY_EXIT = WM_USER + 3; +constexpr UINT CMD_TRAY_TOGGLE_AUTOSTART = WM_USER + 4; +constexpr UINT CMD_TRAY_SETTINGS = WM_USER + 6; +constexpr UINT CMD_TRAY_ABOUT = WM_USER + 7; +constexpr UINT CMD_TRAY_RUNAS_ADMIN = WM_USER + 8; +constexpr UINT CMD_TRAY_ANIM_MODE = WM_USER + 9; +constexpr UINT CMD_TRAY_TOGGLE_SHOW = WM_USER + 10; +constexpr UINT CMD_TRAY_RESET = WM_USER + 11; +constexpr UINT CMD_TRAY_AUTO_CONTRAST = WM_USER + 12; +constexpr UINT CMD_TRAY_AUTO_FOCUS = WM_USER + 13; +constexpr UINT CMD_SHOW_MODE_BASE = WM_USER + 300; +constexpr UINT CMD_SHOW_MODE_CUSTOM = CMD_SHOW_MODE_BASE + static_cast(ShowMode::Count); +constexpr UINT CMD_POSITION_BASE = WM_USER + 200; +constexpr UINT CMD_POSITION_CUSTOM = CMD_POSITION_BASE + static_cast(PositionPreset::Count) + 1; +constexpr UINT CMD_DRAG_MODE_BASE = WM_USER + 400; +constexpr UINT CMD_DRAG_MODE_CUSTOM = CMD_DRAG_MODE_BASE + static_cast(DragSwitchMode::Count); constexpr std::array kShowModeKeys = { L"Menu.AlwaysShow", @@ -129,31 +129,6 @@ void DrawSwatchRect(HDC hdc, RECT rect, const std::wstring &hex, GdiplusGuard &g DeleteObject(hp); } -// GdiplusScope RAII:构造时初始化 GDI+,析构时释放 -class GdiplusScope { -public: - GdiplusScope() { - Gdiplus::GdiplusStartupInput input; - if (Gdiplus::GdiplusStartup(&m_token, &input, nullptr) != Gdiplus::Ok) { - m_token = 0; - } - } - ~GdiplusScope() { - if (m_token != 0) { - Gdiplus::GdiplusShutdown(m_token); - } - } - GdiplusScope(const GdiplusScope &) = delete; - GdiplusScope &operator=(const GdiplusScope &) = delete; - GdiplusScope(GdiplusScope &&) = delete; - GdiplusScope &operator=(GdiplusScope &&) = delete; - - [[nodiscard]] bool Ok() const { return m_token != 0; } - -private: - ULONG_PTR m_token = 0; -}; - } // namespace TrayIcon::TrayIcon() : m_gdiplus(std::make_unique()) {} @@ -215,35 +190,61 @@ void TrayIcon::BuildMenu() { } AppendMenuW(hColorMenu, MF_SEPARATOR, 0, nullptr); bool animOn = ReadIniInt(L"Display", L"AnimMode", 1) != 0; - AppendMenuW(hColorMenu, MF_STRING | (animOn ? MF_CHECKED : 0), WM_TRAY_ANIM_MODE, Lang::Get(L"Menu.BreathingRing")); + AppendMenuW(hColorMenu, MF_STRING | (animOn ? MF_CHECKED : 0), CMD_TRAY_ANIM_MODE, Lang::Get(L"Menu.BreathingRing")); bool autoContrastOn = ReadIniInt(L"Display", L"AutoContrast", 1) != 0; - AppendMenuW(hColorMenu, MF_STRING | (autoContrastOn ? MF_CHECKED : 0), WM_TRAY_AUTO_CONTRAST, Lang::Get(L"Menu.AutoContrast")); + AppendMenuW(hColorMenu, MF_STRING | (autoContrastOn ? MF_CHECKED : 0), CMD_TRAY_AUTO_CONTRAST, Lang::Get(L"Menu.AutoContrast")); AppendMenuW(m_hMenu, MF_POPUP, reinterpret_cast(hColorMenu), Lang::Get(L"Menu.Color")); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - AppendMenuW(m_hMenu, MF_STRING, WM_TRAY_SETTINGS, Lang::Get(L"Menu.Settings")); + HMENU hTrayIconMenu = CreatePopupMenu(); + int curIconMode = ReadIniInt(L"Display", L"TrayIconMode", static_cast(TrayIconMode::Icon)); + AppendMenuW(hTrayIconMenu, MF_STRING | (curIconMode == static_cast(TrayIconMode::Icon) ? MF_CHECKED : 0), + CMD_TRAY_ICON_MODE_ICON, Lang::Get(L"Menu.TrayIconIcon")); + AppendMenuW(hTrayIconMenu, MF_STRING | (curIconMode == static_cast(TrayIconMode::Number) ? MF_CHECKED : 0), + CMD_TRAY_ICON_MODE_NUMBER, Lang::Get(L"Menu.TrayIconNumber")); + AppendMenuW(hTrayIconMenu, MF_SEPARATOR, 0, nullptr); + + HMENU hNumColorMenu = CreatePopupMenu(); + std::wstring curNumColor = ReadIniString(L"Display", L"TrayNumberColor", L"#2997FF"); + UINT numColorIdx = 0; + for (UINT i = 0; i < static_cast(kPredefinedColors.size()); ++i) { + if (std::wstring(kPredefinedColors.at(i)).find(L'_') != std::wstring::npos) { continue; } + MENUITEMINFOW mii = {}; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STATE; + mii.fType = MFT_OWNERDRAW; + mii.wID = CMD_TRAY_NUMBER_COLOR_BASE + i; + if (wcscmp(kPredefinedColors.at(i), curNumColor.c_str()) == 0) { + mii.fState = MFS_CHECKED; + } + InsertMenuItemW(hNumColorMenu, numColorIdx++, TRUE, &mii); + } + AppendMenuW(hTrayIconMenu, MF_POPUP, reinterpret_cast(hNumColorMenu), Lang::Get(L"Menu.TrayNumberColor")); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + AppendMenuW(m_hMenu, MF_POPUP, reinterpret_cast(hTrayIconMenu), Lang::Get(L"Menu.TrayIcon")); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + + AppendMenuW(m_hMenu, MF_STRING, CMD_TRAY_SETTINGS, Lang::Get(L"Menu.Settings")); AppendMenuW(m_hMenu, MF_SEPARATOR, 0, nullptr); HMENU hLangMenu = CreatePopupMenu(); - AppendMenuW(hLangMenu, MF_STRING | (Lang::Current() == LangType::Chinese ? MF_CHECKED : 0), WM_TRAY_LANG_CHINESE, Lang::Get(L"Menu.LangChinese")); - AppendMenuW(hLangMenu, MF_STRING | (Lang::Current() == LangType::English ? MF_CHECKED : 0), WM_TRAY_LANG_ENGLISH, Lang::Get(L"Menu.LangEnglish")); + AppendMenuW(hLangMenu, MF_STRING | (Lang::Current() == LangType::Chinese ? MF_CHECKED : 0), CMD_TRAY_LANG_CHINESE, Lang::Get(L"Menu.LangChinese")); + AppendMenuW(hLangMenu, MF_STRING | (Lang::Current() == LangType::English ? MF_CHECKED : 0), CMD_TRAY_LANG_ENGLISH, Lang::Get(L"Menu.LangEnglish")); AppendMenuW(m_hMenu, MF_POPUP, reinterpret_cast(hLangMenu), Lang::Get(L"Menu.Language")); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) bool autoFocusOn = ReadIniInt(L"Display", L"AutoFocus", 1) != 0; AppendMenuW(m_hMenu, MF_STRING | (autoFocusOn ? MF_CHECKED : 0), - WM_TRAY_AUTO_FOCUS, Lang::Get(L"Menu.AutoFocus")); + CMD_TRAY_AUTO_FOCUS, Lang::Get(L"Menu.AutoFocus")); bool runAsAdmin = ReadIniInt(L"General", L"RunAsAdmin", 0) != 0; AppendMenuW(m_hMenu, MF_STRING | (runAsAdmin ? MF_CHECKED : MF_UNCHECKED), - WM_TRAY_RUNAS_ADMIN, Lang::Get(L"Menu.RunAsAdmin")); + CMD_TRAY_RUNAS_ADMIN, Lang::Get(L"Menu.RunAsAdmin")); AppendMenuW(m_hMenu, MF_STRING | (m_autoStartEnabled ? MF_CHECKED : MF_UNCHECKED), - WM_TRAY_TOGGLE_AUTOSTART, Lang::Get(L"Menu.AutoStart")); + CMD_TRAY_TOGGLE_AUTOSTART, Lang::Get(L"Menu.AutoStart")); AppendMenuW(m_hMenu, MF_SEPARATOR, 0, nullptr); - AppendMenuW(m_hMenu, MF_STRING, WM_TRAY_RESET, Lang::Get(L"Menu.Reset")); - AppendMenuW(m_hMenu, MF_STRING, WM_TRAY_ABOUT, Lang::Get(L"Menu.About")); + AppendMenuW(m_hMenu, MF_STRING, CMD_TRAY_RESET, Lang::Get(L"Menu.Reset")); + AppendMenuW(m_hMenu, MF_STRING, CMD_TRAY_ABOUT, Lang::Get(L"Menu.About")); AppendMenuW(m_hMenu, MF_SEPARATOR, 0, nullptr); - AppendMenuW(m_hMenu, MF_STRING, WM_TRAY_EXIT, Lang::Get(L"Menu.Exit")); + AppendMenuW(m_hMenu, MF_STRING, CMD_TRAY_EXIT, Lang::Get(L"Menu.Exit")); } bool TrayIcon::Initialize(HWND hwnd, HINSTANCE hInstance) { @@ -261,6 +262,15 @@ bool TrayIcon::Initialize(HWND hwnd, HINSTANCE hInstance) { m_nid.uCallbackMessage = WM_TRAYICON; m_nid.hIcon = hIcon; + int modeVal = ReadIniInt(L"Display", L"TrayIconMode", static_cast(TrayIconMode::Icon)); + m_iconMode = (modeVal >= 0 && modeVal < static_cast(TrayIconMode::Count)) + ? static_cast(modeVal) + : TrayIconMode::Icon; + + std::wstring numColor = ReadIniString(L"Display", L"TrayNumberColor", L"#2997FF"); + auto colorArr = ParseMultiColorString(numColor); + m_numberColor = (colorArr.count > 0) ? colorArr.colors[0] : RGB(41, 151, 255); + UpdateTooltip(Lang::Get(L"Tray.DefaultTip")); if (Shell_NotifyIconW(NIM_ADD, &m_nid) == 0) { @@ -286,7 +296,7 @@ void TrayIcon::HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) TrackPopupMenu(m_hMenu, TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, nullptr); PostMessage(hwnd, WM_NULL, 0, 0); } else if (lParam == WM_LBUTTONDBLCLK) { - PostMessage(hwnd, WM_COMMAND, WM_TRAY_TOGGLE_SHOW, 0); + PostMessage(hwnd, WM_COMMAND, CMD_TRAY_TOGGLE_SHOW, 0); } break; @@ -304,7 +314,6 @@ void TrayIcon::HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) HGDIOBJ old = SelectObject(hdc, hFont); TEXTMETRICW tm{}; GetTextMetricsW(hdc, &tm); - m_menuAveWidth = tm.tmAveCharWidth; SelectObject(hdc, old); DeleteObject(hFont); ReleaseDC(hwnd, hdc); @@ -318,7 +327,12 @@ void TrayIcon::HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) case WM_DRAWITEM: { auto *dis = LParamToPtr(lParam); if (dis->CtlType == ODT_MENU) { - DrawColorSwatch(dis); + if (dis->itemID >= CMD_TRAY_NUMBER_COLOR_BASE + && dis->itemID < CMD_TRAY_NUMBER_COLOR_BASE + static_cast(kPredefinedColors.size())) { + DrawColorSwatch(dis, CMD_TRAY_NUMBER_COLOR_BASE); + } else { + DrawColorSwatch(dis, CMD_COLOR_OPTIONS_BASE); + } } break; } @@ -407,6 +421,37 @@ void TrayIcon::HandleColorCommand(int index) { if (m_colorFn) { m_colorFn(kPredefinedColors.at(index)); } } +void TrayIcon::SetTrayIconMode(TrayIconMode mode) { + if (m_iconMode == mode) { return; } + m_iconMode = mode; + WriteIniInt(L"Display", L"TrayIconMode", static_cast(mode)); + m_nTrayNumber = -1; + if (mode == TrayIconMode::Icon) { + UpdateTrayIcon(1); + } +} + +void TrayIcon::SetNumberColor(const std::wstring &hexColor) { + auto colorArr = ParseMultiColorString(hexColor); + m_numberColor = (colorArr.count > 0) ? colorArr.colors[0] : RGB(41, 151, 255); + WriteIniString(L"Display", L"TrayNumberColor", hexColor); + for (auto &hIcon : m_hNumberIcons) { + if (hIcon != nullptr) { + DestroyIcon(hIcon); + hIcon = nullptr; + } + } + if (m_iconMode == TrayIconMode::Number && m_nTrayNumber >= 1) { + int num = m_nTrayNumber; + m_nTrayNumber = -1; + UpdateTrayIcon(num); + } +} + +void TrayIcon::HandleNumberColorCommand(int colorIndex) { + SetNumberColor(kPredefinedColors.at(colorIndex)); +} + void TrayIcon::HandleCommand(WPARAM wParam) { const UINT cmd = LOWORD(wParam); @@ -426,42 +471,48 @@ void TrayIcon::HandleCommand(WPARAM wParam) { HandleColorCommand(static_cast(cmd - CMD_COLOR_OPTIONS_BASE)); return; } + if (cmd >= CMD_TRAY_NUMBER_COLOR_BASE && cmd < CMD_TRAY_NUMBER_COLOR_BASE + static_cast(kPredefinedColors.size())) { + HandleNumberColorCommand(static_cast(cmd - CMD_TRAY_NUMBER_COLOR_BASE)); + return; + } switch (cmd) { - case WM_TRAY_EXIT: HandleExit(); break; - case WM_TRAY_TOGGLE_AUTOSTART: HandleToggleAutoStart(); break; - case WM_TRAY_RUNAS_ADMIN: HandleRunAsAdmin(); break; + case CMD_TRAY_EXIT: HandleExit(); break; + case CMD_TRAY_TOGGLE_AUTOSTART: HandleToggleAutoStart(); break; + case CMD_TRAY_RUNAS_ADMIN: HandleRunAsAdmin(); break; + case CMD_TRAY_ICON_MODE_ICON: SetTrayIconMode(TrayIconMode::Icon); break; + case CMD_TRAY_ICON_MODE_NUMBER: SetTrayIconMode(TrayIconMode::Number); break; case CMD_POSITION_CUSTOM: m_activePositionPreset = PositionPreset::Custom; if (m_editModeFn) { m_editModeFn(); } break; - case WM_TRAY_SETTINGS: + case CMD_TRAY_SETTINGS: if (m_settingsFn) { m_settingsFn(); } break; - case WM_TRAY_LANG_CHINESE: + case CMD_TRAY_LANG_CHINESE: Lang::Set(LangType::Chinese); WriteIniInt(L"General", L"Language", 0); UpdateTooltip(Lang::Get(L"Tray.DefaultTip")); break; - case WM_TRAY_LANG_ENGLISH: + case CMD_TRAY_LANG_ENGLISH: Lang::Set(LangType::English); WriteIniInt(L"General", L"Language", 1); UpdateTooltip(Lang::Get(L"Tray.DefaultTip")); break; - case WM_TRAY_RESET: HandleReset(); break; - case WM_TRAY_ABOUT: + case CMD_TRAY_RESET: HandleReset(); break; + case CMD_TRAY_ABOUT: if (m_aboutFn) { m_aboutFn(); } break; - case WM_TRAY_ANIM_MODE: HandleAnimMode(); break; - case WM_TRAY_AUTO_CONTRAST: HandleAutoContrast(); break; - case WM_TRAY_AUTO_FOCUS: HandleAutoFocus(); break; - case WM_TRAY_TOGGLE_SHOW: HandleToggleShow(); break; + case CMD_TRAY_ANIM_MODE: HandleAnimMode(); break; + case CMD_TRAY_AUTO_CONTRAST: HandleAutoContrast(); break; + case CMD_TRAY_AUTO_FOCUS: HandleAutoFocus(); break; + case CMD_TRAY_TOGGLE_SHOW: HandleToggleShow(); break; default: break; } } -void TrayIcon::DrawColorSwatch(LPDRAWITEMSTRUCT dis) const { - const int colorIndex = static_cast(dis->itemID - CMD_COLOR_OPTIONS_BASE); +void TrayIcon::DrawColorSwatch(LPDRAWITEMSTRUCT dis, UINT base) const { + const int colorIndex = static_cast(dis->itemID - base); if (colorIndex < 0 || static_cast(colorIndex) >= kPredefinedColors.size()) { return; } @@ -484,25 +535,15 @@ void TrayIcon::DrawColorSwatch(LPDRAWITEMSTRUCT dis) const { } // Color swatch - const int labelW = m_menuAveWidth * 3; - const int pad = MulDiv(3, m_dpi, 96); + const int pad = MulDiv(3, m_dpi, 96); RECT cr = dis->rcItem; - cr.left += labelW + pad; + cr.left += pad; cr.top += MulDiv(1, m_dpi, 96); cr.bottom -= MulDiv(1, m_dpi, 96); cr.right = dis->rcItem.right - pad; DrawSwatchRect(dis->hDC, cr, kPredefinedColors.at(colorIndex), *m_gdiplus); - - // Number label - const std::wstring numStr = std::to_wstring(colorIndex + 1); - RECT nr = dis->rcItem; - nr.left += pad; - nr.right = dis->rcItem.left + labelW; - SetBkMode(dis->hDC, TRANSPARENT); - SetTextColor(dis->hDC, (isSel != 0) ? GetSysColor(COLOR_HIGHLIGHTTEXT) : GetSysColor(COLOR_MENUTEXT)); - DrawTextW(dis->hDC, numStr.data(), -1, &nr, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); } bool TrayIcon::Reinitialize() { @@ -520,6 +561,21 @@ void TrayIcon::UpdateTooltip(const std::wstring &tooltip) { } void TrayIcon::UpdateTrayIcon(int displayNumber) { + if (m_iconMode == TrayIconMode::Icon) { + if (m_nTrayNumber == 0) { return; } + HICON hIcon = LoadIcon(m_hInstance, MAKEINTRESOURCE(kTrayDefaultIconResource)); + NOTIFYICONDATAW nid = {}; + nid.cbSize = sizeof(nid); + nid.hWnd = m_nid.hWnd; + nid.uID = m_nid.uID; + nid.uFlags = NIF_ICON; + nid.hIcon = hIcon; + Shell_NotifyIconW(NIM_MODIFY, &nid); + m_nid.hIcon = hIcon; + m_nTrayNumber = 0; + return; + } + if (displayNumber == m_nTrayNumber) { return; } @@ -550,13 +606,12 @@ HICON TrayIcon::GetNumberIcon(int nNumber) { return nullptr; } if (m_hNumberIcons[nNumber] == nullptr) { - m_hNumberIcons[nNumber] = CreateNumberIcon(nNumber); + m_hNumberIcons[nNumber] = CreateNumberIcon(nNumber, m_numberColor, *m_gdiplus); } return m_hNumberIcons[nNumber]; } -HICON TrayIcon::CreateNumberIcon(int nNumber) { - GdiplusScope gdiplus; +HICON TrayIcon::CreateNumberIcon(int nNumber, COLORREF color, GdiplusGuard &gdiplus) { if (!gdiplus.Ok()) { return nullptr; } @@ -571,10 +626,9 @@ HICON TrayIcon::CreateNumberIcon(int nNumber) { std::wstring szNum = std::to_wstring(nNumber); Gdiplus::FontFamily fontFamily(L"Segoe UI"); - constexpr BYTE kA = 255, kR = 41, kG = 151, kB = 255; Gdiplus::Font font(&fontFamily, static_cast(nSize) * 0.85f, Gdiplus::FontStyleBold, Gdiplus::UnitPixel); - Gdiplus::SolidBrush brushText(Gdiplus::Color(kA, kR, kG, kB)); + Gdiplus::SolidBrush brushText(Gdiplus::Color(255, GetRValue(color), GetGValue(color), GetBValue(color))); Gdiplus::StringFormat format; format.SetAlignment(Gdiplus::StringAlignmentCenter); format.SetLineAlignment(Gdiplus::StringAlignmentCenter); diff --git a/src/ui/TrayIcon.h b/src/ui/TrayIcon.h index b867f12..74dcb9b 100644 --- a/src/ui/TrayIcon.h +++ b/src/ui/TrayIcon.h @@ -12,10 +12,13 @@ #include "core/IndicatorConfig.h" #include "util/Utils.h" -constexpr UINT WM_TRAYICON = WM_USER + 2; -constexpr UINT WM_TRAY_LANG_CHINESE = WM_USER + 50; -constexpr UINT WM_TRAY_LANG_ENGLISH = WM_USER + 51; -constexpr UINT CMD_COLOR_OPTIONS_BASE = WM_USER + 100; +constexpr UINT WM_TRAYICON = WM_USER + 2; +constexpr UINT CMD_TRAY_LANG_CHINESE = WM_USER + 50; +constexpr UINT CMD_TRAY_LANG_ENGLISH = WM_USER + 51; +constexpr UINT CMD_TRAY_ICON_MODE_ICON = WM_USER + 60; +constexpr UINT CMD_TRAY_ICON_MODE_NUMBER = WM_USER + 61; +constexpr UINT CMD_COLOR_OPTIONS_BASE = WM_USER + 100; +constexpr UINT CMD_TRAY_NUMBER_COLOR_BASE = WM_USER + 500; constexpr int kTrayDefaultIconResource = 101; class GdiplusGuard; @@ -35,6 +38,8 @@ class TrayIcon { void HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); void UpdateTooltip(const std::wstring &tooltip); void UpdateTrayIcon(int displayNumber); + void SetTrayIconMode(TrayIconMode mode); + void SetNumberColor(const std::wstring &hexColor); void SetActivePositionPreset(PositionPreset preset) { m_activePositionPreset = preset; } void SetEditModeCallback(std::function cb) { m_editModeFn = std::move(cb); } @@ -50,14 +55,15 @@ class TrayIcon { private: NOTIFYICONDATAW m_nid{}; - std::unique_ptr m_gdiplus; + std::unique_ptr m_gdiplus; HINSTANCE m_hInstance = nullptr; HMENU m_hMenu = nullptr; bool m_autoStartEnabled = false; PositionPreset m_activePositionPreset = PositionPreset::TopCenter; - int m_menuAveWidth = 6; int m_dpi = 96; int m_nTrayNumber = -1; + TrayIconMode m_iconMode = TrayIconMode::Icon; + COLORREF m_numberColor = RGB(41, 151, 255); std::array m_hNumberIcons{}; std::function m_colorFn; @@ -73,10 +79,10 @@ class TrayIcon { void BuildMenu(); void HandleCommand(WPARAM wParam); - void DrawColorSwatch(LPDRAWITEMSTRUCT dis) const; + void DrawColorSwatch(LPDRAWITEMSTRUCT dis, UINT base) const; HICON GetNumberIcon(int nNumber); HICON GetTrayIconForNumber(int nDisplay); - static HICON CreateNumberIcon(int nNumber); + static HICON CreateNumberIcon(int nNumber, COLORREF color, GdiplusGuard &gdiplus); static void HandleRunAsAdmin(); static void HandleReset(); @@ -88,6 +94,7 @@ class TrayIcon { void HandleToggleShow(); void HandleToggleAutoStart(); void HandleShowModeCommand(int mode); + void HandleNumberColorCommand(int colorIndex); void HandlePositionCommand(PositionPreset preset); void HandleColorCommand(int index); }; diff --git a/src/util/LangData_en.inc b/src/util/LangData_en.inc index 42eced1..5bcaa72 100644 --- a/src/util/LangData_en.inc +++ b/src/util/LangData_en.inc @@ -52,6 +52,10 @@ std::pair{L"Menu.DragAlt", L"Hold Alt to Switch"}, std::pair{L"Menu.DragCtrl", L"Hold Ctrl to Switch"}, std::pair{L"Menu.DragNever", L"Never Switch"}, std::pair{L"Menu.AutoFocus", L"Cross-Monitor Focus"}, +std::pair{L"Menu.TrayIcon", L"Tray Icon"}, +std::pair{L"Menu.TrayIconIcon", L"Icon"}, +std::pair{L"Menu.TrayIconNumber", L"Desktop Number"}, +std::pair{L"Menu.TrayNumberColor", L"Number Color"}, std::pair{L"Tray.DefaultTip", L"Virtual Desktop Switcher"}, std::pair{L"Tooltip.Desktops", L"Desktops: "}, std::pair{L"Tooltip.Current", L"Current: "}, diff --git a/src/util/LangData_zh.inc b/src/util/LangData_zh.inc index 55e3540..a038944 100644 --- a/src/util/LangData_zh.inc +++ b/src/util/LangData_zh.inc @@ -52,6 +52,10 @@ std::pair{L"Menu.DragAlt", L"按住 Alt 键切换"}, std::pair{L"Menu.DragCtrl", L"按住 Ctrl 键切换"}, std::pair{L"Menu.DragNever", L"不切换"}, std::pair{L"Menu.AutoFocus", L"跨屏自动聚焦"}, +std::pair{L"Menu.TrayIcon", L"托盘图标"}, +std::pair{L"Menu.TrayIconIcon", L"图标"}, +std::pair{L"Menu.TrayIconNumber", L"桌面编号"}, +std::pair{L"Menu.TrayNumberColor", L"编号颜色"}, std::pair{L"Tray.DefaultTip", L"虚拟桌面切换器"}, std::pair{L"Tooltip.Desktops", L"桌面:"}, std::pair{L"Tooltip.Current", L"当前:"}, diff --git a/src/util/Utils.h b/src/util/Utils.h index 4f76d72..905d3aa 100644 --- a/src/util/Utils.h +++ b/src/util/Utils.h @@ -13,12 +13,17 @@ constexpr std::array kPredefinedColors = { L"#FFFFFF", L"#E3E3E5", L"#000000", - L"#FF5F5F", - L"#F6ABB7", - L"#FB7FA4", - L"#F59E0B", + L"#FF3B30", + L"#FF6B6B", + L"#FF9500", + L"#FFCC00", L"#22C55E", + L"#4ECDC4", + L"#2997FF", + L"#5856D6", L"#8771C6", + L"#FF2D55", + L"#FB7FA4", L"#FF9A9E_#FECFEF", L"#FEA5B7_#FFDE9B", L"#A8EDEA_#FED6E3",