diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs index 2d176c7e..f848c10b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs @@ -12,6 +12,7 @@ internal static class SettingKeys { #region MainWindow public const string WindowRect = "WindowRect"; + public const string GuideWindowRect = "GuideWindowRect"; public const string IsNavPaneOpen = "IsNavPaneOpen"; public const string IsInfoBarToggleChecked = "IsInfoBarToggleChecked"; public const string ExcludedAnnouncementIds = "ExcludedAnnouncementIds"; diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconController.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconController.cs new file mode 100644 index 00000000..eab29986 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconController.cs @@ -0,0 +1,54 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.ExceptionService; +using Snap.Hutao.Win32.UI.WindowsAndMessaging; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Windows.Storage; +using static Snap.Hutao.Win32.ConstValues; + +namespace Snap.Hutao.Core.Windowing.NotifyIcon; + +internal sealed class NotifyIconController : IDisposable +{ + private readonly NotifyIconMessageWindow messageWindow; + private readonly System.Drawing.Icon icon; + + public NotifyIconController() + { + messageWindow = new(); + + NotifyIconMethods.Delete(Id); + + StorageFile iconFile = StorageFile.GetFileFromApplicationUriAsync("ms-appx:///Assets/Logo.ico".ToUri()).AsTask().GetAwaiter().GetResult(); + icon = new(iconFile.Path); + + if (!NotifyIconMethods.Add(Id, messageWindow.HWND, "Snap Hutao", NotifyIconMessageWindow.WM_NOTIFYICON_CALLBACK, (HICON)icon.Handle)) + { + HutaoException.InvalidOperation("Failed to create NotifyIcon"); + } + + if (!NotifyIconMethods.SetVersion(Id, NOTIFYICON_VERSION_4)) + { + HutaoException.InvalidOperation("Failed to set NotifyIcon version"); + } + } + + private static ref readonly Guid Id + { + get + { + // MD5 for "Snap.Hutao" + ReadOnlySpan data = [0xEE, 0x01, 0x5C, 0xCB, 0xF3, 0x97, 0xC6, 0x93, 0xE8, 0x77, 0xCE, 0x09, 0x54, 0x90, 0xEE, 0xAC]; + return ref Unsafe.As(ref MemoryMarshal.GetReference(data)); + } + } + + public void Dispose() + { + messageWindow.Dispose(); + NotifyIconMethods.Delete(Id); + icon.Dispose(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconMessageWindow.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconMessageWindow.cs new file mode 100644 index 00000000..e687b0c1 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconMessageWindow.cs @@ -0,0 +1,116 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Win32.Foundation; +using Snap.Hutao.Win32.UI.WindowsAndMessaging; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Snap.Hutao.Win32.User32; + +namespace Snap.Hutao.Core.Windowing.NotifyIcon; + +[SuppressMessage("", "SA1310")] +internal sealed class NotifyIconMessageWindow : IDisposable +{ + public const uint WM_NOTIFYICON_CALLBACK = 0x444U; + private const string WindowClassName = "SnapHutaoNotifyIconMessageWindowClass"; + + private static readonly ConcurrentDictionary WindowTable = []; + + public readonly HWND HWND; + + [SuppressMessage("", "SA1306")] + private uint WM_TASKBARCREATED; + + private bool isDisposed; + + public unsafe NotifyIconMessageWindow() + { + ushort atom; + fixed (char* className = WindowClassName) + { + WNDCLASSW wc = new() + { + lpfnWndProc = WNDPROC.Create(&OnWindowProcedure), + lpszClassName = className, + }; + + atom = RegisterClassW(&wc); + } + + ArgumentOutOfRangeException.ThrowIfEqual(atom, 0); + + // https://learn.microsoft.com/zh,cn/windows/win32/shell/taskbar#taskbar,creation,notification + WM_TASKBARCREATED = RegisterWindowMessageW("TaskbarCreated"); + + HWND = CreateWindowExW(0, WindowClassName, WindowClassName, 0, 0, 0, 0, 0, default, default, default, default); + + if (HWND == default) + { + Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError()); + } + + WindowTable.TryAdd(HWND, this); + } + + ~NotifyIconMessageWindow() + { + Dispose(); + } + + public void Dispose() + { + if (isDisposed) + { + return; + } + + isDisposed = true; + + DestroyWindow(HWND); + WindowTable.TryRemove(HWND, out _); + + GC.SuppressFinalize(this); + } + + [SuppressMessage("", "SH002")] + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] + private static unsafe LRESULT OnWindowProcedure(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam) + { + if (WindowTable.TryGetValue(hwnd, out NotifyIconMessageWindow? window)) + { + if (uMsg == window.WM_TASKBARCREATED) + { + // TODO: Re-add the notify icon. + } + + // https://learn.microsoft.com/zh-cn/windows/win32/api/shellapi/ns-shellapi-notifyicondataw + if (uMsg == WM_NOTIFYICON_CALLBACK) + { + LPARAM2 lParam2 = *(LPARAM2*)&lParam; + WPARAM2 wParam2 = *(WPARAM2*)&wParam; + Debug.WriteLine($"[uMsg: 0x{uMsg:X8}] [X: {wParam2.X} Y: {wParam2.Y}] [Low: 0x{lParam2.Low:X8} High: 0x{lParam2.High:X8}]"); + switch (lParam.Value) + { + + } + } + } + + return DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + + private readonly struct LPARAM2 + { + public readonly uint Low; + public readonly uint High; + } + + private readonly struct WPARAM2 + { + public readonly ushort X; + public readonly ushort Y; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconMethods.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconMethods.cs new file mode 100644 index 00000000..dfaf8f35 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconMethods.cs @@ -0,0 +1,75 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Win32.Foundation; +using Snap.Hutao.Win32.UI.Shell; +using Snap.Hutao.Win32.UI.WindowsAndMessaging; +using static Snap.Hutao.Win32.Shell32; + +namespace Snap.Hutao.Core.Windowing.NotifyIcon; + +internal sealed class NotifyIconMethods +{ + public static BOOL Add(ref readonly NOTIFYICONDATAW data) + { + return Shell_NotifyIconW(NOTIFY_ICON_MESSAGE.NIM_ADD, in data); + } + + [SuppressMessage("", "SH002")] + public static unsafe BOOL Add(Guid id, HWND hWnd, string tip, uint uCallbackMessage, HICON hIcon) + { + NOTIFYICONDATAW data = default; + data.cbSize = (uint)sizeof(NOTIFYICONDATAW); + data.uFlags = NOTIFY_ICON_DATA_FLAGS.NIF_MESSAGE | NOTIFY_ICON_DATA_FLAGS.NIF_ICON | NOTIFY_ICON_DATA_FLAGS.NIF_TIP | NOTIFY_ICON_DATA_FLAGS.NIF_GUID; + data.guidItem = id; + data.hWnd = hWnd; + tip.AsSpan().CopyTo(new(data.szTip, 128)); + data.uCallbackMessage = uCallbackMessage; + data.hIcon = hIcon; + data.dwState = NOTIFY_ICON_STATE.NIS_HIDDEN; + data.dwStateMask = NOTIFY_ICON_STATE.NIS_HIDDEN; + + return Add(in data); + } + + public static BOOL Modify(ref readonly NOTIFYICONDATAW data) + { + return Shell_NotifyIconW(NOTIFY_ICON_MESSAGE.NIM_MODIFY, in data); + } + + public static BOOL Delete(ref readonly NOTIFYICONDATAW data) + { + return Shell_NotifyIconW(NOTIFY_ICON_MESSAGE.NIM_DELETE, in data); + } + + public static unsafe BOOL Delete(Guid id) + { + NOTIFYICONDATAW data = default; + data.cbSize = (uint)sizeof(NOTIFYICONDATAW); + data.uFlags = NOTIFY_ICON_DATA_FLAGS.NIF_GUID; + data.guidItem = id; + + return Delete(in data); + } + + public static BOOL SetFocus(ref readonly NOTIFYICONDATAW data) + { + return Shell_NotifyIconW(NOTIFY_ICON_MESSAGE.NIM_SETFOCUS, in data); + } + + public static BOOL SetVersion(ref readonly NOTIFYICONDATAW data) + { + return Shell_NotifyIconW(NOTIFY_ICON_MESSAGE.NIM_SETVERSION, in data); + } + + public static unsafe BOOL SetVersion(Guid id, uint version) + { + NOTIFYICONDATAW data = default; + data.cbSize = (uint)sizeof(NOTIFYICONDATAW); + data.uFlags = NOTIFY_ICON_DATA_FLAGS.NIF_GUID; + data.guidItem = id; + data.Anonymous.uVersion = version; + + return SetVersion(in data); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs index 494dd476..2a6fad9f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs @@ -89,9 +89,9 @@ internal sealed class WindowController SizeInt32 scaledSize = options.InitSize.Scale(scale); RectInt32 rect = StructMarshal.RectInt32(scaledSize); - if (options.PersistSize) + if (!string.IsNullOrEmpty(options.PersistRectKey)) { - RectInt32 persistedRect = (CompactRect)LocalSetting.Get(SettingKeys.WindowRect, (CompactRect)rect); + RectInt32 persistedRect = (CompactRect)LocalSetting.Get(options.PersistRectKey, (CompactRect)rect); if (persistedRect.Size() >= options.InitSize.Size()) { rect = persistedRect.Scale(scale); @@ -104,7 +104,7 @@ internal sealed class WindowController private void SaveOrSkipWindowSize() { - if (!options.PersistSize) + if (string.IsNullOrEmpty(options.PersistRectKey)) { return; } @@ -116,7 +116,7 @@ internal sealed class WindowController if (!windowPlacement.ShowCmd.HasFlag(SHOW_WINDOW_CMD.SW_SHOWMAXIMIZED)) { double scale = 1.0 / options.GetRasterizationScale(); - LocalSetting.Set(SettingKeys.WindowRect, (CompactRect)window.AppWindow.GetRect().Scale(scale)); + LocalSetting.Set(options.PersistRectKey, (CompactRect)window.AppWindow.GetRect().Scale(scale)); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowOptions.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowOptions.cs index d0ddfc2a..fa110de4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowOptions.cs @@ -38,15 +38,18 @@ internal readonly struct WindowOptions /// /// 是否持久化尺寸 /// + [Obsolete] public readonly bool PersistSize; - public WindowOptions(Window window, FrameworkElement titleBar, SizeInt32 initSize, bool persistSize = false) + public readonly string? PersistRectKey; + + public WindowOptions(Window window, FrameworkElement titleBar, SizeInt32 initSize, string? persistSize = default) { Hwnd = WindowNative.GetWindowHandle(window); InputNonClientPointerSource = InputNonClientPointerSource.GetForWindowId(window.AppWindow.Id); TitleBar = titleBar; InitSize = initSize; - PersistSize = persistSize; + PersistRectKey = persistSize; } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs index 33a5f142..bdad8855 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs @@ -4,9 +4,12 @@ using Microsoft.UI.Xaml; using Snap.Hutao.Core.Windowing.Backdrop; using Snap.Hutao.Core.Windowing.HotKey; +using Snap.Hutao.Win32; using Snap.Hutao.Win32.Foundation; using Snap.Hutao.Win32.UI.Shell; using Snap.Hutao.Win32.UI.WindowsAndMessaging; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using static Snap.Hutao.Win32.ComCtl32; using static Snap.Hutao.Win32.ConstValues; @@ -27,6 +30,7 @@ internal sealed class WindowSubclass : IDisposable // We have to explicitly hold a reference to SUBCLASSPROC private SUBCLASSPROC windowProc = default!; + private UnmanagedAccess unmanagedAccess = default!; public WindowSubclass(Window window, in WindowOptions options, IServiceProvider serviceProvider) { @@ -41,10 +45,11 @@ internal sealed class WindowSubclass : IDisposable /// 尝试设置窗体子类 /// /// 是否设置成功 - public bool Initialize() + public unsafe bool Initialize() { - windowProc = OnSubclassProcedure; - bool windowHooked = SetWindowSubclass(options.Hwnd, windowProc, WindowSubclassId, 0); + windowProc = SUBCLASSPROC.Create(&OnSubclassProcedure); + unmanagedAccess = UnmanagedAccess.Create(this); + bool windowHooked = SetWindowSubclass(options.Hwnd, windowProc, WindowSubclassId, unmanagedAccess); hotKeyController.RegisterAll(); return windowHooked; @@ -57,18 +62,23 @@ internal sealed class WindowSubclass : IDisposable RemoveWindowSubclass(options.Hwnd, windowProc, WindowSubclassId); windowProc = default!; + unmanagedAccess.Dispose(); } [SuppressMessage("", "SH002")] - private unsafe LRESULT OnSubclassProcedure(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData) + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] + private static unsafe LRESULT OnSubclassProcedure(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData) { + WindowSubclass? state = UnmanagedAccess.Get(dwRefData); + ArgumentNullException.ThrowIfNull(state); + switch (uMsg) { case WM_GETMINMAXINFO: { - if (window is IMinMaxInfoHandler handler) + if (state.window is IMinMaxInfoHandler handler) { - handler.HandleMinMaxInfo(ref *(MINMAXINFO*)lParam, options.GetRasterizationScale()); + handler.HandleMinMaxInfo(ref *(MINMAXINFO*)lParam, state.options.GetRasterizationScale()); } break; @@ -82,13 +92,13 @@ internal sealed class WindowSubclass : IDisposable case WM_HOTKEY: { - hotKeyController.OnHotKeyPressed(*(HotKeyParameter*)&lParam); + state.hotKeyController.OnHotKeyPressed(*(HotKeyParameter*)&lParam); break; } case WM_ERASEBKGND: { - if (window.SystemBackdrop is IBackdropNeedEraseBackground) + if (state.window.SystemBackdrop is IBackdropNeedEraseBackground) { return (LRESULT)(int)BOOL.TRUE; } diff --git a/src/Snap.Hutao/Snap.Hutao/GuideWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/GuideWindow.xaml.cs index 904a6fff..edc52cdf 100644 --- a/src/Snap.Hutao/Snap.Hutao/GuideWindow.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/GuideWindow.xaml.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml; +using Snap.Hutao.Core.Setting; using Snap.Hutao.Core.Windowing; using Snap.Hutao.Win32.UI.WindowsAndMessaging; @@ -24,7 +25,7 @@ internal sealed partial class GuideWindow : Window, IWindowOptionsSource, IMinMa public GuideWindow(IServiceProvider serviceProvider) { InitializeComponent(); - windowOptions = new(this, DragableGrid, new(MinWidth, MinHeight)); + windowOptions = new(this, DragableGrid, new(MinWidth, MinHeight), SettingKeys.GuideWindowRect); this.InitializeController(serviceProvider); } diff --git a/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs index ae9b83ff..e69d63a3 100644 --- a/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs @@ -3,6 +3,7 @@ using Microsoft.UI.Xaml; using Snap.Hutao.Control.Extension; +using Snap.Hutao.Core.Setting; using Snap.Hutao.Core.Windowing; using Snap.Hutao.ViewModel.Game; using Snap.Hutao.Win32.UI.WindowsAndMessaging; diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs index 463bc5f4..6bc104fe 100644 --- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml; +using Snap.Hutao.Core.Setting; using Snap.Hutao.Core.Windowing; using Snap.Hutao.Win32.UI.WindowsAndMessaging; @@ -27,7 +28,7 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IMinMax public MainWindow(IServiceProvider serviceProvider) { InitializeComponent(); - windowOptions = new(this, TitleBarView.DragArea, new(1200, 741), true); + windowOptions = new(this, TitleBarView.DragArea, new(1200, 741), SettingKeys.WindowRect); this.InitializeController(serviceProvider); } diff --git a/src/Snap.Hutao/Snap.Hutao/Program.cs b/src/Snap.Hutao/Snap.Hutao/Program.cs index 110500f0..6242f63b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Program.cs +++ b/src/Snap.Hutao/Snap.Hutao/Program.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml; +using Snap.Hutao.Core.Windowing.NotifyIcon; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using WinRT; @@ -49,9 +50,12 @@ public static partial class Program // By adding the using statement, we can dispose the injected services when we closing using (ServiceProvider serviceProvider = DependencyInjection.Initialize()) { - // In a Desktop app this runs a message pump internally, - // and does not return until the application shuts down. - Application.Start(AppInitializationCallback); + using (NotifyIconController notifyIconController = new()) + { + // In a Desktop app this runs a message pump internally, + // and does not return until the application shuts down. + Application.Start(AppInitializationCallback); + } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/AdvApi32.cs b/src/Snap.Hutao/Snap.Hutao/Win32/AdvApi32.cs index fb7f78c9..3ad525ab 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/AdvApi32.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/AdvApi32.cs @@ -14,7 +14,7 @@ namespace Snap.Hutao.Win32; [SuppressMessage("", "SYSLIB1054")] internal static class AdvApi32 { - [DllImport("ADVAPI32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [DllImport("ADVAPI32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true, SetLastError = true)] [SupportedOSPlatform("windows5.1.2600")] public static unsafe extern BOOL ConvertSidToStringSidW(PSID Sid, PWSTR* StringSid); @@ -26,7 +26,7 @@ internal static class AdvApi32 } } - [DllImport("ADVAPI32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [DllImport("ADVAPI32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true, SetLastError = true)] [SupportedOSPlatform("windows5.1.2600")] public static unsafe extern BOOL ConvertStringSidToSidW(PCWSTR StringSid, PSID* Sid); @@ -53,7 +53,7 @@ internal static class AdvApi32 [SupportedOSPlatform("windows5.0")] public static extern WIN32_ERROR RegNotifyChangeKeyValue(HKEY hKey, BOOL bWatchSubtree, REG_NOTIFY_FILTER dwNotifyFilter, [AllowNull] HANDLE hEvent, BOOL fAsynchronous); - [DllImport("ADVAPI32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true)] + [DllImport("ADVAPI32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] [SupportedOSPlatform("windows5.0")] public static unsafe extern WIN32_ERROR RegOpenKeyExW(HKEY hKey, [AllowNull] PCWSTR lpSubKey, [AllowNull] uint ulOptions, REG_SAM_FLAGS samDesired, HKEY* phkResult); diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/ComCtl32.cs b/src/Snap.Hutao/Snap.Hutao/Win32/ComCtl32.cs index f1cb95ba..9f1ee945 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/ComCtl32.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/ComCtl32.cs @@ -18,9 +18,9 @@ internal static class ComCtl32 [DllImport("COMCTL32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] [SupportedOSPlatform("windows5.1.2600")] - public static extern BOOL RemoveWindowSubclass(HWND hWnd, [MarshalAs(UnmanagedType.FunctionPtr)] SUBCLASSPROC pfnSubclass, nuint uIdSubclass); + public static extern BOOL RemoveWindowSubclass(HWND hWnd, SUBCLASSPROC pfnSubclass, nuint uIdSubclass); [DllImport("COMCTL32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] [SupportedOSPlatform("windows5.1.2600")] - public static unsafe extern BOOL SetWindowSubclass(HWND hWnd, [MarshalAs(UnmanagedType.FunctionPtr)] SUBCLASSPROC pfnSubclass, nuint uIdSubclass, nuint dwRefData); + public static unsafe extern BOOL SetWindowSubclass(HWND hWnd, SUBCLASSPROC pfnSubclass, nuint uIdSubclass, nuint dwRefData); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/ConstValues.cs b/src/Snap.Hutao/Snap.Hutao/Win32/ConstValues.cs index 59363b2f..2d225a39 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/ConstValues.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/ConstValues.cs @@ -7,6 +7,8 @@ namespace Snap.Hutao.Win32; internal static class ConstValues { public const uint D3D11_SDK_VERSION = 0x00000007U; + public const uint NOTIFYICON_VERSION = 0x00000003U; + public const uint NOTIFYICON_VERSION_4 = 0x00000004U; public const uint WM_NULL = 0x00000000U; public const uint WM_ERASEBKGND = 0x00000014U; public const uint WM_GETMINMAXINFO = 0x00000024U; diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/HWND.cs b/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/HWND.cs index 56b95bf5..c29e15e6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/HWND.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/HWND.cs @@ -7,8 +7,6 @@ internal readonly struct HWND { public readonly nint Value; - public HWND(nint value) => Value = value; - public static unsafe implicit operator HWND(nint value) => *(HWND*)&value; public static unsafe implicit operator nint(HWND value) => *(nint*)&value; diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/Kernel32.cs b/src/Snap.Hutao/Snap.Hutao/Win32/Kernel32.cs index d9ed6fa4..5186520a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/Kernel32.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/Kernel32.cs @@ -22,7 +22,7 @@ internal static class Kernel32 [SupportedOSPlatform("windows5.0")] public static extern BOOL CloseHandle(HANDLE hObject); - [DllImport("KERNEL32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [DllImport("KERNEL32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true, SetLastError = true)] [SupportedOSPlatform("windows5.1.2600")] public static unsafe extern HANDLE CreateEventW([AllowNull] SECURITY_ATTRIBUTES* lpEventAttributes, BOOL bManualReset, BOOL bInitialState, [AllowNull] PCWSTR lpName); @@ -72,7 +72,7 @@ internal static class Kernel32 } } - [DllImport("KERNEL32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true)] + [DllImport("KERNEL32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] public static extern uint K32GetModuleBaseNameW(HANDLE hProcess, [AllowNull] HMODULE hModule, PWSTR lpBaseName, uint nSize); [DebuggerStepThrough] @@ -128,7 +128,7 @@ internal static class Kernel32 [DllImport("KERNEL32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] public static extern BOOL SetConsoleMode(HANDLE hConsoleHandle, CONSOLE_MODE dwMode); - [DllImport("KERNEL32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [DllImport("KERNEL32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true, SetLastError = true)] public static extern BOOL SetConsoleTitleW(PCWSTR lpConsoleTitle); [DebuggerStepThrough] diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/UI/Shell/SUBCLASSPROC.cs b/src/Snap.Hutao/Snap.Hutao/Win32/UI/Shell/SUBCLASSPROC.cs index 54d8d7df..fd2727d4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/UI/Shell/SUBCLASSPROC.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/UI/Shell/SUBCLASSPROC.cs @@ -6,5 +6,17 @@ using System.Runtime.InteropServices; namespace Snap.Hutao.Win32.UI.Shell; -[UnmanagedFunctionPointer(CallingConvention.Winapi)] -internal delegate LRESULT SUBCLASSPROC(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData); \ No newline at end of file +internal unsafe readonly struct SUBCLASSPROC +{ + private readonly delegate* unmanaged[Stdcall] value; + + public SUBCLASSPROC(delegate* unmanaged[Stdcall] method) + { + value = method; + } + + public static SUBCLASSPROC Create(delegate* unmanaged[Stdcall] method) + { + return new(method); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/HICON.cs b/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/HICON.cs index bf04663f..35dbc4b4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/HICON.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/HICON.cs @@ -8,4 +8,8 @@ namespace Snap.Hutao.Win32.UI.WindowsAndMessaging; internal readonly struct HICON { public readonly nint Value; + + public static unsafe implicit operator HICON(nint value) => *(HICON*)&value; + + public static unsafe implicit operator nint(HICON value) => *(nint*)&value; } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/HMENU.cs b/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/HMENU.cs new file mode 100644 index 00000000..e8810f29 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/HMENU.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Win32.UI.WindowsAndMessaging; + +// RAIIFree: DestroyMenu +// InvalidHandleValue: -1, 0 +internal readonly struct HMENU +{ + public readonly nint Value; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/WINDOW_STYLE.cs b/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/WINDOW_STYLE.cs new file mode 100644 index 00000000..f6451d8f --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/WINDOW_STYLE.cs @@ -0,0 +1,38 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Win32.UI.WindowsAndMessaging; + +[Flags] +[SuppressMessage("", "CA1069")] +internal enum WINDOW_STYLE : uint +{ + WS_OVERLAPPED = 0x0U, + WS_POPUP = 0x80000000U, + WS_CHILD = 0x40000000U, + WS_MINIMIZE = 0x20000000U, + WS_VISIBLE = 0x10000000U, + WS_DISABLED = 0x8000000U, + WS_CLIPSIBLINGS = 0x4000000U, + WS_CLIPCHILDREN = 0x2000000U, + WS_MAXIMIZE = 0x1000000U, + WS_CAPTION = 0xC00000U, + WS_BORDER = 0x800000U, + WS_DLGFRAME = 0x400000U, + WS_VSCROLL = 0x200000U, + WS_HSCROLL = 0x100000U, + WS_SYSMENU = 0x80000U, + WS_THICKFRAME = 0x40000U, + WS_GROUP = 0x20000U, + WS_TABSTOP = 0x10000U, + WS_MINIMIZEBOX = 0x20000U, + WS_MAXIMIZEBOX = 0x10000U, + WS_TILED = 0x0U, + WS_ICONIC = 0x20000000U, + WS_SIZEBOX = 0x40000U, + WS_TILEDWINDOW = 0xCF0000U, + WS_OVERLAPPEDWINDOW = 0xCF0000U, + WS_POPUPWINDOW = 0x80880000U, + WS_CHILDWINDOW = 0x40000000U, + WS_ACTIVECAPTION = 0x1U, +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/WNDCLASSW.cs b/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/WNDCLASSW.cs index c0a7b863..d3b59606 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/WNDCLASSW.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/WNDCLASSW.cs @@ -10,8 +10,6 @@ namespace Snap.Hutao.Win32.UI.WindowsAndMessaging; internal struct WNDCLASSW { public WNDCLASS_STYLES style; - - [MarshalAs(UnmanagedType.FunctionPtr)] public WNDPROC lpfnWndProc; public int cbClsExtra; public int cbWndExtra; diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/WNDPROC.cs b/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/WNDPROC.cs index 5a696bbe..6ef8d114 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/WNDPROC.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/WNDPROC.cs @@ -2,9 +2,20 @@ // Licensed under the MIT license. using Snap.Hutao.Win32.Foundation; -using System.Runtime.InteropServices; namespace Snap.Hutao.Win32.UI.WindowsAndMessaging; -[UnmanagedFunctionPointer(CallingConvention.Winapi)] -internal delegate LRESULT WNDPROC(HWND param0, uint param1, WPARAM param2, LPARAM param3); \ No newline at end of file +internal unsafe readonly struct WNDPROC +{ + private readonly delegate* unmanaged[Stdcall] value; + + public WNDPROC(delegate* unmanaged[Stdcall] method) + { + value = method; + } + + public static WNDPROC Create(delegate* unmanaged[Stdcall] method) + { + return new(method); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/UnmanagedAccess.cs b/src/Snap.Hutao/Snap.Hutao/Win32/UnmanagedAccess.cs new file mode 100644 index 00000000..6fd86d59 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Win32/UnmanagedAccess.cs @@ -0,0 +1,53 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Runtime.InteropServices; + +namespace Snap.Hutao.Win32; + +internal readonly struct UnmanagedAccess : IDisposable + where T : class +{ + private readonly nint handle; + + public UnmanagedAccess(T value) + { + handle = GCHandle.ToIntPtr(GCHandle.Alloc(value)); + } + + public static implicit operator nint(UnmanagedAccess access) + { + return access.handle; + } + + public static implicit operator nuint(UnmanagedAccess access) + { + return (nuint)access.handle; + } + + public void Dispose() + { + GCHandle.FromIntPtr(handle).Free(); + } +} + +internal static class UnmanagedAccess +{ + public static UnmanagedAccess Create(T value) + where T : class + { + return new UnmanagedAccess(value); + } + + public static T? Get(nint handle) + where T : class + { + return GCHandle.FromIntPtr(handle).Target as T; + } + + public static T? Get(nuint handle) + where T : class + { + return GCHandle.FromIntPtr((nint)handle).Target as T; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/User32.cs b/src/Snap.Hutao/Snap.Hutao/Win32/User32.cs index a74f1ed8..ffbd01e0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/User32.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/User32.cs @@ -12,6 +12,7 @@ using System.Runtime.Versioning; namespace Snap.Hutao.Win32; [SuppressMessage("", "SH002")] +[SuppressMessage("", "SA1313")] [SuppressMessage("", "SYSLIB1054")] internal static class User32 { @@ -19,7 +20,30 @@ internal static class User32 [SupportedOSPlatform("windows5.1.2600")] public static extern BOOL AttachThreadInput(uint idAttach, uint idAttachTo, BOOL fAttach); - [DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [DllImport("USER32.dll", ExactSpelling = true, SetLastError = true)] + [SupportedOSPlatform("windows5.0")] + public static unsafe extern HWND CreateWindowExW(WINDOW_EX_STYLE dwExStyle, [AllowNull] PCWSTR lpClassName, [AllowNull] PCWSTR lpWindowName, WINDOW_STYLE dwStyle, int X, int Y, int nWidth, int nHeight, [AllowNull] HWND hWndParent, [AllowNull] HMENU hMenu, [AllowNull] HINSTANCE hInstance, [AllowNull] void* lpParam); + + public static unsafe HWND CreateWindowExW(WINDOW_EX_STYLE dwExStyle, [AllowNull] string className, [AllowNull] string windowName, WINDOW_STYLE dwStyle, int X, int Y, int nWidth, int nHeight, [AllowNull] HWND hWndParent, [AllowNull] HMENU hMenu, [AllowNull] HINSTANCE hInstance, [AllowNull] void* lpParam) + { + fixed (char* lpClassName = className) + { + fixed (char* lpWindowName = windowName) + { + return CreateWindowExW(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); + } + } + } + + [DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] + [SupportedOSPlatform("windows5.0")] + public static extern LRESULT DefWindowProcW(HWND hWnd, uint Msg, WPARAM wParam, LPARAM lParam); + + [DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true, SetLastError = true)] + [SupportedOSPlatform("windows5.0")] + public static extern BOOL DestroyWindow(HWND hWnd); + + [DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true, SetLastError = true)] [SupportedOSPlatform("windows5.0")] public static extern HWND FindWindowExW([AllowNull] HWND hWndParent, [AllowNull] HWND hWndChildAfter, [AllowNull] PCWSTR lpszClass, [AllowNull] PCWSTR lpszWindow); @@ -47,7 +71,7 @@ internal static class User32 [SupportedOSPlatform("windows5.0")] public static extern HWND GetForegroundWindow(); - [DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true, SetLastError = true)] [SupportedOSPlatform("windows5.0")] public static extern nint GetWindowLongPtrW(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex); @@ -93,6 +117,18 @@ internal static class User32 [SupportedOSPlatform("windows6.0.6000")] public static extern BOOL RegisterHotKey([AllowNull] HWND hWnd, int id, HOT_KEY_MODIFIERS fsModifiers, uint vk); + [DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true, SetLastError = true)] + [SupportedOSPlatform("windows5.0")] + public static extern uint RegisterWindowMessageW(PCWSTR lpString); + + public static unsafe uint RegisterWindowMessageW(string @string) + { + fixed (char* lpString = @string) + { + return RegisterWindowMessageW(lpString); + } + } + [DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] [SupportedOSPlatform("windows5.0")] public static extern int ReleaseDC([AllowNull] HWND hWnd, HDC hDC); @@ -127,7 +163,7 @@ internal static class User32 [SupportedOSPlatform("windows5.0")] public static extern BOOL SetForegroundWindow(HWND hWnd); - [DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true, SetLastError = true)] [SupportedOSPlatform("windows5.0")] public static extern BOOL SetPropW(HWND hWnd, PCWSTR lpString, [AllowNull] HANDLE hData); @@ -140,7 +176,7 @@ internal static class User32 } } - [DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true, SetLastError = true)] [SupportedOSPlatform("windows5.0")] public static extern nint SetWindowLongPtrW(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, nint dwNewLong);