diff --git a/LanMountainDesktop.Platform.Abstractions/IMainWindowDesktopLayerService.cs b/LanMountainDesktop.Platform.Abstractions/IMainWindowDesktopLayerService.cs
new file mode 100644
index 0000000..056743b
--- /dev/null
+++ b/LanMountainDesktop.Platform.Abstractions/IMainWindowDesktopLayerService.cs
@@ -0,0 +1,30 @@
+using Avalonia.Controls;
+
+namespace LanMountainDesktop.Platform.Abstractions;
+
+///
+/// 主窗口桌面层服务接口。将主窗口嵌入系统桌面图标层(仅 Windows 支持)。
+///
+public interface IMainWindowDesktopLayerService
+{
+ bool IsSupported { get; }
+ void EnableOrRefresh(Window window);
+ void Disable(Window window);
+}
+
+///
+/// 无操作实现。用于不支持桌面层嵌入的平台(Linux/macOS/移动端)。
+///
+public sealed class NullMainWindowDesktopLayerService : IMainWindowDesktopLayerService
+{
+ public bool IsSupported => false;
+
+ public void EnableOrRefresh(Window window)
+ {
+ PlatformLog.Info("MainWindowDesktopLayer", "Desktop layer requested on an unsupported platform.");
+ }
+
+ public void Disable(Window window)
+ {
+ }
+}
diff --git a/LanMountainDesktop.Platform.Abstractions/IPowerManagementService.cs b/LanMountainDesktop.Platform.Abstractions/IPowerManagementService.cs
new file mode 100644
index 0000000..dc93c50
--- /dev/null
+++ b/LanMountainDesktop.Platform.Abstractions/IPowerManagementService.cs
@@ -0,0 +1,48 @@
+namespace LanMountainDesktop.Platform.Abstractions;
+
+///
+/// 电源管理服务接口。桌面平台提供关机/重启/注销/锁定/睡眠能力;
+/// 移动平台不提供此能力(使用 )。
+///
+public interface IPowerManagementService
+{
+ bool IsShutdownSupported { get; }
+ bool IsRestartSupported { get; }
+ bool IsLogoutSupported { get; }
+ bool IsLockSupported { get; }
+ bool IsSleepSupported { get; }
+
+ Task ShutdownAsync();
+ Task RestartAsync();
+ Task LogoutAsync();
+ Task LockAsync();
+ Task SleepAsync();
+
+ void ShowNativePowerUI(PowerAction action);
+}
+
+public enum PowerAction
+{
+ Shutdown,
+ Restart
+}
+
+///
+/// 无操作实现。用于不支持电源管理的平台(如移动端)。
+///
+public sealed class NullPowerManagementService : IPowerManagementService
+{
+ public bool IsShutdownSupported => false;
+ public bool IsRestartSupported => false;
+ public bool IsLogoutSupported => false;
+ public bool IsLockSupported => false;
+ public bool IsSleepSupported => false;
+
+ public Task ShutdownAsync() => Task.CompletedTask;
+ public Task RestartAsync() => Task.CompletedTask;
+ public Task LogoutAsync() => Task.CompletedTask;
+ public Task LockAsync() => Task.CompletedTask;
+ public Task SleepAsync() => Task.CompletedTask;
+
+ public void ShowNativePowerUI(PowerAction action) { }
+}
diff --git a/LanMountainDesktop.Platform.Abstractions/IWindowPassthroughServices.cs b/LanMountainDesktop.Platform.Abstractions/IWindowPassthroughServices.cs
new file mode 100644
index 0000000..f81f92d
--- /dev/null
+++ b/LanMountainDesktop.Platform.Abstractions/IWindowPassthroughServices.cs
@@ -0,0 +1,68 @@
+using Avalonia;
+using Avalonia.Controls;
+
+namespace LanMountainDesktop.Platform.Abstractions;
+
+///
+/// 窗口置底服务接口。使组件窗口保持在桌面层(Z 序最底)。
+///
+public interface IWindowBottomMostService
+{
+ void SetupBottomMost(Window window);
+ void SendToBottom(Window window);
+ PixelPoint GetScreenPosition(Window window);
+ bool SetScreenPosition(Window window, PixelPoint position, bool queueOnFailure = false);
+ bool IsBottomMostSupported { get; }
+}
+
+///
+/// 窗口交互区域定义。区域外的点击穿透到桌面。
+///
+public readonly record struct WindowInteractiveRegion(
+ Rect Bounds,
+ double CornerRadius,
+ Matrix? ClientToRegionTransform = null,
+ Rect? ClientClipBounds = null,
+ double ClientClipCornerRadius = 0d);
+
+///
+/// 区域点击穿透服务接口。
+///
+public interface IRegionPassthroughService
+{
+ void SetInteractiveRegions(Window window, IReadOnlyList interactiveRegions);
+ void ClearInteractiveRegions(Window window);
+ bool IsRegionPassthroughSupported { get; }
+}
+
+///
+/// 无操作实现:非 Windows 平台窗口置底不可用。
+///
+public sealed class NullWindowBottomMostService : IWindowBottomMostService
+{
+ public bool IsBottomMostSupported => false;
+
+ public void SetupBottomMost(Window window) { }
+
+ public void SendToBottom(Window window) { }
+
+ public PixelPoint GetScreenPosition(Window window) => window.Position;
+
+ public bool SetScreenPosition(Window window, PixelPoint position, bool queueOnFailure = false)
+ {
+ window.Position = position;
+ return true;
+ }
+}
+
+///
+/// 无操作实现:非 Windows 平台区域穿透不可用。
+///
+public sealed class NullRegionPassthroughService : IRegionPassthroughService
+{
+ public bool IsRegionPassthroughSupported => false;
+
+ public void SetInteractiveRegions(Window window, IReadOnlyList interactiveRegions) { }
+
+ public void ClearInteractiveRegions(Window window) { }
+}
diff --git a/LanMountainDesktop.Platform.Abstractions/LanMountainDesktop.Platform.Abstractions.csproj b/LanMountainDesktop.Platform.Abstractions/LanMountainDesktop.Platform.Abstractions.csproj
new file mode 100644
index 0000000..0a8d1e2
--- /dev/null
+++ b/LanMountainDesktop.Platform.Abstractions/LanMountainDesktop.Platform.Abstractions.csproj
@@ -0,0 +1,11 @@
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
diff --git a/LanMountainDesktop.Platform.Abstractions/PlatformLog.cs b/LanMountainDesktop.Platform.Abstractions/PlatformLog.cs
new file mode 100644
index 0000000..3c612e8
--- /dev/null
+++ b/LanMountainDesktop.Platform.Abstractions/PlatformLog.cs
@@ -0,0 +1,35 @@
+namespace LanMountainDesktop.Platform.Abstractions;
+
+///
+/// 平台层日志桥。平台实现项目不引用宿主,
+/// 宿主在启动时通过 接入自己的日志系统(AppLogger)。
+/// 未接入时日志静默丢弃。
+///
+public static class PlatformLog
+{
+ private static IPlatformLogSink? _sink;
+
+ public static void SetSink(IPlatformLogSink sink)
+ {
+ ArgumentNullException.ThrowIfNull(sink);
+ _sink = sink;
+ }
+
+ public static void Info(string category, string message) => _sink?.Info(category, message);
+
+ public static void Warn(string category, string message, Exception? exception = null) =>
+ _sink?.Warn(category, message, exception);
+
+ public static void Error(string category, string message, Exception? exception = null) =>
+ _sink?.Error(category, message, exception);
+}
+
+///
+/// 平台层日志输出目标。
+///
+public interface IPlatformLogSink
+{
+ void Info(string category, string message);
+ void Warn(string category, string message, Exception? exception = null);
+ void Error(string category, string message, Exception? exception = null);
+}
diff --git a/LanMountainDesktop.Platform.Android/LanMountainDesktop.Platform.Android.csproj b/LanMountainDesktop.Platform.Android/LanMountainDesktop.Platform.Android.csproj
new file mode 100644
index 0000000..04040b9
--- /dev/null
+++ b/LanMountainDesktop.Platform.Android/LanMountainDesktop.Platform.Android.csproj
@@ -0,0 +1,17 @@
+
+
+ net10.0-android
+ enable
+ enable
+ 24
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LanMountainDesktop.Platform.MacOS/LanMountainDesktop.Platform.MacOS.csproj b/LanMountainDesktop.Platform.MacOS/LanMountainDesktop.Platform.MacOS.csproj
new file mode 100644
index 0000000..48063b7
--- /dev/null
+++ b/LanMountainDesktop.Platform.MacOS/LanMountainDesktop.Platform.MacOS.csproj
@@ -0,0 +1,15 @@
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
diff --git a/LanMountainDesktop/Services/MacIconService.cs b/LanMountainDesktop.Platform.MacOS/MacIconService.cs
similarity index 98%
rename from LanMountainDesktop/Services/MacIconService.cs
rename to LanMountainDesktop.Platform.MacOS/MacIconService.cs
index 991d03f..5727b60 100644
--- a/LanMountainDesktop/Services/MacIconService.cs
+++ b/LanMountainDesktop.Platform.MacOS/MacIconService.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
@@ -6,10 +6,10 @@ using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
-namespace LanMountainDesktop.Services;
+namespace LanMountainDesktop.Platform.MacOS;
[SupportedOSPlatform("macos")]
-internal static class MacIconService
+public static class MacIconService
{
private const int IconSize = 256;
diff --git a/LanMountainDesktop.Platform.Windows/LanMountainDesktop.Platform.Windows.csproj b/LanMountainDesktop.Platform.Windows/LanMountainDesktop.Platform.Windows.csproj
new file mode 100644
index 0000000..522c0c8
--- /dev/null
+++ b/LanMountainDesktop.Platform.Windows/LanMountainDesktop.Platform.Windows.csproj
@@ -0,0 +1,17 @@
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LanMountainDesktop/Services/UwpManifestIconResolver.cs b/LanMountainDesktop.Platform.Windows/UwpManifestIconResolver.cs
similarity index 99%
rename from LanMountainDesktop/Services/UwpManifestIconResolver.cs
rename to LanMountainDesktop.Platform.Windows/UwpManifestIconResolver.cs
index 7537449..ce2cb7d 100644
--- a/LanMountainDesktop/Services/UwpManifestIconResolver.cs
+++ b/LanMountainDesktop.Platform.Windows/UwpManifestIconResolver.cs
@@ -10,10 +10,10 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
-namespace LanMountainDesktop.Services;
+namespace LanMountainDesktop.Platform.Windows;
[SupportedOSPlatform("windows")]
-internal static class UwpManifestIconResolver
+public static class UwpManifestIconResolver
{
private const int ErrorSuccess = 0;
private const int ErrorInsufficientBuffer = 122;
diff --git a/LanMountainDesktop.Platform.Windows/WindowsDwmInterop.cs b/LanMountainDesktop.Platform.Windows/WindowsDwmInterop.cs
new file mode 100644
index 0000000..399b63f
--- /dev/null
+++ b/LanMountainDesktop.Platform.Windows/WindowsDwmInterop.cs
@@ -0,0 +1,45 @@
+using System.Runtime.InteropServices;
+
+namespace LanMountainDesktop.Platform.Windows;
+
+///
+/// DWM(桌面窗口管理器)互操作封装。
+///
+public static class WindowsDwmInterop
+{
+ public const int WindowAttributeBorderColor = 34;
+ public const uint ColorNone = 0xFFFFFFFE;
+
+ ///
+ /// 移除窗口原生边框颜色(Windows 11 22000+)。
+ /// 失败静默忽略(DWM 属性为尽力而为语义)。
+ ///
+ public static void TryDisableWindowBorder(IntPtr windowHandle)
+ {
+ if (windowHandle == IntPtr.Zero || !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000))
+ {
+ return;
+ }
+
+ try
+ {
+ var borderColor = ColorNone;
+ _ = DwmSetWindowAttribute(
+ windowHandle,
+ WindowAttributeBorderColor,
+ ref borderColor,
+ sizeof(uint));
+ }
+ catch
+ {
+ // DWM attributes are best-effort and unavailable on older/unsupported Windows builds.
+ }
+ }
+
+ [DllImport("dwmapi.dll")]
+ private static extern int DwmSetWindowAttribute(
+ IntPtr windowHandle,
+ int attribute,
+ ref uint attributeValue,
+ int attributeSize);
+}
diff --git a/LanMountainDesktop/Services/WindowsIconService.cs b/LanMountainDesktop.Platform.Windows/WindowsIconService.cs
similarity index 99%
rename from LanMountainDesktop/Services/WindowsIconService.cs
rename to LanMountainDesktop.Platform.Windows/WindowsIconService.cs
index 888faf9..7d08806 100644
--- a/LanMountainDesktop/Services/WindowsIconService.cs
+++ b/LanMountainDesktop.Platform.Windows/WindowsIconService.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
@@ -9,10 +9,10 @@ using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
-namespace LanMountainDesktop.Services;
+namespace LanMountainDesktop.Platform.Windows;
[SupportedOSPlatform("windows")]
-internal static class WindowsIconService
+public static class WindowsIconService
{
private const int HighResolutionIconSize = 256;
private const int MaxShellPath = 1024;
diff --git a/LanMountainDesktop.Platform.Windows/WindowsMainWindowDesktopLayerService.cs b/LanMountainDesktop.Platform.Windows/WindowsMainWindowDesktopLayerService.cs
new file mode 100644
index 0000000..57b3901
--- /dev/null
+++ b/LanMountainDesktop.Platform.Windows/WindowsMainWindowDesktopLayerService.cs
@@ -0,0 +1,238 @@
+using System.Runtime.InteropServices;
+using Avalonia.Controls;
+using LanMountainDesktop.Platform.Abstractions;
+
+namespace LanMountainDesktop.Platform.Windows;
+
+///
+/// Windows 桌面层实现:将窗口设为桌面图标宿主(SHELLDLL_DefView)的子窗口。
+/// 自 LanMountainDesktop.Services.MainWindowDesktopLayerService 迁移,行为不变。
+///
+public sealed class WindowsMainWindowDesktopLayerService : IMainWindowDesktopLayerService
+{
+ private const int GWL_STYLE = -16;
+ private const int GWL_EXSTYLE = -20;
+
+ private const long WS_CHILD = 0x40000000L;
+ private const long WS_POPUP = 0x80000000L;
+ private const long WS_CAPTION = 0x00C00000L;
+ private const long WS_THICKFRAME = 0x00040000L;
+ private const long WS_MINIMIZEBOX = 0x00020000L;
+ private const long WS_MAXIMIZEBOX = 0x00010000L;
+ private const long WS_SYSMENU = 0x00080000L;
+
+ private const uint SWP_NOSIZE = 0x0001;
+ private const uint SWP_NOMOVE = 0x0002;
+ private const uint SWP_NOACTIVATE = 0x0010;
+ private const uint SWP_SHOWWINDOW = 0x0040;
+ private const uint SWP_FRAMECHANGED = 0x0020;
+
+ private static readonly IntPtr HWND_TOP = IntPtr.Zero;
+ private static readonly IntPtr HWND_BOTTOM = new(1);
+
+ private readonly object _gate = new();
+ private readonly Dictionary _restoreStates = [];
+
+ public bool IsSupported => true;
+
+ public void EnableOrRefresh(Window window)
+ {
+ ArgumentNullException.ThrowIfNull(window);
+
+ var handle = GetWindowHandle(window);
+ if (handle == IntPtr.Zero)
+ {
+ window.Opened -= OnDeferredOpened;
+ window.Opened += OnDeferredOpened;
+ return;
+ }
+
+ EnableOrRefresh(handle);
+ }
+
+ public void Disable(Window window)
+ {
+ ArgumentNullException.ThrowIfNull(window);
+ window.Opened -= OnDeferredOpened;
+
+ var handle = GetWindowHandle(window);
+ if (handle == IntPtr.Zero)
+ {
+ return;
+ }
+
+ WindowRestoreState? restoreState;
+ lock (_gate)
+ {
+ if (!_restoreStates.Remove(handle, out restoreState))
+ {
+ return;
+ }
+ }
+
+ try
+ {
+ _ = SetParent(handle, restoreState.Parent);
+ SetWindowLongPtr(handle, GWL_STYLE, restoreState.Style);
+ SetWindowLongPtr(handle, GWL_EXSTYLE, restoreState.ExStyle);
+ _ = SetWindowPos(
+ handle,
+ HWND_TOP,
+ 0,
+ 0,
+ 0,
+ 0,
+ SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW);
+ PlatformLog.Info("MainWindowDesktopLayer", $"Disabled desktop layer. Window={handle}.");
+ }
+ catch (Exception ex)
+ {
+ PlatformLog.Warn("MainWindowDesktopLayer", $"Failed to disable desktop layer. Window={handle}.", ex);
+ }
+ }
+
+ private void OnDeferredOpened(object? sender, EventArgs e)
+ {
+ if (sender is not Window window)
+ {
+ return;
+ }
+
+ window.Opened -= OnDeferredOpened;
+ EnableOrRefresh(window);
+ }
+
+ private void EnableOrRefresh(IntPtr handle)
+ {
+ if (handle == IntPtr.Zero || !IsWindow(handle))
+ {
+ return;
+ }
+
+ SaveRestoreStateIfNeeded(handle);
+ var desktopHost = ResolveDesktopIconHost();
+ if (desktopHost != IntPtr.Zero && IsWindow(desktopHost))
+ {
+ ApplyDesktopChildStyle(handle);
+ if (GetParent(handle) != desktopHost)
+ {
+ _ = SetParent(handle, desktopHost);
+ }
+
+ _ = SetWindowPos(
+ handle,
+ HWND_TOP,
+ 0,
+ 0,
+ 0,
+ 0,
+ SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW);
+ PlatformLog.Info("MainWindowDesktopLayer", $"Enabled desktop layer. Window={handle}; Host={desktopHost}.");
+ return;
+ }
+
+ _ = SetWindowPos(handle, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
+ PlatformLog.Warn("MainWindowDesktopLayer", $"Desktop icon host not found. Falling back to HWND_BOTTOM. Window={handle}.");
+ }
+
+ private void SaveRestoreStateIfNeeded(IntPtr handle)
+ {
+ lock (_gate)
+ {
+ if (_restoreStates.ContainsKey(handle))
+ {
+ return;
+ }
+
+ _restoreStates[handle] = new WindowRestoreState(
+ GetParent(handle),
+ GetWindowLongPtr(handle, GWL_STYLE),
+ GetWindowLongPtr(handle, GWL_EXSTYLE));
+ }
+ }
+
+ private static void ApplyDesktopChildStyle(IntPtr handle)
+ {
+ var style = GetWindowLongPtr(handle, GWL_STYLE).ToInt64();
+ style |= WS_CHILD;
+ style &= ~(WS_POPUP | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU);
+ SetWindowLongPtr(handle, GWL_STYLE, new IntPtr(style));
+ }
+
+ private static IntPtr ResolveDesktopIconHost()
+ {
+ var topLevelWindows = new List();
+ EnumWindows((handle, _) =>
+ {
+ topLevelWindows.Add(handle);
+ return true;
+ }, IntPtr.Zero);
+
+ foreach (var topLevelWindow in topLevelWindows)
+ {
+ var worker = FindWindowEx(topLevelWindow, IntPtr.Zero, "WorkerW", null);
+ if (worker == IntPtr.Zero)
+ {
+ continue;
+ }
+
+ var defView = FindWindowEx(worker, IntPtr.Zero, "SHELLDLL_DefView", null);
+ if (defView != IntPtr.Zero)
+ {
+ return defView;
+ }
+ }
+
+ foreach (var topLevelWindow in topLevelWindows)
+ {
+ var defView = FindWindowEx(topLevelWindow, IntPtr.Zero, "SHELLDLL_DefView", null);
+ if (defView != IntPtr.Zero)
+ {
+ return defView;
+ }
+ }
+
+ return IntPtr.Zero;
+ }
+
+ private static IntPtr GetWindowHandle(Window window)
+ {
+ try
+ {
+ return window.TryGetPlatformHandle()?.Handle ?? IntPtr.Zero;
+ }
+ catch
+ {
+ return IntPtr.Zero;
+ }
+ }
+
+ private sealed record WindowRestoreState(IntPtr Parent, IntPtr Style, IntPtr ExStyle);
+
+ private delegate bool EnumWindowsProc(IntPtr handle, IntPtr lParam);
+
+ [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
+ private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
+
+ [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
+ private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
+
+ [DllImport("user32.dll")]
+ private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint flags);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
+
+ [DllImport("user32.dll")]
+ private static extern IntPtr GetParent(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ private static extern bool IsWindow(IntPtr hWnd);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ private static extern IntPtr FindWindowEx(IntPtr hParent, IntPtr hChildAfter, string? lpszClass, string? lpszWindow);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
+}
diff --git a/LanMountainDesktop.Platform.Windows/WindowsNativeDialogs.cs b/LanMountainDesktop.Platform.Windows/WindowsNativeDialogs.cs
new file mode 100644
index 0000000..549ea51
--- /dev/null
+++ b/LanMountainDesktop.Platform.Windows/WindowsNativeDialogs.cs
@@ -0,0 +1,30 @@
+using System.Runtime.InteropServices;
+
+namespace LanMountainDesktop.Platform.Windows;
+
+///
+/// Windows 原生消息框(MessageBoxW)封装。
+/// 供宿主在 UI 框架尚不可用(启动早期)时显示诊断信息。
+///
+public static class WindowsNativeDialogs
+{
+ public const uint Ok = 0x00000000;
+ public const uint IconInformation = 0x00000040;
+ public const uint IconWarning = 0x00000030;
+
+ ///
+ /// 显示原生消息框。仅在 Windows 上有效,其他平台为空操作。
+ ///
+ public static void Show(string caption, string message, uint type)
+ {
+ if (!OperatingSystem.IsWindows())
+ {
+ return;
+ }
+
+ _ = MessageBoxW(IntPtr.Zero, message, caption, type);
+ }
+
+ [DllImport("user32.dll", EntryPoint = "MessageBoxW", CharSet = CharSet.Unicode, SetLastError = true)]
+ private static extern int MessageBoxW(IntPtr hWnd, string text, string caption, uint type);
+}
diff --git a/LanMountainDesktop.Platform.Windows/WindowsPackageIdentity.cs b/LanMountainDesktop.Platform.Windows/WindowsPackageIdentity.cs
new file mode 100644
index 0000000..55fd69d
--- /dev/null
+++ b/LanMountainDesktop.Platform.Windows/WindowsPackageIdentity.cs
@@ -0,0 +1,43 @@
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace LanMountainDesktop.Platform.Windows;
+
+///
+/// Windows 包标识查询(MSIX/UWP 打包检测)。
+///
+public static class WindowsPackageIdentity
+{
+ private const int AppmodelErrorNoPackage = 15700;
+
+ ///
+ /// 检测当前进程是否具有包标识(以 MSIX 打包运行)。
+ /// 非 Windows 平台返回 false。
+ ///
+ public static bool HasPackageIdentity()
+ {
+ if (!OperatingSystem.IsWindows())
+ {
+ return false;
+ }
+
+ var length = 0;
+ var hr = GetCurrentPackageFullName(ref length, null);
+ if (hr == AppmodelErrorNoPackage)
+ {
+ return false;
+ }
+
+ if (length <= 0)
+ {
+ return hr == 0;
+ }
+
+ var builder = new StringBuilder(length);
+ hr = GetCurrentPackageFullName(ref length, builder);
+ return hr == 0;
+ }
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ private static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder? packageFullName);
+}
diff --git a/LanMountainDesktop.Platform.Windows/WindowsPowerManagementService.cs b/LanMountainDesktop.Platform.Windows/WindowsPowerManagementService.cs
new file mode 100644
index 0000000..7468f26
--- /dev/null
+++ b/LanMountainDesktop.Platform.Windows/WindowsPowerManagementService.cs
@@ -0,0 +1,106 @@
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using LanMountainDesktop.Platform.Abstractions;
+
+namespace LanMountainDesktop.Platform.Windows;
+
+///
+/// Windows 电源管理实现(P/Invoke user32/powrprof)。
+/// 自 LanMountainDesktop.Services.PowerManagementService 迁移,行为不变。
+///
+public sealed class WindowsPowerManagementService : IPowerManagementService
+{
+ public bool IsShutdownSupported => true;
+ public bool IsRestartSupported => true;
+ public bool IsLogoutSupported => true;
+ public bool IsLockSupported => true;
+ public bool IsSleepSupported => true;
+
+ public async Task ShutdownAsync()
+ {
+ await Task.Run(() =>
+ {
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = "shutdown",
+ Arguments = "/s /t 0",
+ UseShellExecute = true,
+ WindowStyle = ProcessWindowStyle.Hidden
+ });
+ });
+ }
+
+ public async Task RestartAsync()
+ {
+ await Task.Run(() =>
+ {
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = "shutdown",
+ Arguments = "/r /t 0",
+ UseShellExecute = true,
+ WindowStyle = ProcessWindowStyle.Hidden
+ });
+ });
+ }
+
+ public async Task LogoutAsync()
+ {
+ await Task.Run(() =>
+ {
+ ExitWindowsEx(0, 0);
+ });
+ }
+
+ public async Task LockAsync()
+ {
+ await Task.Run(() =>
+ {
+ LockWorkStation();
+ });
+ }
+
+ public async Task SleepAsync()
+ {
+ await Task.Run(() =>
+ {
+ SetSuspendState(false, false, false);
+ });
+ }
+
+ public void ShowNativePowerUI(PowerAction action)
+ {
+ // SlideToShutDown.exe 只支持关机,不支持重启
+ // 重启操作应该通过 RestartAsync() 使用 shutdown /r 命令
+ if (action != PowerAction.Shutdown)
+ return;
+
+ var slideToShutDownPath = Environment.ExpandEnvironmentVariables(@"%windir%\System32\SlideToShutDown.exe");
+ if (File.Exists(slideToShutDownPath))
+ {
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = slideToShutDownPath,
+ UseShellExecute = true
+ });
+ return;
+ }
+
+ // 回退到标准关机命令
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = "shutdown",
+ Arguments = "/s /t 5 /c \"LanMountainDesktop: Shutting down...\"",
+ UseShellExecute = true
+ });
+ }
+
+ [DllImport("user32.dll", SetLastError = true)]
+ private static extern bool ExitWindowsEx(uint uFlags, uint dwReason);
+
+ [DllImport("user32.dll")]
+ private static extern void LockWorkStation();
+
+ [DllImport("powrprof.dll", SetLastError = true)]
+ private static extern bool SetSuspendState(bool hibernate, bool forceCritical, bool disableWakeEvent);
+}
diff --git a/LanMountainDesktop.Platform.Windows/WindowsWindowPassthroughServices.cs b/LanMountainDesktop.Platform.Windows/WindowsWindowPassthroughServices.cs
new file mode 100644
index 0000000..ff8e362
--- /dev/null
+++ b/LanMountainDesktop.Platform.Windows/WindowsWindowPassthroughServices.cs
@@ -0,0 +1,1324 @@
+using System.Runtime.InteropServices;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Threading;
+using LanMountainDesktop.Platform.Abstractions;
+
+namespace LanMountainDesktop.Platform.Windows;
+public sealed class WindowsWindowBottomMostService : IWindowBottomMostService
+{
+ private const int GWL_STYLE = -16;
+ private const int GWL_EXSTYLE = -20;
+ private const int GWLP_HWNDPARENT = -8;
+
+ private const uint WS_CHILD = 0x40000000U;
+ private const uint WS_POPUP = 0x80000000U;
+ private const uint WS_CAPTION = 0x00C00000U;
+ private const uint WS_THICKFRAME = 0x00040000U;
+ private const uint WS_MINIMIZEBOX = 0x00020000U;
+ private const uint WS_MAXIMIZEBOX = 0x00010000U;
+ private const uint WS_SYSMENU = 0x00080000U;
+
+ private const uint WS_EX_TOOLWINDOW = 0x00000080U;
+ private const uint WS_EX_APPWINDOW = 0x00040000U;
+ private const uint WS_EX_NOACTIVATE = 0x08000000U;
+ private const uint WS_EX_NOREDIRECTIONBITMAP = 0x00200000U;
+ private const uint AVALONIA_COMPOSITION_EXSTYLE_MASK = WS_EX_NOREDIRECTIONBITMAP;
+
+ private const uint SWP_NOSIZE = 0x0001;
+ private const uint SWP_NOMOVE = 0x0002;
+ private const uint SWP_NOZORDER = 0x0004;
+ private const uint SWP_NOACTIVATE = 0x0010;
+ private const uint SWP_FRAMECHANGED = 0x0020;
+ private const uint SWP_SHOWWINDOW = 0x0040;
+ private const uint SWP_HIDEWINDOW = 0x0080;
+
+ private const uint WM_NCHITTEST = 0x0084;
+ private const int HTTRANSPARENT = -1;
+ private const int HTCLIENT = 1;
+
+ private const int DWMWA_WINDOW_CORNER_PREFERENCE = 33;
+ private const int DWMWA_BORDER_COLOR = 34;
+ private const uint DWMWCP_DONOTROUND = 1;
+ private const uint DWMWA_COLOR_NONE = 0xFFFFFFFEU;
+
+ private static readonly IntPtr HWND_TOP = IntPtr.Zero;
+ private static readonly IntPtr HWND_BOTTOM = new(1);
+ private static readonly object StaticLock = new();
+ private static readonly object TimerLock = new();
+
+ private static readonly Dictionary WindowStates = new();
+
+ private static System.Timers.Timer? _desktopHostMonitorTimer;
+ private static IntPtr _lastResolvedDesktopHost;
+ private static int _monitorDispatchPending;
+
+ public bool IsBottomMostSupported => true;
+
+ public void SetupBottomMost(Window window)
+ {
+ ArgumentNullException.ThrowIfNull(window);
+ if (!OperatingSystem.IsWindows())
+ {
+ return;
+ }
+
+ DesktopWindowState state;
+ lock (StaticLock)
+ {
+ if (WindowStates.TryGetValue(window, out state!))
+ {
+ return;
+ }
+
+ state = new DesktopWindowState(window);
+ WindowStates[window] = state;
+ }
+
+ Win32Properties.SetWindowCornerPreference(window, Win32Properties.WindowCornerPreference.DoNotRound);
+ Win32Properties.AddWindowStylesCallback(window, state.WindowStylesCallback);
+ Win32Properties.AddWndProcHookCallback(window, state.WndProcHookCallback);
+
+ window.Closed += OnWindowClosed;
+
+ var handle = GetWindowHandle(window);
+ if (handle == IntPtr.Zero)
+ {
+ window.Opened += OnWindowOpened;
+ return;
+ }
+
+ RunOnUiThread(() => InitializeAndAttach(state, handle, logSuccess: true));
+ }
+
+ public void SendToBottom(Window window)
+ {
+ ArgumentNullException.ThrowIfNull(window);
+ if (!TryGetWindowState(window, out var state))
+ {
+ SetupBottomMost(window);
+ return;
+ }
+
+ RunOnUiThread(() =>
+ {
+ var handle = GetWindowHandle(window);
+ if (handle == IntPtr.Zero || !IsWindow(handle))
+ {
+ return;
+ }
+
+ RegisterHandle(state, handle);
+ if (state.NeedsNativeRepair &&
+ !ShouldAttemptNativeRepair(
+ false,
+ DateTime.UtcNow,
+ state.NextNativeRepairAttemptUtc))
+ {
+ return;
+ }
+
+ var desktopHost = ResolveDesktopIconHost();
+ if (!state.NeedsNativeRepair &&
+ state.IsDesktopAttached &&
+ state.HasStableDesktopAttachment &&
+ desktopHost != IntPtr.Zero &&
+ state.DesktopHost == desktopHost &&
+ GetParent(handle) == desktopHost &&
+ state.OriginalState is { } originalState &&
+ HasExpectedDesktopRoleStyles(handle, originalState))
+ {
+ _ = SetWindowPos(
+ handle,
+ HWND_TOP,
+ 0,
+ 0,
+ 0,
+ 0,
+ SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
+ return;
+ }
+
+ ApplyDesktopAttachment(
+ state,
+ desktopHost,
+ logSuccess: false,
+ "explicit refresh",
+ allowRetryFailedHost: true);
+ });
+ }
+
+ public PixelPoint GetScreenPosition(Window window)
+ {
+ ArgumentNullException.ThrowIfNull(window);
+ var handle = GetWindowHandle(window);
+ return handle != IntPtr.Zero && GetWindowRect(handle, out var rect)
+ ? new PixelPoint(rect.Left, rect.Top)
+ : window.Position;
+ }
+
+ public bool SetScreenPosition(
+ Window window,
+ PixelPoint position,
+ bool queueOnFailure = false)
+ {
+ ArgumentNullException.ThrowIfNull(window);
+ TryGetWindowState(window, out var state);
+ var handle = GetWindowHandle(window);
+ if (handle == IntPtr.Zero || !IsWindow(handle))
+ {
+ window.Position = position;
+ if (state is not null)
+ {
+ state.PendingScreenPosition = null;
+ state.HasLoggedPositionFailure = false;
+ }
+
+ return true;
+ }
+
+ var nativePosition = new POINT(position.X, position.Y);
+ var style = ReadWindowStyle(handle, GWL_STYLE);
+ var nativeParent = GetParent(handle);
+ if (state is not null)
+ {
+ if (state.NeedsNativeRepair)
+ {
+ if (queueOnFailure)
+ {
+ state.PendingScreenPosition = position;
+ }
+
+ return false;
+ }
+
+ if (state.IsDesktopAttached)
+ {
+ if (state.OriginalState is not { } originalState ||
+ nativeParent != state.DesktopHost ||
+ !HasExpectedDesktopRoleStyles(handle, originalState))
+ {
+ LogPositionFailureOnce(
+ state,
+ $"Refusing to move a desktop window with invalid native attachment state. " +
+ $"Window={handle}; Parent={nativeParent}; ExpectedHost={state.DesktopHost}.");
+ if (queueOnFailure)
+ {
+ state.PendingScreenPosition = position;
+ }
+
+ return false;
+ }
+
+ nativeParent = state.DesktopHost;
+ }
+ }
+
+ if (OriginalWindowUsesParentClientCoordinates(style) &&
+ (nativeParent == IntPtr.Zero || !ScreenToClient(nativeParent, ref nativePosition)))
+ {
+ if (state is not null)
+ {
+ LogPositionFailureOnce(
+ state,
+ $"Could not translate screen position to child-window coordinates. " +
+ $"Window={handle}; Parent={nativeParent}.");
+ if (queueOnFailure)
+ {
+ state.PendingScreenPosition = position;
+ }
+ }
+ return false;
+ }
+
+ if (!SetWindowPos(
+ handle,
+ IntPtr.Zero,
+ nativePosition.X,
+ nativePosition.Y,
+ 0,
+ 0,
+ SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE))
+ {
+ if (state is not null)
+ {
+ LogPositionFailureOnce(
+ state,
+ $"Could not set screen position. Window={handle}; Position={position}; " +
+ $"Error={Marshal.GetLastWin32Error()}.");
+ if (queueOnFailure)
+ {
+ state.PendingScreenPosition = position;
+ }
+ }
+ return false;
+ }
+
+ if (state is not null)
+ {
+ state.PendingScreenPosition = null;
+ state.HasLoggedPositionFailure = false;
+ }
+
+ return true;
+ }
+
+ private static void LogPositionFailureOnce(DesktopWindowState state, string message)
+ {
+ if (state.HasLoggedPositionFailure)
+ {
+ return;
+ }
+
+ PlatformLog.Warn("WindowBottomMost", message);
+ state.HasLoggedPositionFailure = true;
+ }
+
+ private static void TryApplyPendingScreenPosition(DesktopWindowState state)
+ {
+ if (state.NeedsNativeRepair || state.PendingScreenPosition is not { } pendingPosition)
+ {
+ return;
+ }
+
+ _ = new WindowsWindowBottomMostService().SetScreenPosition(
+ state.Window,
+ pendingPosition,
+ queueOnFailure: true);
+ }
+
+ internal static void SetInteractiveRegionsInternal(
+ Window window,
+ IReadOnlyList regions)
+ {
+ if (!TryGetWindowState(window, out var state))
+ {
+ return;
+ }
+
+ var snapshot = new WindowInteractiveRegion[regions.Count];
+ for (var i = 0; i < regions.Count; i++)
+ {
+ snapshot[i] = regions[i];
+ }
+
+ state.InteractiveRegions = snapshot;
+ }
+
+ internal static (uint Style, uint ExStyle) CreateDesktopChildStyles(uint style, uint exStyle)
+ {
+ style |= WS_CHILD;
+ style &= ~(WS_POPUP | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU);
+ return (style, ApplyDesktopRoleExtendedStyles(exStyle));
+ }
+
+ internal static bool IsPointInsideRegion(WindowInteractiveRegion region, Point point)
+ {
+ if (region.ClientClipBounds is { } clientClipBounds &&
+ !IsPointInsideRoundedBounds(clientClipBounds, region.ClientClipCornerRadius, point))
+ {
+ return false;
+ }
+
+ if (region.ClientToRegionTransform is { } clientToRegionTransform)
+ {
+ point = clientToRegionTransform.Transform(point);
+ }
+
+ return IsPointInsideRoundedBounds(region.Bounds, region.CornerRadius, point);
+ }
+
+ private static bool IsPointInsideRoundedBounds(Rect bounds, double cornerRadius, Point point)
+ {
+ if (bounds.Width <= 0 || bounds.Height <= 0 || !bounds.Contains(point))
+ {
+ return false;
+ }
+
+ var radius = Math.Clamp(cornerRadius, 0, Math.Min(bounds.Width, bounds.Height) / 2);
+ if (radius <= 0)
+ {
+ return true;
+ }
+
+ var localX = point.X - bounds.X;
+ var localY = point.Y - bounds.Y;
+ if (localX >= radius && localX <= bounds.Width - radius ||
+ localY >= radius && localY <= bounds.Height - radius)
+ {
+ return true;
+ }
+
+ var centerX = localX < radius ? radius : bounds.Width - radius;
+ var centerY = localY < radius ? radius : bounds.Height - radius;
+ var deltaX = localX - centerX;
+ var deltaY = localY - centerY;
+ return deltaX * deltaX + deltaY * deltaY <= radius * radius;
+ }
+
+ internal static bool OriginalWindowUsesParentClientCoordinates(uint style)
+ {
+ return (style & WS_CHILD) != 0;
+ }
+
+ internal static bool ShouldAttemptNativeRepair(
+ bool hostChanged,
+ DateTime utcNow,
+ DateTime nextRepairAttemptUtc)
+ {
+ return hostChanged || utcNow >= nextRepairAttemptUtc;
+ }
+
+ internal static bool ShouldAttemptDesktopAttachment(
+ bool hostChanged,
+ IntPtr attachedHost,
+ IntPtr currentHost,
+ bool parentMismatch)
+ {
+ return parentMismatch || (hostChanged && attachedHost != currentHost);
+ }
+
+ private static uint ApplyDesktopRoleExtendedStyles(uint exStyle)
+ {
+ // Preserve every compositor-managed bit from Avalonia. In particular, this method must
+ // never opt the window into a second, legacy alpha-composition path.
+ return (exStyle | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE) & ~WS_EX_APPWINDOW;
+ }
+
+ private static void OnWindowOpened(object? sender, EventArgs e)
+ {
+ if (sender is not Window window || !TryGetWindowState(window, out var state))
+ {
+ return;
+ }
+
+ window.Opened -= OnWindowOpened;
+ var handle = GetWindowHandle(window);
+ if (handle != IntPtr.Zero)
+ {
+ RunOnUiThread(() => InitializeAndAttach(state, handle, logSuccess: true));
+ }
+ }
+
+ private static void OnWindowClosed(object? sender, EventArgs e)
+ {
+ if (sender is Window window && TryGetWindowState(window, out var state))
+ {
+ CleanupWindow(state, restoreNativeState: true);
+ }
+ }
+
+ private static void InitializeAndAttach(DesktopWindowState state, IntPtr handle, bool logSuccess)
+ {
+ if (handle == IntPtr.Zero || !IsWindow(handle))
+ {
+ return;
+ }
+
+ RegisterHandle(state, handle);
+ ConfigureDwmAppearance(handle);
+ ApplyDesktopAttachment(state, ResolveDesktopIconHost(), logSuccess, "initial setup");
+ }
+
+ private static void RegisterHandle(DesktopWindowState state, IntPtr handle)
+ {
+ lock (StaticLock)
+ {
+ if (state.Handle != IntPtr.Zero && state.Handle != handle)
+ {
+ state.OriginalState = null;
+ state.DesktopHost = IntPtr.Zero;
+ state.IsDesktopAttached = false;
+ state.HasStableDesktopAttachment = false;
+ ResetNativeRepairState(state);
+ state.AttachToCurrentHostAfterRepair = false;
+ state.HasLoggedFallback = false;
+ state.HasLoggedPositionFailure = false;
+ }
+
+ state.Handle = handle;
+ state.OriginalState ??= new NativeWindowState(
+ GetParent(handle),
+ ReadWindowStyle(handle, GWL_STYLE),
+ ReadWindowStyle(handle, GWL_EXSTYLE));
+ }
+ }
+
+ private static void ApplyDesktopAttachment(
+ DesktopWindowState state,
+ IntPtr desktopHost,
+ bool logSuccess,
+ string reason,
+ bool allowRetryFailedHost = false)
+ {
+ var handle = state.Handle;
+ if (handle == IntPtr.Zero || !IsWindow(handle) || state.OriginalState is not { } originalState)
+ {
+ return;
+ }
+
+ var screenPosition = GetNativeScreenPosition(handle, state.Window.Position);
+ if (state.NeedsNativeRepair)
+ {
+ var failedHost = state.FailedDesktopHost;
+ var attachAfterRepair = state.AttachToCurrentHostAfterRepair;
+ FallBackToBottom(
+ state,
+ screenPosition,
+ "repairing an incomplete native rollback before attachment",
+ logSuccess: false,
+ failedHost);
+ if (state.NeedsNativeRepair)
+ {
+ StartDesktopHostMonitorTimer(desktopHost);
+ return;
+ }
+
+ state.AttachToCurrentHostAfterRepair = false;
+ if (!attachAfterRepair && desktopHost == failedHost && !allowRetryFailedHost)
+ {
+ // The failed host has not changed. Stay in the safe top-level fallback until
+ // Explorer changes or the caller explicitly requests another attachment.
+ StartDesktopHostMonitorTimer(desktopHost);
+ return;
+ }
+ }
+
+ var beforeStyle = ReadWindowStyle(handle, GWL_STYLE);
+ var beforeExStyle = ReadWindowStyle(handle, GWL_EXSTYLE);
+
+ if (desktopHost == IntPtr.Zero || !IsWindow(desktopHost))
+ {
+ FallBackToBottom(
+ state,
+ screenPosition,
+ "desktop icon host is unavailable",
+ logSuccess,
+ desktopHost);
+ StartDesktopHostMonitorTimer(desktopHost);
+ return;
+ }
+
+ if (state.IsDesktopAttached)
+ {
+ if (state.DesktopHost == desktopHost && GetParent(handle) == desktopHost)
+ {
+ if (HasExpectedDesktopRoleStyles(handle, originalState))
+ {
+ StartDesktopHostMonitorTimer(desktopHost);
+ return;
+ }
+
+ FallBackToBottom(
+ state,
+ screenPosition,
+ "desktop child style validation failed",
+ logSuccess,
+ desktopHost);
+ StartDesktopHostMonitorTimer(desktopHost);
+ return;
+ }
+
+ var hostBeingDetached = state.DesktopHost;
+ if (!TryRestoreNativeState(state, screenPosition, showWindow: true))
+ {
+ FallBackToBottom(
+ state,
+ screenPosition,
+ "failed to restore before remount",
+ logSuccess,
+ hostBeingDetached);
+ if (state.NeedsNativeRepair && desktopHost != hostBeingDetached)
+ {
+ state.AttachToCurrentHostAfterRepair = true;
+ }
+
+ if (state.NeedsNativeRepair ||
+ (desktopHost == hostBeingDetached && !allowRetryFailedHost))
+ {
+ StartDesktopHostMonitorTimer(desktopHost);
+ return;
+ }
+ }
+
+ beforeStyle = ReadWindowStyle(handle, GWL_STYLE);
+ beforeExStyle = ReadWindowStyle(handle, GWL_EXSTYLE);
+ }
+
+ beforeExStyle |= originalState.ExStyle & AVALONIA_COMPOSITION_EXSTYLE_MASK;
+ var (expectedStyle, expectedExStyle) = CreateDesktopChildStyles(beforeStyle, beforeExStyle);
+ WriteWindowStyle(handle, GWL_STYLE, expectedStyle);
+ WriteWindowStyle(handle, GWL_EXSTYLE, expectedExStyle);
+
+ _ = SetParent(handle, desktopHost);
+ var setParentError = Marshal.GetLastWin32Error();
+ if (GetParent(handle) != desktopHost)
+ {
+ FallBackToBottom(
+ state,
+ screenPosition,
+ $"SetParent failed with error {setParentError}",
+ logSuccess,
+ desktopHost);
+ StartDesktopHostMonitorTimer(desktopHost);
+ return;
+ }
+
+ state.DesktopHost = desktopHost;
+ state.IsDesktopAttached = true;
+ state.HasStableDesktopAttachment = false;
+
+ var childPosition = new POINT(screenPosition.X, screenPosition.Y);
+ if (!ScreenToClient(desktopHost, ref childPosition) ||
+ !SetWindowPos(
+ handle,
+ HWND_TOP,
+ childPosition.X,
+ childPosition.Y,
+ 0,
+ 0,
+ SWP_NOSIZE | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW) ||
+ !HasExpectedDesktopStyles(handle, expectedStyle, expectedExStyle) ||
+ GetParent(handle) != desktopHost)
+ {
+ var error = Marshal.GetLastWin32Error();
+ FallBackToBottom(
+ state,
+ screenPosition,
+ $"post-attachment validation failed with error {error}",
+ logSuccess,
+ desktopHost);
+ StartDesktopHostMonitorTimer(desktopHost);
+ return;
+ }
+
+ ResetNativeRepairState(state);
+ state.AttachToCurrentHostAfterRepair = false;
+ state.HasLoggedFallback = false;
+ state.HasStableDesktopAttachment = true;
+ ConfigureDwmAppearance(handle);
+ if (logSuccess)
+ {
+ var afterStyle = ReadWindowStyle(handle, GWL_STYLE);
+ var afterExStyle = ReadWindowStyle(handle, GWL_EXSTYLE);
+ PlatformLog.Info(
+ "WindowBottomMost",
+ $"Mounted window to desktop icon host. Window={handle}; Host={desktopHost}; Reason={reason}; " +
+ $"Style=0x{beforeStyle:X8}->0x{afterStyle:X8}; ExStyle=0x{beforeExStyle:X8}->0x{afterExStyle:X8}; " +
+ $"NoRedirectionBitmap={((afterExStyle & WS_EX_NOREDIRECTIONBITMAP) != 0)}.");
+ }
+
+ StartDesktopHostMonitorTimer(desktopHost);
+ }
+
+ private static void FallBackToBottom(
+ DesktopWindowState state,
+ PixelPoint screenPosition,
+ string reason,
+ bool logSuccess,
+ IntPtr failedDesktopHost)
+ {
+ var handle = state.Handle;
+ var wasAttached = state.IsDesktopAttached;
+ var wasStablyAttached = state.HasStableDesktopAttachment;
+ var restored = true;
+ if (state.OriginalState is { } originalState &&
+ (state.NeedsNativeRepair ||
+ wasAttached ||
+ GetParent(handle) != originalState.Parent ||
+ ReadWindowStyle(handle, GWL_STYLE) != originalState.Style ||
+ ReadWindowStyle(handle, GWL_EXSTYLE) != originalState.ExStyle))
+ {
+ restored = TryRestoreNativeState(state, screenPosition, showWindow: true);
+ }
+
+ if (!restored)
+ {
+ MarkNativeRepairPending(state, failedDesktopHost);
+ if (logSuccess || !state.HasLoggedRepairFailure)
+ {
+ PlatformLog.Warn(
+ "WindowBottomMost",
+ $"Native rollback is incomplete; keeping the window in repair state. " +
+ $"Window={handle}; FailedHost={failedDesktopHost}; Reason={reason}.");
+ state.HasLoggedRepairFailure = true;
+ }
+
+ return;
+ }
+
+ state.DesktopHost = IntPtr.Zero;
+ state.IsDesktopAttached = false;
+ state.HasStableDesktopAttachment = false;
+
+ if (IsWindow(handle) &&
+ !SetWindowPos(
+ handle,
+ HWND_BOTTOM,
+ 0,
+ 0,
+ 0,
+ 0,
+ SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW))
+ {
+ MarkNativeRepairPending(state, failedDesktopHost);
+ if (logSuccess || !state.HasLoggedRepairFailure)
+ {
+ PlatformLog.Warn(
+ "WindowBottomMost",
+ $"Native state was restored, but HWND_BOTTOM fallback positioning failed. " +
+ $"Window={handle}; FailedHost={failedDesktopHost}; Reason={reason}; " +
+ $"Error={Marshal.GetLastWin32Error()}.");
+ state.HasLoggedRepairFailure = true;
+ }
+
+ return;
+ }
+
+ ResetNativeRepairState(state);
+
+ if (logSuccess || wasStablyAttached || !state.HasLoggedFallback)
+ {
+ PlatformLog.Warn(
+ "WindowBottomMost",
+ $"Using HWND_BOTTOM fallback. Window={handle}; Reason={reason}; NativeStateRestored={restored}.");
+ state.HasLoggedFallback = true;
+ }
+ }
+
+ private static void MarkNativeRepairPending(DesktopWindowState state, IntPtr failedDesktopHost)
+ {
+ state.FailedDesktopHost = failedDesktopHost;
+ state.NativeRepairAttemptCount = Math.Min(state.NativeRepairAttemptCount + 1, 30);
+ var exponent = Math.Min(state.NativeRepairAttemptCount - 1, 5);
+ var delaySeconds = Math.Min(60, 2 * (1 << exponent));
+ state.NextNativeRepairAttemptUtc = DateTime.UtcNow.AddSeconds(delaySeconds);
+ state.NeedsNativeRepair = true;
+
+ if (state.Handle != IntPtr.Zero && IsWindow(state.Handle))
+ {
+ _ = SetWindowPos(
+ state.Handle,
+ IntPtr.Zero,
+ 0,
+ 0,
+ 0,
+ 0,
+ SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_HIDEWINDOW);
+ }
+ }
+
+ private static void ResetNativeRepairState(DesktopWindowState state)
+ {
+ state.NeedsNativeRepair = false;
+ state.FailedDesktopHost = IntPtr.Zero;
+ state.HasLoggedRepairFailure = false;
+ state.NativeRepairAttemptCount = 0;
+ state.NextNativeRepairAttemptUtc = DateTime.MinValue;
+ }
+
+ private static bool TryRestoreNativeState(
+ DesktopWindowState state,
+ PixelPoint screenPosition,
+ bool showWindow)
+ {
+ var handle = state.Handle;
+ if (handle == IntPtr.Zero || !IsWindow(handle) || state.OriginalState is not { } originalState)
+ {
+ return false;
+ }
+
+ var previousDesktopHost = state.DesktopHost;
+ var wasDesktopAttached = state.IsDesktopAttached;
+ var wasStablyDesktopAttached = state.HasStableDesktopAttachment;
+ state.IsDesktopAttached = false;
+ state.HasStableDesktopAttachment = false;
+ state.DesktopHost = IntPtr.Zero;
+
+ var restoreParentOrOwner = originalState.Parent != IntPtr.Zero && !IsWindow(originalState.Parent)
+ ? IntPtr.Zero
+ : originalState.Parent;
+ var restorePosition = new POINT(screenPosition.X, screenPosition.Y);
+
+ if (OriginalWindowUsesParentClientCoordinates(originalState.Style))
+ {
+ _ = SetParent(handle, restoreParentOrOwner);
+ WriteWindowStyle(handle, GWL_STYLE, originalState.Style);
+ WriteWindowStyle(handle, GWL_EXSTYLE, originalState.ExStyle);
+ if (restoreParentOrOwner != IntPtr.Zero &&
+ !ScreenToClient(restoreParentOrOwner, ref restorePosition))
+ {
+ restorePosition = new POINT(screenPosition.X, screenPosition.Y);
+ }
+ }
+ else
+ {
+ // GetParent returns an owner for a top-level popup. It is not a child-coordinate
+ // parent: first leave the desktop child hierarchy, restore the popup styles, then
+ // restore the owner through GWLP_HWNDPARENT and keep SetWindowPos in screen pixels.
+ _ = SetParent(handle, IntPtr.Zero);
+ WriteWindowStyle(handle, GWL_STYLE, originalState.Style);
+ WriteWindowStyle(handle, GWL_EXSTYLE, originalState.ExStyle);
+ _ = SetWindowLongPtr(handle, GWLP_HWNDPARENT, restoreParentOrOwner);
+ }
+
+ var flags = SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED;
+ if (showWindow)
+ {
+ flags |= SWP_SHOWWINDOW;
+ }
+
+ var positioned = SetWindowPos(
+ handle,
+ IntPtr.Zero,
+ restorePosition.X,
+ restorePosition.Y,
+ 0,
+ 0,
+ flags);
+
+ var hierarchyAndStylesRestored =
+ GetParent(handle) == restoreParentOrOwner &&
+ ReadWindowStyle(handle, GWL_STYLE) == originalState.Style &&
+ ReadWindowStyle(handle, GWL_EXSTYLE) == originalState.ExStyle;
+ if (!hierarchyAndStylesRestored)
+ {
+ // Preserve the logical attachment state until a later repair attempt succeeds.
+ // This prevents the monitor from treating a half-converted child HWND as a valid
+ // top-level fallback.
+ state.DesktopHost = previousDesktopHost;
+ state.IsDesktopAttached = wasDesktopAttached;
+ state.HasStableDesktopAttachment = wasStablyDesktopAttached;
+ }
+
+ return hierarchyAndStylesRestored && positioned;
+ }
+
+ private static bool HasExpectedDesktopStyles(IntPtr handle, uint style, uint exStyle)
+ {
+ return ReadWindowStyle(handle, GWL_STYLE) == style &&
+ ReadWindowStyle(handle, GWL_EXSTYLE) == exStyle;
+ }
+
+ private static bool HasExpectedDesktopRoleStyles(IntPtr handle, NativeWindowState originalState)
+ {
+ var style = ReadWindowStyle(handle, GWL_STYLE);
+ var exStyle = ReadWindowStyle(handle, GWL_EXSTYLE);
+ var expected = CreateDesktopChildStyles(style, exStyle);
+ var requiredAvaloniaBits = originalState.ExStyle & AVALONIA_COMPOSITION_EXSTYLE_MASK;
+ return style == expected.Style &&
+ exStyle == expected.ExStyle &&
+ (exStyle & requiredAvaloniaBits) == requiredAvaloniaBits;
+ }
+
+ private static void ConfigureDwmAppearance(IntPtr handle)
+ {
+ if (handle == IntPtr.Zero || !IsWindow(handle))
+ {
+ return;
+ }
+
+ try
+ {
+ var cornerPreference = DWMWCP_DONOTROUND;
+ _ = DwmSetWindowAttribute(
+ handle,
+ DWMWA_WINDOW_CORNER_PREFERENCE,
+ ref cornerPreference,
+ sizeof(uint));
+
+ var borderColor = DWMWA_COLOR_NONE;
+ _ = DwmSetWindowAttribute(handle, DWMWA_BORDER_COLOR, ref borderColor, sizeof(uint));
+ }
+ catch (Exception ex) when (ex is DllNotFoundException or EntryPointNotFoundException)
+ {
+ // These attributes are best-effort and unavailable on older Windows versions.
+ }
+ }
+
+ private static IntPtr ResolveDesktopIconHost()
+ {
+ var topLevelWindows = new List();
+ EnumWindows((handle, _) =>
+ {
+ topLevelWindows.Add(handle);
+ return true;
+ }, IntPtr.Zero);
+
+ foreach (var topLevelWindow in topLevelWindows)
+ {
+ var worker = FindWindowEx(topLevelWindow, IntPtr.Zero, "WorkerW", null);
+ if (worker == IntPtr.Zero)
+ {
+ continue;
+ }
+
+ var defView = FindWindowEx(worker, IntPtr.Zero, "SHELLDLL_DefView", null);
+ if (defView != IntPtr.Zero)
+ {
+ return defView;
+ }
+ }
+
+ foreach (var topLevelWindow in topLevelWindows)
+ {
+ var defView = FindWindowEx(topLevelWindow, IntPtr.Zero, "SHELLDLL_DefView", null);
+ if (defView != IntPtr.Zero)
+ {
+ return defView;
+ }
+ }
+
+ return IntPtr.Zero;
+ }
+
+ private static void StartDesktopHostMonitorTimer(IntPtr currentHost)
+ {
+ lock (TimerLock)
+ {
+ if (_desktopHostMonitorTimer != null)
+ {
+ return;
+ }
+
+ _lastResolvedDesktopHost = currentHost;
+ _desktopHostMonitorTimer = new System.Timers.Timer(TimeSpan.FromSeconds(2))
+ {
+ AutoReset = true
+ };
+ _desktopHostMonitorTimer.Elapsed += (_, _) => MonitorDesktopHostAttachments();
+ _desktopHostMonitorTimer.Start();
+ }
+ }
+
+ private static void MonitorDesktopHostAttachments()
+ {
+ var desktopHost = ResolveDesktopIconHost();
+ var hostChanged = false;
+ lock (TimerLock)
+ {
+ if (desktopHost != _lastResolvedDesktopHost)
+ {
+ _lastResolvedDesktopHost = desktopHost;
+ hostChanged = true;
+ }
+ }
+
+ List states;
+ lock (StaticLock)
+ {
+ states = [.. WindowStates.Values];
+ }
+
+ var requiresCleanupOrRepair = false;
+ var now = DateTime.UtcNow;
+ foreach (var state in states)
+ {
+ if ((state.NeedsNativeRepair && now >= state.NextNativeRepairAttemptUtc) ||
+ (!state.NeedsNativeRepair && state.PendingScreenPosition.HasValue) ||
+ state.Handle != IntPtr.Zero && !IsWindow(state.Handle) ||
+ state.IsDesktopAttached &&
+ (GetParent(state.Handle) != state.DesktopHost ||
+ state.OriginalState is not { } originalState ||
+ !HasExpectedDesktopRoleStyles(state.Handle, originalState)))
+ {
+ requiresCleanupOrRepair = true;
+ break;
+ }
+ }
+
+ if (!hostChanged && !requiresCleanupOrRepair ||
+ Interlocked.Exchange(ref _monitorDispatchPending, 1) != 0)
+ {
+ return;
+ }
+
+ Dispatcher.UIThread.Post(() =>
+ {
+ try
+ {
+ var currentHost = ResolveDesktopIconHost();
+ var effectiveHostChanged = hostChanged || currentHost != desktopHost;
+ List currentStates;
+ lock (StaticLock)
+ {
+ currentStates = [.. WindowStates.Values];
+ }
+
+ foreach (var state in currentStates)
+ {
+ if (state.Handle == IntPtr.Zero)
+ {
+ // A window can be registered before Avalonia creates its native handle.
+ continue;
+ }
+
+ if (!IsWindow(state.Handle))
+ {
+ CleanupWindow(state, restoreNativeState: false);
+ continue;
+ }
+
+ if (state.NeedsNativeRepair)
+ {
+ if (!ShouldAttemptNativeRepair(
+ effectiveHostChanged,
+ DateTime.UtcNow,
+ state.NextNativeRepairAttemptUtc))
+ {
+ continue;
+ }
+
+ var failedHost = state.FailedDesktopHost;
+ var attachAfterRepair = state.AttachToCurrentHostAfterRepair;
+ var screenPosition = GetNativeScreenPosition(state.Handle, state.Window.Position);
+ FallBackToBottom(
+ state,
+ screenPosition,
+ "retrying incomplete native rollback",
+ logSuccess: false,
+ failedHost);
+ if (state.NeedsNativeRepair)
+ {
+ continue;
+ }
+
+ state.AttachToCurrentHostAfterRepair = false;
+ if (currentHost != IntPtr.Zero &&
+ (attachAfterRepair ||
+ effectiveHostChanged && currentHost != failedHost))
+ {
+ ApplyDesktopAttachment(
+ state,
+ currentHost,
+ logSuccess: true,
+ "desktop host changed while native state was being repaired");
+ }
+
+ TryApplyPendingScreenPosition(state);
+ continue;
+ }
+
+ var attachmentDrift = state.IsDesktopAttached &&
+ (GetParent(state.Handle) != state.DesktopHost ||
+ state.OriginalState is not { } originalState ||
+ !HasExpectedDesktopRoleStyles(state.Handle, originalState));
+ if (ShouldAttemptDesktopAttachment(
+ effectiveHostChanged,
+ state.DesktopHost,
+ currentHost,
+ attachmentDrift))
+ {
+ ApplyDesktopAttachment(state, currentHost, logSuccess: true, "desktop host changed");
+ }
+
+ TryApplyPendingScreenPosition(state);
+ }
+
+ lock (TimerLock)
+ {
+ _lastResolvedDesktopHost = currentHost;
+ }
+ }
+ finally
+ {
+ Interlocked.Exchange(ref _monitorDispatchPending, 0);
+ }
+ }, DispatcherPriority.Background);
+ }
+
+ private static void CleanupWindow(DesktopWindowState state, bool restoreNativeState)
+ {
+ state.Window.Opened -= OnWindowOpened;
+ state.Window.Closed -= OnWindowClosed;
+ Win32Properties.RemoveWindowStylesCallback(state.Window, state.WindowStylesCallback);
+ Win32Properties.RemoveWndProcHookCallback(state.Window, state.WndProcHookCallback);
+
+ if (restoreNativeState && state.Handle != IntPtr.Zero && IsWindow(state.Handle))
+ {
+ var screenPosition = GetNativeScreenPosition(state.Handle, state.Window.Position);
+ _ = TryRestoreNativeState(state, screenPosition, showWindow: false);
+ }
+
+ lock (StaticLock)
+ {
+ WindowStates.Remove(state.Window);
+ }
+
+ StopDesktopHostMonitorTimerIfIdle();
+ }
+
+ private static void StopDesktopHostMonitorTimerIfIdle()
+ {
+ lock (StaticLock)
+ {
+ if (WindowStates.Count > 0)
+ {
+ return;
+ }
+ }
+
+ lock (TimerLock)
+ {
+ _desktopHostMonitorTimer?.Stop();
+ _desktopHostMonitorTimer?.Dispose();
+ _desktopHostMonitorTimer = null;
+ _lastResolvedDesktopHost = IntPtr.Zero;
+ }
+ }
+
+ private static IntPtr HandleWindowMessage(
+ DesktopWindowState state,
+ IntPtr hWnd,
+ uint message,
+ IntPtr wParam,
+ IntPtr lParam,
+ ref bool handled)
+ {
+ if (message != WM_NCHITTEST)
+ {
+ return IntPtr.Zero;
+ }
+
+ if (state.NeedsNativeRepair)
+ {
+ handled = true;
+ return (IntPtr)HTTRANSPARENT;
+ }
+
+ var screenPoint = new POINT(
+ unchecked((short)(lParam.ToInt64() & 0xFFFF)),
+ unchecked((short)((lParam.ToInt64() >> 16) & 0xFFFF)));
+ if (!ScreenToClient(hWnd, ref screenPoint))
+ {
+ handled = true;
+ return (IntPtr)HTTRANSPARENT;
+ }
+
+ var point = ConvertPhysicalClientPointToDip(
+ new Point(screenPoint.X, screenPoint.Y),
+ GetWindowDpiScale(hWnd));
+ var regions = state.InteractiveRegions;
+ foreach (var region in regions)
+ {
+ if (IsPointInsideRegion(region, point))
+ {
+ handled = true;
+ return (IntPtr)HTCLIENT;
+ }
+ }
+
+ handled = true;
+ return (IntPtr)HTTRANSPARENT;
+ }
+
+ internal static Point ConvertPhysicalClientPointToDip(Point physicalPoint, double dpiScale)
+ {
+ var scale = double.IsFinite(dpiScale) ? Math.Max(0.1, dpiScale) : 1d;
+ return new Point(physicalPoint.X / scale, physicalPoint.Y / scale);
+ }
+
+ private static double GetWindowDpiScale(IntPtr handle)
+ {
+ try
+ {
+ var dpi = GetDpiForWindow(handle);
+ return dpi > 0 ? dpi / 96.0 : 1.0;
+ }
+ catch (EntryPointNotFoundException)
+ {
+ return 1.0;
+ }
+ }
+
+ private static PixelPoint GetNativeScreenPosition(IntPtr handle, PixelPoint fallback)
+ {
+ return GetWindowRect(handle, out var rect)
+ ? new PixelPoint(rect.Left, rect.Top)
+ : fallback;
+ }
+
+ private static bool TryGetWindowState(Window window, out DesktopWindowState state)
+ {
+ lock (StaticLock)
+ {
+ return WindowStates.TryGetValue(window, out state!);
+ }
+ }
+
+ private static void RunOnUiThread(Action action)
+ {
+ if (Dispatcher.UIThread.CheckAccess())
+ {
+ action();
+ }
+ else
+ {
+ Dispatcher.UIThread.Post(action);
+ }
+ }
+
+ private static uint ReadWindowStyle(IntPtr handle, int index)
+ {
+ return unchecked((uint)GetWindowLongPtr(handle, index).ToInt64());
+ }
+
+ private static void WriteWindowStyle(IntPtr handle, int index, uint value)
+ {
+ _ = SetWindowLongPtr(handle, index, new IntPtr(unchecked((int)value)));
+ }
+
+ private static IntPtr GetWindowHandle(Window window)
+ {
+ try
+ {
+ return window.TryGetPlatformHandle()?.Handle ?? IntPtr.Zero;
+ }
+ catch
+ {
+ return IntPtr.Zero;
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct RECT
+ {
+ public int Left;
+ public int Top;
+ public int Right;
+ public int Bottom;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct POINT(int x, int y)
+ {
+ public int X = x;
+ public int Y = y;
+ }
+
+ private sealed class DesktopWindowState
+ {
+ public DesktopWindowState(Window window)
+ {
+ Window = window;
+ WindowStylesCallback = ApplyWindowStyles;
+ WndProcHookCallback = ProcessWindowMessage;
+ }
+
+ public Window Window { get; }
+ public IntPtr Handle { get; set; }
+ public NativeWindowState? OriginalState { get; set; }
+ public IntPtr DesktopHost { get; set; }
+ public volatile bool IsDesktopAttached;
+ public volatile bool HasStableDesktopAttachment;
+ public volatile bool NeedsNativeRepair;
+ public IntPtr FailedDesktopHost { get; set; }
+ public bool AttachToCurrentHostAfterRepair { get; set; }
+ public int NativeRepairAttemptCount { get; set; }
+ public DateTime NextNativeRepairAttemptUtc { get; set; }
+ public PixelPoint? PendingScreenPosition { get; set; }
+ public bool HasLoggedPositionFailure { get; set; }
+ public bool HasLoggedFallback { get; set; }
+ public bool HasLoggedRepairFailure { get; set; }
+ public volatile WindowInteractiveRegion[] InteractiveRegions = [];
+ public Win32Properties.CustomWindowStylesCallback WindowStylesCallback { get; }
+ public Win32Properties.CustomWndProcHookCallback WndProcHookCallback { get; }
+
+ private (uint style, uint exStyle) ApplyWindowStyles(uint style, uint exStyle)
+ {
+ return IsDesktopAttached
+ ? CreateDesktopChildStyles(style, exStyle)
+ : (style, ApplyDesktopRoleExtendedStyles(exStyle));
+ }
+
+ private IntPtr ProcessWindowMessage(
+ IntPtr hWnd,
+ uint message,
+ IntPtr wParam,
+ IntPtr lParam,
+ ref bool handled)
+ {
+ return HandleWindowMessage(this, hWnd, message, wParam, lParam, ref handled);
+ }
+ }
+
+ private readonly record struct NativeWindowState(IntPtr Parent, uint Style, uint ExStyle);
+
+ private delegate bool EnumWindowsProc(IntPtr handle, IntPtr lParam);
+
+ [DllImport("user32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
+
+ [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr", SetLastError = true)]
+ private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
+
+ [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", SetLastError = true)]
+ private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool SetWindowPos(
+ IntPtr hWnd,
+ IntPtr hWndInsertAfter,
+ int x,
+ int y,
+ int cx,
+ int cy,
+ uint flags);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
+
+ [DllImport("user32.dll")]
+ private static extern IntPtr GetParent(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool IsWindow(IntPtr hWnd);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ private static extern IntPtr FindWindowEx(
+ IntPtr hParent,
+ IntPtr hChildAfter,
+ string? lpszClass,
+ string? lpszWindow);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
+
+ [DllImport("user32.dll")]
+ private static extern uint GetDpiForWindow(IntPtr hWnd);
+
+ [DllImport("dwmapi.dll")]
+ private static extern int DwmSetWindowAttribute(
+ IntPtr hWnd,
+ int dwAttribute,
+ ref uint pvAttribute,
+ int cbAttribute);
+}
+
+public sealed class WindowsRegionPassthroughService : IRegionPassthroughService
+{
+ public bool IsRegionPassthroughSupported => true;
+
+ public void SetInteractiveRegions(
+ Window window,
+ IReadOnlyList interactiveRegions)
+ {
+ ArgumentNullException.ThrowIfNull(window);
+ ArgumentNullException.ThrowIfNull(interactiveRegions);
+ WindowsWindowBottomMostService.SetInteractiveRegionsInternal(window, interactiveRegions);
+ }
+
+ public void ClearInteractiveRegions(Window window)
+ {
+ ArgumentNullException.ThrowIfNull(window);
+ WindowsWindowBottomMostService.SetInteractiveRegionsInternal(window, []);
+ }
+}
diff --git a/LanMountainDesktop.Tests/WindowPassthroughServiceTests.cs b/LanMountainDesktop.Tests/WindowPassthroughServiceTests.cs
index be91727..069a697 100644
--- a/LanMountainDesktop.Tests/WindowPassthroughServiceTests.cs
+++ b/LanMountainDesktop.Tests/WindowPassthroughServiceTests.cs
@@ -1,6 +1,7 @@
-using System.Reflection;
+using System.Reflection;
using Avalonia;
-using LanMountainDesktop.Services;
+using LanMountainDesktop.Platform.Abstractions;
+using LanMountainDesktop.Platform.Windows;
using Xunit;
namespace LanMountainDesktop.Tests;
@@ -52,7 +53,7 @@ public sealed class WindowPassthroughServiceTests
[Fact]
public void NativeIntegration_UsesAvaloniaCallbacksWithoutManualWndProcSubclassing()
{
- var source = ReadRepositoryFile("LanMountainDesktop", "Services", "WindowPassthroughService.cs");
+ var source = ReadRepositoryFile("LanMountainDesktop.Platform.Windows", "WindowsWindowPassthroughServices.cs");
Assert.Contains("Win32Properties.AddWindowStylesCallback", source);
Assert.Contains("Win32Properties.AddWndProcHookCallback", source);
@@ -158,7 +159,7 @@ public sealed class WindowPassthroughServiceTests
Assert.False(Invoke("OriginalWindowUsesParentClientCoordinates", WsPopup));
Assert.False(Invoke("OriginalWindowUsesParentClientCoordinates", 0U));
- var source = ReadRepositoryFile("LanMountainDesktop", "Services", "WindowPassthroughService.cs");
+ var source = ReadRepositoryFile("LanMountainDesktop.Platform.Windows", "WindowsWindowPassthroughServices.cs");
Assert.Contains("GWLP_HWNDPARENT", source);
Assert.Contains("restore the owner through GWLP_HWNDPARENT", source);
}
@@ -213,7 +214,7 @@ public sealed class WindowPassthroughServiceTests
[Fact]
public void HitTestCoordinates_AreResolvedFromTheLiveWindowState()
{
- var source = ReadRepositoryFile("LanMountainDesktop", "Services", "WindowPassthroughService.cs");
+ var source = ReadRepositoryFile("LanMountainDesktop.Platform.Windows", "WindowsWindowPassthroughServices.cs");
Assert.Contains("ScreenToClient(hWnd, ref screenPoint)", source);
Assert.Contains("GetDpiForWindow(handle)", source);
@@ -229,9 +230,8 @@ public sealed class WindowPassthroughServiceTests
private static T Invoke(string methodName, params object[] arguments)
{
- var serviceType = typeof(IWindowBottomMostService).Assembly.GetType(
- "LanMountainDesktop.Services.WindowsWindowBottomMostService",
- throwOnError: true)!;
+ // Windows 实现已迁移至 Platform.Windows(跨平台重构),类型现为 public。
+ var serviceType = typeof(WindowsWindowBottomMostService);
var method = serviceType.GetMethod(
methodName,
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
diff --git a/LanMountainDesktop.slnx b/LanMountainDesktop.slnx
index f143eac..f607885 100644
--- a/LanMountainDesktop.slnx
+++ b/LanMountainDesktop.slnx
@@ -1,4 +1,8 @@
+
+
+
+
diff --git a/LanMountainDesktop/App.axaml.cs b/LanMountainDesktop/App.axaml.cs
index 90043c3..a7a6f92 100644
--- a/LanMountainDesktop/App.axaml.cs
+++ b/LanMountainDesktop/App.axaml.cs
@@ -18,6 +18,7 @@ using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.DesktopHost;
using LanMountainDesktop.Models;
+using LanMountainDesktop.Platform.Abstractions;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.ExternalIpc;
diff --git a/LanMountainDesktop/LanMountainDesktop.csproj b/LanMountainDesktop/LanMountainDesktop.csproj
index 6cb23c7..d4497df 100644
--- a/LanMountainDesktop/LanMountainDesktop.csproj
+++ b/LanMountainDesktop/LanMountainDesktop.csproj
@@ -30,6 +30,9 @@
+
+
+
diff --git a/LanMountainDesktop/Services/FusedDesktopManagerService.cs b/LanMountainDesktop/Services/FusedDesktopManagerService.cs
index 3598253..0e43221 100644
--- a/LanMountainDesktop/Services/FusedDesktopManagerService.cs
+++ b/LanMountainDesktop/Services/FusedDesktopManagerService.cs
@@ -10,6 +10,7 @@ using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.DesktopEditing;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Models;
+using LanMountainDesktop.Platform.Abstractions;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Views;
diff --git a/LanMountainDesktop/Services/MainWindowDesktopLayerService.cs b/LanMountainDesktop/Services/MainWindowDesktopLayerService.cs
index 5ace9ca..220e977 100644
--- a/LanMountainDesktop/Services/MainWindowDesktopLayerService.cs
+++ b/LanMountainDesktop/Services/MainWindowDesktopLayerService.cs
@@ -1,17 +1,14 @@
using System;
-using System.Collections.Generic;
-using System.Runtime.InteropServices;
-using Avalonia.Controls;
+using LanMountainDesktop.Platform.Abstractions;
+using LanMountainDesktop.Platform.Windows;
namespace LanMountainDesktop.Services;
-public interface IMainWindowDesktopLayerService
-{
- bool IsSupported { get; }
- void EnableOrRefresh(Window window);
- void Disable(Window window);
-}
-
+///
+/// 桌面层服务工厂。接口与实现位于平台层:
+/// 接口 在 Platform.Abstractions,
+/// Windows 实现(P/Invoke)在 Platform.Windows。
+///
public static class MainWindowDesktopLayerServiceFactory
{
private static readonly object Gate = new();
@@ -21,252 +18,15 @@ public static class MainWindowDesktopLayerServiceFactory
{
lock (Gate)
{
- return _instance ??= OperatingSystem.IsWindows()
- ? new WindowsMainWindowDesktopLayerService()
- : new NullMainWindowDesktopLayerService();
+ if (_instance is null)
+ {
+ PlatformLogBridge.Install();
+ _instance = OperatingSystem.IsWindows()
+ ? new WindowsMainWindowDesktopLayerService()
+ : new NullMainWindowDesktopLayerService();
+ }
+
+ return _instance;
}
}
}
-
-internal sealed class WindowsMainWindowDesktopLayerService : IMainWindowDesktopLayerService
-{
- private const int GWL_STYLE = -16;
- private const int GWL_EXSTYLE = -20;
-
- private const long WS_CHILD = 0x40000000L;
- private const long WS_POPUP = 0x80000000L;
- private const long WS_CAPTION = 0x00C00000L;
- private const long WS_THICKFRAME = 0x00040000L;
- private const long WS_MINIMIZEBOX = 0x00020000L;
- private const long WS_MAXIMIZEBOX = 0x00010000L;
- private const long WS_SYSMENU = 0x00080000L;
-
- private const uint SWP_NOSIZE = 0x0001;
- private const uint SWP_NOMOVE = 0x0002;
- private const uint SWP_NOACTIVATE = 0x0010;
- private const uint SWP_SHOWWINDOW = 0x0040;
- private const uint SWP_FRAMECHANGED = 0x0020;
-
- private static readonly IntPtr HWND_TOP = IntPtr.Zero;
- private static readonly IntPtr HWND_BOTTOM = new(1);
-
- private readonly object _gate = new();
- private readonly Dictionary _restoreStates = [];
-
- public bool IsSupported => true;
-
- public void EnableOrRefresh(Window window)
- {
- ArgumentNullException.ThrowIfNull(window);
-
- var handle = GetWindowHandle(window);
- if (handle == IntPtr.Zero)
- {
- window.Opened -= OnDeferredOpened;
- window.Opened += OnDeferredOpened;
- return;
- }
-
- EnableOrRefresh(handle);
- }
-
- public void Disable(Window window)
- {
- ArgumentNullException.ThrowIfNull(window);
- window.Opened -= OnDeferredOpened;
-
- var handle = GetWindowHandle(window);
- if (handle == IntPtr.Zero)
- {
- return;
- }
-
- WindowRestoreState? restoreState;
- lock (_gate)
- {
- if (!_restoreStates.Remove(handle, out restoreState))
- {
- return;
- }
- }
-
- try
- {
- _ = SetParent(handle, restoreState.Parent);
- SetWindowLongPtr(handle, GWL_STYLE, restoreState.Style);
- SetWindowLongPtr(handle, GWL_EXSTYLE, restoreState.ExStyle);
- _ = SetWindowPos(
- handle,
- HWND_TOP,
- 0,
- 0,
- 0,
- 0,
- SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW);
- AppLogger.Info("MainWindowDesktopLayer", $"Disabled desktop layer. Window={handle}.");
- }
- catch (Exception ex)
- {
- AppLogger.Warn("MainWindowDesktopLayer", $"Failed to disable desktop layer. Window={handle}.", ex);
- }
- }
-
- private void OnDeferredOpened(object? sender, EventArgs e)
- {
- if (sender is not Window window)
- {
- return;
- }
-
- window.Opened -= OnDeferredOpened;
- EnableOrRefresh(window);
- }
-
- private void EnableOrRefresh(IntPtr handle)
- {
- if (handle == IntPtr.Zero || !IsWindow(handle))
- {
- return;
- }
-
- SaveRestoreStateIfNeeded(handle);
- var desktopHost = ResolveDesktopIconHost();
- if (desktopHost != IntPtr.Zero && IsWindow(desktopHost))
- {
- ApplyDesktopChildStyle(handle);
- if (GetParent(handle) != desktopHost)
- {
- _ = SetParent(handle, desktopHost);
- }
-
- _ = SetWindowPos(
- handle,
- HWND_TOP,
- 0,
- 0,
- 0,
- 0,
- SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW);
- AppLogger.Info("MainWindowDesktopLayer", $"Enabled desktop layer. Window={handle}; Host={desktopHost}.");
- return;
- }
-
- _ = SetWindowPos(handle, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
- AppLogger.Warn("MainWindowDesktopLayer", $"Desktop icon host not found. Falling back to HWND_BOTTOM. Window={handle}.");
- }
-
- private void SaveRestoreStateIfNeeded(IntPtr handle)
- {
- lock (_gate)
- {
- if (_restoreStates.ContainsKey(handle))
- {
- return;
- }
-
- _restoreStates[handle] = new WindowRestoreState(
- GetParent(handle),
- GetWindowLongPtr(handle, GWL_STYLE),
- GetWindowLongPtr(handle, GWL_EXSTYLE));
- }
- }
-
- private static void ApplyDesktopChildStyle(IntPtr handle)
- {
- var style = GetWindowLongPtr(handle, GWL_STYLE).ToInt64();
- style |= WS_CHILD;
- style &= ~(WS_POPUP | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU);
- SetWindowLongPtr(handle, GWL_STYLE, new IntPtr(style));
- }
-
- private static IntPtr ResolveDesktopIconHost()
- {
- var topLevelWindows = new List();
- EnumWindows((handle, _) =>
- {
- topLevelWindows.Add(handle);
- return true;
- }, IntPtr.Zero);
-
- foreach (var topLevelWindow in topLevelWindows)
- {
- var worker = FindWindowEx(topLevelWindow, IntPtr.Zero, "WorkerW", null);
- if (worker == IntPtr.Zero)
- {
- continue;
- }
-
- var defView = FindWindowEx(worker, IntPtr.Zero, "SHELLDLL_DefView", null);
- if (defView != IntPtr.Zero)
- {
- return defView;
- }
- }
-
- foreach (var topLevelWindow in topLevelWindows)
- {
- var defView = FindWindowEx(topLevelWindow, IntPtr.Zero, "SHELLDLL_DefView", null);
- if (defView != IntPtr.Zero)
- {
- return defView;
- }
- }
-
- return IntPtr.Zero;
- }
-
- private static IntPtr GetWindowHandle(Window window)
- {
- try
- {
- return window.TryGetPlatformHandle()?.Handle ?? IntPtr.Zero;
- }
- catch
- {
- return IntPtr.Zero;
- }
- }
-
- private sealed record WindowRestoreState(IntPtr Parent, IntPtr Style, IntPtr ExStyle);
-
- private delegate bool EnumWindowsProc(IntPtr handle, IntPtr lParam);
-
- [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
- private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
-
- [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
- private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
-
- [DllImport("user32.dll")]
- private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint flags);
-
- [DllImport("user32.dll", SetLastError = true)]
- private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
-
- [DllImport("user32.dll")]
- private static extern IntPtr GetParent(IntPtr hWnd);
-
- [DllImport("user32.dll")]
- private static extern bool IsWindow(IntPtr hWnd);
-
- [DllImport("user32.dll", SetLastError = true)]
- private static extern IntPtr FindWindowEx(IntPtr hParent, IntPtr hChildAfter, string? lpszClass, string? lpszWindow);
-
- [DllImport("user32.dll", SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
-}
-
-internal sealed class NullMainWindowDesktopLayerService : IMainWindowDesktopLayerService
-{
- public bool IsSupported => false;
-
- public void EnableOrRefresh(Window window)
- {
- AppLogger.Info("MainWindowDesktopLayer", "Desktop layer requested on an unsupported platform.");
- }
-
- public void Disable(Window window)
- {
- }
-}
diff --git a/LanMountainDesktop/Services/PlatformLogBridge.cs b/LanMountainDesktop/Services/PlatformLogBridge.cs
new file mode 100644
index 0000000..cb022d1
--- /dev/null
+++ b/LanMountainDesktop/Services/PlatformLogBridge.cs
@@ -0,0 +1,35 @@
+using System;
+using LanMountainDesktop.Platform.Abstractions;
+
+namespace LanMountainDesktop.Services;
+
+///
+/// 将平台层日志(PlatformLog)接入宿主 AppLogger。
+/// 在应用启动早期调用 一次。
+///
+internal static class PlatformLogBridge
+{
+ private static bool _installed;
+
+ public static void Install()
+ {
+ if (_installed)
+ {
+ return;
+ }
+
+ _installed = true;
+ PlatformLog.SetSink(new AppLoggerSink());
+ }
+
+ private sealed class AppLoggerSink : IPlatformLogSink
+ {
+ public void Info(string category, string message) => AppLogger.Info(category, message);
+
+ public void Warn(string category, string message, Exception? exception = null) =>
+ AppLogger.Warn(category, message, exception);
+
+ public void Error(string category, string message, Exception? exception = null) =>
+ AppLogger.Error(category, message, exception);
+ }
+}
diff --git a/LanMountainDesktop/Services/PowerManagementService.cs b/LanMountainDesktop/Services/PowerManagementService.cs
index 2adc836..831cb76 100644
--- a/LanMountainDesktop/Services/PowerManagementService.cs
+++ b/LanMountainDesktop/Services/PowerManagementService.cs
@@ -1,33 +1,17 @@
using System;
using System.Diagnostics;
-using System.Runtime.InteropServices;
using System.Threading.Tasks;
+using LanMountainDesktop.Platform.Abstractions;
+using LanMountainDesktop.Platform.Windows;
namespace LanMountainDesktop.Services;
-public interface IPowerManagementService
-{
- bool IsShutdownSupported { get; }
- bool IsRestartSupported { get; }
- bool IsLogoutSupported { get; }
- bool IsLockSupported { get; }
- bool IsSleepSupported { get; }
-
- Task ShutdownAsync();
- Task RestartAsync();
- Task LogoutAsync();
- Task LockAsync();
- Task SleepAsync();
-
- void ShowNativePowerUI(PowerAction action);
-}
-
-public enum PowerAction
-{
- Shutdown,
- Restart
-}
-
+///
+/// 电源管理服务工厂。接口与实现位于平台层:
+/// 接口 在 Platform.Abstractions,
+/// Windows 实现(P/Invoke)在 Platform.Windows。
+/// Linux 实现基于 systemctl/loginctl 命令行,无平台专属 API,保留在宿主内。
+///
public static class PowerManagementServiceFactory
{
private static IPowerManagementService? _instance;
@@ -51,103 +35,6 @@ public static class PowerManagementServiceFactory
}
}
-internal sealed class WindowsPowerManagementService : IPowerManagementService
-{
- public bool IsShutdownSupported => true;
- public bool IsRestartSupported => true;
- public bool IsLogoutSupported => true;
- public bool IsLockSupported => true;
- public bool IsSleepSupported => true;
-
- public async Task ShutdownAsync()
- {
- await Task.Run(() =>
- {
- Process.Start(new ProcessStartInfo
- {
- FileName = "shutdown",
- Arguments = "/s /t 0",
- UseShellExecute = true,
- WindowStyle = ProcessWindowStyle.Hidden
- });
- });
- }
-
- public async Task RestartAsync()
- {
- await Task.Run(() =>
- {
- Process.Start(new ProcessStartInfo
- {
- FileName = "shutdown",
- Arguments = "/r /t 0",
- UseShellExecute = true,
- WindowStyle = ProcessWindowStyle.Hidden
- });
- });
- }
-
- public async Task LogoutAsync()
- {
- await Task.Run(() =>
- {
- ExitWindowsEx(0, 0);
- });
- }
-
- public async Task LockAsync()
- {
- await Task.Run(() =>
- {
- LockWorkStation();
- });
- }
-
- public async Task SleepAsync()
- {
- await Task.Run(() =>
- {
- SetSuspendState(false, false, false);
- });
- }
-
- public void ShowNativePowerUI(PowerAction action)
- {
- // SlideToShutDown.exe 只支持关机,不支持重启
- // 重启操作应该通过 RestartAsync() 使用 shutdown /r 命令
- if (action != PowerAction.Shutdown)
- return;
-
- var slideToShutDownPath = Environment.ExpandEnvironmentVariables(@"%windir%\System32\SlideToShutDown.exe");
- if (System.IO.File.Exists(slideToShutDownPath))
- {
- Process.Start(new ProcessStartInfo
- {
- FileName = slideToShutDownPath,
- UseShellExecute = true
- });
- return;
- }
-
- // 回退到标准关机命令
- Process.Start(new ProcessStartInfo
- {
- FileName = "shutdown",
- Arguments = "/s /t 5 /c \"LanMountainDesktop: Shutting down...\"",
- UseShellExecute = true
- });
- }
-
- [DllImport("user32.dll", SetLastError = true)]
- private static extern bool ExitWindowsEx(uint uFlags, uint dwReason);
-
- [DllImport("user32.dll")]
- private static extern void LockWorkStation();
-
- [DllImport("powrprof.dll", SetLastError = true)]
- private static extern bool SetSuspendState(bool hibernate, bool forceCritical, bool disableWakeEvent);
-}
-
internal sealed class LinuxPowerManagementService : IPowerManagementService
{
public bool IsShutdownSupported => true;
@@ -226,20 +113,3 @@ internal sealed class LinuxPowerManagementService : IPowerManagementService
});
}
}
-
-internal sealed class NullPowerManagementService : IPowerManagementService
-{
- public bool IsShutdownSupported => false;
- public bool IsRestartSupported => false;
- public bool IsLogoutSupported => false;
- public bool IsLockSupported => false;
- public bool IsSleepSupported => false;
-
- public Task ShutdownAsync() => Task.CompletedTask;
- public Task RestartAsync() => Task.CompletedTask;
- public Task LogoutAsync() => Task.CompletedTask;
- public Task LockAsync() => Task.CompletedTask;
- public Task SleepAsync() => Task.CompletedTask;
-
- public void ShowNativePowerUI(PowerAction action) { }
-}
diff --git a/LanMountainDesktop/Services/WindowPassthroughService.cs b/LanMountainDesktop/Services/WindowPassthroughService.cs
index 745b20f..d6473d6 100644
--- a/LanMountainDesktop/Services/WindowPassthroughService.cs
+++ b/LanMountainDesktop/Services/WindowPassthroughService.cs
@@ -1,36 +1,14 @@
using System;
-using System.Collections.Generic;
-using System.Runtime.InteropServices;
-using System.Threading;
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Threading;
+using LanMountainDesktop.Platform.Abstractions;
+using LanMountainDesktop.Platform.Windows;
namespace LanMountainDesktop.Services;
-public interface IWindowBottomMostService
-{
- void SetupBottomMost(Window window);
- void SendToBottom(Window window);
- PixelPoint GetScreenPosition(Window window);
- bool SetScreenPosition(Window window, PixelPoint position, bool queueOnFailure = false);
- bool IsBottomMostSupported { get; }
-}
-
-public readonly record struct WindowInteractiveRegion(
- Rect Bounds,
- double CornerRadius,
- Matrix? ClientToRegionTransform = null,
- Rect? ClientClipBounds = null,
- double ClientClipCornerRadius = 0d);
-
-public interface IRegionPassthroughService
-{
- void SetInteractiveRegions(Window window, IReadOnlyList interactiveRegions);
- void ClearInteractiveRegions(Window window);
- bool IsRegionPassthroughSupported { get; }
-}
-
+///
+/// 窗口置底/区域穿透服务工厂。接口与实现位于平台层:
+/// 接口(IWindowBottomMostService / IRegionPassthroughService / WindowInteractiveRegion)
+/// 在 Platform.Abstractions,Windows 实现(P/Invoke)在 Platform.Windows。
+///
public static class WindowBottomMostServiceFactory
{
private static IWindowBottomMostService? _instance;
@@ -40,9 +18,15 @@ public static class WindowBottomMostServiceFactory
{
lock (_lock)
{
- return _instance ??= OperatingSystem.IsWindows()
- ? new WindowsWindowBottomMostService()
- : new NullWindowBottomMostService();
+ if (_instance is null)
+ {
+ PlatformLogBridge.Install();
+ _instance = OperatingSystem.IsWindows()
+ ? new WindowsWindowBottomMostService()
+ : new NullWindowBottomMostService();
+ }
+
+ return _instance;
}
}
}
@@ -56,1350 +40,15 @@ public static class RegionPassthroughServiceFactory
{
lock (_lock)
{
- return _instance ??= OperatingSystem.IsWindows()
- ? new WindowsRegionPassthroughService()
- : new NullRegionPassthroughService();
+ if (_instance is null)
+ {
+ PlatformLogBridge.Install();
+ _instance = OperatingSystem.IsWindows()
+ ? new WindowsRegionPassthroughService()
+ : new NullRegionPassthroughService();
+ }
+
+ return _instance;
}
}
}
-
-internal sealed class WindowsWindowBottomMostService : IWindowBottomMostService
-{
- private const int GWL_STYLE = -16;
- private const int GWL_EXSTYLE = -20;
- private const int GWLP_HWNDPARENT = -8;
-
- private const uint WS_CHILD = 0x40000000U;
- private const uint WS_POPUP = 0x80000000U;
- private const uint WS_CAPTION = 0x00C00000U;
- private const uint WS_THICKFRAME = 0x00040000U;
- private const uint WS_MINIMIZEBOX = 0x00020000U;
- private const uint WS_MAXIMIZEBOX = 0x00010000U;
- private const uint WS_SYSMENU = 0x00080000U;
-
- private const uint WS_EX_TOOLWINDOW = 0x00000080U;
- private const uint WS_EX_APPWINDOW = 0x00040000U;
- private const uint WS_EX_NOACTIVATE = 0x08000000U;
- private const uint WS_EX_NOREDIRECTIONBITMAP = 0x00200000U;
- private const uint AVALONIA_COMPOSITION_EXSTYLE_MASK = WS_EX_NOREDIRECTIONBITMAP;
-
- private const uint SWP_NOSIZE = 0x0001;
- private const uint SWP_NOMOVE = 0x0002;
- private const uint SWP_NOZORDER = 0x0004;
- private const uint SWP_NOACTIVATE = 0x0010;
- private const uint SWP_FRAMECHANGED = 0x0020;
- private const uint SWP_SHOWWINDOW = 0x0040;
- private const uint SWP_HIDEWINDOW = 0x0080;
-
- private const uint WM_NCHITTEST = 0x0084;
- private const int HTTRANSPARENT = -1;
- private const int HTCLIENT = 1;
-
- private const int DWMWA_WINDOW_CORNER_PREFERENCE = 33;
- private const int DWMWA_BORDER_COLOR = 34;
- private const uint DWMWCP_DONOTROUND = 1;
- private const uint DWMWA_COLOR_NONE = 0xFFFFFFFEU;
-
- private static readonly IntPtr HWND_TOP = IntPtr.Zero;
- private static readonly IntPtr HWND_BOTTOM = new(1);
- private static readonly object StaticLock = new();
- private static readonly object TimerLock = new();
-
- private static readonly Dictionary WindowStates = new();
-
- private static System.Timers.Timer? _desktopHostMonitorTimer;
- private static IntPtr _lastResolvedDesktopHost;
- private static int _monitorDispatchPending;
-
- public bool IsBottomMostSupported => true;
-
- public void SetupBottomMost(Window window)
- {
- ArgumentNullException.ThrowIfNull(window);
- if (!OperatingSystem.IsWindows())
- {
- return;
- }
-
- DesktopWindowState state;
- lock (StaticLock)
- {
- if (WindowStates.TryGetValue(window, out state!))
- {
- return;
- }
-
- state = new DesktopWindowState(window);
- WindowStates[window] = state;
- }
-
- Win32Properties.SetWindowCornerPreference(window, Win32Properties.WindowCornerPreference.DoNotRound);
- Win32Properties.AddWindowStylesCallback(window, state.WindowStylesCallback);
- Win32Properties.AddWndProcHookCallback(window, state.WndProcHookCallback);
-
- window.Closed += OnWindowClosed;
-
- var handle = GetWindowHandle(window);
- if (handle == IntPtr.Zero)
- {
- window.Opened += OnWindowOpened;
- return;
- }
-
- RunOnUiThread(() => InitializeAndAttach(state, handle, logSuccess: true));
- }
-
- public void SendToBottom(Window window)
- {
- ArgumentNullException.ThrowIfNull(window);
- if (!TryGetWindowState(window, out var state))
- {
- SetupBottomMost(window);
- return;
- }
-
- RunOnUiThread(() =>
- {
- var handle = GetWindowHandle(window);
- if (handle == IntPtr.Zero || !IsWindow(handle))
- {
- return;
- }
-
- RegisterHandle(state, handle);
- if (state.NeedsNativeRepair &&
- !ShouldAttemptNativeRepair(
- false,
- DateTime.UtcNow,
- state.NextNativeRepairAttemptUtc))
- {
- return;
- }
-
- var desktopHost = ResolveDesktopIconHost();
- if (!state.NeedsNativeRepair &&
- state.IsDesktopAttached &&
- state.HasStableDesktopAttachment &&
- desktopHost != IntPtr.Zero &&
- state.DesktopHost == desktopHost &&
- GetParent(handle) == desktopHost &&
- state.OriginalState is { } originalState &&
- HasExpectedDesktopRoleStyles(handle, originalState))
- {
- _ = SetWindowPos(
- handle,
- HWND_TOP,
- 0,
- 0,
- 0,
- 0,
- SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
- return;
- }
-
- ApplyDesktopAttachment(
- state,
- desktopHost,
- logSuccess: false,
- "explicit refresh",
- allowRetryFailedHost: true);
- });
- }
-
- public PixelPoint GetScreenPosition(Window window)
- {
- ArgumentNullException.ThrowIfNull(window);
- var handle = GetWindowHandle(window);
- return handle != IntPtr.Zero && GetWindowRect(handle, out var rect)
- ? new PixelPoint(rect.Left, rect.Top)
- : window.Position;
- }
-
- public bool SetScreenPosition(
- Window window,
- PixelPoint position,
- bool queueOnFailure = false)
- {
- ArgumentNullException.ThrowIfNull(window);
- TryGetWindowState(window, out var state);
- var handle = GetWindowHandle(window);
- if (handle == IntPtr.Zero || !IsWindow(handle))
- {
- window.Position = position;
- if (state is not null)
- {
- state.PendingScreenPosition = null;
- state.HasLoggedPositionFailure = false;
- }
-
- return true;
- }
-
- var nativePosition = new POINT(position.X, position.Y);
- var style = ReadWindowStyle(handle, GWL_STYLE);
- var nativeParent = GetParent(handle);
- if (state is not null)
- {
- if (state.NeedsNativeRepair)
- {
- if (queueOnFailure)
- {
- state.PendingScreenPosition = position;
- }
-
- return false;
- }
-
- if (state.IsDesktopAttached)
- {
- if (state.OriginalState is not { } originalState ||
- nativeParent != state.DesktopHost ||
- !HasExpectedDesktopRoleStyles(handle, originalState))
- {
- LogPositionFailureOnce(
- state,
- $"Refusing to move a desktop window with invalid native attachment state. " +
- $"Window={handle}; Parent={nativeParent}; ExpectedHost={state.DesktopHost}.");
- if (queueOnFailure)
- {
- state.PendingScreenPosition = position;
- }
-
- return false;
- }
-
- nativeParent = state.DesktopHost;
- }
- }
-
- if (OriginalWindowUsesParentClientCoordinates(style) &&
- (nativeParent == IntPtr.Zero || !ScreenToClient(nativeParent, ref nativePosition)))
- {
- if (state is not null)
- {
- LogPositionFailureOnce(
- state,
- $"Could not translate screen position to child-window coordinates. " +
- $"Window={handle}; Parent={nativeParent}.");
- if (queueOnFailure)
- {
- state.PendingScreenPosition = position;
- }
- }
- return false;
- }
-
- if (!SetWindowPos(
- handle,
- IntPtr.Zero,
- nativePosition.X,
- nativePosition.Y,
- 0,
- 0,
- SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE))
- {
- if (state is not null)
- {
- LogPositionFailureOnce(
- state,
- $"Could not set screen position. Window={handle}; Position={position}; " +
- $"Error={Marshal.GetLastWin32Error()}.");
- if (queueOnFailure)
- {
- state.PendingScreenPosition = position;
- }
- }
- return false;
- }
-
- if (state is not null)
- {
- state.PendingScreenPosition = null;
- state.HasLoggedPositionFailure = false;
- }
-
- return true;
- }
-
- private static void LogPositionFailureOnce(DesktopWindowState state, string message)
- {
- if (state.HasLoggedPositionFailure)
- {
- return;
- }
-
- AppLogger.Warn("WindowBottomMost", message);
- state.HasLoggedPositionFailure = true;
- }
-
- private static void TryApplyPendingScreenPosition(DesktopWindowState state)
- {
- if (state.NeedsNativeRepair || state.PendingScreenPosition is not { } pendingPosition)
- {
- return;
- }
-
- _ = new WindowsWindowBottomMostService().SetScreenPosition(
- state.Window,
- pendingPosition,
- queueOnFailure: true);
- }
-
- internal static void SetInteractiveRegionsInternal(
- Window window,
- IReadOnlyList regions)
- {
- if (!TryGetWindowState(window, out var state))
- {
- return;
- }
-
- var snapshot = new WindowInteractiveRegion[regions.Count];
- for (var i = 0; i < regions.Count; i++)
- {
- snapshot[i] = regions[i];
- }
-
- state.InteractiveRegions = snapshot;
- }
-
- internal static (uint Style, uint ExStyle) CreateDesktopChildStyles(uint style, uint exStyle)
- {
- style |= WS_CHILD;
- style &= ~(WS_POPUP | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU);
- return (style, ApplyDesktopRoleExtendedStyles(exStyle));
- }
-
- internal static bool IsPointInsideRegion(WindowInteractiveRegion region, Point point)
- {
- if (region.ClientClipBounds is { } clientClipBounds &&
- !IsPointInsideRoundedBounds(clientClipBounds, region.ClientClipCornerRadius, point))
- {
- return false;
- }
-
- if (region.ClientToRegionTransform is { } clientToRegionTransform)
- {
- point = clientToRegionTransform.Transform(point);
- }
-
- return IsPointInsideRoundedBounds(region.Bounds, region.CornerRadius, point);
- }
-
- private static bool IsPointInsideRoundedBounds(Rect bounds, double cornerRadius, Point point)
- {
- if (bounds.Width <= 0 || bounds.Height <= 0 || !bounds.Contains(point))
- {
- return false;
- }
-
- var radius = Math.Clamp(cornerRadius, 0, Math.Min(bounds.Width, bounds.Height) / 2);
- if (radius <= 0)
- {
- return true;
- }
-
- var localX = point.X - bounds.X;
- var localY = point.Y - bounds.Y;
- if (localX >= radius && localX <= bounds.Width - radius ||
- localY >= radius && localY <= bounds.Height - radius)
- {
- return true;
- }
-
- var centerX = localX < radius ? radius : bounds.Width - radius;
- var centerY = localY < radius ? radius : bounds.Height - radius;
- var deltaX = localX - centerX;
- var deltaY = localY - centerY;
- return deltaX * deltaX + deltaY * deltaY <= radius * radius;
- }
-
- internal static bool OriginalWindowUsesParentClientCoordinates(uint style)
- {
- return (style & WS_CHILD) != 0;
- }
-
- internal static bool ShouldAttemptNativeRepair(
- bool hostChanged,
- DateTime utcNow,
- DateTime nextRepairAttemptUtc)
- {
- return hostChanged || utcNow >= nextRepairAttemptUtc;
- }
-
- internal static bool ShouldAttemptDesktopAttachment(
- bool hostChanged,
- IntPtr attachedHost,
- IntPtr currentHost,
- bool parentMismatch)
- {
- return parentMismatch || (hostChanged && attachedHost != currentHost);
- }
-
- private static uint ApplyDesktopRoleExtendedStyles(uint exStyle)
- {
- // Preserve every compositor-managed bit from Avalonia. In particular, this method must
- // never opt the window into a second, legacy alpha-composition path.
- return (exStyle | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE) & ~WS_EX_APPWINDOW;
- }
-
- private static void OnWindowOpened(object? sender, EventArgs e)
- {
- if (sender is not Window window || !TryGetWindowState(window, out var state))
- {
- return;
- }
-
- window.Opened -= OnWindowOpened;
- var handle = GetWindowHandle(window);
- if (handle != IntPtr.Zero)
- {
- RunOnUiThread(() => InitializeAndAttach(state, handle, logSuccess: true));
- }
- }
-
- private static void OnWindowClosed(object? sender, EventArgs e)
- {
- if (sender is Window window && TryGetWindowState(window, out var state))
- {
- CleanupWindow(state, restoreNativeState: true);
- }
- }
-
- private static void InitializeAndAttach(DesktopWindowState state, IntPtr handle, bool logSuccess)
- {
- if (handle == IntPtr.Zero || !IsWindow(handle))
- {
- return;
- }
-
- RegisterHandle(state, handle);
- ConfigureDwmAppearance(handle);
- ApplyDesktopAttachment(state, ResolveDesktopIconHost(), logSuccess, "initial setup");
- }
-
- private static void RegisterHandle(DesktopWindowState state, IntPtr handle)
- {
- lock (StaticLock)
- {
- if (state.Handle != IntPtr.Zero && state.Handle != handle)
- {
- state.OriginalState = null;
- state.DesktopHost = IntPtr.Zero;
- state.IsDesktopAttached = false;
- state.HasStableDesktopAttachment = false;
- ResetNativeRepairState(state);
- state.AttachToCurrentHostAfterRepair = false;
- state.HasLoggedFallback = false;
- state.HasLoggedPositionFailure = false;
- }
-
- state.Handle = handle;
- state.OriginalState ??= new NativeWindowState(
- GetParent(handle),
- ReadWindowStyle(handle, GWL_STYLE),
- ReadWindowStyle(handle, GWL_EXSTYLE));
- }
- }
-
- private static void ApplyDesktopAttachment(
- DesktopWindowState state,
- IntPtr desktopHost,
- bool logSuccess,
- string reason,
- bool allowRetryFailedHost = false)
- {
- var handle = state.Handle;
- if (handle == IntPtr.Zero || !IsWindow(handle) || state.OriginalState is not { } originalState)
- {
- return;
- }
-
- var screenPosition = GetNativeScreenPosition(handle, state.Window.Position);
- if (state.NeedsNativeRepair)
- {
- var failedHost = state.FailedDesktopHost;
- var attachAfterRepair = state.AttachToCurrentHostAfterRepair;
- FallBackToBottom(
- state,
- screenPosition,
- "repairing an incomplete native rollback before attachment",
- logSuccess: false,
- failedHost);
- if (state.NeedsNativeRepair)
- {
- StartDesktopHostMonitorTimer(desktopHost);
- return;
- }
-
- state.AttachToCurrentHostAfterRepair = false;
- if (!attachAfterRepair && desktopHost == failedHost && !allowRetryFailedHost)
- {
- // The failed host has not changed. Stay in the safe top-level fallback until
- // Explorer changes or the caller explicitly requests another attachment.
- StartDesktopHostMonitorTimer(desktopHost);
- return;
- }
- }
-
- var beforeStyle = ReadWindowStyle(handle, GWL_STYLE);
- var beforeExStyle = ReadWindowStyle(handle, GWL_EXSTYLE);
-
- if (desktopHost == IntPtr.Zero || !IsWindow(desktopHost))
- {
- FallBackToBottom(
- state,
- screenPosition,
- "desktop icon host is unavailable",
- logSuccess,
- desktopHost);
- StartDesktopHostMonitorTimer(desktopHost);
- return;
- }
-
- if (state.IsDesktopAttached)
- {
- if (state.DesktopHost == desktopHost && GetParent(handle) == desktopHost)
- {
- if (HasExpectedDesktopRoleStyles(handle, originalState))
- {
- StartDesktopHostMonitorTimer(desktopHost);
- return;
- }
-
- FallBackToBottom(
- state,
- screenPosition,
- "desktop child style validation failed",
- logSuccess,
- desktopHost);
- StartDesktopHostMonitorTimer(desktopHost);
- return;
- }
-
- var hostBeingDetached = state.DesktopHost;
- if (!TryRestoreNativeState(state, screenPosition, showWindow: true))
- {
- FallBackToBottom(
- state,
- screenPosition,
- "failed to restore before remount",
- logSuccess,
- hostBeingDetached);
- if (state.NeedsNativeRepair && desktopHost != hostBeingDetached)
- {
- state.AttachToCurrentHostAfterRepair = true;
- }
-
- if (state.NeedsNativeRepair ||
- (desktopHost == hostBeingDetached && !allowRetryFailedHost))
- {
- StartDesktopHostMonitorTimer(desktopHost);
- return;
- }
- }
-
- beforeStyle = ReadWindowStyle(handle, GWL_STYLE);
- beforeExStyle = ReadWindowStyle(handle, GWL_EXSTYLE);
- }
-
- beforeExStyle |= originalState.ExStyle & AVALONIA_COMPOSITION_EXSTYLE_MASK;
- var (expectedStyle, expectedExStyle) = CreateDesktopChildStyles(beforeStyle, beforeExStyle);
- WriteWindowStyle(handle, GWL_STYLE, expectedStyle);
- WriteWindowStyle(handle, GWL_EXSTYLE, expectedExStyle);
-
- _ = SetParent(handle, desktopHost);
- var setParentError = Marshal.GetLastWin32Error();
- if (GetParent(handle) != desktopHost)
- {
- FallBackToBottom(
- state,
- screenPosition,
- $"SetParent failed with error {setParentError}",
- logSuccess,
- desktopHost);
- StartDesktopHostMonitorTimer(desktopHost);
- return;
- }
-
- state.DesktopHost = desktopHost;
- state.IsDesktopAttached = true;
- state.HasStableDesktopAttachment = false;
-
- var childPosition = new POINT(screenPosition.X, screenPosition.Y);
- if (!ScreenToClient(desktopHost, ref childPosition) ||
- !SetWindowPos(
- handle,
- HWND_TOP,
- childPosition.X,
- childPosition.Y,
- 0,
- 0,
- SWP_NOSIZE | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW) ||
- !HasExpectedDesktopStyles(handle, expectedStyle, expectedExStyle) ||
- GetParent(handle) != desktopHost)
- {
- var error = Marshal.GetLastWin32Error();
- FallBackToBottom(
- state,
- screenPosition,
- $"post-attachment validation failed with error {error}",
- logSuccess,
- desktopHost);
- StartDesktopHostMonitorTimer(desktopHost);
- return;
- }
-
- ResetNativeRepairState(state);
- state.AttachToCurrentHostAfterRepair = false;
- state.HasLoggedFallback = false;
- state.HasStableDesktopAttachment = true;
- ConfigureDwmAppearance(handle);
- if (logSuccess)
- {
- var afterStyle = ReadWindowStyle(handle, GWL_STYLE);
- var afterExStyle = ReadWindowStyle(handle, GWL_EXSTYLE);
- AppLogger.Info(
- "WindowBottomMost",
- $"Mounted window to desktop icon host. Window={handle}; Host={desktopHost}; Reason={reason}; " +
- $"Style=0x{beforeStyle:X8}->0x{afterStyle:X8}; ExStyle=0x{beforeExStyle:X8}->0x{afterExStyle:X8}; " +
- $"NoRedirectionBitmap={((afterExStyle & WS_EX_NOREDIRECTIONBITMAP) != 0)}.");
- }
-
- StartDesktopHostMonitorTimer(desktopHost);
- }
-
- private static void FallBackToBottom(
- DesktopWindowState state,
- PixelPoint screenPosition,
- string reason,
- bool logSuccess,
- IntPtr failedDesktopHost)
- {
- var handle = state.Handle;
- var wasAttached = state.IsDesktopAttached;
- var wasStablyAttached = state.HasStableDesktopAttachment;
- var restored = true;
- if (state.OriginalState is { } originalState &&
- (state.NeedsNativeRepair ||
- wasAttached ||
- GetParent(handle) != originalState.Parent ||
- ReadWindowStyle(handle, GWL_STYLE) != originalState.Style ||
- ReadWindowStyle(handle, GWL_EXSTYLE) != originalState.ExStyle))
- {
- restored = TryRestoreNativeState(state, screenPosition, showWindow: true);
- }
-
- if (!restored)
- {
- MarkNativeRepairPending(state, failedDesktopHost);
- if (logSuccess || !state.HasLoggedRepairFailure)
- {
- AppLogger.Warn(
- "WindowBottomMost",
- $"Native rollback is incomplete; keeping the window in repair state. " +
- $"Window={handle}; FailedHost={failedDesktopHost}; Reason={reason}.");
- state.HasLoggedRepairFailure = true;
- }
-
- return;
- }
-
- state.DesktopHost = IntPtr.Zero;
- state.IsDesktopAttached = false;
- state.HasStableDesktopAttachment = false;
-
- if (IsWindow(handle) &&
- !SetWindowPos(
- handle,
- HWND_BOTTOM,
- 0,
- 0,
- 0,
- 0,
- SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW))
- {
- MarkNativeRepairPending(state, failedDesktopHost);
- if (logSuccess || !state.HasLoggedRepairFailure)
- {
- AppLogger.Warn(
- "WindowBottomMost",
- $"Native state was restored, but HWND_BOTTOM fallback positioning failed. " +
- $"Window={handle}; FailedHost={failedDesktopHost}; Reason={reason}; " +
- $"Error={Marshal.GetLastWin32Error()}.");
- state.HasLoggedRepairFailure = true;
- }
-
- return;
- }
-
- ResetNativeRepairState(state);
-
- if (logSuccess || wasStablyAttached || !state.HasLoggedFallback)
- {
- AppLogger.Warn(
- "WindowBottomMost",
- $"Using HWND_BOTTOM fallback. Window={handle}; Reason={reason}; NativeStateRestored={restored}.");
- state.HasLoggedFallback = true;
- }
- }
-
- private static void MarkNativeRepairPending(DesktopWindowState state, IntPtr failedDesktopHost)
- {
- state.FailedDesktopHost = failedDesktopHost;
- state.NativeRepairAttemptCount = Math.Min(state.NativeRepairAttemptCount + 1, 30);
- var exponent = Math.Min(state.NativeRepairAttemptCount - 1, 5);
- var delaySeconds = Math.Min(60, 2 * (1 << exponent));
- state.NextNativeRepairAttemptUtc = DateTime.UtcNow.AddSeconds(delaySeconds);
- state.NeedsNativeRepair = true;
-
- if (state.Handle != IntPtr.Zero && IsWindow(state.Handle))
- {
- _ = SetWindowPos(
- state.Handle,
- IntPtr.Zero,
- 0,
- 0,
- 0,
- 0,
- SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_HIDEWINDOW);
- }
- }
-
- private static void ResetNativeRepairState(DesktopWindowState state)
- {
- state.NeedsNativeRepair = false;
- state.FailedDesktopHost = IntPtr.Zero;
- state.HasLoggedRepairFailure = false;
- state.NativeRepairAttemptCount = 0;
- state.NextNativeRepairAttemptUtc = DateTime.MinValue;
- }
-
- private static bool TryRestoreNativeState(
- DesktopWindowState state,
- PixelPoint screenPosition,
- bool showWindow)
- {
- var handle = state.Handle;
- if (handle == IntPtr.Zero || !IsWindow(handle) || state.OriginalState is not { } originalState)
- {
- return false;
- }
-
- var previousDesktopHost = state.DesktopHost;
- var wasDesktopAttached = state.IsDesktopAttached;
- var wasStablyDesktopAttached = state.HasStableDesktopAttachment;
- state.IsDesktopAttached = false;
- state.HasStableDesktopAttachment = false;
- state.DesktopHost = IntPtr.Zero;
-
- var restoreParentOrOwner = originalState.Parent != IntPtr.Zero && !IsWindow(originalState.Parent)
- ? IntPtr.Zero
- : originalState.Parent;
- var restorePosition = new POINT(screenPosition.X, screenPosition.Y);
-
- if (OriginalWindowUsesParentClientCoordinates(originalState.Style))
- {
- _ = SetParent(handle, restoreParentOrOwner);
- WriteWindowStyle(handle, GWL_STYLE, originalState.Style);
- WriteWindowStyle(handle, GWL_EXSTYLE, originalState.ExStyle);
- if (restoreParentOrOwner != IntPtr.Zero &&
- !ScreenToClient(restoreParentOrOwner, ref restorePosition))
- {
- restorePosition = new POINT(screenPosition.X, screenPosition.Y);
- }
- }
- else
- {
- // GetParent returns an owner for a top-level popup. It is not a child-coordinate
- // parent: first leave the desktop child hierarchy, restore the popup styles, then
- // restore the owner through GWLP_HWNDPARENT and keep SetWindowPos in screen pixels.
- _ = SetParent(handle, IntPtr.Zero);
- WriteWindowStyle(handle, GWL_STYLE, originalState.Style);
- WriteWindowStyle(handle, GWL_EXSTYLE, originalState.ExStyle);
- _ = SetWindowLongPtr(handle, GWLP_HWNDPARENT, restoreParentOrOwner);
- }
-
- var flags = SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED;
- if (showWindow)
- {
- flags |= SWP_SHOWWINDOW;
- }
-
- var positioned = SetWindowPos(
- handle,
- IntPtr.Zero,
- restorePosition.X,
- restorePosition.Y,
- 0,
- 0,
- flags);
-
- var hierarchyAndStylesRestored =
- GetParent(handle) == restoreParentOrOwner &&
- ReadWindowStyle(handle, GWL_STYLE) == originalState.Style &&
- ReadWindowStyle(handle, GWL_EXSTYLE) == originalState.ExStyle;
- if (!hierarchyAndStylesRestored)
- {
- // Preserve the logical attachment state until a later repair attempt succeeds.
- // This prevents the monitor from treating a half-converted child HWND as a valid
- // top-level fallback.
- state.DesktopHost = previousDesktopHost;
- state.IsDesktopAttached = wasDesktopAttached;
- state.HasStableDesktopAttachment = wasStablyDesktopAttached;
- }
-
- return hierarchyAndStylesRestored && positioned;
- }
-
- private static bool HasExpectedDesktopStyles(IntPtr handle, uint style, uint exStyle)
- {
- return ReadWindowStyle(handle, GWL_STYLE) == style &&
- ReadWindowStyle(handle, GWL_EXSTYLE) == exStyle;
- }
-
- private static bool HasExpectedDesktopRoleStyles(IntPtr handle, NativeWindowState originalState)
- {
- var style = ReadWindowStyle(handle, GWL_STYLE);
- var exStyle = ReadWindowStyle(handle, GWL_EXSTYLE);
- var expected = CreateDesktopChildStyles(style, exStyle);
- var requiredAvaloniaBits = originalState.ExStyle & AVALONIA_COMPOSITION_EXSTYLE_MASK;
- return style == expected.Style &&
- exStyle == expected.ExStyle &&
- (exStyle & requiredAvaloniaBits) == requiredAvaloniaBits;
- }
-
- private static void ConfigureDwmAppearance(IntPtr handle)
- {
- if (handle == IntPtr.Zero || !IsWindow(handle))
- {
- return;
- }
-
- try
- {
- var cornerPreference = DWMWCP_DONOTROUND;
- _ = DwmSetWindowAttribute(
- handle,
- DWMWA_WINDOW_CORNER_PREFERENCE,
- ref cornerPreference,
- sizeof(uint));
-
- var borderColor = DWMWA_COLOR_NONE;
- _ = DwmSetWindowAttribute(handle, DWMWA_BORDER_COLOR, ref borderColor, sizeof(uint));
- }
- catch (Exception ex) when (ex is DllNotFoundException or EntryPointNotFoundException)
- {
- // These attributes are best-effort and unavailable on older Windows versions.
- }
- }
-
- private static IntPtr ResolveDesktopIconHost()
- {
- var topLevelWindows = new List();
- EnumWindows((handle, _) =>
- {
- topLevelWindows.Add(handle);
- return true;
- }, IntPtr.Zero);
-
- foreach (var topLevelWindow in topLevelWindows)
- {
- var worker = FindWindowEx(topLevelWindow, IntPtr.Zero, "WorkerW", null);
- if (worker == IntPtr.Zero)
- {
- continue;
- }
-
- var defView = FindWindowEx(worker, IntPtr.Zero, "SHELLDLL_DefView", null);
- if (defView != IntPtr.Zero)
- {
- return defView;
- }
- }
-
- foreach (var topLevelWindow in topLevelWindows)
- {
- var defView = FindWindowEx(topLevelWindow, IntPtr.Zero, "SHELLDLL_DefView", null);
- if (defView != IntPtr.Zero)
- {
- return defView;
- }
- }
-
- return IntPtr.Zero;
- }
-
- private static void StartDesktopHostMonitorTimer(IntPtr currentHost)
- {
- lock (TimerLock)
- {
- if (_desktopHostMonitorTimer != null)
- {
- return;
- }
-
- _lastResolvedDesktopHost = currentHost;
- _desktopHostMonitorTimer = new System.Timers.Timer(TimeSpan.FromSeconds(2))
- {
- AutoReset = true
- };
- _desktopHostMonitorTimer.Elapsed += (_, _) => MonitorDesktopHostAttachments();
- _desktopHostMonitorTimer.Start();
- }
- }
-
- private static void MonitorDesktopHostAttachments()
- {
- var desktopHost = ResolveDesktopIconHost();
- var hostChanged = false;
- lock (TimerLock)
- {
- if (desktopHost != _lastResolvedDesktopHost)
- {
- _lastResolvedDesktopHost = desktopHost;
- hostChanged = true;
- }
- }
-
- List states;
- lock (StaticLock)
- {
- states = [.. WindowStates.Values];
- }
-
- var requiresCleanupOrRepair = false;
- var now = DateTime.UtcNow;
- foreach (var state in states)
- {
- if ((state.NeedsNativeRepair && now >= state.NextNativeRepairAttemptUtc) ||
- (!state.NeedsNativeRepair && state.PendingScreenPosition.HasValue) ||
- state.Handle != IntPtr.Zero && !IsWindow(state.Handle) ||
- state.IsDesktopAttached &&
- (GetParent(state.Handle) != state.DesktopHost ||
- state.OriginalState is not { } originalState ||
- !HasExpectedDesktopRoleStyles(state.Handle, originalState)))
- {
- requiresCleanupOrRepair = true;
- break;
- }
- }
-
- if (!hostChanged && !requiresCleanupOrRepair ||
- Interlocked.Exchange(ref _monitorDispatchPending, 1) != 0)
- {
- return;
- }
-
- Dispatcher.UIThread.Post(() =>
- {
- try
- {
- var currentHost = ResolveDesktopIconHost();
- var effectiveHostChanged = hostChanged || currentHost != desktopHost;
- List currentStates;
- lock (StaticLock)
- {
- currentStates = [.. WindowStates.Values];
- }
-
- foreach (var state in currentStates)
- {
- if (state.Handle == IntPtr.Zero)
- {
- // A window can be registered before Avalonia creates its native handle.
- continue;
- }
-
- if (!IsWindow(state.Handle))
- {
- CleanupWindow(state, restoreNativeState: false);
- continue;
- }
-
- if (state.NeedsNativeRepair)
- {
- if (!ShouldAttemptNativeRepair(
- effectiveHostChanged,
- DateTime.UtcNow,
- state.NextNativeRepairAttemptUtc))
- {
- continue;
- }
-
- var failedHost = state.FailedDesktopHost;
- var attachAfterRepair = state.AttachToCurrentHostAfterRepair;
- var screenPosition = GetNativeScreenPosition(state.Handle, state.Window.Position);
- FallBackToBottom(
- state,
- screenPosition,
- "retrying incomplete native rollback",
- logSuccess: false,
- failedHost);
- if (state.NeedsNativeRepair)
- {
- continue;
- }
-
- state.AttachToCurrentHostAfterRepair = false;
- if (currentHost != IntPtr.Zero &&
- (attachAfterRepair ||
- effectiveHostChanged && currentHost != failedHost))
- {
- ApplyDesktopAttachment(
- state,
- currentHost,
- logSuccess: true,
- "desktop host changed while native state was being repaired");
- }
-
- TryApplyPendingScreenPosition(state);
- continue;
- }
-
- var attachmentDrift = state.IsDesktopAttached &&
- (GetParent(state.Handle) != state.DesktopHost ||
- state.OriginalState is not { } originalState ||
- !HasExpectedDesktopRoleStyles(state.Handle, originalState));
- if (ShouldAttemptDesktopAttachment(
- effectiveHostChanged,
- state.DesktopHost,
- currentHost,
- attachmentDrift))
- {
- ApplyDesktopAttachment(state, currentHost, logSuccess: true, "desktop host changed");
- }
-
- TryApplyPendingScreenPosition(state);
- }
-
- lock (TimerLock)
- {
- _lastResolvedDesktopHost = currentHost;
- }
- }
- finally
- {
- Interlocked.Exchange(ref _monitorDispatchPending, 0);
- }
- }, DispatcherPriority.Background);
- }
-
- private static void CleanupWindow(DesktopWindowState state, bool restoreNativeState)
- {
- state.Window.Opened -= OnWindowOpened;
- state.Window.Closed -= OnWindowClosed;
- Win32Properties.RemoveWindowStylesCallback(state.Window, state.WindowStylesCallback);
- Win32Properties.RemoveWndProcHookCallback(state.Window, state.WndProcHookCallback);
-
- if (restoreNativeState && state.Handle != IntPtr.Zero && IsWindow(state.Handle))
- {
- var screenPosition = GetNativeScreenPosition(state.Handle, state.Window.Position);
- _ = TryRestoreNativeState(state, screenPosition, showWindow: false);
- }
-
- lock (StaticLock)
- {
- WindowStates.Remove(state.Window);
- }
-
- StopDesktopHostMonitorTimerIfIdle();
- }
-
- private static void StopDesktopHostMonitorTimerIfIdle()
- {
- lock (StaticLock)
- {
- if (WindowStates.Count > 0)
- {
- return;
- }
- }
-
- lock (TimerLock)
- {
- _desktopHostMonitorTimer?.Stop();
- _desktopHostMonitorTimer?.Dispose();
- _desktopHostMonitorTimer = null;
- _lastResolvedDesktopHost = IntPtr.Zero;
- }
- }
-
- private static IntPtr HandleWindowMessage(
- DesktopWindowState state,
- IntPtr hWnd,
- uint message,
- IntPtr wParam,
- IntPtr lParam,
- ref bool handled)
- {
- if (message != WM_NCHITTEST)
- {
- return IntPtr.Zero;
- }
-
- if (state.NeedsNativeRepair)
- {
- handled = true;
- return (IntPtr)HTTRANSPARENT;
- }
-
- var screenPoint = new POINT(
- unchecked((short)(lParam.ToInt64() & 0xFFFF)),
- unchecked((short)((lParam.ToInt64() >> 16) & 0xFFFF)));
- if (!ScreenToClient(hWnd, ref screenPoint))
- {
- handled = true;
- return (IntPtr)HTTRANSPARENT;
- }
-
- var point = ConvertPhysicalClientPointToDip(
- new Point(screenPoint.X, screenPoint.Y),
- GetWindowDpiScale(hWnd));
- var regions = state.InteractiveRegions;
- foreach (var region in regions)
- {
- if (IsPointInsideRegion(region, point))
- {
- handled = true;
- return (IntPtr)HTCLIENT;
- }
- }
-
- handled = true;
- return (IntPtr)HTTRANSPARENT;
- }
-
- internal static Point ConvertPhysicalClientPointToDip(Point physicalPoint, double dpiScale)
- {
- var scale = double.IsFinite(dpiScale) ? Math.Max(0.1, dpiScale) : 1d;
- return new Point(physicalPoint.X / scale, physicalPoint.Y / scale);
- }
-
- private static double GetWindowDpiScale(IntPtr handle)
- {
- try
- {
- var dpi = GetDpiForWindow(handle);
- return dpi > 0 ? dpi / 96.0 : 1.0;
- }
- catch (EntryPointNotFoundException)
- {
- return 1.0;
- }
- }
-
- private static PixelPoint GetNativeScreenPosition(IntPtr handle, PixelPoint fallback)
- {
- return GetWindowRect(handle, out var rect)
- ? new PixelPoint(rect.Left, rect.Top)
- : fallback;
- }
-
- private static bool TryGetWindowState(Window window, out DesktopWindowState state)
- {
- lock (StaticLock)
- {
- return WindowStates.TryGetValue(window, out state!);
- }
- }
-
- private static void RunOnUiThread(Action action)
- {
- if (Dispatcher.UIThread.CheckAccess())
- {
- action();
- }
- else
- {
- Dispatcher.UIThread.Post(action);
- }
- }
-
- private static uint ReadWindowStyle(IntPtr handle, int index)
- {
- return unchecked((uint)GetWindowLongPtr(handle, index).ToInt64());
- }
-
- private static void WriteWindowStyle(IntPtr handle, int index, uint value)
- {
- _ = SetWindowLongPtr(handle, index, new IntPtr(unchecked((int)value)));
- }
-
- private static IntPtr GetWindowHandle(Window window)
- {
- try
- {
- return window.TryGetPlatformHandle()?.Handle ?? IntPtr.Zero;
- }
- catch
- {
- return IntPtr.Zero;
- }
- }
-
- [StructLayout(LayoutKind.Sequential)]
- private struct RECT
- {
- public int Left;
- public int Top;
- public int Right;
- public int Bottom;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- private struct POINT(int x, int y)
- {
- public int X = x;
- public int Y = y;
- }
-
- private sealed class DesktopWindowState
- {
- public DesktopWindowState(Window window)
- {
- Window = window;
- WindowStylesCallback = ApplyWindowStyles;
- WndProcHookCallback = ProcessWindowMessage;
- }
-
- public Window Window { get; }
- public IntPtr Handle { get; set; }
- public NativeWindowState? OriginalState { get; set; }
- public IntPtr DesktopHost { get; set; }
- public volatile bool IsDesktopAttached;
- public volatile bool HasStableDesktopAttachment;
- public volatile bool NeedsNativeRepair;
- public IntPtr FailedDesktopHost { get; set; }
- public bool AttachToCurrentHostAfterRepair { get; set; }
- public int NativeRepairAttemptCount { get; set; }
- public DateTime NextNativeRepairAttemptUtc { get; set; }
- public PixelPoint? PendingScreenPosition { get; set; }
- public bool HasLoggedPositionFailure { get; set; }
- public bool HasLoggedFallback { get; set; }
- public bool HasLoggedRepairFailure { get; set; }
- public volatile WindowInteractiveRegion[] InteractiveRegions = [];
- public Win32Properties.CustomWindowStylesCallback WindowStylesCallback { get; }
- public Win32Properties.CustomWndProcHookCallback WndProcHookCallback { get; }
-
- private (uint style, uint exStyle) ApplyWindowStyles(uint style, uint exStyle)
- {
- return IsDesktopAttached
- ? CreateDesktopChildStyles(style, exStyle)
- : (style, ApplyDesktopRoleExtendedStyles(exStyle));
- }
-
- private IntPtr ProcessWindowMessage(
- IntPtr hWnd,
- uint message,
- IntPtr wParam,
- IntPtr lParam,
- ref bool handled)
- {
- return HandleWindowMessage(this, hWnd, message, wParam, lParam, ref handled);
- }
- }
-
- private readonly record struct NativeWindowState(IntPtr Parent, uint Style, uint ExStyle);
-
- private delegate bool EnumWindowsProc(IntPtr handle, IntPtr lParam);
-
- [DllImport("user32.dll")]
- [return: MarshalAs(UnmanagedType.Bool)]
- private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
-
- [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr", SetLastError = true)]
- private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
-
- [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", SetLastError = true)]
- private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
-
- [DllImport("user32.dll", SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- private static extern bool SetWindowPos(
- IntPtr hWnd,
- IntPtr hWndInsertAfter,
- int x,
- int y,
- int cx,
- int cy,
- uint flags);
-
- [DllImport("user32.dll", SetLastError = true)]
- private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
-
- [DllImport("user32.dll")]
- private static extern IntPtr GetParent(IntPtr hWnd);
-
- [DllImport("user32.dll")]
- [return: MarshalAs(UnmanagedType.Bool)]
- private static extern bool IsWindow(IntPtr hWnd);
-
- [DllImport("user32.dll", SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- private static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);
-
- [DllImport("user32.dll", SetLastError = true)]
- private static extern IntPtr FindWindowEx(
- IntPtr hParent,
- IntPtr hChildAfter,
- string? lpszClass,
- string? lpszWindow);
-
- [DllImport("user32.dll", SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
-
- [DllImport("user32.dll")]
- private static extern uint GetDpiForWindow(IntPtr hWnd);
-
- [DllImport("dwmapi.dll")]
- private static extern int DwmSetWindowAttribute(
- IntPtr hWnd,
- int dwAttribute,
- ref uint pvAttribute,
- int cbAttribute);
-}
-
-internal sealed class WindowsRegionPassthroughService : IRegionPassthroughService
-{
- public bool IsRegionPassthroughSupported => true;
-
- public void SetInteractiveRegions(
- Window window,
- IReadOnlyList interactiveRegions)
- {
- ArgumentNullException.ThrowIfNull(window);
- ArgumentNullException.ThrowIfNull(interactiveRegions);
- WindowsWindowBottomMostService.SetInteractiveRegionsInternal(window, interactiveRegions);
- }
-
- public void ClearInteractiveRegions(Window window)
- {
- ArgumentNullException.ThrowIfNull(window);
- WindowsWindowBottomMostService.SetInteractiveRegionsInternal(window, []);
- }
-}
-
-internal sealed class NullWindowBottomMostService : IWindowBottomMostService
-{
- public bool IsBottomMostSupported => false;
- public void SetupBottomMost(Window window) { }
- public void SendToBottom(Window window) { }
- public PixelPoint GetScreenPosition(Window window) => window.Position;
- public bool SetScreenPosition(
- Window window,
- PixelPoint position,
- bool queueOnFailure = false)
- {
- window.Position = position;
- return true;
- }
-}
-
-internal sealed class NullRegionPassthroughService : IRegionPassthroughService
-{
- public bool IsRegionPassthroughSupported => false;
- public void SetInteractiveRegions(Window window, IReadOnlyList interactiveRegions) { }
- public void ClearInteractiveRegions(Window window) { }
-}
diff --git a/LanMountainDesktop/Services/WindowsNativeDialogService.cs b/LanMountainDesktop/Services/WindowsNativeDialogService.cs
index 02edc02..bf13d7a 100644
--- a/LanMountainDesktop/Services/WindowsNativeDialogService.cs
+++ b/LanMountainDesktop/Services/WindowsNativeDialogService.cs
@@ -1,41 +1,32 @@
using System;
-using System.Runtime.InteropServices;
+using LanMountainDesktop.Platform.Windows;
namespace LanMountainDesktop.Services;
+///
+/// 原生对话框门面。P/Invoke 实现位于 Platform.Windows(WindowsNativeDialogs)。
+///
internal static class WindowsNativeDialogService
{
- private const uint Ok = 0x00000000;
- private const uint IconInformation = 0x00000040;
- private const uint IconWarning = 0x00000030;
-
public static void ShowInformation(string caption, string message)
{
- Show(caption, message, Ok | IconInformation, "NativeDialog");
+ Show(caption, message, WindowsNativeDialogs.Ok | WindowsNativeDialogs.IconInformation, "NativeDialog");
}
public static void ShowWarning(string caption, string message)
{
- Show(caption, message, Ok | IconWarning, "StartupDiagnostics");
+ Show(caption, message, WindowsNativeDialogs.Ok | WindowsNativeDialogs.IconWarning, "StartupDiagnostics");
}
private static void Show(string caption, string message, uint type, string logCategory)
{
- if (!OperatingSystem.IsWindows())
- {
- return;
- }
-
try
{
- _ = MessageBoxW(IntPtr.Zero, message, caption, type);
+ WindowsNativeDialogs.Show(caption, message, type);
}
catch (Exception ex)
{
AppLogger.Warn(logCategory, "Failed to show native dialog.", ex);
}
}
-
- [DllImport("user32.dll", EntryPoint = "MessageBoxW", CharSet = CharSet.Unicode, SetLastError = true)]
- private static extern int MessageBoxW(IntPtr hWnd, string text, string caption, uint type);
}
diff --git a/LanMountainDesktop/Services/WindowsNotificationListener.cs b/LanMountainDesktop/Services/WindowsNotificationListener.cs
index 6e9fdbd..5f80b5c 100644
--- a/LanMountainDesktop/Services/WindowsNotificationListener.cs
+++ b/LanMountainDesktop/Services/WindowsNotificationListener.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -8,6 +8,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using LanMountainDesktop.Models;
+using LanMountainDesktop.Platform.Windows;
namespace LanMountainDesktop.Services;
@@ -320,26 +321,7 @@ internal sealed class WindowsNotificationListener : IPlatformNotificationListene
private static bool HasPackageIdentity()
{
- if (!OperatingSystem.IsWindows())
- {
- return false;
- }
-
- var length = 0;
- var hr = GetCurrentPackageFullName(ref length, null);
- if (hr == AppmodelErrorNoPackage)
- {
- return false;
- }
-
- if (length <= 0)
- {
- return hr == 0;
- }
-
- var builder = new StringBuilder(length);
- hr = GetCurrentPackageFullName(ref length, builder);
- return hr == 0;
+ return WindowsPackageIdentity.HasPackageIdentity();
}
private static string TryReadPackageFamilyName(object? appInfo)
@@ -497,8 +479,4 @@ internal sealed class WindowsNotificationListener : IPlatformNotificationListene
_cts.Dispose();
}
- private const int AppmodelErrorNoPackage = 15700;
-
- [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
- private static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder? packageFullName);
}
diff --git a/LanMountainDesktop/Services/WindowsStartMenuService.cs b/LanMountainDesktop/Services/WindowsStartMenuService.cs
index 10a89f9..2214c3d 100644
--- a/LanMountainDesktop/Services/WindowsStartMenuService.cs
+++ b/LanMountainDesktop/Services/WindowsStartMenuService.cs
@@ -4,6 +4,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using LanMountainDesktop.Models;
+using LanMountainDesktop.Platform.Windows;
namespace LanMountainDesktop.Services;
diff --git a/LanMountainDesktop/Views/Components/FileManagerWidget.axaml.cs b/LanMountainDesktop/Views/Components/FileManagerWidget.axaml.cs
index 620cd70..b6f568b 100644
--- a/LanMountainDesktop/Views/Components/FileManagerWidget.axaml.cs
+++ b/LanMountainDesktop/Views/Components/FileManagerWidget.axaml.cs
@@ -14,6 +14,8 @@ using Avalonia.Platform;
using FluentIcons.Avalonia;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
+using LanMountainDesktop.Platform.MacOS;
+using LanMountainDesktop.Platform.Windows;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
diff --git a/LanMountainDesktop/Views/Components/ShortcutWidget.axaml.cs b/LanMountainDesktop/Views/Components/ShortcutWidget.axaml.cs
index 4992960..638b86f 100644
--- a/LanMountainDesktop/Views/Components/ShortcutWidget.axaml.cs
+++ b/LanMountainDesktop/Views/Components/ShortcutWidget.axaml.cs
@@ -11,6 +11,8 @@ using Avalonia.Media.Imaging;
using FluentIcons.Avalonia;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
+using LanMountainDesktop.Platform.MacOS;
+using LanMountainDesktop.Platform.Windows;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
diff --git a/LanMountainDesktop/Views/DesktopWidgetWindow.axaml.cs b/LanMountainDesktop/Views/DesktopWidgetWindow.axaml.cs
index fb8f70f..a1a99a7 100644
--- a/LanMountainDesktop/Views/DesktopWidgetWindow.axaml.cs
+++ b/LanMountainDesktop/Views/DesktopWidgetWindow.axaml.cs
@@ -7,6 +7,7 @@ using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.DesktopEditing;
using LanMountainDesktop.Models;
+using LanMountainDesktop.Platform.Abstractions;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
diff --git a/LanMountainDesktop/Views/FusedDesktopComponentLibraryWindow.axaml.cs b/LanMountainDesktop/Views/FusedDesktopComponentLibraryWindow.axaml.cs
index d8112a3..99c5645 100644
--- a/LanMountainDesktop/Views/FusedDesktopComponentLibraryWindow.axaml.cs
+++ b/LanMountainDesktop/Views/FusedDesktopComponentLibraryWindow.axaml.cs
@@ -1,11 +1,11 @@
using System;
-using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Interactivity;
using LanMountainDesktop.Appearance;
+using LanMountainDesktop.Platform.Windows;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Settings.Core;
@@ -14,9 +14,6 @@ namespace LanMountainDesktop.Views;
public partial class FusedDesktopComponentLibraryWindow : Window
{
- private const int DwmWindowAttributeBorderColor = 34;
- private const uint DwmColorNone = 0xFFFFFFFE;
-
private static readonly LocalizationService LocalizationService = new();
public FusedDesktopComponentLibraryWindow()
@@ -124,30 +121,8 @@ public partial class FusedDesktopComponentLibraryWindow : Window
private void TryDisableNativeWindowBorder()
{
- if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000))
- {
- return;
- }
-
- try
- {
- var handle = TryGetPlatformHandle()?.Handle ?? IntPtr.Zero;
- if (handle == IntPtr.Zero)
- {
- return;
- }
-
- var borderColor = DwmColorNone;
- _ = DwmSetWindowAttribute(
- handle,
- DwmWindowAttributeBorderColor,
- ref borderColor,
- sizeof(uint));
- }
- catch
- {
- // DWM attributes are best-effort and unavailable on older/unsupported Windows builds.
- }
+ var handle = TryGetPlatformHandle()?.Handle ?? IntPtr.Zero;
+ WindowsDwmInterop.TryDisableWindowBorder(handle);
}
protected override void OnClosed(EventArgs e)
@@ -163,11 +138,4 @@ public partial class FusedDesktopComponentLibraryWindow : Window
var mainWindow = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow as MainWindow;
mainWindow?.UnregisterFusedLibraryWindow(this);
}
-
- [DllImport("dwmapi.dll")]
- private static extern int DwmSetWindowAttribute(
- IntPtr windowHandle,
- int attribute,
- ref uint attributeValue,
- int attributeSize);
}
diff --git a/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs b/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs
index a6da705..286bfc4 100644
--- a/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs
+++ b/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs
@@ -18,6 +18,7 @@ using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.DesktopEditing;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Models;
+using LanMountainDesktop.Platform.Abstractions;
using LanMountainDesktop.Services;
using LanMountainDesktop.Settings.Core;
using LanMountainDesktop.Theme;
diff --git a/LanMountainDesktop/Views/MainWindow.DesktopPaging.cs b/LanMountainDesktop/Views/MainWindow.DesktopPaging.cs
index a676328..7b515f2 100644
--- a/LanMountainDesktop/Views/MainWindow.DesktopPaging.cs
+++ b/LanMountainDesktop/Views/MainWindow.DesktopPaging.cs
@@ -16,6 +16,7 @@ using Avalonia.Threading;
using Avalonia.VisualTree;
using FluentAvalonia.UI.Controls;
using LanMountainDesktop.Models;
+using LanMountainDesktop.Platform.Windows;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Theme;