From 0f767f7e77a3ca973becb0e203035862ffb41b93 Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Sun, 12 May 2024 22:36:22 +0800 Subject: [PATCH] contextmenu --- src/Snap.Hutao/Snap.Hutao/App.xaml.cs | 2 - .../CastServiceExtension.cs | 26 ------- .../Snap.Hutao/Core/LifeCycle/Activation.cs | 2 + .../Core/Logging/ConsoleWindowLifeTime.cs | 9 ++- .../Backdrop/IWindowNeedEraseBackground.cs | 6 ++ .../InputActiveDesktopAcrylicBackdrop.cs | 41 +++++++++++ .../NotifyIcon/NotifyIconContextMenu.xaml | 45 ++++++++++++ .../NotifyIcon/NotifyIconContextMenu.xaml.cs | 17 +++++ .../NotifyIcon/NotifyIconController.cs | 16 ++++- .../NotifyIcon/NotifyIconMessageWindow.cs | 28 ++------ .../Windowing/NotifyIcon/NotifyIconMethods.cs | 15 ++++ .../NotifyIcon/NotifyIconXamlHostWindow.cs | 71 +++++++++++++++++++ .../Core/Windowing/NotifyIcon/PointUInt16.cs | 10 +++ .../Core/Windowing/WindowExtension.cs | 2 + .../Core/Windowing/XamlWindowSubclass.cs | 8 +-- src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj | 6 ++ .../ViewModel/NotifyIconViewModel.cs | 43 +++++++++++ .../ViewModel/Setting/SettingViewModel.cs | 3 +- .../HttpRequestMessageBuilderExtension.cs | 22 +++--- .../Snap.Hutao/Win32/Foundation/COLORREF.cs | 9 +++ .../Snap.Hutao/Win32/Foundation/RECT.cs | 10 +-- src/Snap.Hutao/Snap.Hutao/Win32/Macros.cs | 6 ++ .../Win32/Registry/RegistryWatcher.cs | 10 ++- src/Snap.Hutao/Snap.Hutao/Win32/Shell32.cs | 15 ++++ .../Snap.Hutao/Win32/StructMarshal.cs | 6 ++ .../Win32/UI/Shell/NOTIFYICONIDENTIFIER.cs | 15 ++++ .../LAYERED_WINDOW_ATTRIBUTES_FLAGS.cs | 11 +++ src/Snap.Hutao/Snap.Hutao/Win32/User32.cs | 8 +++ 28 files changed, 388 insertions(+), 74 deletions(-) delete mode 100644 src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/CastServiceExtension.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Windowing/Backdrop/IWindowNeedEraseBackground.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Windowing/Backdrop/InputActiveDesktopAcrylicBackdrop.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconContextMenu.xaml create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconContextMenu.xaml.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconXamlHostWindow.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/PointUInt16.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/ViewModel/NotifyIconViewModel.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Win32/Foundation/COLORREF.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Win32/UI/Shell/NOTIFYICONIDENTIFIER.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/LAYERED_WINDOW_ATTRIBUTES_FLAGS.cs diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs index 2af98f7a..443ad36f 100644 --- a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs @@ -44,8 +44,6 @@ public sealed partial class App : Application private readonly IActivation activation; private readonly ILogger logger; - private NotifyIconController? notifyIconController; - /// /// Initializes the singleton application object. /// diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/CastServiceExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/CastServiceExtension.cs deleted file mode 100644 index d869b27d..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/CastServiceExtension.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Snap.Hutao.Core.DependencyInjection.Abstraction; - -namespace Snap.Hutao.Core.DependencyInjection; - -/// -/// 对象扩展 -/// -[HighQuality] -internal static class CastServiceExtension -{ - /// - /// 的链式调用扩展 - /// - /// 目标转换类型 - /// 对象 - /// 转换类型后的对象 - [Obsolete("Not useful anymore")] - public static T? As(this ICastService service) - where T : class - { - return service as T; - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs index 4146482e..32b20e11 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs @@ -3,6 +3,7 @@ using CommunityToolkit.WinUI.Notifications; using Microsoft.Extensions.Caching.Memory; +using Microsoft.UI.Xaml; using Snap.Hutao.Core.LifeCycle.InterProcess; using Snap.Hutao.Core.Setting; using Snap.Hutao.Core.Windowing.HotKey; @@ -63,6 +64,7 @@ internal sealed partial class Activation : IActivation, IDisposable serviceProvider.GetRequiredService().RegisterAll(); if (LocalSetting.Get(SettingKeys.IsNotifyIconEnabled, true)) { + serviceProvider.GetRequiredService().DispatcherShutdownMode = DispatcherShutdownMode.OnExplicitShutdown; _ = serviceProvider.GetRequiredService(); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/ConsoleWindowLifeTime.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/ConsoleWindowLifeTime.cs index 09c626d2..75cc6316 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Logging/ConsoleWindowLifeTime.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Logging/ConsoleWindowLifeTime.cs @@ -10,11 +10,18 @@ namespace Snap.Hutao.Core.Logging; internal sealed class ConsoleWindowLifeTime : IDisposable { + public const bool DebugModeEnabled = +#if IS_ALPHA_BUILD + true; +#else + false; +#endif + private readonly bool consoleWindowAllocated; public ConsoleWindowLifeTime() { - if (LocalSetting.Get(SettingKeys.IsAllocConsoleDebugModeEnabled, false)) + if (LocalSetting.Get(SettingKeys.IsAllocConsoleDebugModeEnabled, DebugModeEnabled)) { consoleWindowAllocated = AllocConsole(); if (consoleWindowAllocated) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Backdrop/IWindowNeedEraseBackground.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Backdrop/IWindowNeedEraseBackground.cs new file mode 100644 index 00000000..9d86ca46 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Backdrop/IWindowNeedEraseBackground.cs @@ -0,0 +1,6 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core.Windowing.Backdrop; + +internal interface IWindowNeedEraseBackground; \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Backdrop/InputActiveDesktopAcrylicBackdrop.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Backdrop/InputActiveDesktopAcrylicBackdrop.cs new file mode 100644 index 00000000..5e37a079 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Backdrop/InputActiveDesktopAcrylicBackdrop.cs @@ -0,0 +1,41 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.UI.Composition; +using Microsoft.UI.Composition.SystemBackdrops; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media; +using System.Collections.Concurrent; + +namespace Snap.Hutao.Core.Windowing.Backdrop; + +// https://github.com/microsoft/microsoft-ui-xaml/blob/winui3/release/1.5-stable/controls/dev/Materials/DesktopAcrylicBackdrop/DesktopAcrylicBackdrop.cpp +internal sealed class InputActiveDesktopAcrylicBackdrop : SystemBackdrop +{ + private readonly ConcurrentDictionary controllers = []; + + protected override void OnTargetConnected(ICompositionSupportsSystemBackdrop target, XamlRoot xamlRoot) + { + base.OnTargetConnected(target, xamlRoot); + + DesktopAcrylicController newController = new(); + SystemBackdropConfiguration configuration = GetDefaultSystemBackdropConfiguration(target, xamlRoot); + + configuration.IsInputActive = true; + + newController.AddSystemBackdropTarget(target); + newController.SetSystemBackdropConfiguration(configuration); + controllers.TryAdd(target, newController); + } + + protected override void OnTargetDisconnected(ICompositionSupportsSystemBackdrop target) + { + base.OnTargetDisconnected(target); + + if (controllers.TryRemove(target, out DesktopAcrylicController? controller)) + { + controller.RemoveSystemBackdropTarget(target); + controller.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconContextMenu.xaml b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconContextMenu.xaml new file mode 100644 index 00000000..064802d8 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconContextMenu.xaml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconContextMenu.xaml.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconContextMenu.xaml.cs new file mode 100644 index 00000000..c6c26e00 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconContextMenu.xaml.cs @@ -0,0 +1,17 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.UI.Xaml.Controls; +using Snap.Hutao.ViewModel; + +namespace Snap.Hutao.Core.Windowing.NotifyIcon; + +internal sealed partial class NotifyIconContextMenu : Flyout +{ + public NotifyIconContextMenu() + { + AllowFocusOnInteraction = false; + InitializeComponent(); + Root.DataContext = Ioc.Default.GetRequiredService(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconController.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconController.cs index 2dede31c..3834df9f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconController.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconController.cs @@ -1,7 +1,10 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; using Snap.Hutao.Core.ExceptionService; +using Snap.Hutao.Win32.Foundation; using Snap.Hutao.Win32.UI.WindowsAndMessaging; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -13,6 +16,8 @@ namespace Snap.Hutao.Core.Windowing.NotifyIcon; [Injection(InjectAs.Singleton)] internal sealed class NotifyIconController : IDisposable { + private readonly LazySlim lazyMenu = new(() => new()); + private readonly NotifyIconXamlHostWindow xamlHostWindow; private readonly NotifyIconMessageWindow messageWindow; private readonly System.Drawing.Icon icon; @@ -21,6 +26,8 @@ internal sealed class NotifyIconController : IDisposable StorageFile iconFile = StorageFile.GetFileFromApplicationUriAsync("ms-appx:///Assets/Logo.ico".ToUri()).AsTask().GetAwaiter().GetResult(); icon = new(iconFile.Path); + xamlHostWindow = new(); + messageWindow = new() { TaskbarCreated = window => @@ -31,10 +38,15 @@ internal sealed class NotifyIconController : IDisposable HutaoException.InvalidOperation("Failed to recreate NotifyIcon"); } }, + ContextMenuRequested = (window, point) => + { + Flyout flyout = lazyMenu.Value; + RECT iconRect = NotifyIconMethods.GetRect(Id, window.HWND); + xamlHostWindow.ShowFlyoutAt(flyout, new Windows.Foundation.Point(point.X, point.Y), iconRect); + }, }; NotifyIconMethods.Delete(Id); - if (!NotifyIconMethods.Add(Id, messageWindow.HWND, "Snap Hutao", NotifyIconMessageWindow.WM_NOTIFYICON_CALLBACK, (HICON)icon.Handle)) { HutaoException.InvalidOperation("Failed to create NotifyIcon"); @@ -61,5 +73,7 @@ internal sealed class NotifyIconController : IDisposable messageWindow.Dispose(); NotifyIconMethods.Delete(Id); icon.Dispose(); + + xamlHostWindow.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 index b6c0e585..d4f02e6d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconMessageWindow.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconMessageWindow.cs @@ -1,9 +1,12 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.UI; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using Snap.Hutao.Core.Windowing.Backdrop; using Snap.Hutao.Win32.Foundation; using Snap.Hutao.Win32.UI.WindowsAndMessaging; using System.Collections.Concurrent; @@ -64,6 +67,8 @@ internal sealed class NotifyIconMessageWindow : IDisposable public Action? TaskbarCreated { get; set; } + public Action? ContextMenuRequested { get; set; } + public HWND HWND { get; } public void Dispose() @@ -100,7 +105,7 @@ internal sealed class NotifyIconMessageWindow : IDisposable if (uMsg is WM_NOTIFYICON_CALLBACK) { LPARAM2 lParam2 = *(LPARAM2*)&lParam; - WPARAM2 wParam2 = *(WPARAM2*)&wParam; + PointUInt16 wParam2 = *(PointUInt16*)&wParam; switch (lParam2.Low) { @@ -123,6 +128,7 @@ internal sealed class NotifyIconMessageWindow : IDisposable case WM_RBUTTONUP: break; case WM_CONTEXTMENU: + window.ContextMenuRequested?.Invoke(window, wParam2); Debug.WriteLine($"[uMsg: 0x{uMsg:X8}] [X: {wParam2.X} Y: {wParam2.Y}] [Low: WM_CONTEXTMENU High: 0x{lParam2.High:X8}]"); break; default: @@ -152,24 +158,4 @@ internal sealed class NotifyIconMessageWindow : IDisposable public readonly uint Low; public readonly uint High; } - - private readonly struct WPARAM2 - { - public readonly ushort X; - public readonly ushort Y; - } -} - -internal sealed class NotifyIconXamlHostWindow : Window -{ - public NotifyIconXamlHostWindow() - { - Content = new Border(); - - OverlappedPresenter presenter = OverlappedPresenter.Create(); - presenter.SetBorderAndTitleBar(false, false); - presenter.IsAlwaysOnTop = true; - presenter.IsResizable = false; - AppWindow.SetPresenter(presenter); - } } \ 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 index dfaf8f35..9a9653ce 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconMethods.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconMethods.cs @@ -4,6 +4,7 @@ using Snap.Hutao.Win32.Foundation; using Snap.Hutao.Win32.UI.Shell; using Snap.Hutao.Win32.UI.WindowsAndMessaging; +using System.Runtime.InteropServices; using static Snap.Hutao.Win32.Shell32; namespace Snap.Hutao.Core.Windowing.NotifyIcon; @@ -52,6 +53,20 @@ internal sealed class NotifyIconMethods return Delete(in data); } + [SuppressMessage("", "SH002")] + public static unsafe RECT GetRect(Guid id, HWND hWND) + { + NOTIFYICONIDENTIFIER identifier = new() + { + cbSize = (uint)sizeof(NOTIFYICONIDENTIFIER), + hWnd = hWND, + guidItem = id, + }; + + Marshal.ThrowExceptionForHR(Shell_NotifyIconGetRect(ref identifier, out RECT rect)); + return rect; + } + public static BOOL SetFocus(ref readonly NOTIFYICONDATAW data) { return Shell_NotifyIconW(NOTIFY_ICON_MESSAGE.NIM_SETFOCUS, in data); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconXamlHostWindow.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconXamlHostWindow.cs new file mode 100644 index 00000000..c22457e0 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconXamlHostWindow.cs @@ -0,0 +1,71 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.UI; +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Media; +using Snap.Hutao.Core.Windowing.Backdrop; +using Snap.Hutao.Win32; +using Snap.Hutao.Win32.Foundation; +using Snap.Hutao.Win32.UI.WindowsAndMessaging; +using Windows.Foundation; +using WinRT.Interop; +using static Snap.Hutao.Win32.User32; + +namespace Snap.Hutao.Core.Windowing.NotifyIcon; + +internal sealed class NotifyIconXamlHostWindow : Window, IDisposable, IWindowNeedEraseBackground +{ + private readonly XamlWindowSubclass subclass; + + public NotifyIconXamlHostWindow() + { + Content = new Border(); + + this.SetLayeredWindow(); + + AppWindow.Title = "SnapHutaoNotifyIconXamlHost"; + AppWindow.IsShownInSwitchers = false; + if (AppWindow.Presenter is OverlappedPresenter presenter) + { + presenter.IsMaximizable = false; + presenter.IsMinimizable = false; + presenter.IsResizable = false; + presenter.IsAlwaysOnTop = true; + presenter.SetBorderAndTitleBar(false, false); + } + + XamlWindowOptions options = new(this, default!, default); + subclass = new(this, options); + subclass.Initialize(); + + Activate(); + } + + public void ShowFlyoutAt(FlyoutBase flyout, Point point, RECT icon) + { + icon.left -= 8; + icon.top -= 8; + icon.right += 8; + icon.bottom += 8; + + HWND hwnd = WindowNative.GetWindowHandle(this); + ShowWindow(hwnd, SHOW_WINDOW_CMD.SW_NORMAL); + SetForegroundWindow(hwnd); + + AppWindow.MoveAndResize(StructMarshal.RectInt32(icon)); + flyout.ShowAt(Content, new() + { + Placement = FlyoutPlacementMode.Auto, + ShowMode = FlyoutShowMode.Transient, + }); + } + + public void Dispose() + { + subclass.Dispose(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/PointUInt16.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/PointUInt16.cs new file mode 100644 index 00000000..75949941 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/PointUInt16.cs @@ -0,0 +1,10 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core.Windowing.NotifyIcon; + +internal readonly struct PointUInt16 +{ + public readonly ushort X; + public readonly ushort Y; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowExtension.cs index dfd5793d..f70680b3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowExtension.cs @@ -6,6 +6,7 @@ using Snap.Hutao.Win32.Foundation; using Snap.Hutao.Win32.UI.WindowsAndMessaging; using System.Runtime.CompilerServices; using WinRT.Interop; +using static Snap.Hutao.Win32.Macros; using static Snap.Hutao.Win32.User32; namespace Snap.Hutao.Core.Windowing; @@ -27,5 +28,6 @@ internal static class WindowExtension nint style = GetWindowLongPtrW(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); style |= (nint)WINDOW_EX_STYLE.WS_EX_LAYERED; SetWindowLongPtrW(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, style); + SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 0, LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_COLORKEY | LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_ALPHA); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowSubclass.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowSubclass.cs index e5d8b9bf..9278ce29 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowSubclass.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowSubclass.cs @@ -3,10 +3,12 @@ using Microsoft.UI.Xaml; using Snap.Hutao.Core.Windowing.Backdrop; +using Snap.Hutao.Core.Windowing.NotifyIcon; using Snap.Hutao.Win32; using Snap.Hutao.Win32.Foundation; using Snap.Hutao.Win32.UI.Shell; using Snap.Hutao.Win32.UI.WindowsAndMessaging; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using static Snap.Hutao.Win32.ComCtl32; @@ -36,9 +38,7 @@ internal sealed class XamlWindowSubclass : IDisposable { windowProc = SUBCLASSPROC.Create(&OnSubclassProcedure); unmanagedAccess = UnmanagedAccess.Create(this); - bool windowHooked = SetWindowSubclass(options.Hwnd, windowProc, WindowSubclassId, unmanagedAccess); - - return windowHooked; + return SetWindowSubclass(options.Hwnd, windowProc, WindowSubclassId, unmanagedAccess); } public void Dispose() @@ -75,7 +75,7 @@ internal sealed class XamlWindowSubclass : IDisposable case WM_ERASEBKGND: { - if (state.window.SystemBackdrop is IBackdropNeedEraseBackground) + if (state.window is IWindowNeedEraseBackground || state.window.SystemBackdrop is IBackdropNeedEraseBackground) { return (LRESULT)(int)BOOL.TRUE; } diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index acaf7798..0f929257 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -109,6 +109,7 @@ + @@ -355,6 +356,11 @@ + + + MSBuild:Compile + + MSBuild:Compile diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/NotifyIconViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/NotifyIconViewModel.cs new file mode 100644 index 00000000..265658f7 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/NotifyIconViewModel.cs @@ -0,0 +1,43 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using CommunityToolkit.Mvvm.ComponentModel; +using Snap.Hutao.Core; +using System.Globalization; +using System.Text; + +namespace Snap.Hutao.ViewModel; + +[ConstructorGenerated] +[Injection(InjectAs.Singleton)] +internal sealed partial class NotifyIconViewModel : ObservableObject +{ + private readonly RuntimeOptions runtimeOptions; + private readonly App app; + + public string Title + { + [SuppressMessage("", "IDE0027")] + get + { + string name = new StringBuilder() + .Append("App") + .AppendIf(runtimeOptions.IsElevated, "Elevated") +#if DEBUG + .Append("Dev") +#endif + .Append("NameAndVersion") + .ToString(); + + string? format = SH.GetString(CultureInfo.CurrentCulture, name); + ArgumentException.ThrowIfNullOrEmpty(format); + return string.Format(CultureInfo.CurrentCulture, format, runtimeOptions.Version); + } + } + + [Command("ExitCommand")] + private void Exit() + { + app.Exit(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs index 4db47dc9..dc5ab5e7 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs @@ -8,6 +8,7 @@ using Microsoft.Windows.AppLifecycle; using Snap.Hutao.Control.Extension; using Snap.Hutao.Core; using Snap.Hutao.Core.Caching; +using Snap.Hutao.Core.Logging; using Snap.Hutao.Core.Setting; using Snap.Hutao.Core.Shell; using Snap.Hutao.Core.Windowing; @@ -154,7 +155,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel public bool IsAllocConsoleDebugModeEnabled { - get => LocalSetting.Get(SettingKeys.IsAllocConsoleDebugModeEnabled, false); + get => LocalSetting.Get(SettingKeys.IsAllocConsoleDebugModeEnabled, ConsoleWindowLifeTime.DebugModeEnabled); set { if (IsViewDisposed) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Request/Builder/HttpRequestMessageBuilderExtension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Request/Builder/HttpRequestMessageBuilderExtension.cs index 5fc1f143..0ec080e4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Request/Builder/HttpRequestMessageBuilderExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Request/Builder/HttpRequestMessageBuilderExtension.cs @@ -11,7 +11,7 @@ namespace Snap.Hutao.Web.Request.Builder; internal static class HttpRequestMessageBuilderExtension { - private const string RequestErrorMessage = "请求异常已忽略"; + private const string RequestErrorMessage = "请求异常已忽略: {0}"; internal static async ValueTask TryCatchSendAsync(this HttpRequestMessageBuilder builder, HttpClient httpClient, ILogger logger, CancellationToken token) where TResult : class @@ -29,7 +29,7 @@ internal static class HttpRequestMessageBuilderExtension } catch (HttpRequestException ex) { - logger.LogWarning(ex, RequestErrorMessage); + logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri); if (ex.StatusCode is HttpStatusCode.BadGateway) { @@ -50,22 +50,22 @@ internal static class HttpRequestMessageBuilderExtension } catch (IOException ex) { - logger.LogWarning(ex, RequestErrorMessage); + logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri); return default; } catch (JsonException ex) { - logger.LogWarning(ex, RequestErrorMessage); + logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri); return default; } catch (HttpContentSerializationException ex) { - logger.LogWarning(ex, RequestErrorMessage); + logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri); return default; } catch (SocketException ex) { - logger.LogWarning(ex, RequestErrorMessage); + logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri); return default; } } @@ -80,23 +80,23 @@ internal static class HttpRequestMessageBuilderExtension } catch (HttpRequestException ex) { - logger.LogWarning(ex, RequestErrorMessage); + logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri); } catch (IOException ex) { - logger.LogWarning(ex, RequestErrorMessage); + logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri); } catch (JsonException ex) { - logger.LogWarning(ex, RequestErrorMessage); + logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri); } catch (HttpContentSerializationException ex) { - logger.LogWarning(ex, RequestErrorMessage); + logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri); } catch (SocketException ex) { - logger.LogWarning(ex, RequestErrorMessage); + logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri); } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/COLORREF.cs b/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/COLORREF.cs new file mode 100644 index 00000000..69cab145 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/COLORREF.cs @@ -0,0 +1,9 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Win32.Foundation; + +internal readonly struct COLORREF +{ + public readonly uint Value; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/RECT.cs b/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/RECT.cs index faffe63a..ea6417e6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/RECT.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/RECT.cs @@ -4,10 +4,10 @@ namespace Snap.Hutao.Win32.Foundation; [SuppressMessage("", "SA1307")] -internal readonly struct RECT +internal struct RECT { - public readonly int left; - public readonly int top; - public readonly int right; - public readonly int bottom; + public int left; + public int top; + public int right; + public int bottom; } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/Macros.cs b/src/Snap.Hutao/Snap.Hutao/Win32/Macros.cs index fbf6c287..4eec00f7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/Macros.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/Macros.cs @@ -28,4 +28,10 @@ internal static class Macros // 0x80000000 or 0x80070000 | LOWBYTE(x) return x <= 0 ? (int)x : (int)(((uint)x & 0x0000FFFFU) | ((uint)FACILITY_CODE.FACILITY_WIN32 << 16) | 0x80000000U); } + + public static unsafe COLORREF RGB(byte r, byte g, byte b) + { + uint value = (ushort)(r | g << 8) | (uint)b << 16; + return *(COLORREF*)&value; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/Registry/RegistryWatcher.cs b/src/Snap.Hutao/Snap.Hutao/Win32/Registry/RegistryWatcher.cs index 1d393fe6..5454eb49 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/Registry/RegistryWatcher.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/Registry/RegistryWatcher.cs @@ -129,8 +129,14 @@ internal sealed partial class RegistryWatcher : IDisposable if (!disposed) { - // Before exiting, signal the Dispose method. - disposeEvent.Reset(); + try + { + // Before exiting, signal the Dispose method. + disposeEvent.Reset(); + } + catch + { + } } } catch (OperationCanceledException) diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/Shell32.cs b/src/Snap.Hutao/Snap.Hutao/Win32/Shell32.cs index 16d20e04..0f9f96d3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/Shell32.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/Shell32.cs @@ -34,6 +34,21 @@ internal static class Shell32 } } + [DllImport("SHELL32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] + [SupportedOSPlatform("windows6.1")] + public static unsafe extern HRESULT Shell_NotifyIconGetRect(NOTIFYICONIDENTIFIER* identifier, RECT* iconLocation); + + public static unsafe HRESULT Shell_NotifyIconGetRect(ref readonly NOTIFYICONIDENTIFIER identifier, out RECT iconLocation) + { + fixed (NOTIFYICONIDENTIFIER* p = &identifier) + { + fixed (RECT* pRect = &iconLocation) + { + return Shell_NotifyIconGetRect(p, pRect); + } + } + } + [DllImport("SHELL32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] [SupportedOSPlatform("windows5.1.2600")] public static unsafe extern BOOL Shell_NotifyIconW(NOTIFY_ICON_MESSAGE dwMessage, NOTIFYICONDATAW* lpData); diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/StructMarshal.cs b/src/Snap.Hutao/Snap.Hutao/Win32/StructMarshal.cs index 9cdb3ec2..94be5c4b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/StructMarshal.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/StructMarshal.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Win32.Foundation; using System.Buffers.Binary; using System.Numerics; using System.Runtime.CompilerServices; @@ -32,6 +33,11 @@ internal static class StructMarshal return new(0, 0, size.X, size.Y); } + public static RectInt32 RectInt32(RECT rect) + { + return new(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); + } + public static RectInt32 RectInt32(SizeInt32 size) { return new(0, 0, size.Width, size.Height); diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/UI/Shell/NOTIFYICONIDENTIFIER.cs b/src/Snap.Hutao/Snap.Hutao/Win32/UI/Shell/NOTIFYICONIDENTIFIER.cs new file mode 100644 index 00000000..70172486 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Win32/UI/Shell/NOTIFYICONIDENTIFIER.cs @@ -0,0 +1,15 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Win32.Foundation; + +namespace Snap.Hutao.Win32.UI.Shell; + +[SuppressMessage("", "SA1307")] +internal struct NOTIFYICONIDENTIFIER +{ + public uint cbSize; + public HWND hWnd; + public uint uID; + public Guid guidItem; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/LAYERED_WINDOW_ATTRIBUTES_FLAGS.cs b/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/LAYERED_WINDOW_ATTRIBUTES_FLAGS.cs new file mode 100644 index 00000000..080015d0 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Win32/UI/WindowsAndMessaging/LAYERED_WINDOW_ATTRIBUTES_FLAGS.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Win32.UI.WindowsAndMessaging; + +[Flags] +internal enum LAYERED_WINDOW_ATTRIBUTES_FLAGS : uint +{ + LWA_ALPHA = 2u, + LWA_COLORKEY = 1u, +} \ 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 ffbd01e0..87962e2a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/User32.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/User32.cs @@ -163,6 +163,10 @@ internal static class User32 [SupportedOSPlatform("windows5.0")] public static extern BOOL SetForegroundWindow(HWND hWnd); + [DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true, SetLastError = true)] + [SupportedOSPlatform("windows5.0")] + public static extern BOOL SetLayeredWindowAttributes(HWND hwnd, COLORREF crKey, byte bAlpha, LAYERED_WINDOW_ATTRIBUTES_FLAGS dwFlags); + [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); @@ -180,6 +184,10 @@ internal static class User32 [SupportedOSPlatform("windows5.0")] public static extern nint SetWindowLongPtrW(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, nint dwNewLong); + [DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] + [SupportedOSPlatform("windows5.0")] + public static extern BOOL ShowWindow(HWND hWnd, SHOW_WINDOW_CMD nCmdShow); + [DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] [SupportedOSPlatform("windows5.0")] public static extern BOOL UnregisterHotKey([AllowNull] HWND hWnd, int id);