diff --git a/src/modules/powerdisplay/PowerDisplay/Assets/PowerDisplay.ico b/src/modules/powerdisplay/PowerDisplay/Assets/PowerDisplay.ico deleted file mode 100644 index a9f170a8bd..0000000000 Binary files a/src/modules/powerdisplay/PowerDisplay/Assets/PowerDisplay.ico and /dev/null differ diff --git a/src/modules/powerdisplay/PowerDisplay/Helpers/TrayIconHelper.cs b/src/modules/powerdisplay/PowerDisplay/Helpers/TrayIconHelper.cs deleted file mode 100644 index 58d5326ff9..0000000000 --- a/src/modules/powerdisplay/PowerDisplay/Helpers/TrayIconHelper.cs +++ /dev/null @@ -1,484 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Drawing; -using System.Runtime.InteropServices; -using ManagedCommon; -using Microsoft.UI.Xaml; -using PowerDisplay.Native; -using static PowerDisplay.Native.PInvoke; - -namespace PowerDisplay.Helpers -{ - /// - /// System tray icon helper class - /// - public partial class TrayIconHelper : IDisposable - { - private const uint NifMessage = 0x00000001; - private const uint NifIcon = 0x00000002; - private const uint NifTip = 0x00000004; - private const uint NifInfo = 0x00000010; - - private const uint NimAdd = 0x00000000; - private const uint NimModify = 0x00000001; - private const uint NimDelete = 0x00000002; - - private const uint WmUser = 0x0400; - private const uint WmTrayicon = WmUser + 1; - private const uint WmLbuttonup = 0x0202; - private const uint WmRbuttonup = 0x0205; - private const uint WmCommand = 0x0111; - - private uint _wmTaskbarCreated; // TaskbarCreated message ID - - // Menu item IDs - private const int IdShow = 1001; - private const int IdExit = 1002; - private const int IdRefresh = 1003; - private const int IdSettings = 1004; - - private delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); - - private const uint MfString = 0x00000000; - private const uint MfSeparator = 0x00000800; - private const uint TpmLeftalign = 0x0000; - private const uint TpmReturncmd = 0x0100; - - private const int SwHide = 0; - private const int SwShow = 5; - - private IntPtr _messageWindowHandle; - private NOTIFYICONDATA _notifyIconData; - private bool _isDisposed; - private WndProc _wndProc; - private Window _mainWindow; - private Action? _onShowWindow; - private Action? _onExitApplication; - private Action? _onRefresh; - private Action? _onSettings; - private bool _isWindowVisible = true; - private System.Drawing.Icon? _trayIcon; // Keep icon reference to prevent garbage collection - - public TrayIconHelper(Window mainWindow) - { - _mainWindow = mainWindow; - _wndProc = WindowProc; - - // Register TaskbarCreated message - _wmTaskbarCreated = RegisterWindowMessage("TaskbarCreated"); - Logger.LogInfo($"Registered TaskbarCreated message: {_wmTaskbarCreated}"); - - if (!CreateMessageWindow()) - { - Logger.LogError("Failed to create message window"); - return; - } - - CreateTrayIcon(); - } - - /// - /// Set callback functions - /// - public void SetCallbacks(Action onShow, Action onExit, Action? onRefresh = null, Action? onSettings = null) - { - _onShowWindow = onShow; - _onExitApplication = onExit; - _onRefresh = onRefresh; - _onSettings = onSettings; - } - - /// - /// Create message window - using system predefined Message window class - /// - private bool CreateMessageWindow() - { - try - { - Logger.LogDebug("Creating message window using system Message class..."); - - // Use system predefined "Message" window class, no registration needed - // HWND_MESSAGE (-3) creates pure message window, no hInstance needed - _messageWindowHandle = CreateWindowEx( - 0, // dwExStyle - "Message", // lpClassName - system predefined message window class - string.Empty, // lpWindowName - 0, // dwStyle - 0, - 0, - 0, - 0, // x, y, width, height - new IntPtr(-3), // hWndParent = HWND_MESSAGE (pure message window) - IntPtr.Zero, // hMenu - IntPtr.Zero, // hInstance - not needed - IntPtr.Zero); // lpParam - - if (_messageWindowHandle == IntPtr.Zero) - { - var error = Marshal.GetLastWin32Error(); - Logger.LogError($"CreateWindowEx failed with error: {error}"); - return false; - } - - Logger.LogInfo($"Message window created successfully: {_messageWindowHandle}"); - - // Set window procedure to handle our messages - SetWindowLongPtr(_messageWindowHandle, -4, Marshal.GetFunctionPointerForDelegate(_wndProc)); - - return true; - } - catch (Exception ex) - { - Logger.LogError($"CreateMessageWindow exception: {ex.Message}"); - return false; - } - } - - /// - /// Create tray icon - /// - private unsafe void CreateTrayIcon() - { - if (_messageWindowHandle == IntPtr.Zero) - { - Logger.LogError("Cannot create tray icon: invalid message window handle"); - return; - } - - // First try to delete any existing old icon (if any) - var tempData = new NOTIFYICONDATA - { - CbSize = (uint)sizeof(NOTIFYICONDATA), - HWnd = _messageWindowHandle, - UID = 1, - }; - Shell_NotifyIcon(NimDelete, ref tempData); - - // Get icon handle - var iconHandle = GetDefaultIcon(); - if (iconHandle == IntPtr.Zero) - { - Logger.LogError("Cannot create tray icon: invalid icon handle"); - return; - } - - _notifyIconData = new NOTIFYICONDATA - { - CbSize = (uint)sizeof(NOTIFYICONDATA), - HWnd = _messageWindowHandle, - UID = 1, - UFlags = NifMessage | NifIcon | NifTip, - UCallbackMessage = WmTrayicon, - HIcon = iconHandle, - }; - _notifyIconData.SetTip("Power Display"); - - // Retry mechanism: try up to 3 times to create tray icon - const int maxRetries = 3; - const int retryDelayMs = 500; - - for (int attempt = 1; attempt <= maxRetries; attempt++) - { - Logger.LogDebug($"Creating tray icon (attempt {attempt}/{maxRetries})..."); - - bool result = Shell_NotifyIcon(NimAdd, ref _notifyIconData); - if (result) - { - Logger.LogInfo($"Tray icon created successfully on attempt {attempt}"); - return; - } - - var lastError = Marshal.GetLastWin32Error(); - Logger.LogWarning($"Failed to create tray icon on attempt {attempt}. Error: {lastError}"); - - // Analyze specific error and provide suggestions - switch (lastError) - { - case 0: // ERROR_SUCCESS - may be false success - Logger.LogWarning("Shell_NotifyIcon returned false but GetLastWin32Error is 0"); - break; - case 1400: // ERROR_INVALID_WINDOW_HANDLE - Logger.LogWarning("Invalid window handle - message window may not be properly created"); - break; - case 1418: // ERROR_THREAD_1_INACTIVE - Logger.LogWarning("Thread inactive - may need to wait for Explorer to be ready"); - break; - case 1414: // ERROR_INVALID_ICON_HANDLE - Logger.LogWarning("Invalid icon handle - icon may have been garbage collected"); - break; - default: - Logger.LogWarning($"Unexpected error code: {lastError}"); - break; - } - - // If not the last attempt, wait and retry - if (attempt < maxRetries) - { - Logger.LogDebug($"Retrying in {retryDelayMs}ms..."); - System.Threading.Thread.Sleep(retryDelayMs); - - // Re-get icon handle to prevent handle invalidation - iconHandle = GetDefaultIcon(); - _notifyIconData.HIcon = iconHandle; - } - } - - Logger.LogError($"Failed to create tray icon after {maxRetries} attempts"); - } - - /// - /// Get default icon - /// - private IntPtr GetDefaultIcon() - { - try - { - // First release previous icon - _trayIcon?.Dispose(); - _trayIcon = null; - - // Try to load icon from Assets folder in exe directory - var exePath = System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName; - if (!string.IsNullOrEmpty(exePath)) - { - var exeDir = System.IO.Path.GetDirectoryName(exePath); - if (!string.IsNullOrEmpty(exeDir)) - { - var iconPath = System.IO.Path.Combine(exeDir, "Assets", "PowerDisplay.ico"); - - Logger.LogDebug($"Attempting to load icon from: {iconPath}"); - - if (System.IO.File.Exists(iconPath)) - { - // Create icon and save as class member to prevent garbage collection - _trayIcon = new System.Drawing.Icon(iconPath); - Logger.LogInfo($"Successfully loaded custom icon from {iconPath}"); - return _trayIcon.Handle; - } - else - { - Logger.LogWarning($"Icon file not found at: {iconPath}"); - } - } - } - } - catch (Exception ex) - { - Logger.LogError($"Failed to load PowerDisplay icon: {ex.Message}"); - _trayIcon?.Dispose(); - _trayIcon = null; - } - - // If loading fails, use system default icon - var systemIconHandle = LoadIcon(IntPtr.Zero, new IntPtr(32512)); // IDI_APPLICATION - Logger.LogInfo($"Using system default icon: {systemIconHandle}"); - return systemIconHandle; - } - - /// - /// Window message processing - /// - private IntPtr WindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) - { - if (msg == _wmTaskbarCreated) - { - // Explorer restarted, need to recreate tray icon - Logger.LogInfo("TaskbarCreated message received - recreating tray icon"); - CreateTrayIcon(); - return IntPtr.Zero; - } - - switch (msg) - { - case WmTrayicon: - HandleTrayIconMessage(lParam); - break; - case WmCommand: - HandleMenuCommand(wParam); - break; - } - - return DefWindowProc(hWnd, msg, wParam, lParam); - } - - /// - /// Handle tray icon messages - /// - private void HandleTrayIconMessage(IntPtr lParam) - { - switch ((uint)lParam) - { - case WmLbuttonup: - // Left click - show/hide window - ToggleWindowVisibility(); - break; - case WmRbuttonup: - // Right click - show menu - ShowContextMenu(); - break; - } - } - - /// - /// Toggle window visibility state - /// - private void ToggleWindowVisibility() - { - _isWindowVisible = !_isWindowVisible; - if (_isWindowVisible) - { - _onShowWindow?.Invoke(); - } - else - { - // Hide window logic will be implemented in MainWindow - if (_mainWindow != null) - { - var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(_mainWindow); - ShowWindow(hWnd, SwHide); - } - } - } - - /// - /// Show right-click menu - /// - private void ShowContextMenu() - { - var hMenu = CreatePopupMenu(); - - AppendMenu(hMenu, MfString, IdShow, _isWindowVisible ? "Hide Window" : "Show Window"); - if (_onRefresh != null) - { - AppendMenu(hMenu, MfString, IdRefresh, "Refresh Monitors"); - } - - if (_onSettings != null) - { - AppendMenu(hMenu, MfString, IdSettings, "Settings"); - } - - AppendMenu(hMenu, MfSeparator, 0, string.Empty); - AppendMenu(hMenu, MfString, IdExit, "Exit"); - - GetCursorPos(out POINT pt); - SetForegroundWindow(_messageWindowHandle); - - var cmd = TrackPopupMenu(hMenu, TpmLeftalign | TpmReturncmd, pt.X, pt.Y, 0, _messageWindowHandle, IntPtr.Zero); - - if (cmd != 0) - { - HandleMenuCommand(new IntPtr(cmd)); - } - - DestroyMenu(hMenu); - } - - /// - /// Handle menu commands - /// - private void HandleMenuCommand(IntPtr commandId) - { - switch (commandId.ToInt32()) - { - case IdShow: - ToggleWindowVisibility(); - break; - case IdRefresh: - _onRefresh?.Invoke(); - break; - case IdSettings: - _onSettings?.Invoke(); - break; - case IdExit: - _onExitApplication?.Invoke(); - break; - } - } - - /// - /// Show balloon tip - /// - public void ShowBalloonTip(string title, string text, uint timeout = 3000) - { - _notifyIconData.UFlags |= NifInfo; - _notifyIconData.SetInfoTitle(title); - _notifyIconData.SetInfo(text); - _notifyIconData.UTimeout = timeout; - _notifyIconData.DwInfoFlags = 1; // NIIF_INFO - - Shell_NotifyIcon(NimModify, ref _notifyIconData); - } - - /// - /// Update tray icon tooltip text - /// - public void UpdateTooltip(string tooltip) - { - _notifyIconData.SetTip(tooltip); - Shell_NotifyIcon(NimModify, ref _notifyIconData); - } - - /// - /// Recreate tray icon (for failure recovery) - /// - public void RecreateTrayIcon() - { - Logger.LogInfo("Manually recreating tray icon..."); - CreateTrayIcon(); - } - - public void Dispose() - { - if (!_isDisposed) - { - Logger.LogDebug("Disposing TrayIconHelper..."); - - // Remove tray icon - try - { - Shell_NotifyIcon(NimDelete, ref _notifyIconData); - Logger.LogInfo("Tray icon removed successfully"); - } - catch (Exception ex) - { - Logger.LogError($"Error removing tray icon: {ex.Message}"); - } - - // Release icon resources - try - { - _trayIcon?.Dispose(); - _trayIcon = null; - Logger.LogInfo("Icon resources disposed successfully"); - } - catch (Exception ex) - { - Logger.LogError($"Error disposing icon: {ex.Message}"); - } - - // Destroy message window - try - { - if (_messageWindowHandle != IntPtr.Zero) - { - DestroyWindow(_messageWindowHandle); - _messageWindowHandle = IntPtr.Zero; - Logger.LogInfo("Message window destroyed successfully"); - } - } - catch (Exception ex) - { - Logger.LogError($"Error destroying message window: {ex.Message}"); - } - - _isDisposed = true; - GC.SuppressFinalize(this); - Logger.LogDebug("TrayIconHelper disposed completely"); - } - } - } -} diff --git a/src/modules/powerdisplay/PowerDisplay/PowerDisplay.csproj b/src/modules/powerdisplay/PowerDisplay/PowerDisplay.csproj index 208852f3c3..2e9f8ff011 100644 --- a/src/modules/powerdisplay/PowerDisplay/PowerDisplay.csproj +++ b/src/modules/powerdisplay/PowerDisplay/PowerDisplay.csproj @@ -17,7 +17,6 @@ true ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps PowerToys.PowerDisplay - Assets\PowerDisplay.ico PowerToys.PowerDisplay.pri true diff --git a/src/modules/powerdisplay/PowerDisplay/PowerDisplayXAML/MainWindow.xaml.cs b/src/modules/powerdisplay/PowerDisplay/PowerDisplayXAML/MainWindow.xaml.cs index 0eeac3f537..d8a2adf68d 100644 --- a/src/modules/powerdisplay/PowerDisplay/PowerDisplayXAML/MainWindow.xaml.cs +++ b/src/modules/powerdisplay/PowerDisplay/PowerDisplayXAML/MainWindow.xaml.cs @@ -36,7 +36,6 @@ namespace PowerDisplay { private readonly ISettingsUtils _settingsUtils = new SettingsUtils(); private MainViewModel _viewModel = null!; - private TrayIconHelper _trayIcon = null!; private AppWindow _appWindow = null!; private bool _isExiting; @@ -56,9 +55,6 @@ namespace PowerDisplay // Setup window properties SetupWindow(); - // Initialize tray icon - InitializeTrayIcon(); - // Initialize UI text InitializeUIText(); @@ -230,29 +226,6 @@ namespace PowerDisplay HideWindow(); } - private void InitializeTrayIcon() - { - _trayIcon = new TrayIconHelper(this); - _trayIcon.SetCallbacks( - onShow: ShowWindow, - onExit: ExitApplication, - onRefresh: () => _viewModel?.RefreshCommand?.Execute(null), - onSettings: OpenSettings); - } - - private void OpenSettings() - { - try - { - // Open PowerToys Settings to PowerDisplay page - PowerDisplay.Helpers.SettingsDeepLink.OpenPowerDisplaySettings(); - } - catch (Exception ex) - { - Logger.LogError($"Failed to open settings: {ex.Message}"); - } - } - public void ShowWindow() { Logger.LogInfo("[SHOWWINDOW] Method entry"); @@ -450,9 +423,6 @@ namespace PowerDisplay { _isExiting = true; - // 立即释放托盘图标 - _trayIcon?.Dispose(); - // 快速清理 ViewModel if (_viewModel != null) { @@ -907,7 +877,6 @@ namespace PowerDisplay public void Dispose() { _viewModel?.Dispose(); - _trayIcon?.Dispose(); GC.SuppressFinalize(this); } }