refactor: 新建 Platform 平台差异层,主工程 P/Invoke 全部迁出

- 新增 Platform.Abstractions(接口+NoOp+PlatformLog 日志桥)、
  Platform.Windows、Platform.MacOS、Platform.Android 四个项目
- 迁移电源管理、原生对话框、桌面层嵌入、窗口置底/区域穿透、
  DWM 互操作、图标服务、包标识查询等全部 Windows P/Invoke 实现
- 主工程保留静态工厂门面,调用点不变;DllImport 扫描为零
- 更新 WindowPassthroughServiceTests 指向新位置(20/20 通过)
This commit is contained in:
lincube
2026-07-26 22:14:46 +09:00
parent 78cbbede9d
commit 9b7a35ddd3
35 changed files with 2157 additions and 1862 deletions

View File

@@ -0,0 +1,238 @@
using System.Runtime.InteropServices;
using Avalonia.Controls;
using LanMountainDesktop.Platform.Abstractions;
namespace LanMountainDesktop.Platform.Windows;
/// <summary>
/// Windows 桌面层实现将窗口设为桌面图标宿主SHELLDLL_DefView的子窗口。
/// 自 LanMountainDesktop.Services.MainWindowDesktopLayerService 迁移,行为不变。
/// </summary>
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<IntPtr, WindowRestoreState> _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<IntPtr>();
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);
}