mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-08-18 15:33:33 +08:00
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:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user