Compare commits

..

1 Commits

Author SHA1 Message Date
qhy040404
6864124c64 add alpha build constant 2024-05-12 12:21:43 +08:00
91 changed files with 479 additions and 1731 deletions

View File

@@ -11,6 +11,15 @@ var version = "version";
var repoDir = "repoDir";
var outputPath = "outputPath";
// Extension
static ProcessArgumentBuilder AppendIf(this ProcessArgumentBuilder builder, string text, bool condition)
{
return condition ? builder.Append(text) : builder;
}
// Properties
string solution
{
get => System.IO.Path.Combine(repoDir, "src", "Snap.Hutao", "Snap.Hutao.sln");
@@ -157,6 +166,7 @@ Task("Build binary package")
.Append("/p:AppxPackageSigningEnabled=false")
.Append("/p:AppxBundle=Never")
.Append("/p:AppxPackageOutput=" + outputPath)
.AppendIf("/p:AlphaConstants=IS_ALPHA_BUILD", !AppVeyor.IsRunningOnAppVeyor)
};
DotNetBuild(project, settings);

View File

@@ -6,7 +6,6 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources/>
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.SettingsControls/SettingsCard/SettingsCard.xaml"/>
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.TokenizingTextBox/TokenizingTextBox.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Loading.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Image/CachedImage.xaml"/>

View File

