Refactor TrayIconService to replace CsWin32 dependencies

Replaced CsWin32-generated types and P/Invoke methods with
custom `LibraryImport` declarations and primitive types (`nint`,
`nuint`) to improve accessibility and reduce reliance on
CsWin32. Updated `TrayIconService` to use `nint` for handles
and replaced safe handle types. Simplified `WindowProc` logic
and updated methods like `SetupTrayIcon` and `Destroy` to use
new interop methods.

Added custom constants, enums, and structs for window messages
and menu flags. Removed obsolete entries from `NativeMethods.txt`.
These changes enhance maintainability and ensure compatibility
with standard .NET interop practices.
This commit is contained in:
Yu Leng
2025-11-27 21:43:59 +08:00
parent 7c69874689
commit 1c33cf0348
2 changed files with 105 additions and 58 deletions

View File

@@ -19,14 +19,14 @@ namespace PowerDisplay.Helpers
{ {
/// <summary> /// <summary>
/// Window procedure delegate for handling window messages. /// Window procedure delegate for handling window messages.
/// Defined locally because CsWin32 generates WNDPROC as internal with allowMarshaling: false. /// Uses primitive types to avoid accessibility issues with CsWin32-generated types.
/// </summary> /// </summary>
/// <param name="hwnd">Handle to the window.</param> /// <param name="hwnd">Handle to the window.</param>
/// <param name="msg">The message.</param> /// <param name="msg">The message.</param>
/// <param name="wParam">Additional message information.</param> /// <param name="wParam">Additional message information.</param>
/// <param name="lParam">Additional message information.</param> /// <param name="lParam">Additional message information.</param>
/// <returns>The result of the message processing.</returns> /// <returns>The result of the message processing.</returns>
internal delegate LRESULT WndProcDelegate(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam); internal delegate nint WndProcDelegate(nint hwnd, uint msg, nuint wParam, nint lParam);
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Stylistically, window messages are WM_*")] [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Stylistically, window messages are WM_*")]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Stylistically, window messages are WM_*")] [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Stylistically, window messages are WM_*")]
@@ -43,12 +43,12 @@ namespace PowerDisplay.Helpers
private readonly uint WM_TASKBAR_RESTART; private readonly uint WM_TASKBAR_RESTART;
private Window? _window; private Window? _window;
private HWND _hwnd; private nint _hwnd;
private IntPtr _originalWndProc; private nint _originalWndProc;
private WndProcDelegate? _trayWndProc; private WndProcDelegate? _trayWndProc;
private NOTIFYICONDATAW? _trayIconData; private NOTIFYICONDATAW? _trayIconData;
private DestroyIconSafeHandle? _largeIcon; private nint _largeIcon;
private DestroyMenuSafeHandle? _popupMenu; private nint _popupMenu;
public TrayIconService( public TrayIconService(
ISettingsUtils settingsUtils, ISettingsUtils settingsUtils,
@@ -66,7 +66,7 @@ namespace PowerDisplay.Helpers
// TaskbarCreated is the message that's broadcast when explorer.exe // TaskbarCreated is the message that's broadcast when explorer.exe
// restarts. We need to know when that happens to be able to bring our // restarts. We need to know when that happens to be able to bring our
// notification area icon back // notification area icon back
WM_TASKBAR_RESTART = PInvoke.RegisterWindowMessage("TaskbarCreated"); WM_TASKBAR_RESTART = RegisterWindowMessageNative("TaskbarCreated");
} }
public void SetupTrayIcon(bool? showSystemTrayIcon = null) public void SetupTrayIcon(bool? showSystemTrayIcon = null)
@@ -79,7 +79,7 @@ namespace PowerDisplay.Helpers
if (_window is null) if (_window is null)
{ {
_window = new Window(); _window = new Window();
_hwnd = new HWND(WindowNative.GetWindowHandle(_window)); _hwnd = WindowNative.GetWindowHandle(_window);
// LOAD BEARING: If you don't stick the pointer to HotKeyPrc into a // LOAD BEARING: If you don't stick the pointer to HotKeyPrc into a
// member (and instead like, use a local), then the pointer we marshal // member (and instead like, use a local), then the pointer we marshal
@@ -87,7 +87,7 @@ namespace PowerDisplay.Helpers
// and our **WindProc will explode**. // and our **WindProc will explode**.
_trayWndProc = WindowProc; _trayWndProc = WindowProc;
var hotKeyPrcPointer = Marshal.GetFunctionPointerForDelegate(_trayWndProc); var hotKeyPrcPointer = Marshal.GetFunctionPointerForDelegate(_trayWndProc);
_originalWndProc = PInvoke.SetWindowLongPtr(_hwnd, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, hotKeyPrcPointer); _originalWndProc = SetWindowLongPtrNative(_hwnd, GWL_WNDPROC, hotKeyPrcPointer);
} }
if (_trayIconData is null) if (_trayIconData is null)
@@ -102,11 +102,11 @@ namespace PowerDisplay.Helpers
_trayIconData = new NOTIFYICONDATAW() _trayIconData = new NOTIFYICONDATAW()
{ {
cbSize = (uint)sizeof(NOTIFYICONDATAW), cbSize = (uint)sizeof(NOTIFYICONDATAW),
hWnd = _hwnd, hWnd = new HWND(_hwnd),
uID = MY_NOTIFY_ID, uID = MY_NOTIFY_ID,
uFlags = NOTIFY_ICON_DATA_FLAGS.NIF_MESSAGE | NOTIFY_ICON_DATA_FLAGS.NIF_ICON | NOTIFY_ICON_DATA_FLAGS.NIF_TIP, uFlags = NOTIFY_ICON_DATA_FLAGS.NIF_MESSAGE | NOTIFY_ICON_DATA_FLAGS.NIF_ICON | NOTIFY_ICON_DATA_FLAGS.NIF_TIP,
uCallbackMessage = WM_TRAY_ICON, uCallbackMessage = WM_TRAY_ICON,
hIcon = (HICON)_largeIcon.DangerousGetHandle(), hIcon = new HICON(_largeIcon),
szTip = GetString("AppName"), szTip = GetString("AppName"),
}; };
} }
@@ -115,13 +115,16 @@ namespace PowerDisplay.Helpers
var d = (NOTIFYICONDATAW)_trayIconData; var d = (NOTIFYICONDATAW)_trayIconData;
// Add the notification icon // Add the notification icon
PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_ADD, in d); unsafe
if (_popupMenu is null)
{ {
_popupMenu = PInvoke.CreatePopupMenu_SafeHandle(); Shell_NotifyIconNative((uint)NOTIFY_ICON_MESSAGE.NIM_ADD, &d);
PInvoke.InsertMenu(_popupMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING, PInvoke.WM_USER + 1, GetString("TrayMenu_Settings")); }
PInvoke.InsertMenu(_popupMenu, 1, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING, PInvoke.WM_USER + 2, GetString("TrayMenu_Exit"));
if (_popupMenu == 0)
{
_popupMenu = CreatePopupMenu();
InsertMenuNative(_popupMenu, 0, (uint)(MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING), PInvoke.WM_USER + 1, GetString("TrayMenu_Settings"));
InsertMenuNative(_popupMenu, 1, (uint)(MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING), PInvoke.WM_USER + 2, GetString("TrayMenu_Exit"));
} }
} }
else else
@@ -135,29 +138,32 @@ namespace PowerDisplay.Helpers
if (_trayIconData is not null) if (_trayIconData is not null)
{ {
var d = (NOTIFYICONDATAW)_trayIconData; var d = (NOTIFYICONDATAW)_trayIconData;
if (PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_DELETE, in d)) unsafe
{ {
_trayIconData = null; if (Shell_NotifyIconNative((uint)NOTIFY_ICON_MESSAGE.NIM_DELETE, &d))
{
_trayIconData = null;
}
} }
} }
if (_popupMenu is not null) if (_popupMenu != 0)
{ {
_popupMenu.Close(); DestroyMenu(_popupMenu);
_popupMenu = null; _popupMenu = 0;
} }
if (_largeIcon is not null) if (_largeIcon != 0)
{ {
_largeIcon.Close(); DestroyIcon(_largeIcon);
_largeIcon = null; _largeIcon = 0;
} }
if (_window is not null) if (_window is not null)
{ {
_window.Close(); _window.Close();
_window = null; _window = null;
_hwnd = HWND.Null; _hwnd = 0;
} }
} }
@@ -180,18 +186,18 @@ namespace PowerDisplay.Helpers
} }
} }
private DestroyIconSafeHandle GetAppIconHandle() private nint GetAppIconHandle()
{ {
var exePath = Path.Combine(AppContext.BaseDirectory, "PowerToys.PowerDisplay.exe"); var exePath = Path.Combine(AppContext.BaseDirectory, "PowerToys.PowerDisplay.exe");
PInvoke.ExtractIconEx(exePath, 0, out var largeIcon, out _, 1); ExtractIconExNative(exePath, 0, out var largeIcon, out _, 1);
return largeIcon; return largeIcon;
} }
private LRESULT WindowProc( private nint WindowProc(
HWND hwnd, nint hwnd,
uint uMsg, uint uMsg,
WPARAM wParam, nuint wParam,
LPARAM lParam) nint lParam)
{ {
switch (uMsg) switch (uMsg)
{ {
@@ -237,15 +243,15 @@ namespace PowerDisplay.Helpers
} }
else if (uMsg == WM_TRAY_ICON) else if (uMsg == WM_TRAY_ICON)
{ {
switch ((uint)lParam.Value) switch ((uint)lParam)
{ {
case PInvoke.WM_RBUTTONUP: case PInvoke.WM_RBUTTONUP:
{ {
if (_popupMenu is not null) if (_popupMenu != 0)
{ {
PInvoke.GetCursorPos(out var cursorPos); GetCursorPos(out var cursorPos);
PInvoke.SetForegroundWindow(_hwnd); SetForegroundWindow(_hwnd);
PInvoke.TrackPopupMenuEx(_popupMenu, (uint)TRACK_POPUP_MENU_FLAGS.TPM_LEFTALIGN | (uint)TRACK_POPUP_MENU_FLAGS.TPM_BOTTOMALIGN, cursorPos.X, cursorPos.Y, _hwnd, null); TrackPopupMenuExNative(_popupMenu, (uint)TRACK_POPUP_MENU_FLAGS.TPM_LEFTALIGN | (uint)TRACK_POPUP_MENU_FLAGS.TPM_BOTTOMALIGN, cursorPos.X, cursorPos.Y, _hwnd, 0);
} }
} }
@@ -261,16 +267,62 @@ namespace PowerDisplay.Helpers
break; break;
} }
nint result; return CallWindowProcIntPtr(_originalWndProc, hwnd, uMsg, wParam, lParam);
unsafe
{
result = CallWindowProcIntPtr(_originalWndProc, (nint)hwnd.Value, uMsg, wParam.Value, lParam.Value);
}
return new LRESULT(result);
} }
[LibraryImport("user32.dll", EntryPoint = "CallWindowProcW")] [LibraryImport("user32.dll", EntryPoint = "CallWindowProcW")]
private static partial nint CallWindowProcIntPtr(IntPtr lpPrevWndFunc, nint hWnd, uint msg, nuint wParam, nint lParam); private static partial nint CallWindowProcIntPtr(IntPtr lpPrevWndFunc, nint hWnd, uint msg, nuint wParam, nint lParam);
[LibraryImport("user32.dll", EntryPoint = "RegisterWindowMessageW", StringMarshalling = StringMarshalling.Utf16)]
private static partial uint RegisterWindowMessageNative(string lpString);
[LibraryImport("user32.dll", EntryPoint = "SetWindowLongPtrW")]
private static partial nint SetWindowLongPtrNative(nint hWnd, int nIndex, nint dwNewLong);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool GetCursorPos(out POINT lpPoint);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool SetForegroundWindow(nint hWnd);
// Shell APIs - use uint for enums and unsafe pointer for struct
[LibraryImport("shell32.dll", EntryPoint = "Shell_NotifyIconW")]
[return: MarshalAs(UnmanagedType.Bool)]
private static unsafe partial bool Shell_NotifyIconNative(uint dwMessage, NOTIFYICONDATAW* lpData);
[LibraryImport("shell32.dll", EntryPoint = "ExtractIconExW", StringMarshalling = StringMarshalling.Utf16)]
private static partial uint ExtractIconExNative(string lpszFile, int nIconIndex, out nint phiconLarge, out nint phiconSmall, uint nIcons);
// Menu APIs
[LibraryImport("user32.dll")]
private static partial nint CreatePopupMenu();
[LibraryImport("user32.dll", EntryPoint = "InsertMenuW", StringMarshalling = StringMarshalling.Utf16)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool InsertMenuNative(nint hMenu, uint uPosition, uint uFlags, nuint uIDNewItem, string? lpNewItem);
[LibraryImport("user32.dll", EntryPoint = "TrackPopupMenuEx")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool TrackPopupMenuExNative(nint hMenu, uint uFlags, int x, int y, nint hwnd, nint lptpm);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool DestroyMenu(nint hMenu);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool DestroyIcon(nint hIcon);
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int X;
public int Y;
}
// GWL_WNDPROC constant
private const int GWL_WNDPROC = -4;
} }
} }

View File

@@ -1,19 +1,14 @@
// TrayIcon APIs // Types/Structs only - functions use LibraryImport
Shell_NotifyIcon NOTIFYICONDATAW
WM_USER NOTIFY_ICON_MESSAGE
WM_WINDOWPOSCHANGING NOTIFY_ICON_DATA_FLAGS
RegisterWindowMessageW MENU_ITEM_FLAGS
ExtractIconEx
TRACK_POPUP_MENU_FLAGS TRACK_POPUP_MENU_FLAGS
// Constants for window messages
WM_USER
WM_COMMAND WM_COMMAND
WM_RBUTTONUP WM_RBUTTONUP
WM_LBUTTONUP WM_LBUTTONUP
WM_LBUTTONDBLCLK WM_LBUTTONDBLCLK
CreatePopupMenu WM_WINDOWPOSCHANGING
TrackPopupMenuEx
InsertMenu
SetWindowLongPtr
CallWindowProc
GetCursorPos
SetForegroundWindow
WNDPROC