diff --git a/.cursor/skills/.gitkeep b/.cursor/skills/.gitkeep
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/.cursor/skills/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
index aa56451..f43665b 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -16,6 +16,7 @@
+
diff --git a/LanMountainDesktop/LanMountainDesktop.csproj b/LanMountainDesktop/LanMountainDesktop.csproj
index 4af574e..464bcdd 100644
--- a/LanMountainDesktop/LanMountainDesktop.csproj
+++ b/LanMountainDesktop/LanMountainDesktop.csproj
@@ -56,6 +56,7 @@
+
diff --git a/LanMountainDesktop/Platform/Windows/ChromePatchState.cs b/LanMountainDesktop/Platform/Windows/ChromePatchState.cs
new file mode 100644
index 0000000..7ee09a6
--- /dev/null
+++ b/LanMountainDesktop/Platform/Windows/ChromePatchState.cs
@@ -0,0 +1,6 @@
+namespace LanMountainDesktop.Platform.Windows;
+
+internal static class ChromePatchState
+{
+ public static bool UseSystemChrome { get; set; }
+}
diff --git a/LanMountainDesktop/Platform/Windows/PatcherEntrance.cs b/LanMountainDesktop/Platform/Windows/PatcherEntrance.cs
new file mode 100644
index 0000000..6cecf3d
--- /dev/null
+++ b/LanMountainDesktop/Platform/Windows/PatcherEntrance.cs
@@ -0,0 +1,12 @@
+using HarmonyLib;
+
+namespace LanMountainDesktop.Platform.Windows;
+
+internal static class PatcherEntrance
+{
+ public static void InstallPatchers()
+ {
+ var harmony = new Harmony("dev.lanmountain.desktop.patchers");
+ harmony.PatchAll(typeof(PatcherEntrance).Assembly);
+ }
+}
diff --git a/LanMountainDesktop/Platform/Windows/Patches/AppWindowInitializeAppWindowPatcher.cs b/LanMountainDesktop/Platform/Windows/Patches/AppWindowInitializeAppWindowPatcher.cs
new file mode 100644
index 0000000..0941f4e
--- /dev/null
+++ b/LanMountainDesktop/Platform/Windows/Patches/AppWindowInitializeAppWindowPatcher.cs
@@ -0,0 +1,25 @@
+using System.Runtime.CompilerServices;
+using Avalonia;
+using Avalonia.Controls;
+using FluentAvalonia.UI.Windowing;
+using LanMountainDesktop.Platform.Windows;
+using HarmonyLib;
+
+namespace LanMountainDesktop.Platform.Windows.Patches;
+
+[HarmonyPatch(typeof(FAAppWindow), "InitializeAppWindow")]
+internal class AppWindowInitializeAppWindowPatcher
+{
+ [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_PseudoClasses")]
+ private static extern IPseudoClasses GetPseudoClasses(StyledElement window);
+
+ [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_IsWindows")]
+ private static extern void SetIsWindowsProperty(FAAppWindow window, bool v);
+
+ static void Postfix(FAAppWindow __instance)
+ {
+ if (!ChromePatchState.UseSystemChrome) return;
+ GetPseudoClasses(__instance).Remove(":windows");
+ SetIsWindowsProperty(__instance, false);
+ }
+}
diff --git a/LanMountainDesktop/Platform/Windows/Patches/Win32WindowManagerConstructorPatcher.cs b/LanMountainDesktop/Platform/Windows/Patches/Win32WindowManagerConstructorPatcher.cs
new file mode 100644
index 0000000..e67b3f8
--- /dev/null
+++ b/LanMountainDesktop/Platform/Windows/Patches/Win32WindowManagerConstructorPatcher.cs
@@ -0,0 +1,21 @@
+using FluentAvalonia.UI.Windowing;
+using HarmonyLib;
+using LanMountainDesktop.Platform.Windows;
+
+namespace LanMountainDesktop.Platform.Windows.Patches;
+
+[HarmonyPatch]
+internal class Win32WindowManagerConstructorPatcher
+{
+ [HarmonyTargetMethod]
+ static System.Reflection.MethodBase TargetMethod()
+ {
+ var type = AccessTools.TypeByName("FluentAvalonia.UI.Windowing.Win32WindowManager");
+ return AccessTools.Constructor(type!, [typeof(FAAppWindow)]);
+ }
+
+ static bool Prefix(FAAppWindow window)
+ {
+ return !ChromePatchState.UseSystemChrome;
+ }
+}
diff --git a/LanMountainDesktop/Program.cs b/LanMountainDesktop/Program.cs
index 1e1a50d..f911de9 100644
--- a/LanMountainDesktop/Program.cs
+++ b/LanMountainDesktop/Program.cs
@@ -87,6 +87,8 @@ public sealed class Program
AppLogger.Info("SingleInstance", "Activation acknowledged before Avalonia App was ready.");
});
+ LoadChromePatchState();
+ InstallChromePatchersIfNeeded();
BuildAvaloniaApp(renderMode).StartWithClassicDesktopLifetime(args);
AppLogger.Info("Startup", "Application exited normally.");
}
@@ -198,6 +200,49 @@ public sealed class Program
}
}
+ private static void LoadChromePatchState()
+ {
+ try
+ {
+ var snapshot = HostSettingsFacadeProvider.GetOrCreate()
+ .Settings
+ .LoadSnapshot(LanMountainDesktop.PluginSdk.SettingsScope.App);
+ if (OperatingSystem.IsWindows())
+ {
+ LanMountainDesktop.Platform.Windows.ChromePatchState.UseSystemChrome = snapshot.UseSystemChrome;
+ }
+ }
+ catch (Exception ex)
+ {
+ AppLogger.Warn("Startup", "Failed to load chrome patch state. Falling back to FA chrome.", ex);
+ }
+ }
+
+ private static void InstallChromePatchersIfNeeded()
+ {
+ if (!OperatingSystem.IsWindows())
+ {
+ return;
+ }
+
+ var arch = System.Runtime.InteropServices.RuntimeInformation.OSArchitecture;
+ if (arch != System.Runtime.InteropServices.Architecture.X64 &&
+ arch != System.Runtime.InteropServices.Architecture.X86)
+ {
+ return;
+ }
+
+ try
+ {
+ LanMountainDesktop.Platform.Windows.PatcherEntrance.InstallPatchers();
+ AppLogger.Info("Startup", $"Chrome patchers installed. UseSystemChrome={LanMountainDesktop.Platform.Windows.ChromePatchState.UseSystemChrome}.");
+ }
+ catch (Exception ex)
+ {
+ AppLogger.Warn("Startup", "Failed to install chrome patchers.", ex);
+ }
+ }
+
private static void WaitForRestartParentExit(int processId, DateTime deadlineUtc)
{
try
diff --git a/LanMountainDesktop/ViewModels/SettingsViewModels.cs b/LanMountainDesktop/ViewModels/SettingsViewModels.cs
index e09021f..ff8255a 100644
--- a/LanMountainDesktop/ViewModels/SettingsViewModels.cs
+++ b/LanMountainDesktop/ViewModels/SettingsViewModels.cs
@@ -31,12 +31,14 @@ public sealed partial class SettingsWindowViewModel : ViewModelBase
{
_localizationService = new();
_languageCode = "zh-CN";
+ IsWindowsOs = OperatingSystem.IsWindows();
}
public SettingsWindowViewModel(LocalizationService localizationService, string languageCode)
{
_localizationService = localizationService;
_languageCode = languageCode;
+ IsWindowsOs = OperatingSystem.IsWindows();
}
private string L(string key) => _localizationService.GetString(_languageCode, key, key);
@@ -86,6 +88,10 @@ public sealed partial class SettingsWindowViewModel : ViewModelBase
[ObservableProperty]
private bool _isDrawerOpen;
+ /// 用于标题栏右侧系统按钮占位(与 SecRandom / ClassIsland 一致,仅 Windows 显示)。
+ [ObservableProperty]
+ private bool _isWindowsOs;
+
public SettingsWindowViewModel Initialize()
{
RefreshLanguage(_languageCode);
@@ -855,7 +861,7 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
return;
}
- PersistCurrentState(restartRequired: false);
+ PersistCurrentState(restartRequired: true);
}
partial void OnSelectedCornerRadiusStyleChanged(SelectionOption? value)
diff --git a/LanMountainDesktop/Views/SettingsWindow.axaml b/LanMountainDesktop/Views/SettingsWindow.axaml
index d95e0b2..9ca9764 100644
--- a/LanMountainDesktop/Views/SettingsWindow.axaml
+++ b/LanMountainDesktop/Views/SettingsWindow.axaml
@@ -1,26 +1,26 @@
-
+
-
+
960
-
+
-
+
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+ PointerPressed="OnTitleBarDragZonePointerPressed" />
-
+
+
+
+
+
+
+
+
+
@@ -182,4 +195,4 @@
-
+
diff --git a/LanMountainDesktop/Views/SettingsWindow.axaml.cs b/LanMountainDesktop/Views/SettingsWindow.axaml.cs
index 0608187..83f1e66 100644
--- a/LanMountainDesktop/Views/SettingsWindow.axaml.cs
+++ b/LanMountainDesktop/Views/SettingsWindow.axaml.cs
@@ -5,9 +5,10 @@ using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
-using Avalonia.Platform;
+using Avalonia.Media;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
+using FluentAvalonia.UI.Windowing;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
@@ -16,7 +17,7 @@ using Symbol = FluentIcons.Common.Symbol;
namespace LanMountainDesktop.Views;
-public partial class SettingsWindow : Window, ISettingsPageHostContext
+public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext
{
private const double BaseSettingsContainerWidth = 960d;
private const double MinSettingsContentWidth = 320d;
@@ -56,7 +57,7 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
_hostApplicationLifecycle = hostApplicationLifecycle;
DataContext = ViewModel;
InitializeComponent();
- Icon = _appLogoService.CreateWindowIcon();
+ SetValue(Window.IconProperty, _appLogoService.CreateWindowIcon());
ApplyChromeMode(useSystemChrome);
if (RootNavigationView is not null)
@@ -75,6 +76,14 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
private void OnLoaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
+ TitleBar.Height = 48;
+ TitleBar.ExtendsContentIntoTitleBar = true;
+
+ // SecRandom MainWindow:标题栏按钮悬停/按下/非活动色,与系统 caption 更一致
+ TitleBar.ButtonHoverBackgroundColor = Color.FromArgb(23, 0, 0, 0);
+ TitleBar.ButtonPressedBackgroundColor = Color.FromArgb(52, 0, 0, 0);
+ TitleBar.ButtonInactiveForegroundColor = Colors.Gray;
+
SyncPendingRestartState();
SyncTitleText();
UpdateChromeMetrics();
@@ -160,11 +169,11 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
{
_useSystemChrome = useSystemChrome || OperatingSystem.IsMacOS();
+ ExtendClientAreaToDecorationsHint = true;
+ WindowDecorations = WindowDecorations.Full;
+
if (_useSystemChrome)
{
- ExtendClientAreaToDecorationsHint = true;
- WindowDecorations = WindowDecorations.Full;
-
if (WindowTitleBarHost is { })
{
WindowTitleBarHost.IsVisible = false;
@@ -172,9 +181,6 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
return;
}
- WindowDecorations = WindowDecorations.BorderOnly;
- ExtendClientAreaToDecorationsHint = true;
-
if (WindowTitleBarHost is { })
{
WindowTitleBarHost.IsVisible = true;
@@ -195,21 +201,32 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
}
RootNavigationView.MenuItems.Clear();
+ RootNavigationView.FooterMenuItems.Clear();
+
SettingsPageCategory? previousCategory = null;
foreach (var page in ViewModel.Pages)
{
+ var item = new FANavigationViewItem
+ {
+ Content = page.Title,
+ Tag = page.PageId,
+ IconSource = CreateSettingsIconSource(MapIcon(page.IconKey))
+ };
+
+ if (page.Category == SettingsPageCategory.About ||
+ page.Category == SettingsPageCategory.Dev)
+ {
+ RootNavigationView.FooterMenuItems.Add(item);
+ continue;
+ }
+
if (previousCategory is not null && previousCategory != page.Category)
{
RootNavigationView.MenuItems.Add(new FANavigationViewItemSeparator());
}
- RootNavigationView.MenuItems.Add(new FANavigationViewItem
- {
- Content = page.Title,
- Tag = page.PageId,
- IconSource = CreateSettingsIconSource(MapIcon(page.IconKey))
- });
+ RootNavigationView.MenuItems.Add(item);
previousCategory = page.Category;
}
@@ -293,7 +310,10 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
return;
}
- foreach (var item in RootNavigationView.MenuItems.OfType())
+ var allItems = RootNavigationView.MenuItems.OfType()
+ .Concat(RootNavigationView.FooterMenuItems.OfType());
+
+ foreach (var item in allItems)
{
if (string.Equals(item.Tag as string, pageId, StringComparison.OrdinalIgnoreCase))
{
@@ -494,7 +514,7 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
TelemetryServices.Usage?.TrackSettingsWindowClosed("SettingsWindow.OnClosed", ViewModel.CurrentPageId);
}
- private void OnWindowTitleBarPointerPressed(object? sender, PointerPressedEventArgs e)
+ private void OnTitleBarDragZonePointerPressed(object? sender, PointerPressedEventArgs e)
{
_ = sender;
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
@@ -518,13 +538,6 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
RequestResponsiveLayoutRefresh();
}
- private void OnCloseWindowClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
- {
- _ = sender;
- _ = e;
- Close();
- }
-
private void OnRootNavigationViewPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
_ = sender;
@@ -573,6 +586,10 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
{
return;
}
+
+ TogglePaneButtonIcon.Icon = RootNavigationView.IsPaneOpen
+ ? FluentIcons.Common.Icon.LineHorizontal3
+ : FluentIcons.Common.Icon.Navigation;
}
private void UpdateChromeMetrics()
@@ -594,8 +611,6 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
RestartNowButton is null ||
RestartButtonIcon is null ||
RestartButtonTextBlock is null ||
- CloseWindowButton is null ||
- CloseWindowButtonIcon is null ||
DrawerTitleTextBlock is null ||
RootNavigationView is null)
{
@@ -606,7 +621,7 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
var height = Bounds.Height > 1 ? Bounds.Height : Math.Max(Height, MinHeight);
var layoutScale = Math.Clamp(Math.Min(width / 1120d, height / 760d), 0.90, 1.18);
- var titleBarHeight = Math.Clamp(48d * layoutScale, 44d, 58d);
+ const double titleBarHeight = 48d;
var titleBarButtonWidth = Math.Clamp(40d * layoutScale, 36d, 48d);
var titleBarButtonHeight = Math.Clamp(32d * layoutScale, 30d, 38d);
var titleFontSize = Math.Clamp(12d * layoutScale, 11d, 14d);
@@ -618,7 +633,6 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
ExtendClientAreaTitleBarHeightHint = titleBarHeight;
WindowTitleBarHost.Height = titleBarHeight;
- WindowTitleBarHost.Padding = new Thickness(chromePadding, 0, chromePadding, 0);
TogglePaneButton.Width = titleBarButtonWidth;
TogglePaneButton.Height = titleBarButtonHeight;
@@ -636,10 +650,6 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
RestartButtonIcon.FontSize = titleBarIconSize;
RestartButtonTextBlock.FontSize = titleFontSize;
- CloseWindowButton.Width = titleBarButtonWidth;
- CloseWindowButton.Height = titleBarButtonHeight;
- CloseWindowButtonIcon.FontSize = titleBarIconSize;
-
DrawerTitleTextBlock.FontSize = drawerTitleFontSize;
}
diff --git a/_b.txt b/_b.txt
new file mode 100644
index 0000000..9ecc1c2
--- /dev/null
+++ b/_b.txt
@@ -0,0 +1,13 @@
+ 正在确定要还原的项目…
+ 已还原 D:\github\LanMountainDesktop\LanMountainDesktop.PluginIsolation.Contracts\LanMountainDesktop.PluginIsolation.Contracts.csproj (用时 265 毫秒)。
+ 已还原 D:\github\LanMountainDesktop\LanMountainDesktop.Shared.Contracts\LanMountainDesktop.Shared.Contracts.csproj (用时 597 毫秒)。
+ 已还原 D:\github\LanMountainDesktop\LanMountainDesktop.Shared.IPC\LanMountainDesktop.Shared.IPC.csproj (用时 264 毫秒)。
+C:\Program Files\dotnet\sdk\10.0.201\NuGet.targets(196,5): error : 磁盘空间不足。 [D:\github\LanMountainDesktop\LanMountainDesktop\LanMountainDesktop.csproj]
+
+生成失败。
+
+C:\Program Files\dotnet\sdk\10.0.201\NuGet.targets(196,5): error : 磁盘空间不足。 [D:\github\LanMountainDesktop\LanMountainDesktop\LanMountainDesktop.csproj]
+ 0 个警告
+ 1 个错误
+
+已用时间 00:00:07.94