@@ -8,9 +8,7 @@ using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.LifeCycle.InterProcess;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Core.Windowing.HotKey;
using Snap.Hutao.Core.Windowing.NotifyIcon;
using Snap.Hutao.Core.Shell;
using System.Diagnostics;
namespace Snap.Hutao;
@@ -41,7 +39,7 @@ public sealed partial class App : Application
""";
private readonly IServiceProvider serviceProvider;
private readonly IAppActivation activation;
private readonly IActivation activation;
private readonly ILogger<App> logger;
/// <summary>
@@ -50,23 +48,17 @@ public sealed partial class App : Application
/// <param name="serviceProvider">服务提供器</param>
public App(IServiceProvider serviceProvider)
{
// DispatcherShutdownMode = DispatcherShutdownMode.OnExplicitShutdown;
// Load app resource
InitializeComponent();
activation = serviceProvider.GetRequiredService<IAppActivation>();
activation = serviceProvider.GetRequiredService<IActivation>();
logger = serviceProvider.GetRequiredService<ILogger<App>>();
serviceProvider.GetRequiredService<ExceptionRecorder>().Record(this);
this.serviceProvider = serviceProvider;
}
public bool IsExiting { get; private set; }
public new void Exit()
{
IsExiting = true;
base.Exit();
}
/// <inheritdoc/>
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
@@ -83,9 +75,11 @@ public sealed partial class App : Application
logger.LogColorizedInformation((ConsoleBanner, ConsoleColor.DarkYellow));
LogDiagnosticInformation();
// Manually invoke
// manually invoke
activation.Activate(HutaoActivationArguments.FromAppActivationArguments(activatedEventArgs));
activation.PostInitialization();
activation.Initialize();
serviceProvider.GetRequiredService<IJumpListInterop>().ConfigureAsync().SafeForget();
}
catch
{

View File

@@ -0,0 +1,26 @@
// 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,11 +3,8 @@
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;
using Snap.Hutao.Core.Windowing.NotifyIcon;
using Snap.Hutao.Service.DailyNote;
using Snap.Hutao.Service.Discord;
using Snap.Hutao.Service.Hutao;
@@ -23,9 +20,9 @@ namespace Snap.Hutao.Core.LifeCycle;
/// </summary>
[HighQuality]
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IAppActivation))]
[Injection(InjectAs.Singleton, typeof(IActivation))]
[SuppressMessage("", "CA1001")]
internal sealed partial class AppActivation : IAppActivation, IAppActivationActionHandlersAccess, IDisposable
internal sealed partial class Activation : IActivation
{
public const string Action = nameof(Action);
public const string Uid = nameof(Uid);
@@ -38,15 +35,13 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
private const string UrlActionRefresh = "/REFRESH";
private readonly IServiceProvider serviceProvider;
private readonly ICurrentXamlWindowReference currentWindowReference;
private readonly ICurrentWindowReference currentWindowReference;
private readonly ITaskContext taskContext;
private readonly SemaphoreSlim activateSemaphore = new(1);
/// <inheritdoc/>
public void Activate(HutaoActivationArguments args)
{
// Before activate, we try to redirect to the opened process in App,
// And we check if it's a toast activation.
if (ToastNotificationManagerCompat.WasCurrentProcessToastActivated())
{
return;
@@ -56,52 +51,10 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
}
/// <inheritdoc/>
public void PostInitialization()
public void Initialize()
{
serviceProvider.GetRequiredService<PrivateNamedPipeServer>().RunAsync().SafeForget();
ToastNotificationManagerCompat.OnActivated += NotificationActivate;
serviceProvider.GetRequiredService<HotKeyOptions>().RegisterAll();
if (LocalSetting.Get(SettingKeys.IsNotifyIconEnabled, true))
{
serviceProvider.GetRequiredService<App>().DispatcherShutdownMode = DispatcherShutdownMode.OnExplicitShutdown;
_ = serviceProvider.GetRequiredService<NotifyIconController>();
}
}
public void Dispose()
{
activateSemaphore.Dispose();
}
public async ValueTask HandleLaunchGameActionAsync(string? uid = null)
{
serviceProvider
.GetRequiredService<IMemoryCache>()
.Set(ViewModel.Game.LaunchGameViewModel.DesiredUid, uid);
await taskContext.SwitchToMainThreadAsync();
if (currentWindowReference.Window is null)
{
currentWindowReference.Window = serviceProvider.GetRequiredService<LaunchGameWindow>();
return;
}
if (currentWindowReference.Window is MainWindow)
{
await serviceProvider
.GetRequiredService<INavigationService>()
.NavigateAsync<View.Page.LaunchGamePage>(INavigationAwaiter.Default, true)
.ConfigureAwait(false);
return;
}
else
{
// We have a non-Main Window, just exit current process anyway
Process.GetCurrentProcess().Kill();
}
}
private void NotificationActivate(ToastNotificationActivatedEventArgsCompat args)
@@ -141,6 +94,12 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
ArgumentNullException.ThrowIfNull(args.LaunchActivatedArguments);
switch (args.LaunchActivatedArguments)
{
case LaunchGame:
{
await HandleLaunchGameActionAsync().ConfigureAwait(false);
break;
}
default:
{
await HandleNormalLaunchActionAsync().ConfigureAwait(false);
@@ -153,9 +112,10 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
private async ValueTask HandleNormalLaunchActionAsync()
{
// Increase launch times
LocalSetting.Update(SettingKeys.LaunchTimes, 0, x => unchecked(x + 1));
LocalSetting.Update(SettingKeys.LaunchTimes, 0, x => x + 1);
// If the guide is completed, we check if there's any unfulfilled resource category present.
// If it's the first time launch, we show the guide window anyway.
// Otherwise, we check if there's any unfulfilled resource category present.
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language) >= GuideState.StaticResourceBegin)
{
if (StaticResource.IsAnyUnfulfilledCategoryPresent())
@@ -164,11 +124,10 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
}
}
// If it's the first time launch, show the guide window anyway.
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language) < GuideState.Completed)
{
await taskContext.SwitchToMainThreadAsync();
currentWindowReference.Window = serviceProvider.GetRequiredService<GuideWindow>();
serviceProvider.GetRequiredService<GuideWindow>();
}
else
{
@@ -185,7 +144,7 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
await taskContext.SwitchToMainThreadAsync();
currentWindowReference.Window = serviceProvider.GetRequiredService<MainWindow>();
serviceProvider.GetRequiredService<MainWindow>();
await taskContext.SwitchToBackgroundAsync();
@@ -199,7 +158,10 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
hutaoUserServiceInitialization.InitializeInternalAsync().SafeForget();
}
serviceProvider.GetRequiredService<IDiscordService>().SetNormalActivityAsync().SafeForget();
serviceProvider
.GetRequiredService<IDiscordService>()
.SetNormalActivityAsync()
.SafeForget();
}
private async ValueTask HandleUrlActivationAsync(Uri uri, bool isRedirectTo)
@@ -282,4 +244,34 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
}
}
}
private async ValueTask HandleLaunchGameActionAsync(string? uid = null)
{
serviceProvider
.GetRequiredService<IMemoryCache>()
.Set(ViewModel.Game.LaunchGameViewModel.DesiredUid, uid);
await taskContext.SwitchToMainThreadAsync();
if (currentWindowReference.Window is null)
{
serviceProvider.GetRequiredService<LaunchGameWindow>();
return;
}
if (currentWindowReference.Window is MainWindow)
{
await serviceProvider
.GetRequiredService<INavigationService>()
.NavigateAsync<View.Page.LaunchGamePage>(INavigationAwaiter.Default, true)
.ConfigureAwait(false);
return;
}
else
{
// We have a non-Main Window, just exit current process anyway
Process.GetCurrentProcess().Kill();
}
}
}

View File

@@ -5,19 +5,19 @@ using Microsoft.UI.Xaml;
namespace Snap.Hutao.Core.LifeCycle;
[Injection(InjectAs.Singleton, typeof(ICurrentXamlWindowReference))]
internal sealed class CurrentXamlWindowReference : ICurrentXamlWindowReference
[Injection(InjectAs.Singleton, typeof(ICurrentWindowReference))]
internal sealed class CurrentWindowReference : ICurrentWindowReference
{
private readonly WeakReference<Window> reference = new(default!);
[SuppressMessage("", "SH007")]
public Window? Window
public Window Window
{
get
{
reference.TryGetTarget(out Window? window);
return window!;
}
set => reference.SetTarget(value!);
set => reference.SetTarget(value);
}
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Windowing;
using Snap.Hutao.Win32.Foundation;
using WinRT.Interop;
namespace Snap.Hutao.Core.LifeCycle;
internal static class CurrentWindowReferenceExtension
{
public static XamlRoot GetXamlRoot(this ICurrentWindowReference reference)
{
return reference.Window.Content.XamlRoot;
}
public static HWND GetWindowHandle(this ICurrentWindowReference reference)
{
return reference.Window is IWindowOptionsSource optionsSource
? optionsSource.WindowOptions.Hwnd
: WindowNative.GetWindowHandle(reference.Window);
}
}

View File

@@ -1,23 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Windowing;
using Snap.Hutao.Win32.Foundation;
using WinRT.Interop;
namespace Snap.Hutao.Core.LifeCycle;
internal static class CurrentXamlWindowReferenceExtension
{
public static XamlRoot GetXamlRoot(this ICurrentXamlWindowReference reference)
{
ArgumentNullException.ThrowIfNull(reference.Window);
return reference.Window.Content.XamlRoot;
}
public static HWND GetWindowHandle(this ICurrentXamlWindowReference reference)
{
return WindowExtension.GetWindowHandle(reference.Window);
}
}

View File

@@ -0,0 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.LifeCycle;
/// <summary>
/// 激活
/// </summary>
internal interface IActivation
{
void Activate(HutaoActivationArguments args);
void Initialize();
}

View File

@@ -1,16 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.LifeCycle;
internal interface IAppActivation
{
void Activate(HutaoActivationArguments args);
void PostInitialization();
}
internal interface IAppActivationActionHandlersAccess
{
ValueTask HandleLaunchGameActionAsync(string? uid = null);
}

View File

@@ -5,10 +5,10 @@ using Microsoft.UI.Xaml;
namespace Snap.Hutao.Core.LifeCycle;
internal interface ICurrentXamlWindowReference
internal interface ICurrentWindowReference
{
/// <summary>
/// Only set in WindowController
/// </summary>
public Window? Window { get; set; }
public Window Window { get; set; }
}

View File

@@ -16,6 +16,6 @@ internal sealed partial class PrivateNamedPipeMessageDispatcher
return;
}
serviceProvider.GetRequiredService<IAppActivation>().Activate(args);
serviceProvider.GetRequiredService<IActivation>().Activate(args);
}
}

View File

@@ -10,18 +10,11 @@ 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, DebugModeEnabled))
if (LocalSetting.Get(SettingKeys.IsAllocConsoleDebugModeEnabled, false))
{
consoleWindowAllocated = AllocConsole();
if (consoleWindowAllocated)

View File

@@ -12,13 +12,8 @@ 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";
#endregion
#region Infrastructure
public const string ExcludedAnnouncementIds = "ExcludedAnnouncementIds";
#endregion
@@ -30,7 +25,6 @@ internal static class SettingKeys
public const string StaticResourceImageArchive = "StaticResourceImageArchive";
public const string HotKeyMouseClickRepeatForever = "HotKeyMouseClickRepeatForever";
public const string IsAllocConsoleDebugModeEnabled = "IsAllocConsoleDebugModeEnabled2";
public const string IsNotifyIconEnabled = "IsNotifyIconEnabled";
#endregion
#region Passport

View File

@@ -0,0 +1,16 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Shell;
/// <summary>
/// 跳转列表交互
/// </summary>
internal interface IJumpListInterop
{
/// <summary>
/// 异步配置跳转列表
/// </summary>
/// <returns>任务</returns>
ValueTask ConfigureAsync();
}

View File

@@ -0,0 +1,36 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.LifeCycle;
using Windows.UI.StartScreen;
namespace Snap.Hutao.Core.Shell;
/// <summary>
/// 跳转列表交互
/// </summary>
[HighQuality]
[Injection(InjectAs.Transient, typeof(IJumpListInterop))]
internal sealed class JumpListInterop : IJumpListInterop
{
/// <summary>
/// 异步配置跳转列表
/// </summary>
/// <returns>任务</returns>
public async ValueTask ConfigureAsync()
{
if (JumpList.IsSupported())
{
JumpList list = await JumpList.LoadCurrentAsync();
list.Items.Clear();
JumpListItem launchGameItem = JumpListItem.CreateWithArguments(Activation.LaunchGame, SH.CoreJumpListHelperLaunchGameItemDisplayName);
launchGameItem.Logo = "ms-appx:///Resource/Navigation/LaunchGame.png".ToUri();
list.Items.Add(launchGameItem);
await list.SaveAsync();
}
}
}

View File

@@ -1,6 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Windowing.Backdrop;
internal interface IWindowNeedEraseBackground;

View File

@@ -1,41 +0,0 @@
// 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

@@ -1,50 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Hosting;
using Microsoft.UI.Xaml.Media;
using System.Runtime.CompilerServices;
using WinRT;
namespace Snap.Hutao.Core.Windowing.Backdrop;
internal sealed class SystemBackdropDesktopWindowXamlSourceAccess : SystemBackdrop
{
private readonly SystemBackdrop? innerBackdrop;
public SystemBackdropDesktopWindowXamlSourceAccess(SystemBackdrop? systemBackdrop)
{
innerBackdrop = systemBackdrop;
}
public DesktopWindowXamlSource? DesktopWindowXamlSource
{
get; private set;
}
protected override void OnTargetConnected(ICompositionSupportsSystemBackdrop target, XamlRoot xamlRoot)
{
DesktopWindowXamlSource = DesktopWindowXamlSource.FromAbi(target.As<IInspectable>().ThisPtr);
if (innerBackdrop is not null)
{
ProtectedOnTargetConnected(innerBackdrop, target, xamlRoot);
}
}
protected override void OnTargetDisconnected(ICompositionSupportsSystemBackdrop target)
{
DesktopWindowXamlSource = null;
if (innerBackdrop is not null)
{
ProtectedOnTargetDisconnected(innerBackdrop, target);
}
}
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = nameof(OnTargetConnected))]
private static extern void ProtectedOnTargetConnected(SystemBackdrop systemBackdrop, ICompositionSupportsSystemBackdrop target, XamlRoot xamlRoot);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = nameof(OnTargetDisconnected))]
private static extern void ProtectedOnTargetDisconnected(SystemBackdrop systemBackdrop, ICompositionSupportsSystemBackdrop target);
}

View File

@@ -9,9 +9,9 @@ using Windows.UI;
namespace Snap.Hutao.Core.Windowing.Backdrop;
internal sealed class TransparentBackdrop : SystemBackdrop, IBackdropNeedEraseBackground
internal sealed class TransparentBackdrop : SystemBackdrop, IDisposable, IBackdropNeedEraseBackground
{
private object? compositorLock;
private readonly object compositorLock = new();
private Color tintColor;
private Windows.UI.Composition.CompositionColorBrush? brush;
@@ -31,14 +31,27 @@ internal sealed class TransparentBackdrop : SystemBackdrop, IBackdropNeedEraseBa
{
get
{
return LazyInitializer.EnsureInitialized(ref compositor, ref compositorLock, () =>
if (compositor is null)
{
DispatcherQueue.EnsureSystemDispatcherQueue();
return new Windows.UI.Composition.Compositor();
});
lock (compositorLock)
{
if (compositor is null)
{
DispatcherQueue.EnsureSystemDispatcherQueue();
compositor = new Windows.UI.Composition.Compositor();
}
}
}
return compositor;
}
}
public void Dispose()
{
compositor?.Dispose();
}
protected override void OnTargetConnected(ICompositionSupportsSystemBackdrop connectedTarget, XamlRoot xamlRoot)
{
brush ??= Compositor.CreateColorBrush(tintColor);
@@ -48,13 +61,5 @@ internal sealed class TransparentBackdrop : SystemBackdrop, IBackdropNeedEraseBa
protected override void OnTargetDisconnected(ICompositionSupportsSystemBackdrop disconnectedTarget)
{
disconnectedTarget.SystemBackdrop = null;
if (compositorLock is not null)
{
lock (compositorLock)
{
compositor?.Dispose();
}
}
}
}

View File

@@ -17,10 +17,10 @@ namespace Snap.Hutao.Core.Windowing.HotKey;
[SuppressMessage("", "SA1124")]
internal sealed class HotKeyCombination : ObservableObject
{
private readonly ICurrentWindowReference currentWindowReference;
private readonly IInfoBarService infoBarService;
private readonly RuntimeOptions runtimeOptions;
private readonly HWND hwnd;
private readonly string settingKey;
private readonly int hotKeyId;
private readonly HotKeyParameter defaultHotKeyParameter;
@@ -36,12 +36,12 @@ internal sealed class HotKeyCombination : ObservableObject
private VirtualKey key;
private bool isEnabled;
public HotKeyCombination(IServiceProvider serviceProvider, HWND hwnd, string settingKey, int hotKeyId, HOT_KEY_MODIFIERS defaultModifiers, VirtualKey defaultKey)
public HotKeyCombination(IServiceProvider serviceProvider, string settingKey, int hotKeyId, HOT_KEY_MODIFIERS defaultModifiers, VirtualKey defaultKey)
{
currentWindowReference = serviceProvider.GetRequiredService<ICurrentWindowReference>();
infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
this.hwnd = hwnd;
this.settingKey = settingKey;
this.hotKeyId = hotKeyId;
defaultHotKeyParameter = new(defaultModifiers, defaultKey);
@@ -53,7 +53,7 @@ internal sealed class HotKeyCombination : ObservableObject
HotKeyParameter actual = LocalSettingGetHotKeyParameter();
modifiers = actual.Modifiers;
InitializeModifiersCompositionFields();
InitializeModifiersComposeFields();
key = actual.Key;
keyNameValue = VirtualKeys.GetList().Single(v => v.Value == key);
@@ -164,8 +164,8 @@ internal sealed class HotKeyCombination : ObservableObject
_ = (value, registered) switch
{
(true, false) => Register(),
(false, true) => Unregister(),
(true, false) => RegisterForCurrentWindow(),
(false, true) => UnregisterForCurrentWindow(),
_ => false,
};
}
@@ -174,7 +174,7 @@ internal sealed class HotKeyCombination : ObservableObject
public string DisplayName { get => ToString(); }
public bool Register()
public bool RegisterForCurrentWindow()
{
if (!runtimeOptions.IsElevated || !IsEnabled)
{
@@ -186,6 +186,7 @@ internal sealed class HotKeyCombination : ObservableObject
return true;
}
HWND hwnd = currentWindowReference.GetWindowHandle();
BOOL result = RegisterHotKey(hwnd, hotKeyId, Modifiers, (uint)Key);
registered = result;
@@ -197,7 +198,7 @@ internal sealed class HotKeyCombination : ObservableObject
return result;
}
public bool Unregister()
public bool UnregisterForCurrentWindow()
{
if (!runtimeOptions.IsElevated)
{
@@ -209,6 +210,7 @@ internal sealed class HotKeyCombination : ObservableObject
return true;
}
HWND hwnd = currentWindowReference.GetWindowHandle();
BOOL result = UnregisterHotKey(hwnd, hotKeyId);
registered = !result;
return result;
@@ -270,7 +272,7 @@ internal sealed class HotKeyCombination : ObservableObject
Modifiers = modifiers;
}
private void InitializeModifiersCompositionFields()
private void InitializeModifiersComposeFields()
{
if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_WIN))
{
@@ -307,7 +309,7 @@ internal sealed class HotKeyCombination : ObservableObject
HotKeyParameter current = new(Modifiers, Key);
LocalSetting.Set(settingKey, *(int*)&current);
Unregister();
Register();
UnregisterForCurrentWindow();
RegisterForCurrentWindow();
}
}

View File

@@ -0,0 +1,97 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.UI.Input.KeyboardAndMouse;
using System.Runtime.InteropServices;
using static Snap.Hutao.Win32.User32;
namespace Snap.Hutao.Core.Windowing.HotKey;
[SuppressMessage("", "CA1001")]
[Injection(InjectAs.Singleton, typeof(IHotKeyController))]
[ConstructorGenerated]
internal sealed partial class HotKeyController : IHotKeyController
{
private static readonly WaitCallback RunMouseClickRepeatForever = MouseClickRepeatForever;
private readonly object syncRoot = new();
private readonly HotKeyOptions hotKeyOptions;
private volatile CancellationTokenSource? cancellationTokenSource;
public void RegisterAll()
{
hotKeyOptions.MouseClickRepeatForeverKeyCombination.RegisterForCurrentWindow();
}
public void UnregisterAll()
{
hotKeyOptions.MouseClickRepeatForeverKeyCombination.UnregisterForCurrentWindow();
}
public void OnHotKeyPressed(in HotKeyParameter parameter)
{
if (parameter.Equals(hotKeyOptions.MouseClickRepeatForeverKeyCombination))
{
ToggleMouseClickRepeatForever();
}
}
private static unsafe INPUT CreateInputForMouseEvent(MOUSE_EVENT_FLAGS flags)
{
INPUT input = default;
input.type = INPUT_TYPE.INPUT_MOUSE;
input.Anonymous.mi.dwFlags = flags;
return input;
}
[SuppressMessage("", "SH007")]
private static unsafe void MouseClickRepeatForever(object? state)
{
CancellationToken token = (CancellationToken)state!;
// We want to use this thread for a long time
while (!token.IsCancellationRequested)
{
INPUT[] inputs =
[
CreateInputForMouseEvent(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN),
CreateInputForMouseEvent(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTUP),
];
if (SendInput(inputs.AsSpan(), sizeof(INPUT)) is 0)
{
Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError());
}
if (token.IsCancellationRequested)
{
return;
}
Thread.Sleep(System.Random.Shared.Next(100, 150));
}
}
private void ToggleMouseClickRepeatForever()
{
lock (syncRoot)
{
if (hotKeyOptions.IsMouseClickRepeatForeverOn)
{
// Turn off
cancellationTokenSource?.Cancel();
cancellationTokenSource = default;
hotKeyOptions.IsMouseClickRepeatForeverOn = false;
}
else
{
// Turn on
cancellationTokenSource = new();
ThreadPool.QueueUserWorkItem(RunMouseClickRepeatForever, cancellationTokenSource.Token);
hotKeyOptions.IsMouseClickRepeatForeverOn = true;
}
}
}
}

View File

@@ -1,92 +0,0 @@
// 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.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static Snap.Hutao.Win32.ConstValues;
using static Snap.Hutao.Win32.User32;
namespace Snap.Hutao.Core.Windowing.HotKey;
internal sealed class HotKeyMessageWindow : IDisposable
{
private const string WindowClassName = "SnapHutaoHotKeyMessageWindowClass";
private static readonly ConcurrentDictionary<HWND, HotKeyMessageWindow> WindowTable = [];
private bool isDisposed;
public unsafe HotKeyMessageWindow()
{
ushort atom;
fixed (char* className = WindowClassName)
{
WNDCLASSW wc = new()
{
lpfnWndProc = WNDPROC.Create(&OnWindowProcedure),
lpszClassName = className,
};
atom = RegisterClassW(&wc);
}
ArgumentOutOfRangeException.ThrowIfEqual<ushort>(atom, 0);
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);
}
~HotKeyMessageWindow()
{
Dispose();
}
public Action<HotKeyParameter>? HotKeyPressed { get; set; }
public HWND HWND { get; }
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 HotKeyMessageWindow? window))
{
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
switch (uMsg)
{
case WM_HOTKEY:
window.HotKeyPressed?.Invoke(*(HotKeyParameter*)&lParam);
break;
default:
break;
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
}

View File

@@ -4,35 +4,19 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Model;
using Snap.Hutao.Win32.UI.Input.KeyboardAndMouse;
using System.Runtime.InteropServices;
using Windows.System;
using static Snap.Hutao.Win32.User32;
namespace Snap.Hutao.Core.Windowing.HotKey;
[Injection(InjectAs.Singleton)]
internal sealed partial class HotKeyOptions : ObservableObject, IDisposable
internal sealed partial class HotKeyOptions : ObservableObject
{
private static readonly WaitCallback RunMouseClickRepeatForever = MouseClickRepeatForever;
private readonly object syncRoot = new();
private readonly HotKeyMessageWindow hotKeyMessageWindow;
private volatile CancellationTokenSource? cancellationTokenSource;
private bool isDisposed;
private bool isMouseClickRepeatForeverOn;
private HotKeyCombination mouseClickRepeatForeverKeyCombination;
public HotKeyOptions(IServiceProvider serviceProvider)
{
hotKeyMessageWindow = new()
{
HotKeyPressed = OnHotKeyPressed,
};
mouseClickRepeatForeverKeyCombination = new(serviceProvider, hotKeyMessageWindow.HWND, SettingKeys.HotKeyMouseClickRepeatForever, 100000, default, VirtualKey.F8);
mouseClickRepeatForeverKeyCombination = new(serviceProvider, SettingKeys.HotKeyMouseClickRepeatForever, 100000, default, VirtualKey.F8);
}
public List<NameValue<VirtualKey>> VirtualKeys { get; } = HotKey.VirtualKeys.GetList();
@@ -48,96 +32,4 @@ internal sealed partial class HotKeyOptions : ObservableObject, IDisposable
get => mouseClickRepeatForeverKeyCombination;
set => SetProperty(ref mouseClickRepeatForeverKeyCombination, value);
}
public void Dispose()
{
if (isDisposed)
{
return;
}
isDisposed = true;
UnregisterAll();
hotKeyMessageWindow.Dispose();
cancellationTokenSource?.Dispose();
GC.SuppressFinalize(this);
}
public void RegisterAll()
{
MouseClickRepeatForeverKeyCombination.Register();
}
private static unsafe INPUT CreateInputForMouseEvent(MOUSE_EVENT_FLAGS flags)
{
INPUT input = default;
input.type = INPUT_TYPE.INPUT_MOUSE;
input.Anonymous.mi.dwFlags = flags;
return input;
}
[SuppressMessage("", "SH007")]
private static unsafe void MouseClickRepeatForever(object? state)
{
CancellationToken token = (CancellationToken)state!;
// We want to use this thread for a long time
while (!token.IsCancellationRequested)
{
INPUT[] inputs =
[
CreateInputForMouseEvent(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN),
CreateInputForMouseEvent(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTUP),
];
if (SendInput(inputs.AsSpan(), sizeof(INPUT)) is 0)
{
Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError());
}
if (token.IsCancellationRequested)
{
return;
}
Thread.Sleep(System.Random.Shared.Next(100, 150));
}
}
private void UnregisterAll()
{
MouseClickRepeatForeverKeyCombination.Unregister();
}
[SuppressMessage("", "SH002")]
private void OnHotKeyPressed(HotKeyParameter parameter)
{
if (parameter.Equals(MouseClickRepeatForeverKeyCombination))
{
ToggleMouseClickRepeatForever();
}
}
private void ToggleMouseClickRepeatForever()
{
lock (syncRoot)
{
if (IsMouseClickRepeatForeverOn)
{
// Turn off
cancellationTokenSource?.Cancel();
cancellationTokenSource = default;
IsMouseClickRepeatForeverOn = false;
}
else
{
// Turn on
cancellationTokenSource = new();
ThreadPool.QueueUserWorkItem(RunMouseClickRepeatForever, cancellationTokenSource.Token);
IsMouseClickRepeatForeverOn = true;
}
}
}
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Windowing.HotKey;
internal interface IHotKeyController
{
void OnHotKeyPressed(in HotKeyParameter parameter);
void RegisterAll();
void UnregisterAll();
}

View File

@@ -6,10 +6,10 @@ namespace Snap.Hutao.Core.Windowing;
/// <summary>
/// 为扩展窗体提供必要的选项
/// </summary>
internal interface IXamlWindowOptionsSource
internal interface IWindowOptionsSource
{
/// <summary>
/// 窗体选项
/// </summary>
XamlWindowOptions WindowOptions { get; }
WindowOptions WindowOptions { get; }
}

View File

@@ -1,67 +0,0 @@
<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
Margin="4,0"
HorizontalAlignment="Right"
Orientation="Horizontal"
Spacing="2">
<AppBarButton Command="{Binding ShowWindowCommand}" Label="{shcm:ResourceString Name=CoreWindowingNotifyIconViewLabel}">
<AppBarButton.Icon>
<FontIcon
Width="20"
Height="20"
Glyph="&#xE80F;"/>
</AppBarButton.Icon>
</AppBarButton>
<AppBarButton Command="{Binding LaunchGameCommand}" Label="{shcm:ResourceString Name=CoreWindowingNotifyIconLaunchGameLabel}">
<AppBarButton.Icon>
<FontIcon
Width="20"
Height="20"
Glyph="&#xE7FC;"/>
</AppBarButton.Icon>
</AppBarButton>
<AppBarButton Command="{Binding ExitCommand}" Label="{shcm:ResourceString Name=CoreWindowingNotifyIconExitLabel}">
<AppBarButton.Icon>
<FontIcon
Width="20"
Height="20"
Glyph="&#xE7E8;"/>
</AppBarButton.Icon>
</AppBarButton>
</StackPanel>
</Grid>
</Grid>
</Flyout>

View File

@@ -1,17 +0,0 @@
// 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(IServiceProvider serviceProvider)
{
AllowFocusOnInteraction = false;
InitializeComponent();
Root.DataContext = serviceProvider.GetRequiredService<NotifyIconViewModel>();
}
}

View File

@@ -1,92 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Win32.Foundation;
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;
[Injection(InjectAs.Singleton)]
internal sealed class NotifyIconController : IDisposable
{
private readonly LazySlim<NotifyIconContextMenu> lazyMenu;
private readonly NotifyIconXamlHostWindow xamlHostWindow;
private readonly NotifyIconMessageWindow messageWindow;
private readonly System.Drawing.Icon icon;
public NotifyIconController(IServiceProvider serviceProvider)
{
lazyMenu = new(() => new(serviceProvider));
StorageFile iconFile = StorageFile.GetFileFromApplicationUriAsync("ms-appx:///Assets/Logo.ico".ToUri()).AsTask().GetAwaiter().GetResult();
icon = new(iconFile.Path);
xamlHostWindow = new();
messageWindow = new()
{
TaskbarCreated = OnRecreateNotifyIconRequested,
ContextMenuRequested = OnContextMenuRequested,
};
CreateNotifyIcon();
}
private static ref readonly Guid Id
{
get
{
// MD5 for "Snap.Hutao"
ReadOnlySpan<byte> data = [0xEE, 0x01, 0x5C, 0xCB, 0xF3, 0x97, 0xC6, 0x93, 0xE8, 0x77, 0xCE, 0x09, 0x54, 0x90, 0xEE, 0xAC];
return ref Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(data));
}
}
public void Dispose()
{
messageWindow.Dispose();
NotifyIconMethods.Delete(Id);
icon.Dispose();
xamlHostWindow.Dispose();
}
private void OnRecreateNotifyIconRequested(NotifyIconMessageWindow window)
{
NotifyIconMethods.Delete(Id);
if (!NotifyIconMethods.Add(Id, window.HWND, "Snap Hutao", NotifyIconMessageWindow.WM_NOTIFYICON_CALLBACK, (HICON)icon.Handle))
{
HutaoException.InvalidOperation("Failed to recreate NotifyIcon");
}
if (!NotifyIconMethods.SetVersion(Id, NOTIFYICON_VERSION_4))
{
HutaoException.InvalidOperation("Failed to set NotifyIcon version");
}
}
private void CreateNotifyIcon()
{
NotifyIconMethods.Delete(Id);
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 void OnContextMenuRequested(NotifyIconMessageWindow window, PointUInt16 point)
{
RECT iconRect = NotifyIconMethods.GetRect(Id, window.HWND);
xamlHostWindow.ShowFlyoutAt(lazyMenu.Value, new Windows.Foundation.Point(point.X, point.Y), iconRect);
}
}

View File

@@ -1,151 +0,0 @@
// 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.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static Snap.Hutao.Win32.ConstValues;
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<HWND, NotifyIconMessageWindow> WindowTable = [];
[SuppressMessage("", "SA1306")]
private readonly 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<ushort>(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 Action<NotifyIconMessageWindow>? TaskbarCreated { get; set; }
public Action<NotifyIconMessageWindow, PointUInt16>? ContextMenuRequested { get; set; }
public HWND HWND { get; }
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))
{
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
if (uMsg == window.WM_TASKBARCREATED)
{
// TODO: Re-add the notify icon.
window.TaskbarCreated?.Invoke(window);
}
// https://learn.microsoft.com/zh-cn/windows/win32/api/shellapi/ns-shellapi-notifyicondataw
if (uMsg is WM_NOTIFYICON_CALLBACK)
{
LPARAM2 lParam2 = *(LPARAM2*)&lParam;
PointUInt16 wParam2 = *(PointUInt16*)&wParam;
switch (lParam2.Low)
{
case WM_CONTEXTMENU:
window.ContextMenuRequested?.Invoke(window, wParam2);
break;
case WM_MOUSEMOVE:
// X: wParam2.X Y: wParam2.Y Low: WM_MOUSEMOVE
break;
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
break;
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
break;
case NIN_SELECT:
// X: wParam2.X Y: wParam2.Y Low: NIN_SELECT
break;
case NIN_POPUPOPEN:
// X: wParam2.X Y: 0? Low: NIN_POPUPOPEN
break;
case NIN_POPUPCLOSE:
// X: wParam2.X Y: 0? Low: NIN_POPUPCLOSE
break;
default:
break;
}
}
else
{
switch (uMsg)
{
case WM_ACTIVATEAPP:
break;
case WM_DWMNCRENDERINGCHANGED:
break;
default:
break;
}
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
private readonly struct LPARAM2
{
public readonly uint Low;
public readonly uint High;
}
}

View File

@@ -1,90 +0,0 @@
// 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 System.Runtime.InteropServices;
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);
}
[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);
}
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);
}
}

View File

@@ -1,69 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
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

@@ -1,10 +0,0 @@
// 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

@@ -22,21 +22,23 @@ using static Snap.Hutao.Win32.User32;
namespace Snap.Hutao.Core.Windowing;
[SuppressMessage("", "CA1001")]
internal sealed class XamlWindowController
internal sealed class WindowController
{
private readonly Window window;
private readonly XamlWindowOptions options;
private readonly WindowOptions options;
private readonly IServiceProvider serviceProvider;
private readonly XamlWindowSubclass subclass;
private readonly XamlWindowNonRudeHWND windowNonRudeHWND;
private readonly WindowSubclass subclass;
private readonly WindowNonRudeHWND windowNonRudeHWND;
public XamlWindowController(Window window, in XamlWindowOptions options, IServiceProvider serviceProvider)
public WindowController(Window window, in WindowOptions options, IServiceProvider serviceProvider)
{
this.window = window;
this.options = options;
this.serviceProvider = serviceProvider;
subclass = new(window, options);
// Window reference must be set before Window Subclass created
serviceProvider.GetRequiredService<ICurrentWindowReference>().Window = window;
subclass = new(window, options, serviceProvider);
windowNonRudeHWND = new(options.Hwnd);
InitializeCore();
@@ -87,9 +89,9 @@ internal sealed class XamlWindowController
SizeInt32 scaledSize = options.InitSize.Scale(scale);
RectInt32 rect = StructMarshal.RectInt32(scaledSize);
if (!string.IsNullOrEmpty(options.PersistRectKey))
if (options.PersistSize)
{
RectInt32 persistedRect = (CompactRect)LocalSetting.Get(options.PersistRectKey, (CompactRect)rect);
RectInt32 persistedRect = (CompactRect)LocalSetting.Get(SettingKeys.WindowRect, (CompactRect)rect);
if (persistedRect.Size() >= options.InitSize.Size())
{
rect = persistedRect.Scale(scale);
@@ -102,7 +104,7 @@ internal sealed class XamlWindowController
private void SaveOrSkipWindowSize()
{
if (string.IsNullOrEmpty(options.PersistRectKey))
if (!options.PersistSize)
{
return;
}
@@ -114,7 +116,7 @@ internal sealed class XamlWindowController
if (!windowPlacement.ShowCmd.HasFlag(SHOW_WINDOW_CMD.SW_SHOWMAXIMIZED))
{
double scale = 1.0 / options.GetRasterizationScale();
LocalSetting.Set(options.PersistRectKey, (CompactRect)window.AppWindow.GetRect().Scale(scale));
LocalSetting.Set(SettingKeys.WindowRect, (CompactRect)window.AppWindow.GetRect().Scale(scale));
}
}
@@ -135,25 +137,9 @@ internal sealed class XamlWindowController
private void OnWindowClosed(object sender, WindowEventArgs args)
{
if (LocalSetting.Get(SettingKeys.IsNotifyIconEnabled, true) && !serviceProvider.GetRequiredService<App>().IsExiting)
{
args.Handled = true;
window.Hide();
ICurrentXamlWindowReference currentXamlWindowReference = serviceProvider.GetRequiredService<ICurrentXamlWindowReference>();
if (currentXamlWindowReference.Window == window)
{
currentXamlWindowReference.Window = default!;
}
GC.Collect(GC.MaxGeneration);
}
else
{
SaveOrSkipWindowSize();
subclass?.Dispose();
windowNonRudeHWND?.Dispose();
}
SaveOrSkipWindowSize();
subclass?.Dispose();
windowNonRudeHWND?.Dispose();
}
private void ExtendsContentIntoTitleBar()
@@ -170,7 +156,7 @@ internal sealed class XamlWindowController
private bool UpdateSystemBackdrop(BackdropType backdropType)
{
SystemBackdrop? actualBackdop = backdropType switch
window.SystemBackdrop = backdropType switch
{
BackdropType.Transparent => new Backdrop.TransparentBackdrop(),
BackdropType.MicaAlt => new MicaBackdrop() { Kind = MicaKind.BaseAlt },
@@ -179,8 +165,6 @@ internal sealed class XamlWindowController
_ => null,
};
window.SystemBackdrop = new Backdrop.SystemBackdropDesktopWindowXamlSourceAccess(actualBackdop);
return true;
}

View File

@@ -2,67 +2,30 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Hosting;
using Snap.Hutao.Core.Windowing.Backdrop;
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;
internal static class WindowExtension
{
private static readonly ConditionalWeakTable<Window, XamlWindowController> WindowControllers = [];
private static readonly ConditionalWeakTable<Window, WindowController> WindowControllers = [];
public static void InitializeController<TWindow>(this TWindow window, IServiceProvider serviceProvider)
where TWindow : Window, IXamlWindowOptionsSource
where TWindow : Window, IWindowOptionsSource
{
XamlWindowController windowController = new(window, window.WindowOptions, serviceProvider);
WindowController windowController = new(window, window.WindowOptions, serviceProvider);
WindowControllers.Add(window, windowController);
}
public static bool IsControllerInitialized<TWindow>(this TWindow window)
where TWindow : Window
{
return WindowControllers.TryGetValue(window, out _);
}
public static void SetLayeredWindow(this Window window)
{
HWND hwnd = (HWND)WindowNative.GetWindowHandle(window);
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);
}
public static void Show(this Window window)
{
ShowWindow(GetWindowHandle(window), SHOW_WINDOW_CMD.SW_NORMAL);
}
public static void Hide(this Window window)
{
ShowWindow(GetWindowHandle(window), SHOW_WINDOW_CMD.SW_HIDE);
}
public static DesktopWindowXamlSource? GetDesktopWindowXamlSource(this Window window)
{
if (window.SystemBackdrop is SystemBackdropDesktopWindowXamlSourceAccess access)
{
return access.DesktopWindowXamlSource;
}
return default;
}
public static HWND GetWindowHandle(this Window? window)
{
return window is IXamlWindowOptionsSource optionsSource
? optionsSource.WindowOptions.Hwnd
: WindowNative.GetWindowHandle(window);
}
}

View File

@@ -6,13 +6,13 @@ using static Snap.Hutao.Win32.User32;
namespace Snap.Hutao.Core.Windowing;
internal sealed class XamlWindowNonRudeHWND : IDisposable
internal sealed class WindowNonRudeHWND : IDisposable
{
// https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist2-markfullscreenwindow#remarks
private const string NonRudeHWND = "NonRudeHWND";
private readonly HWND hwnd;
public XamlWindowNonRudeHWND(HWND hwnd)
public WindowNonRudeHWND(HWND hwnd)
{
this.hwnd = hwnd;
SetPropW(hwnd, NonRudeHWND, BOOL.TRUE);

View File

@@ -13,7 +13,7 @@ namespace Snap.Hutao.Core.Windowing;
/// <summary>
/// Window 选项
/// </summary>
internal readonly struct XamlWindowOptions
internal readonly struct WindowOptions
{
/// <summary>
/// 窗体句柄
@@ -38,18 +38,15 @@ internal readonly struct XamlWindowOptions
/// <summary>
/// 是否持久化尺寸
/// </summary>
[Obsolete]
public readonly bool PersistSize;
public readonly string? PersistRectKey;
public XamlWindowOptions(Window window, FrameworkElement titleBar, SizeInt32 initSize, string? persistSize = default)
public WindowOptions(Window window, FrameworkElement titleBar, SizeInt32 initSize, bool persistSize = false)
{
Hwnd = WindowNative.GetWindowHandle(window);
InputNonClientPointerSource = InputNonClientPointerSource.GetForWindowId(window.AppWindow.Id);
TitleBar = titleBar;
InitSize = initSize;
PersistRectKey = persistSize;
PersistSize = persistSize;
}
/// <summary>

View File

@@ -3,65 +3,72 @@
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Windowing.Backdrop;
using Snap.Hutao.Core.Windowing.NotifyIcon;
using Snap.Hutao.Win32;
using Snap.Hutao.Core.Windowing.HotKey;
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;
using static Snap.Hutao.Win32.ConstValues;
namespace Snap.Hutao.Core.Windowing;
/// <summary>
/// 窗体子类管理器
/// </summary>
[HighQuality]
internal sealed class XamlWindowSubclass : IDisposable
internal sealed class WindowSubclass : IDisposable
{
private const int WindowSubclassId = 101;
private readonly Window window;
private readonly XamlWindowOptions options;
private readonly WindowOptions options;
private readonly IServiceProvider serviceProvider;
private readonly IHotKeyController hotKeyController;
// We have to explicitly hold a reference to SUBCLASSPROC
private SUBCLASSPROC windowProc = default!;
private UnmanagedAccess<XamlWindowSubclass> unmanagedAccess = default!;
public XamlWindowSubclass(Window window, in XamlWindowOptions options)
public WindowSubclass(Window window, in WindowOptions options, IServiceProvider serviceProvider)
{
this.window = window;
this.options = options;
this.serviceProvider = serviceProvider;
hotKeyController = serviceProvider.GetRequiredService<IHotKeyController>();
}
public unsafe bool Initialize()
/// <summary>
/// 尝试设置窗体子类
/// </summary>
/// <returns>是否设置成功</returns>
public bool Initialize()
{
windowProc = SUBCLASSPROC.Create(&OnSubclassProcedure);
unmanagedAccess = UnmanagedAccess.Create(this);
return SetWindowSubclass(options.Hwnd, windowProc, WindowSubclassId, unmanagedAccess);
windowProc = OnSubclassProcedure;
bool windowHooked = SetWindowSubclass(options.Hwnd, windowProc, WindowSubclassId, 0);
hotKeyController.RegisterAll();
return windowHooked;
}
/// <inheritdoc/>
public void Dispose()
{
hotKeyController.UnregisterAll();
RemoveWindowSubclass(options.Hwnd, windowProc, WindowSubclassId);
windowProc = default!;
unmanagedAccess.Dispose();
}
[SuppressMessage("", "SH002")]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
private static unsafe LRESULT OnSubclassProcedure(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData)
private unsafe LRESULT OnSubclassProcedure(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData)
{
XamlWindowSubclass? state = UnmanagedAccess.Get<XamlWindowSubclass>(dwRefData);
ArgumentNullException.ThrowIfNull(state);
switch (uMsg)
{
case WM_GETMINMAXINFO:
{
if (state.window is IMinMaxInfoHandler handler)
if (window is IMinMaxInfoHandler handler)
{
handler.HandleMinMaxInfo(ref *(MINMAXINFO*)lParam, state.options.GetRasterizationScale());
handler.HandleMinMaxInfo(ref *(MINMAXINFO*)lParam, options.GetRasterizationScale());
}
break;
@@ -73,9 +80,15 @@ internal sealed class XamlWindowSubclass : IDisposable
return default;
}
case WM_HOTKEY:
{
hotKeyController.OnHotKeyPressed(*(HotKeyParameter*)&lParam);
break;
}
case WM_ERASEBKGND:
{
if (state.window is IWindowNeedEraseBackground || state.window.SystemBackdrop is IBackdropNeedEraseBackground)
if (window.SystemBackdrop is IBackdropNeedEraseBackground)
{
return (LRESULT)(int)BOOL.TRUE;
}

View File

@@ -13,7 +13,7 @@ namespace Snap.Hutao.Factory.ContentDialog;
[Injection(InjectAs.Singleton, typeof(IContentDialogFactory))]
internal sealed partial class ContentDialogFactory : IContentDialogFactory
{
private readonly ICurrentXamlWindowReference currentWindowReference;
private readonly ICurrentWindowReference currentWindowReference;
private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext;
private readonly AppOptions appOptions;

View File

@@ -18,7 +18,7 @@ namespace Snap.Hutao.Factory.Picker;
[Injection(InjectAs.Transient, typeof(IFileSystemPickerInteraction))]
internal sealed partial class FileSystemPickerInteraction : IFileSystemPickerInteraction
{
private readonly ICurrentXamlWindowReference currentWindowReference;
private readonly ICurrentWindowReference currentWindowReference;
public unsafe ValueResult<bool, ValueFile> PickFile(string? title, string? defaultFileName, (string Name, string Type)[]? filters)
{

View File

@@ -2,7 +2,6 @@
// 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;
@@ -12,7 +11,7 @@ namespace Snap.Hutao;
/// 指引窗口
/// </summary>
[Injection(InjectAs.Singleton)]
internal sealed partial class GuideWindow : Window, IXamlWindowOptionsSource, IMinMaxInfoHandler
internal sealed partial class GuideWindow : Window, IWindowOptionsSource, IMinMaxInfoHandler
{
private const int MinWidth = 1000;
private const int MinHeight = 650;
@@ -20,16 +19,16 @@ internal sealed partial class GuideWindow : Window, IXamlWindowOptionsSource, IM
private const int MaxWidth = 1200;
private const int MaxHeight = 800;
private readonly XamlWindowOptions windowOptions;
private readonly WindowOptions windowOptions;
public GuideWindow(IServiceProvider serviceProvider)
{
InitializeComponent();
windowOptions = new(this, DragableGrid, new(MinWidth, MinHeight), SettingKeys.GuideWindowRect);
windowOptions = new(this, DragableGrid, new(MinWidth, MinHeight));
this.InitializeController(serviceProvider);
}
XamlWindowOptions IXamlWindowOptionsSource.WindowOptions { get => windowOptions; }
WindowOptions IWindowOptionsSource.WindowOptions { get => windowOptions; }
public unsafe void HandleMinMaxInfo(ref MINMAXINFO info, double scalingFactor)
{

View File

@@ -3,7 +3,6 @@
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;
@@ -15,7 +14,7 @@ namespace Snap.Hutao;
/// </summary>
[HighQuality]
[Injection(InjectAs.Singleton)]
internal sealed partial class LaunchGameWindow : Window, IDisposable, IXamlWindowOptionsSource, IMinMaxInfoHandler
internal sealed partial class LaunchGameWindow : Window, IDisposable, IWindowOptionsSource, IMinMaxInfoHandler
{
private const int MinWidth = 240;
private const int MinHeight = 240;
@@ -23,7 +22,7 @@ internal sealed partial class LaunchGameWindow : Window, IDisposable, IXamlWindo
private const int MaxWidth = 320;
private const int MaxHeight = 320;
private readonly XamlWindowOptions windowOptions;
private readonly WindowOptions windowOptions;
private readonly IServiceScope scope;
/// <summary>
@@ -41,7 +40,7 @@ internal sealed partial class LaunchGameWindow : Window, IDisposable, IXamlWindo
}
/// <inheritdoc/>
public XamlWindowOptions WindowOptions { get => windowOptions; }
public WindowOptions WindowOptions { get => windowOptions; }
/// <inheritdoc/>
public void Dispose()

View File

@@ -1,9 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Content;
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Core.Windowing;
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
@@ -14,12 +12,13 @@ namespace Snap.Hutao;
/// </summary>
[HighQuality]
[Injection(InjectAs.Singleton)]
internal sealed partial class MainWindow : Window, IXamlWindowOptionsSource, IMinMaxInfoHandler
[SuppressMessage("", "CA1001")]
internal sealed partial class MainWindow : Window, IWindowOptionsSource, IMinMaxInfoHandler
{
private const int MinWidth = 1000;
private const int MinHeight = 600;
private readonly XamlWindowOptions windowOptions;
private readonly WindowOptions windowOptions;
/// <summary>
/// 构造一个新的主窗体
@@ -28,18 +27,12 @@ internal sealed partial class MainWindow : Window, IXamlWindowOptionsSource, IMi
public MainWindow(IServiceProvider serviceProvider)
{
InitializeComponent();
windowOptions = new(this, TitleBarView.DragArea, new(1200, 741), SettingKeys.WindowRect);
windowOptions = new(this, TitleBarView.DragArea, new(1200, 741), true);
this.InitializeController(serviceProvider);
if (this.GetDesktopWindowXamlSource() is { } desktopWindowXamlSource)
{
DesktopChildSiteBridge desktopChildSiteBridge = desktopWindowXamlSource.SiteBridge;
desktopChildSiteBridge.ResizePolicy = ContentSizePolicy.ResizeContentToParentWindow;
}
}
/// <inheritdoc/>
public XamlWindowOptions WindowOptions { get => windowOptions; }
public WindowOptions WindowOptions { get => windowOptions; }
/// <inheritdoc/>
public unsafe void HandleMinMaxInfo(ref MINMAXINFO pInfo, double scalingFactor)

View File

@@ -2,7 +2,6 @@
// 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;

View File

@@ -192,15 +192,6 @@
<data name="CoreWindowHotkeyCombinationRegisterFailed" xml:space="preserve">
<value>[{0}] 热键 [{1}] 注册失败</value>
</data>
<data name="CoreWindowingNotifyIconExitLabel" xml:space="preserve">
<value>退出</value>
</data>
<data name="CoreWindowingNotifyIconLaunchGameLabel" xml:space="preserve">
<value>启动游戏</value>
</data>
<data name="CoreWindowingNotifyIconViewLabel" xml:space="preserve">
<value>窗口</value>
</data>
<data name="CoreWindowThemeDark" xml:space="preserve">
<value>深色</value>
</data>

View File

@@ -77,8 +77,8 @@ internal sealed partial class DailyNoteNotificationOperation
.AddAttributionText(attribution)
.AddButton(new ToastButton()
.SetContent(SH.ServiceDailyNoteNotifierActionLaunchGameButton)
.AddArgument(AppActivation.Action, AppActivation.LaunchGame)
.AddArgument(AppActivation.Uid, entry.Uid))
.AddArgument(Activation.Action, Activation.LaunchGame)
.AddArgument(Activation.Uid, entry.Uid))
.AddButton(new ToastButtonDismiss(SH.ServiceDailyNoteNotifierActionLaunchGameDismiss));
if (options.IsReminderNotification)

View File

@@ -25,7 +25,7 @@
<AppxBundle>Never</AppxBundle>
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
<StartupObject>Snap.Hutao.Program</StartupObject>
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT</DefineConstants>
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT;$(AlphaConstants)</DefineConstants>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<DebugSymbols>true</DebugSymbols>
<DebugType>embedded</DebugType>
@@ -109,7 +109,6 @@
<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" />
@@ -356,11 +355,6 @@
<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

@@ -46,13 +46,7 @@
</DataTemplate>
<DataTemplate x:Key="MonsterBaseValueTemplate">
<cwc:SettingsCard MinHeight="40" Padding="0,0,16,0">
<cwc:SettingsCard.Resources>
<x:Double x:Key="SettingsCardLeftIndention">16</x:Double>
</cwc:SettingsCard.Resources>
<cwc:SettingsCard.Header>
<TextBlock Text="{Binding Name}" TextWrapping="NoWrap"/>
</cwc:SettingsCard.Header>
<cwc:SettingsCard Header="{Binding Name}">
<TextBlock Text="{Binding Value}"/>
</cwc:SettingsCard>
</DataTemplate>
@@ -100,6 +94,23 @@
Margin="6,8,0,0"
LocalSettingKeySuffixForCurrent="WikiMonsterPage.Monsters"/>
</CommandBar.Content>
<!--<AppBarElementContainer Visibility="Collapsed">
<AutoSuggestBox
Width="240"
Height="36"
Margin="16,6,6,0"
HorizontalAlignment="Stretch"
VerticalContentAlignment="Center"
PlaceholderText="{shcm:ResourceString Name=ViewPageWiKiMonsterAutoSuggestBoxPlaceHolder}"
QueryIcon="{shcm:FontIcon Glyph=&#xE721;}"
Text="{Binding FilterText, Mode=TwoWay}">
<mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="QuerySubmitted">
<mxic:InvokeCommandAction Command="{Binding FilterCommand}" CommandParameter="{Binding FilterText}"/>
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
</AutoSuggestBox>
</AppBarElementContainer>-->
</CommandBar>
</Border>
</Border>

View File

@@ -112,7 +112,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
{
if (await Initialization.Task.ConfigureAwait(false))
{
if (data.Data is AppActivation.ImportUIAFFromClipboard)
if (data.Data is Activation.ImportUIAFFromClipboard)
{
await ImportUIAFFromClipboardAsync().ConfigureAwait(false);
return true;

View File

@@ -1,93 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml;
using Snap.Hutao.Core;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.Windowing;
using System.Globalization;
using System.Text;
namespace Snap.Hutao.ViewModel;
[ConstructorGenerated]
[Injection(InjectAs.Singleton)]
internal sealed partial class NotifyIconViewModel : ObservableObject
{
private readonly ICurrentXamlWindowReference currentXamlWindowReference;
private readonly IServiceProvider serviceProvider;
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("ShowWindowCommand")]
private void ShowWindow()
{
switch (currentXamlWindowReference.Window)
{
case MainWindow mainWindow:
{
// MainWindow is activated, bring to foreground
mainWindow.Show();
mainWindow.WindowOptions.BringToForeground();
return;
}
case null:
{
// MainWindow is hided, show it
MainWindow mainWindow = serviceProvider.GetRequiredService<MainWindow>();
currentXamlWindowReference.Window = mainWindow;
// TODO: Can actually be no any window is initialized
mainWindow.Show();
mainWindow.WindowOptions.BringToForeground();
break;
}
case Window otherWindow:
{
otherWindow.Show();
(otherWindow as IXamlWindowOptionsSource)?.WindowOptions.BringToForeground();
return;
}
}
}
[Command("LaunchGameCommand")]
private async Task LaunchGame()
{
if (serviceProvider.GetRequiredService<IAppActivation>() is IAppActivationActionHandlersAccess access)
{
await access.HandleLaunchGameActionAsync();
}
ShowWindow();
}
[Command("ExitCommand")]
private void Exit()
{
app.Exit();
}
}

View File

@@ -8,7 +8,6 @@ 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;
@@ -155,7 +154,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
public bool IsAllocConsoleDebugModeEnabled
{
get => LocalSetting.Get(SettingKeys.IsAllocConsoleDebugModeEnabled, ConsoleWindowLifeTime.DebugModeEnabled);
get => LocalSetting.Get(SettingKeys.IsAllocConsoleDebugModeEnabled, false);
set
{
if (IsViewDisposed)

View File

@@ -4,7 +4,6 @@
using Microsoft.Extensions.Caching.Memory;
using Snap.Hutao.Core;
using Snap.Hutao.Core.Caching;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Service.Notification;
@@ -47,6 +46,7 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
private readonly ILogger<TestViewModel> logger;
private readonly IMemoryCache memoryCache;
private readonly ITaskContext taskContext;
private readonly MainWindow mainWindow;
private UploadAnnouncement announcement = new();
@@ -117,17 +117,14 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
[Command("ExceptionCommand")]
private static void ThrowTestException()
{
HutaoException.Throw("Test Exception");
Must.NeverHappen();
}
[Command("ResetMainWindowSizeCommand")]
private void ResetMainWindowSize()
{
if (serviceProvider.GetRequiredService<ICurrentXamlWindowReference>().Window is MainWindow mainWindow)
{
double scale = mainWindow.WindowOptions.GetRasterizationScale();
mainWindow.AppWindow.Resize(new Windows.Graphics.SizeInt32(1372, 772).Scale(scale));
}
double scale = mainWindow.WindowOptions.GetRasterizationScale();
mainWindow.AppWindow.Resize(new Windows.Graphics.SizeInt32(1372, 772).Scale(scale));
}
[Command("UploadAnnouncementCommand")]
@@ -189,7 +186,7 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
{
IDirect3DDevice direct3DDevice = WinRT.IInspectable.FromAbi((nint)inspectable).ObjRef.AsInterface<IDirect3DDevice>();
HWND hwnd = serviceProvider.GetRequiredService<ICurrentXamlWindowReference>().GetWindowHandle();
HWND hwnd = serviceProvider.GetRequiredService<ICurrentWindowReference>().GetWindowHandle();
GraphicsCaptureItem.As<IGraphicsCaptureItemInterop>().CreateForWindow(hwnd, out GraphicsCaptureItem item);
using (Direct3D11CaptureFramePool framePool = Direct3D11CaptureFramePool.CreateFreeThreaded(direct3DDevice, DirectXPixelFormat.B8G8R8A8UIntNormalized, 2, item.Size))

View File

@@ -195,7 +195,7 @@ internal class MiHoYoJSBridge
};
}
protected virtual JsResult<Dictionary<string, string>> GetDataSignV1(JsParam param)
protected virtual JsResult<Dictionary<string, string>> GetDynamicSecrectV1(JsParam param)
{
DataSignOptions options = DataSignOptions.CreateForGeneration1(SaltType.LK2, true);
return new()
@@ -207,7 +207,7 @@ internal class MiHoYoJSBridge
};
}
protected virtual JsResult<Dictionary<string, string>> GetDataSignV2(JsParam<DataSignV2Payload> param)
protected virtual JsResult<Dictionary<string, string>> GetDynamicSecrectV2(JsParam<DynamicSecrect2Playload> param)
{
DataSignOptions options = DataSignOptions.CreateForGeneration2(SaltType.X4, false, param.Payload.Body, param.Payload.GetQueryParam());
return new()
@@ -451,8 +451,8 @@ internal class MiHoYoJSBridge
"getCookieInfo" => GetCookieInfo(param),
"getCookieToken" => await GetCookieTokenAsync(param).ConfigureAwait(false),
"getCurrentLocale" => GetCurrentLocale(param),
"getDS" => GetDataSignV1(param),
"getDS2" => GetDataSignV2(param),
"getDS" => GetDynamicSecrectV1(param),
"getDS2" => GetDynamicSecrectV2(param),
"getHTTPRequestHeaders" => GetHttpRequestHeader(param),
"getStatusBarHeight" => GetStatusBarHeight(param),
"getUserInfo" => await GetUserInfoAsync(param).ConfigureAwait(false),

View File

@@ -7,7 +7,7 @@ namespace Snap.Hutao.Web.Bridge.Model;
/// DS2请求
/// </summary>
[HighQuality]
internal sealed class DataSignV2Payload
internal sealed class DynamicSecrect2Playload
{
/// <summary>
/// q

View File

@@ -11,7 +11,7 @@ namespace Snap.Hutao.Web.Request.Builder;
internal static class HttpRequestMessageBuilderExtension
{
private const string RequestErrorMessage = "请求异常已忽略: {0}";
private const string RequestErrorMessage = "请求异常已忽略";
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, builder.HttpRequestMessage.RequestUri);
logger.LogWarning(ex, RequestErrorMessage);
if (ex.StatusCode is HttpStatusCode.BadGateway)
{
@@ -50,22 +50,22 @@ internal static class HttpRequestMessageBuilderExtension
}
catch (IOException ex)
{
logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri);
logger.LogWarning(ex, RequestErrorMessage);
return default;
}
catch (JsonException ex)
{
logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri);
logger.LogWarning(ex, RequestErrorMessage);
return default;
}
catch (HttpContentSerializationException ex)
{
logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri);
logger.LogWarning(ex, RequestErrorMessage);
return default;
}
catch (SocketException ex)
{
logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri);
logger.LogWarning(ex, RequestErrorMessage);
return default;
}
}
@@ -80,23 +80,23 @@ internal static class HttpRequestMessageBuilderExtension
}
catch (HttpRequestException ex)
{
logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri);
logger.LogWarning(ex, RequestErrorMessage);
}
catch (IOException ex)
{
logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri);
logger.LogWarning(ex, RequestErrorMessage);
}
catch (JsonException ex)
{
logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri);
logger.LogWarning(ex, RequestErrorMessage);
}
catch (HttpContentSerializationException ex)
{
logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri);
logger.LogWarning(ex, RequestErrorMessage);
}
catch (SocketException ex)
{
logger.LogWarning(ex, RequestErrorMessage, builder.HttpRequestMessage.RequestUri);
logger.LogWarning(ex, RequestErrorMessage);
}
}
}

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.Win32;
[SuppressMessage("", "SYSLIB1054")]
internal static class AdvApi32
{
[DllImport("ADVAPI32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true, SetLastError = true)]
[DllImport("ADVAPI32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, 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, ExactSpelling = true, SetLastError = true)]
[DllImport("ADVAPI32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, 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, ExactSpelling = true)]
[DllImport("ADVAPI32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, 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);

View File

@@ -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, SUBCLASSPROC pfnSubclass, nuint uIdSubclass);
public static extern BOOL RemoveWindowSubclass(HWND hWnd, [MarshalAs(UnmanagedType.FunctionPtr)] SUBCLASSPROC pfnSubclass, nuint uIdSubclass);
[DllImport("COMCTL32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
[SupportedOSPlatform("windows5.1.2600")]
public static unsafe extern BOOL SetWindowSubclass(HWND hWnd, SUBCLASSPROC pfnSubclass, nuint uIdSubclass, nuint dwRefData);
public static unsafe extern BOOL SetWindowSubclass(HWND hWnd, [MarshalAs(UnmanagedType.FunctionPtr)] SUBCLASSPROC pfnSubclass, nuint uIdSubclass, nuint dwRefData);
}

View File

@@ -6,25 +6,12 @@ namespace Snap.Hutao.Win32;
[SuppressMessage("", "SA1310")]
internal static class ConstValues
{
public const uint WM_NULL = 0x00000000U;
public const uint NOTIFYICON_VERSION = 0x00000003U;
public const uint NOTIFYICON_VERSION_4 = 0x00000004U;
public const uint D3D11_SDK_VERSION = 0x00000007U;
public const uint WM_NULL = 0x00000000U;
public const uint WM_ERASEBKGND = 0x00000014U;
public const uint WM_ACTIVATEAPP = 0x0000001CU;
public const uint WM_GETMINMAXINFO = 0x00000024U;
public const uint WM_CONTEXTMENU = 0x000007BU;
public const uint WM_NCRBUTTONDOWN = 0x000000A4U;
public const uint WM_NCRBUTTONUP = 0x000000A5U;
public const uint WM_MOUSEMOVE = 0x00000200U;
public const uint WM_LBUTTONDOWN = 0x00000201U;
public const uint WM_LBUTTONUP = 0x00000202U;
public const uint WM_RBUTTONDOWN = 0x00000204U;
public const uint WM_RBUTTONUP = 0x00000205U;
public const uint WM_HOTKEY = 0x00000312U;
public const uint WM_DWMNCRENDERINGCHANGED = 0x0000031FU;
public const uint NIN_SELECT = 0x00000400U;
public const uint NIN_POPUPOPEN = 0x00000406U;
public const uint NIN_POPUPCLOSE = 0x00000407U;
public const uint INFINITE = 0xFFFFFFFFU;
}

View File

@@ -1,9 +0,0 @@
// 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

@@ -3,8 +3,6 @@
namespace Snap.Hutao.Win32.Foundation;
// RAIIFree: CloseHandle
// InvalidHandleValue: -1, 0
internal readonly struct HANDLE
{
public readonly nint Value;

View File

@@ -1,11 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.Foundation;
// RAIIFree: FreeLibrary
// InvalidHandleValue: 0
internal readonly struct HINSTANCE
{
public readonly nint Value;
}

View File

@@ -3,8 +3,6 @@
namespace Snap.Hutao.Win32.Foundation;
// RAIIFree: FreeLibrary
// InvalidHandleValue: 0
internal readonly struct HMODULE
{
public readonly nint Value;

View File

@@ -7,6 +7,8 @@ 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;

View File

@@ -4,10 +4,10 @@
namespace Snap.Hutao.Win32.Foundation;
[SuppressMessage("", "SA1307")]
internal struct RECT
internal readonly struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
public readonly int left;
public readonly int top;
public readonly int right;
public readonly int bottom;
}

View File

@@ -3,7 +3,6 @@
namespace Snap.Hutao.Win32.Graphics.Gdi;
// InvalidHandleValue: -1, 0
internal readonly struct HDC
{
public static readonly HDC NULL = 0;

View File

@@ -3,7 +3,6 @@
namespace Snap.Hutao.Win32.Graphics.Gdi;
// InvalidHandleValue: -1, 0
internal readonly struct HMONITOR
{
public readonly nint Value;

View File

@@ -22,7 +22,7 @@ internal static class Kernel32
[SupportedOSPlatform("windows5.0")]
public static extern BOOL CloseHandle(HANDLE hObject);
[DllImport("KERNEL32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true, SetLastError = true)]
[DllImport("KERNEL32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, 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, ExactSpelling = true)]
[DllImport("KERNEL32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, 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, ExactSpelling = true, SetLastError = true)]
[DllImport("KERNEL32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
public static extern BOOL SetConsoleTitleW(PCWSTR lpConsoleTitle);
[DebuggerStepThrough]

View File

@@ -28,10 +28,4 @@ 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

@@ -3,8 +3,6 @@
namespace Snap.Hutao.Win32.Registry;
// RAIIFree: RegCloseKey
// InvalidHandleValue: -1, 0
[SuppressMessage("", "SA1310")]
internal readonly struct HKEY
{

View File

@@ -129,14 +129,8 @@ internal sealed partial class RegistryWatcher : IDisposable
if (!disposed)
{
try
{
// Before exiting, signal the Dispose method.
disposeEvent.Reset();
}
catch
{
}
// Before exiting, signal the Dispose method.
disposeEvent.Reset();
}
}
catch (OperationCanceledException)

View File

@@ -3,7 +3,6 @@
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.System.Com;
using Snap.Hutao.Win32.UI.Shell;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
@@ -33,31 +32,4 @@ 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);
public static unsafe BOOL Shell_NotifyIconW(NOTIFY_ICON_MESSAGE dwMessage, ref readonly NOTIFYICONDATAW data)
{
fixed (NOTIFYICONDATAW* p = &data)
{
return Shell_NotifyIconW(dwMessage, p);
}
}
}

View File

@@ -1,7 +1,6 @@
// 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;
@@ -33,11 +32,6 @@ 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

@@ -3,7 +3,6 @@
namespace Snap.Hutao.Win32.System.WinRT;
// RAIIFree: WindowsDeleteString
internal readonly struct HSTRING
{
public readonly nint Value;

View File

@@ -1,38 +0,0 @@
// 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.Runtime.InteropServices;
namespace Snap.Hutao.Win32.UI.Shell;
[SuppressMessage("", "SA1307")]
internal struct NOTIFYICONDATAW
{
public uint cbSize;
public HWND hWnd;
public uint uID;
public NOTIFY_ICON_DATA_FLAGS uFlags;
public uint uCallbackMessage;
public HICON hIcon;
public unsafe fixed char szTip[128];
public NOTIFY_ICON_STATE dwState;
public NOTIFY_ICON_STATE dwStateMask;
public unsafe fixed char szInfo[256];
public Union Anonymous;
public unsafe fixed char szInfoTitle[64];
public NOTIFY_ICON_INFOTIP_FLAGS dwInfoFlags;
public Guid guidItem;
public HICON hBalloonIcon;
[StructLayout(LayoutKind.Explicit)]
internal struct Union
{
[FieldOffset(0)]
public uint uTimeout;
[FieldOffset(0)]
public uint uVersion;
}
}

View File

@@ -1,15 +0,0 @@
// 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

@@ -1,17 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.UI.Shell;
[Flags]
internal enum NOTIFY_ICON_DATA_FLAGS : uint
{
NIF_MESSAGE = 0x1U,
NIF_ICON = 0x2U,
NIF_TIP = 0x4U,
NIF_STATE = 0x8U,
NIF_INFO = 0x10U,
NIF_GUID = 0x20U,
NIF_REALTIME = 0x40U,
NIF_SHOWTIP = 0x80U,
}

View File

@@ -1,18 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.UI.Shell;
[Flags]
internal enum NOTIFY_ICON_INFOTIP_FLAGS : uint
{
NIIF_NONE = 0U,
NIIF_INFO = 1U,
NIIF_WARNING = 2U,
NIIF_ERROR = 3U,
NIIF_USER = 4U,
NIIF_ICON_MASK = 0xFU,
NIIF_NOSOUND = 0x10U,
NIIF_LARGE_ICON = 0x20U,
NIIF_RESPECT_QUIET_TIME = 0x80U,
}

View File

@@ -1,13 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.UI.Shell;
internal enum NOTIFY_ICON_MESSAGE : uint
{
NIM_ADD = 0U,
NIM_MODIFY = 1U,
NIM_DELETE = 2U,
NIM_SETFOCUS = 3U,
NIM_SETVERSION = 4U,
}

View File

@@ -1,11 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.UI.Shell;
[Flags]
internal enum NOTIFY_ICON_STATE : uint
{
NIS_HIDDEN = 1U,
NIS_SHAREDICON = 2U,
}

View File

@@ -6,17 +6,5 @@ using System.Runtime.InteropServices;
namespace Snap.Hutao.Win32.UI.Shell;
internal unsafe readonly struct SUBCLASSPROC
{
private readonly delegate* unmanaged[Stdcall]<HWND, uint, WPARAM, LPARAM, nuint, nuint, LRESULT> value;
public SUBCLASSPROC(delegate* unmanaged[Stdcall]<HWND, uint, WPARAM, LPARAM, nuint, nuint, LRESULT> method)
{
value = method;
}
public static SUBCLASSPROC Create(delegate* unmanaged[Stdcall]<HWND, uint, WPARAM, LPARAM, nuint, nuint, LRESULT> method)
{
return new(method);
}
}
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate LRESULT SUBCLASSPROC(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData);

View File

@@ -1,11 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.UI.WindowsAndMessaging;
// RAIIFree: DeleteObject
// InvalidHandleValue: -1, 0
internal readonly struct HBRUSH
{
public readonly nint Value;
}

View File

@@ -1,11 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.UI.WindowsAndMessaging;
// RAIIFree: DestroyCursor
// InvalidHandleValue: -1, 0
internal readonly struct HCURSOR
{
public readonly nint Value;
}

View File

@@ -1,15 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.UI.WindowsAndMessaging;
// RAIIFree: DestroyIcon
// InvalidHandleValue: -1, 0
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;
}

View File

@@ -1,11 +0,0 @@
// 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;
}

View File

@@ -1,11 +0,0 @@
// 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

@@ -1,38 +0,0 @@
// 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,
}

View File

@@ -1,22 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Win32.UI.WindowsAndMessaging;
[SuppressMessage("", "SA1307")]
internal struct WNDCLASSW
{
public WNDCLASS_STYLES style;
public WNDPROC lpfnWndProc;
public int cbClsExtra;
public int cbWndExtra;
public HINSTANCE hInstance;
public HICON hIcon;
public HCURSOR hCursor;
public HBRUSH hbrBackground;
public PCWSTR lpszMenuName;
public PCWSTR lpszClassName;
}

View File

@@ -1,22 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.UI.WindowsAndMessaging;
[Flags]
internal enum WNDCLASS_STYLES : uint
{
CS_VREDRAW = 0x1U,
CS_HREDRAW = 0x2U,
CS_DBLCLKS = 0x8U,
CS_OWNDC = 0x20U,
CS_CLASSDC = 0x40U,
CS_PARENTDC = 0x80U,
CS_NOCLOSE = 0x200U,
CS_SAVEBITS = 0x800U,
CS_BYTEALIGNCLIENT = 0x1000U,
CS_BYTEALIGNWINDOW = 0x2000U,
CS_GLOBALCLASS = 0x4000U,
CS_IME = 0x10000U,
CS_DROPSHADOW = 0x20000U,
}

View File

@@ -1,21 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
namespace Snap.Hutao.Win32.UI.WindowsAndMessaging;
internal unsafe readonly struct WNDPROC
{
private readonly delegate* unmanaged[Stdcall]<HWND, uint, WPARAM, LPARAM, LRESULT> value;
public WNDPROC(delegate* unmanaged[Stdcall]<HWND, uint, WPARAM, LPARAM, LRESULT> method)
{
value = method;
}
public static WNDPROC Create(delegate* unmanaged[Stdcall]<HWND, uint, WPARAM, LPARAM, LRESULT> method)
{
return new(method);
}
}

View File

@@ -1,53 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.InteropServices;
namespace Snap.Hutao.Win32;
internal readonly struct UnmanagedAccess<T> : IDisposable
where T : class
{
private readonly nint handle;
public UnmanagedAccess(T value)
{
handle = GCHandle.ToIntPtr(GCHandle.Alloc(value));
}
public static implicit operator nint(UnmanagedAccess<T> access)
{
return access.handle;
}
public static implicit operator nuint(UnmanagedAccess<T> access)
{
return (nuint)access.handle;
}
public void Dispose()
{
GCHandle.FromIntPtr(handle).Free();
}
}
internal static class UnmanagedAccess
{
public static UnmanagedAccess<T> Create<T>(T value)
where T : class
{
return new UnmanagedAccess<T>(value);
}
public static T? Get<T>(nint handle)
where T : class
{
return GCHandle.FromIntPtr(handle).Target as T;
}
public static T? Get<T>(nuint handle)
where T : class
{
return GCHandle.FromIntPtr((nint)handle).Target as T;
}
}

View File

@@ -12,7 +12,6 @@ using System.Runtime.Versioning;
namespace Snap.Hutao.Win32;
[SuppressMessage("", "SH002")]
[SuppressMessage("", "SA1313")]
[SuppressMessage("", "SYSLIB1054")]
internal static class User32
{
@@ -20,30 +19,7 @@ internal static class User32
[SupportedOSPlatform("windows5.1.2600")]
public static extern BOOL AttachThreadInput(uint idAttach, uint idAttachTo, BOOL fAttach);
[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)]
[DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[SupportedOSPlatform("windows5.0")]
public static extern HWND FindWindowExW([AllowNull] HWND hWndParent, [AllowNull] HWND hWndChildAfter, [AllowNull] PCWSTR lpszClass, [AllowNull] PCWSTR lpszWindow);
@@ -71,7 +47,7 @@ internal static class User32
[SupportedOSPlatform("windows5.0")]
public static extern HWND GetForegroundWindow();
[DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true, SetLastError = true)]
[DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[SupportedOSPlatform("windows5.0")]
public static extern nint GetWindowLongPtrW(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex);
@@ -101,34 +77,10 @@ internal static class User32
}
}
[DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true, SetLastError = true)]
[SupportedOSPlatform("windows5.0")]
public static unsafe extern ushort RegisterClassW(WNDCLASSW* lpWndClass);
public static unsafe ushort RegisterClassW(ref readonly WNDCLASSW lpWndClass)
{
fixed (WNDCLASSW* pWndClass = &lpWndClass)
{
return RegisterClassW(pWndClass);
}
}
[DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
[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);
@@ -163,11 +115,7 @@ 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)]
[DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[SupportedOSPlatform("windows5.0")]
public static extern BOOL SetPropW(HWND hWnd, PCWSTR lpString, [AllowNull] HANDLE hData);
@@ -180,14 +128,10 @@ internal static class User32
}
}
[DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true, SetLastError = true)]
[DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[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);