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;
using System.Diagnostics;
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 WindowResizeReason _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 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;
internal bool _ignoreWmChar;
private WindowTransparencyLevel _transparencyLevel;
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;
private PlatformThemeVariant _currentThemeVariant;
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
};
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);
if (this is not PopupImpl)
{
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);
_transparencyLevel = _isUsingComposition ? WindowTransparencyLevel.Transparent : WindowTransparencyLevel.None;
s_instances.Add(this);
}
internal 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 => _transparencyLevel;
private set
{
if (_transparencyLevel != value)
{
_transparencyLevel = value;
TransparencyLevelChanged?.Invoke(value);
}
}
}
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(IReadOnlyList transparencyLevels)
{
var windowsVersion = Win32Platform.WindowsVersion;
foreach (var level in transparencyLevels)
{
if (!IsSupported(level, windowsVersion))
continue;
if (level == TransparencyLevel)
return;
if (level == WindowTransparencyLevel.Transparent)
SetTransparencyTransparent(windowsVersion);
else if (level == WindowTransparencyLevel.Blur)
SetTransparencyBlur(windowsVersion);
else if (level == WindowTransparencyLevel.AcrylicBlur)
SetTransparencyAcrylicBlur(windowsVersion);
else if (level == WindowTransparencyLevel.Mica)
SetTransparencyMica(windowsVersion);
TransparencyLevel = level;
return;
}
// If we get here, we didn't find a supported level. Use the defualt of Transparent or
// None, depending on whether composition is enabled.
if (_isUsingComposition)
{
SetTransparencyTransparent(windowsVersion);
TransparencyLevel = WindowTransparencyLevel.Transparent;
}
else
{
TransparencyLevel = WindowTransparencyLevel.None;
}
}
private bool IsSupported(WindowTransparencyLevel level, Version windowsVersion)
{
// Only None is suppported when composition is disabled.
if (!_isUsingComposition)
return level == WindowTransparencyLevel.None;
// When composition is enabled, None is not supported because the backing visual always
// has an alpha channel
if (level == WindowTransparencyLevel.None)
return false;
// Transparent only supported on Windows 8+.
if (level == WindowTransparencyLevel.Transparent)
return windowsVersion >= PlatformConstants.Windows8;
// Blur only supported on Windows 8 and lower.
if (level == WindowTransparencyLevel.Blur)
return windowsVersion < PlatformConstants.Windows10;
// Acrylic is supported on Windows >= 10.0.15063.
if (level == WindowTransparencyLevel.AcrylicBlur)
return windowsVersion >= WinUiCompositionShared.MinAcrylicVersion;
// Mica is supported on Windows >= 10.0.22000.
if (level == WindowTransparencyLevel.Mica)
return windowsVersion >= WinUiCompositionShared.MinHostBackdropVersion;
return false;
}
private void SetTransparencyTransparent(Version windowsVersion)
{
// Transparent only supported with composition on Windows 8+.
if (!_isUsingComposition || windowsVersion < PlatformConstants.Windows8)
return;
if (windowsVersion < PlatformConstants.Windows10)
{
// Some of the AccentState Enum's values have different meanings on Windows 8.x than on
// Windows 10, hence using ACCENT_ENABLE_BLURBEHIND to disable blurbehind ¯\_(ツ)_/¯.
// Hey, I'm just porting what was here before.
SetAccentState(AccentState.ACCENT_ENABLE_BLURBEHIND);
var blurInfo = new DWM_BLURBEHIND(false);
DwmEnableBlurBehindWindow(_hwnd, ref blurInfo);
}
SetUseHostBackdropBrush(false);
_blurHost?.SetBlur(BlurEffect.None);
}
private void SetTransparencyBlur(Version windowsVersion)
{
// Blur only supported with composition on Windows 8 and lower.
if (!_isUsingComposition || windowsVersion >= PlatformConstants.Windows10)
return;
// Some of the AccentState Enum's values have different meanings on Windows 8.x than on
// Windows 10.
SetAccentState(AccentState.ACCENT_DISABLED);
var blurInfo = new DWM_BLURBEHIND(true);
DwmEnableBlurBehindWindow(_hwnd, ref blurInfo);
}
private void SetTransparencyAcrylicBlur(Version windowsVersion)
{
// Acrylic blur only supported with composition on Windows >= 10.0.15063.
if (!_isUsingComposition || windowsVersion < WinUiCompositionShared.MinAcrylicVersion)
return;
SetUseHostBackdropBrush(true);
_blurHost?.SetBlur(BlurEffect.Acrylic);
}
private void SetTransparencyMica(Version windowsVersion)
{
// Mica only supported with composition on Windows >= 10.0.22000.
if (!_isUsingComposition || windowsVersion < WinUiCompositionShared.MinHostBackdropVersion)
return;
SetUseHostBackdropBrush(false);
_blurHost?.SetBlur(_currentThemeVariant switch
{
PlatformThemeVariant.Light => BlurEffect.MicaLight,
PlatformThemeVariant.Dark => BlurEffect.MicaDark,
_ => throw new ArgumentOutOfRangeException()
});
}
private void SetAccentState(AccentState state)
{
var accent = new AccentPolicy();
var accentStructSize = Marshal.SizeOf(accent);
//Some of the AccentState Enum's values have different meanings on Windows 8.x than on Windows 10
accent.AccentState = state;
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);
}
private void SetUseHostBackdropBrush(bool useHostBackdropBrush)
{
if (Win32Platform.WindowsVersion < WinUiCompositionShared.MinHostBackdropVersion)
return;
unsafe
{
var pvUseBackdropBrush = useHostBackdropBrush ? 1 : 0;
DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_USE_HOSTBACKDROPBRUSH, &pvUseBackdropBrush, sizeof(int));
}
}
public IEnumerable