using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Collections.Pooled;
using Avalonia.Controls.Platform;
using Avalonia.Controls;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Input;
using Avalonia.Metadata;
using Avalonia.OpenGL.Egl;
using Avalonia.Platform.Storage;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering;
using Avalonia.Win32.DirectX;
using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
using Avalonia.Win32.OpenGl.Angle;
using Avalonia.Win32.OpenGl;
using Avalonia.Win32.WinRT.Composition;
using Avalonia.Win32.WinRT;
using static Avalonia.Win32.Interop.UnmanagedMethods;
using Avalonia.Input.Platform;
namespace Avalonia.Win32
{
///
/// Window implementation for Win32 platform.
///
internal partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
{
private static readonly List s_instances = new();
private static readonly IntPtr s_defaultCursor = LoadCursor(
IntPtr.Zero, new IntPtr((int)UnmanagedMethods.Cursor.IDC_ARROW));
private static readonly Dictionary s_edgeLookup =
new()
{
{ WindowEdge.East, HitTestValues.HTRIGHT },
{ WindowEdge.North, HitTestValues.HTTOP },
{ WindowEdge.NorthEast, HitTestValues.HTTOPRIGHT },
{ WindowEdge.NorthWest, HitTestValues.HTTOPLEFT },
{ WindowEdge.South, HitTestValues.HTBOTTOM },
{ WindowEdge.SouthEast, HitTestValues.HTBOTTOMRIGHT },
{ WindowEdge.SouthWest, HitTestValues.HTBOTTOMLEFT },
{ WindowEdge.West, HitTestValues.HTLEFT }
};
private SavedWindowInfo _savedWindowInfo;
private bool _isFullScreenActive;
private bool _isClientAreaExtended;
private Thickness _extendedMargins;
private Thickness _offScreenMargin;
private double _extendTitleBarHint = -1;
private readonly bool _isUsingComposition;
private readonly IBlurHost? _blurHost;
private PlatformResizeReason _resizeReason;
private MOUSEMOVEPOINT _lastWmMousePoint;
#if USE_MANAGED_DRAG
private readonly ManagedWindowResizeDragHelper _managedDrag;
#endif
private const WindowStyles WindowStateMask = (WindowStyles.WS_MAXIMIZE | WindowStyles.WS_MINIMIZE);
private readonly TouchDevice _touchDevice;
private readonly WindowsMouseDevice _mouseDevice;
private readonly PenDevice _penDevice;
private readonly ManagedDeferredRendererLock _rendererLock;
private readonly FramebufferManager _framebuffer;
private readonly object? _gl;
private readonly bool _wmPointerEnabled;
private readonly Win32NativeControlHost _nativeControlHost;
private readonly IStorageProvider _storageProvider;
private WndProc _wndProcDelegate;
private string? _className;
private IntPtr _hwnd;
private IInputRoot? _owner;
private WindowProperties _windowProperties;
private bool _trackingMouse;//ToDo - there is something missed. Needs investigation @Steven Kirk
private bool _topmost;
private double _scaling = 1;
private WindowState _showWindowState;
private WindowState _lastWindowState;
private OleDropTarget? _dropTarget;
private Size _minSize;
private Size _maxSize;
private POINT _maxTrackSize;
private WindowImpl? _parent;
private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default;
private bool _isCloseRequested;
private bool _shown;
private bool _hiddenWindowIsParent;
private uint _langid;
private bool _ignoreWmChar;
private const int MaxPointerHistorySize = 512;
private static readonly PooledList s_intermediatePointsPooledList = new();
private static POINTER_TOUCH_INFO[]? s_historyTouchInfos;
private static POINTER_PEN_INFO[]? s_historyPenInfos;
private static POINTER_INFO[]? s_historyInfos;
private static MOUSEMOVEPOINT[]? s_mouseHistoryInfos;
public WindowImpl()
{
_touchDevice = new TouchDevice();
_mouseDevice = new WindowsMouseDevice();
_penDevice = new PenDevice();
#if USE_MANAGED_DRAG
_managedDrag = new ManagedWindowResizeDragHelper(this, capture =>
{
if (capture)
UnmanagedMethods.SetCapture(Handle.Handle);
else
UnmanagedMethods.ReleaseCapture();
});
#endif
_windowProperties = new WindowProperties
{
ShowInTaskbar = false,
IsResizable = true,
Decorations = SystemDecorations.Full
};
_rendererLock = new ManagedDeferredRendererLock();
var glPlatform = AvaloniaLocator.Current.GetService();
var compositionConnector = AvaloniaLocator.Current.GetService();
var isUsingAngleDX11 = glPlatform is AngleWin32PlatformGraphics angle &&
angle.PlatformApi == AngleOptions.PlatformApi.DirectX11;
_isUsingComposition = compositionConnector is { } && isUsingAngleDX11;
DxgiConnection? dxgiConnection = null;
var isUsingDxgiSwapchain = false;
if (!_isUsingComposition)
{
dxgiConnection = AvaloniaLocator.Current.GetService();
isUsingDxgiSwapchain = dxgiConnection is { } && isUsingAngleDX11;
}
_wmPointerEnabled = Win32Platform.WindowsVersion >= PlatformConstants.Windows8;
CreateWindow();
_framebuffer = new FramebufferManager(_hwnd);
UpdateInputMethod(GetKeyboardLayout(0));
if (glPlatform != null)
{
if (_isUsingComposition)
{
var cgl = compositionConnector!.CreateSurface(this);
_blurHost = cgl;
_gl = cgl;
}
else if (isUsingDxgiSwapchain)
{
var dxgigl = new DxgiSwapchainWindow(dxgiConnection!, this);
_gl = dxgigl;
}
else
{
if (glPlatform is AngleWin32PlatformGraphics)
_gl = new EglGlPlatformSurface(this);
else if (glPlatform is WglPlatformOpenGlInterface)
_gl = new WglGlPlatformSurface(this);
}
}
Screen = new ScreenImpl();
_storageProvider = new Win32StorageProvider(this);
_nativeControlHost = new Win32NativeControlHost(this, _isUsingComposition);
s_instances.Add(this);
}
private IInputRoot Owner
=> _owner ?? throw new InvalidOperationException($"{nameof(SetInputRoot)} must have been called");
public Action? Activated { get; set; }
public Func? Closing { get; set; }
public Action? Closed { get; set; }
public Action? Deactivated { get; set; }
public Action? Input { get; set; }
public Action? Paint { get; set; }
public Action? Resized { get; set; }
public Action? ScalingChanged { get; set; }
public Action? PositionChanged { get; set; }
public Action? WindowStateChanged { get; set; }
public Action? LostFocus { get; set; }
public Action? TransparencyLevelChanged { get; set; }
public Thickness BorderThickness
{
get
{
if (HasFullDecorations)
{
var style = GetStyle();
var exStyle = GetExtendedStyle();
var padding = new RECT();
if (AdjustWindowRectEx(ref padding, (uint)style, false, (uint)exStyle))
{
return new Thickness(-padding.left, -padding.top, padding.right, padding.bottom);
}
else
{
throw new Win32Exception();
}
}
else
{
return new Thickness();
}
}
}
private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.IsPrimary)?.Scaling ?? 1;
public double RenderScaling => _scaling;
public double DesktopScaling => RenderScaling;
public Size ClientSize
{
get
{
GetClientRect(_hwnd, out var rect);
return new Size(rect.right, rect.bottom) / RenderScaling;
}
}
public Size? FrameSize
{
get
{
if (DwmIsCompositionEnabled(out var compositionEnabled) != 0 || !compositionEnabled)
{
GetWindowRect(_hwnd, out var rcWindow);
return new Size(rcWindow.Width, rcWindow.Height) / RenderScaling;
}
DwmGetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out var rect, Marshal.SizeOf());
return new Size(rect.Width, rect.Height) / RenderScaling;
}
}
public IScreenImpl Screen { get; }
public IPlatformHandle Handle { get; private set; }
public virtual Size MaxAutoSizeHint => new Size(_maxTrackSize.X / RenderScaling, _maxTrackSize.Y / RenderScaling);
public IMouseDevice MouseDevice => _mouseDevice;
public WindowState WindowState
{
get
{
if (!IsWindowVisible(_hwnd))
{
return _showWindowState;
}
if (_isFullScreenActive)
{
return WindowState.FullScreen;
}
var placement = default(WINDOWPLACEMENT);
GetWindowPlacement(_hwnd, ref placement);
return placement.ShowCmd switch
{
ShowWindowCommand.Maximize => WindowState.Maximized,
ShowWindowCommand.Minimize => WindowState.Minimized,
_ => WindowState.Normal
};
}
set
{
if (IsWindowVisible(_hwnd) && _lastWindowState != value)
{
ShowWindow(value, value != WindowState.Minimized); // If the window is minimized, it shouldn't be activated
}
_lastWindowState = value;
_showWindowState = value;
}
}
public WindowTransparencyLevel TransparencyLevel { get; private set; }
protected IntPtr Hwnd => _hwnd;
private bool IsMouseInPointerEnabled => _wmPointerEnabled && IsMouseInPointerEnabled();
public object? TryGetFeature(Type featureType)
{
if (featureType == typeof(ITextInputMethodImpl))
{
return Imm32InputMethod.Current;
}
if (featureType == typeof(INativeControlHostImpl))
{
return _nativeControlHost;
}
if (featureType == typeof(IStorageProvider))
{
return _storageProvider;
}
if (featureType == typeof(IClipboard))
{
return AvaloniaLocator.Current.GetRequiredService();
}
return null;
}
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{
TransparencyLevel = EnableBlur(transparencyLevel);
}
private WindowTransparencyLevel EnableBlur(WindowTransparencyLevel transparencyLevel)
{
if (Win32Platform.WindowsVersion.Major >= 6)
{
if (DwmIsCompositionEnabled(out var compositionEnabled) != 0 || !compositionEnabled)
{
return WindowTransparencyLevel.None;
}
else if (Win32Platform.WindowsVersion.Major >= 10)
{
return Win10EnableBlur(transparencyLevel);
}
else if (Win32Platform.WindowsVersion.Minor >= 2)
{
return Win8xEnableBlur(transparencyLevel);
}
else
{
return Win7EnableBlur(transparencyLevel);
}
}
else
{
return WindowTransparencyLevel.None;
}
}
private WindowTransparencyLevel Win7EnableBlur(WindowTransparencyLevel transparencyLevel)
{
if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur)
{
transparencyLevel = WindowTransparencyLevel.Blur;
}
var blurInfo = new DWM_BLURBEHIND(false);
if (transparencyLevel == WindowTransparencyLevel.Blur)
{
blurInfo = new DWM_BLURBEHIND(true);
}
DwmEnableBlurBehindWindow(_hwnd, ref blurInfo);
if (transparencyLevel == WindowTransparencyLevel.Transparent)
{
return WindowTransparencyLevel.None;
}
else
{
return transparencyLevel;
}
}
private WindowTransparencyLevel Win8xEnableBlur(WindowTransparencyLevel transparencyLevel)
{
var accent = new AccentPolicy();
var accentStructSize = Marshal.SizeOf();
if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur)
{
transparencyLevel = WindowTransparencyLevel.Blur;
}
if (transparencyLevel == WindowTransparencyLevel.Transparent)
{
accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND;
}
else
{
accent.AccentState = AccentState.ACCENT_DISABLED;
}
var accentPtr = Marshal.AllocHGlobal(accentStructSize);
Marshal.StructureToPtr(accent, accentPtr, false);
var data = new WindowCompositionAttributeData();
data.Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY;
data.SizeOfData = accentStructSize;
data.Data = accentPtr;
SetWindowCompositionAttribute(_hwnd, ref data);
Marshal.FreeHGlobal(accentPtr);
if (transparencyLevel >= WindowTransparencyLevel.Blur)
{
Win7EnableBlur(transparencyLevel);
}
return transparencyLevel;
}
private WindowTransparencyLevel Win10EnableBlur(WindowTransparencyLevel transparencyLevel)
{
if (_isUsingComposition)
{
var effect = transparencyLevel switch
{
WindowTransparencyLevel.Mica => BlurEffect.Mica,
WindowTransparencyLevel.AcrylicBlur => BlurEffect.Acrylic,
WindowTransparencyLevel.Blur => BlurEffect.Acrylic,
_ => BlurEffect.None
};
if (Win32Platform.WindowsVersion >= WinUiCompositionShared.MinHostBackdropVersion)
{
unsafe
{
int pvUseBackdropBrush = effect == BlurEffect.Acrylic ? 1 : 0;
DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_USE_HOSTBACKDROPBRUSH, &pvUseBackdropBrush, sizeof(int));
}
}
if (Win32Platform.WindowsVersion < WinUiCompositionShared.MinHostBackdropVersion && effect == BlurEffect.Mica)
{
effect = BlurEffect.Acrylic;
}
_blurHost?.SetBlur(effect);
return transparencyLevel;
}
else
{
bool canUseAcrylic = Win32Platform.WindowsVersion.Major > 10 || Win32Platform.WindowsVersion.Build >= 19628;
var accent = new AccentPolicy();
var accentStructSize = Marshal.SizeOf();
if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur && !canUseAcrylic)
{
transparencyLevel = WindowTransparencyLevel.Blur;
}
switch (transparencyLevel)
{
default:
case WindowTransparencyLevel.None:
accent.AccentState = AccentState.ACCENT_DISABLED;
break;
case WindowTransparencyLevel.Transparent:
accent.AccentState = AccentState.ACCENT_ENABLE_TRANSPARENTGRADIENT;
break;
case WindowTransparencyLevel.Blur:
accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND;
break;
case WindowTransparencyLevel.AcrylicBlur:
case WindowTransparencyLevel.ForceAcrylicBlur: // hack-force acrylic.
case WindowTransparencyLevel.Mica:
accent.AccentState = AccentState.ACCENT_ENABLE_ACRYLIC;
transparencyLevel = WindowTransparencyLevel.AcrylicBlur;
break;
}
accent.AccentFlags = 2;
accent.GradientColor = 0x01000000;
var accentPtr = Marshal.AllocHGlobal(accentStructSize);
Marshal.StructureToPtr(accent, accentPtr, false);
var data = new WindowCompositionAttributeData();
data.Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY;
data.SizeOfData = accentStructSize;
data.Data = accentPtr;
SetWindowCompositionAttribute(_hwnd, ref data);
Marshal.FreeHGlobal(accentPtr);
return transparencyLevel;
}
}
public IEnumerable