mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-08-18 13:30:47 +08:00
1325 lines
44 KiB
C#
1325 lines
44 KiB
C#
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<Window, DesktopWindowState> 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<WindowInteractiveRegion> 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<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 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<DesktopWindowState> 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<DesktopWindowState> 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<WindowInteractiveRegion> interactiveRegions)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(window);
|
|
ArgumentNullException.ThrowIfNull(interactiveRegions);
|
|
WindowsWindowBottomMostService.SetInteractiveRegionsInternal(window, interactiveRegions);
|
|
}
|
|
|
|
public void ClearInteractiveRegions(Window window)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(window);
|
|
WindowsWindowBottomMostService.SetInteractiveRegionsInternal(window, []);
|
|
}
|
|
}
|