Remove tray icon functionality from PowerDisplay

The tray icon functionality has been completely removed from the
PowerDisplay application. This includes:

- Deletion of the `PowerDisplay.ico` file.
- Removal of the `TrayIconHelper.cs` class, which managed the tray
  icon's creation, updates, and interactions.
- Elimination of all references to `TrayIconHelper` in
  `MainWindow.xaml.cs`, including tray icon initialization, event
  handling, and disposal logic.
- Removal of the `<ApplicationIcon>` property in `PowerDisplay.csproj`.

These changes simplify the application by reducing its responsibilities
and dependencies, potentially aligning with a new design direction.
This commit is contained in:
Yu Leng
2025-11-13 13:06:37 +08:00
parent 0b7109dee4
commit d822745c98
4 changed files with 0 additions and 516 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 468 KiB

View File

@@ -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
{
/// <summary>
/// System tray icon helper class
/// </summary>
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();
}
/// <summary>
/// Set callback functions
/// </summary>
public void SetCallbacks(Action onShow, Action onExit, Action? onRefresh = null, Action? onSettings = null)
{
_onShowWindow = onShow;
_onExitApplication = onExit;
_onRefresh = onRefresh;
_onSettings = onSettings;
}
/// <summary>
/// Create message window - using system predefined Message window class
/// </summary>
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;
}
}
/// <summary>
/// Create tray icon
/// </summary>
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");
}
/// <summary>
/// Get default icon
/// </summary>
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;
}
/// <summary>
/// Window message processing
/// </summary>
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);
}
/// <summary>
/// Handle tray icon messages
/// </summary>
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;
}
}
/// <summary>
/// Toggle window visibility state
/// </summary>
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);
}
}
}
/// <summary>
/// Show right-click menu
/// </summary>
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);
}
/// <summary>
/// Handle menu commands
/// </summary>
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;
}
}
/// <summary>
/// Show balloon tip
/// </summary>
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);
}
/// <summary>
/// Update tray icon tooltip text
/// </summary>
public void UpdateTooltip(string tooltip)
{
_notifyIconData.SetTip(tooltip);
Shell_NotifyIcon(NimModify, ref _notifyIconData);
}
/// <summary>
/// Recreate tray icon (for failure recovery)
/// </summary>
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");
}
}
}
}

View File

@@ -17,7 +17,6 @@
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps</OutputPath>
<AssemblyName>PowerToys.PowerDisplay</AssemblyName>
<ApplicationIcon>Assets\PowerDisplay.ico</ApplicationIcon>
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
<ProjectPriFileName>PowerToys.PowerDisplay.pri</ProjectPriFileName>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

View File

@@ -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);
}
}