csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
303 lines
11 KiB
303 lines
11 KiB
using System;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using Avalonia.Reactive;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using Avalonia.Controls.ApplicationLifetimes;
|
|
using Avalonia.Controls.Platform;
|
|
using Avalonia.Input;
|
|
using Avalonia.Input.Platform;
|
|
using Avalonia.Platform;
|
|
using Avalonia.Rendering;
|
|
using Avalonia.Rendering.Composition;
|
|
using Avalonia.Threading;
|
|
using Avalonia.Utilities;
|
|
using Avalonia.Win32.Input;
|
|
using Avalonia.Win32.Interop;
|
|
using static Avalonia.Win32.Interop.UnmanagedMethods;
|
|
|
|
namespace Avalonia
|
|
{
|
|
public static class Win32ApplicationExtensions
|
|
{
|
|
public static AppBuilder UseWin32(this AppBuilder builder)
|
|
{
|
|
return builder
|
|
.UseStandardRuntimePlatformSubsystem()
|
|
.UseWindowingSubsystem(
|
|
() => Win32.Win32Platform.Initialize(
|
|
AvaloniaLocator.Current.GetService<Win32PlatformOptions>() ?? new Win32PlatformOptions()),
|
|
"Win32");
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace Avalonia.Win32
|
|
{
|
|
internal class Win32Platform : IWindowingPlatform, IPlatformIconLoader, IPlatformLifetimeEventsImpl
|
|
{
|
|
private static readonly Win32Platform s_instance = new();
|
|
private static Win32PlatformOptions? s_options;
|
|
private static Compositor? s_compositor;
|
|
internal const int TIMERID_DISPATCHER = 1;
|
|
|
|
private WndProc? _wndProcDelegate;
|
|
private IntPtr _hwnd;
|
|
private Win32DispatcherImpl _dispatcher;
|
|
|
|
public Win32Platform()
|
|
{
|
|
CreateMessageWindow();
|
|
_dispatcher = new Win32DispatcherImpl(_hwnd);
|
|
}
|
|
|
|
internal static Win32Platform Instance => s_instance;
|
|
internal static IPlatformSettings PlatformSettings => AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
|
|
|
|
internal IntPtr Handle => _hwnd;
|
|
|
|
/// <summary>
|
|
/// Gets the actual WindowsVersion. Same as the info returned from RtlGetVersion.
|
|
/// </summary>
|
|
public static Version WindowsVersion { get; } = RtlGetVersion();
|
|
|
|
internal static bool UseOverlayPopups => Options.OverlayPopups;
|
|
|
|
public static Win32PlatformOptions Options
|
|
=> s_options ?? throw new InvalidOperationException($"{nameof(Win32Platform)} hasn't been initialized");
|
|
|
|
internal static Compositor Compositor
|
|
=> s_compositor ?? throw new InvalidOperationException($"{nameof(Win32Platform)} hasn't been initialized");
|
|
|
|
public static void Initialize()
|
|
{
|
|
Initialize(new Win32PlatformOptions());
|
|
}
|
|
|
|
public static void Initialize(Win32PlatformOptions options)
|
|
{
|
|
s_options = options;
|
|
|
|
SetDpiAwareness();
|
|
|
|
var renderTimer = options.ShouldRenderOnUIThread ? new UiThreadRenderTimer(60) : new DefaultRenderTimer(60);
|
|
|
|
AvaloniaLocator.CurrentMutable
|
|
.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
|
|
.Bind<ICursorFactory>().ToConstant(CursorFactory.Instance)
|
|
.Bind<IKeyboardDevice>().ToConstant(WindowsKeyboardDevice.Instance)
|
|
.Bind<IPlatformSettings>().ToSingleton<Win32PlatformSettings>()
|
|
.Bind<IDispatcherImpl>().ToConstant(s_instance._dispatcher)
|
|
.Bind<IRenderTimer>().ToConstant(renderTimer)
|
|
.Bind<IWindowingPlatform>().ToConstant(s_instance)
|
|
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control)
|
|
{
|
|
OpenContextMenu =
|
|
{
|
|
// Add Shift+F10
|
|
new KeyGesture(Key.F10, KeyModifiers.Shift)
|
|
}
|
|
})
|
|
.Bind<IPlatformIconLoader>().ToConstant(s_instance)
|
|
.Bind<NonPumpingLockHelper.IHelperImpl>().ToConstant(NonPumpingWaitHelperImpl.Instance)
|
|
.Bind<IMountedVolumeInfoProvider>().ToConstant(new WindowsMountedVolumeInfoProvider())
|
|
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(s_instance);
|
|
|
|
IPlatformGraphics? platformGraphics;
|
|
if (options.CustomPlatformGraphics is not null)
|
|
{
|
|
if (options.CompositionMode?.Contains(Win32CompositionMode.RedirectionSurface) == false)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.CustomPlatformGraphics)} is only " +
|
|
$"compatible with {nameof(Win32CompositionMode)}.{nameof(Win32CompositionMode.RedirectionSurface)}");
|
|
}
|
|
|
|
platformGraphics = options.CustomPlatformGraphics;
|
|
}
|
|
else
|
|
{
|
|
platformGraphics = Win32GlManager.Initialize();
|
|
}
|
|
|
|
if (OleContext.Current != null)
|
|
AvaloniaLocator.CurrentMutable.Bind<IPlatformDragSource>().ToSingleton<DragSource>();
|
|
|
|
s_compositor = new Compositor( platformGraphics);
|
|
}
|
|
|
|
public event EventHandler<ShutdownRequestedEventArgs>? ShutdownRequested;
|
|
|
|
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]
|
|
private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
|
|
{
|
|
if (msg == (int)WindowsMessage.WM_DISPATCH_WORK_ITEM
|
|
&& wParam.ToInt64() == Win32DispatcherImpl.SignalW
|
|
&& lParam.ToInt64() == Win32DispatcherImpl.SignalL)
|
|
_dispatcher?.DispatchWorkItem();
|
|
|
|
if(msg == (uint)WindowsMessage.WM_QUERYENDSESSION)
|
|
{
|
|
if (ShutdownRequested != null)
|
|
{
|
|
var e = new ShutdownRequestedEventArgs();
|
|
|
|
ShutdownRequested(this, e);
|
|
|
|
if(e.Cancel)
|
|
{
|
|
return IntPtr.Zero;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (msg == (uint)WindowsMessage.WM_SETTINGCHANGE
|
|
&& PlatformSettings is Win32PlatformSettings win32PlatformSettings)
|
|
{
|
|
var changedSetting = Marshal.PtrToStringAuto(lParam);
|
|
if (changedSetting == "ImmersiveColorSet" // dark/light mode
|
|
|| changedSetting == "WindowsThemeElement") // high contrast mode
|
|
{
|
|
win32PlatformSettings.OnColorValuesChanged();
|
|
}
|
|
}
|
|
|
|
if (msg == (uint)WindowsMessage.WM_TIMER)
|
|
{
|
|
if (wParam == (IntPtr)TIMERID_DISPATCHER)
|
|
_dispatcher?.FireTimer();
|
|
}
|
|
|
|
TrayIconImpl.ProcWnd(hWnd, msg, wParam, lParam);
|
|
|
|
return DefWindowProc(hWnd, msg, wParam, lParam);
|
|
}
|
|
|
|
private void CreateMessageWindow()
|
|
{
|
|
// Ensure that the delegate doesn't get garbage collected by storing it as a field.
|
|
_wndProcDelegate = WndProc;
|
|
|
|
WNDCLASSEX wndClassEx = new WNDCLASSEX
|
|
{
|
|
cbSize = Marshal.SizeOf<WNDCLASSEX>(),
|
|
lpfnWndProc = _wndProcDelegate,
|
|
hInstance = GetModuleHandle(null),
|
|
lpszClassName = "AvaloniaMessageWindow " + Guid.NewGuid(),
|
|
};
|
|
|
|
ushort atom = RegisterClassEx(ref wndClassEx);
|
|
|
|
if (atom == 0)
|
|
{
|
|
throw new Win32Exception();
|
|
}
|
|
|
|
_hwnd = CreateWindowEx(0, atom, null, 0, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
|
|
|
|
if (_hwnd == IntPtr.Zero)
|
|
{
|
|
throw new Win32Exception();
|
|
}
|
|
}
|
|
|
|
public ITrayIconImpl CreateTrayIcon()
|
|
{
|
|
return new TrayIconImpl();
|
|
}
|
|
|
|
public IWindowImpl CreateWindow()
|
|
{
|
|
return new WindowImpl();
|
|
}
|
|
|
|
public IWindowImpl CreateEmbeddableWindow()
|
|
{
|
|
var embedded = new EmbeddedWindowImpl();
|
|
embedded.Show(true, false);
|
|
return embedded;
|
|
}
|
|
|
|
public IWindowIconImpl LoadIcon(string fileName)
|
|
{
|
|
using (var stream = File.OpenRead(fileName))
|
|
{
|
|
return new IconImpl(stream);
|
|
}
|
|
}
|
|
|
|
public IWindowIconImpl LoadIcon(Stream stream)
|
|
{
|
|
return new IconImpl(stream);
|
|
}
|
|
|
|
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
|
|
{
|
|
using (var memoryStream = new MemoryStream())
|
|
{
|
|
bitmap.Save(memoryStream);
|
|
return new IconImpl(memoryStream);
|
|
}
|
|
}
|
|
|
|
private static void SetDpiAwareness()
|
|
{
|
|
// Ideally we'd set DPI awareness in the manifest but this doesn't work for netcoreapp2.0
|
|
// apps as they are actually dlls run by a console loader. Instead we have to do it in code,
|
|
// but there are various ways to do this depending on the OS version.
|
|
var user32 = LoadLibrary("user32.dll");
|
|
var method = GetProcAddress(user32, nameof(SetProcessDpiAwarenessContext));
|
|
|
|
var dpiAwareness = Options.DpiAwareness;
|
|
|
|
if (method != IntPtr.Zero)
|
|
{
|
|
if (dpiAwareness == Win32DpiAwareness.Unaware)
|
|
{
|
|
if (SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else if (dpiAwareness == Win32DpiAwareness.SystemDpiAware)
|
|
{
|
|
if (SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else if (dpiAwareness == Win32DpiAwareness.PerMonitorDpiAware)
|
|
{
|
|
if (SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) ||
|
|
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
var shcore = LoadLibrary("shcore.dll");
|
|
method = GetProcAddress(shcore, nameof(SetProcessDpiAwareness));
|
|
|
|
if (method != IntPtr.Zero)
|
|
{
|
|
var awareness = (dpiAwareness) switch
|
|
{
|
|
Win32DpiAwareness.Unaware => PROCESS_DPI_AWARENESS.PROCESS_DPI_UNAWARE,
|
|
Win32DpiAwareness.SystemDpiAware => PROCESS_DPI_AWARENESS.PROCESS_SYSTEM_DPI_AWARE,
|
|
Win32DpiAwareness.PerMonitorDpiAware => PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE,
|
|
_ => PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE,
|
|
};
|
|
|
|
SetProcessDpiAwareness(awareness);
|
|
return;
|
|
}
|
|
|
|
if (dpiAwareness != Win32DpiAwareness.Unaware)
|
|
SetProcessDPIAware();
|
|
}
|
|
}
|
|
}
|
|
|