contextmenu

This commit is contained in:
DismissedLight
2024-05-12 22:36:22 +08:00
parent dafd3128c2
commit 0f767f7e77
28 changed files with 388 additions and 74 deletions

View File

@@ -44,8 +44,6 @@ public sealed partial class App : Application
private readonly IActivation activation;
private readonly ILogger<App> logger;
private NotifyIconController? notifyIconController;
/// <summary>
/// Initializes the singleton application object.
/// </summary>

View File

@@ -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;
/// <summary>
/// 对象扩展
/// </summary>
[HighQuality]
internal static class CastServiceExtension
{
/// <summary>
/// <see langword="as"/> 的链式调用扩展
/// </summary>
/// <typeparam name="T">目标转换类型</typeparam>
/// <param name="service">对象</param>
/// <returns>转换类型后的对象</returns>
[Obsolete("Not useful anymore")]
public static T? As<T>(this ICastService service)
where T : class
{
return service as T;
}
}

View File

@@ -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<HotKeyOptions>().RegisterAll();
if (LocalSetting.Get(SettingKeys.IsNotifyIconEnabled, true))
{
serviceProvider.GetRequiredService<App>().DispatcherShutdownMode = DispatcherShutdownMode.OnExplicitShutdown;
_ = serviceProvider.GetRequiredService<NotifyIconController>();
}
}

View File

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

View File

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

View File

@@ -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<ICompositionSupportsSystemBackdrop, DesktopAcrylicController> 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();
}
}
}

View File

@@ -0,0 +1,45 @@
<Flyout
x:Class="Snap.Hutao.Core.Windowing.NotifyIcon.NotifyIconContextMenu"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shcwb="using:Snap.Hutao.Core.Windowing.Backdrop"
xmlns:shv="using:Snap.Hutao.ViewModel"
ShouldConstrainToRootBounds="False"
mc:Ignorable="d">
<Flyout.SystemBackdrop>
<shcwb:InputActiveDesktopAcrylicBackdrop/>
</Flyout.SystemBackdrop>
<Flyout.FlyoutPresenterStyle>
<Style BasedOn="{StaticResource DefaultFlyoutPresenterStyle}" TargetType="FlyoutPresenter">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0"/>
</Style>
</Flyout.FlyoutPresenterStyle>
<Grid
x:Name="Root"
d:DataContext="{d:DesignInstance shv:NotifyIconViewModel}"
Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Margin="8" Text="{Binding Title}"/>
<Grid Grid.Row="1" Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}">
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<AppBarButton
Margin="4,0"
Command="{Binding ExitCommand}"
Icon="{shcm:FontIcon Glyph=&#xE7E8;}"
Label="退出"
Style="{ThemeResource DefaultAppBarButtonStyle}"/>
</StackPanel>
</Grid>
</Grid>
</Flyout>

View File

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

View File

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

View File

@@ -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<NotifyIconMessageWindow>? TaskbarCreated { get; set; }
public Action<NotifyIconMessageWindow, PointUInt16>? 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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -109,6 +109,7 @@
<None Remove="Control\Theme\TransitionCollection.xaml" />
<None Remove="Control\Theme\Uri.xaml" />
<None Remove="Control\Theme\WindowOverride.xaml" />
<None Remove="Core\Windowing\NotifyIcon\NotifyIconContextMenu.xaml" />
<None Remove="GuideWindow.xaml" />
<None Remove="IdentifyMonitorWindow.xaml" />
<None Remove="IdentityStructs.json" />
@@ -355,6 +356,11 @@
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
</ItemGroup>
<ItemGroup>
<Page Update="Core\Windowing\NotifyIcon\NotifyIconContextMenu.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\SpiralAbyssUploadRecordHomaNotLoginDialog.xaml">
<Generator>MSBuild:Compile</Generator>

View File

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

View File

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

View File

@@ -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<TResult?> TryCatchSendAsync<TResult>(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);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,
}

View File

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