A cross-platform UI framework for .NET
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.
 
 
 

121 lines
4.4 KiB

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia.Threading;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32;
internal class Win32DispatcherImpl : IControlledDispatcherImpl
{
private readonly IntPtr _messageWindow;
private static Thread? s_uiThread;
private readonly Stopwatch _clock = Stopwatch.StartNew();
public Win32DispatcherImpl(IntPtr messageWindow)
{
_messageWindow = messageWindow;
s_uiThread = Thread.CurrentThread;
}
public bool CurrentThreadIsLoopThread => s_uiThread == Thread.CurrentThread;
internal const int SignalW = unchecked((int)0xdeadbeaf);
internal const int SignalL = unchecked((int)0x12345678);
public void Signal() =>
// Messages from PostMessage are always processed before any user input,
// so Win32 should call us ASAP
PostMessage(
_messageWindow,
(int)WindowsMessage.WM_DISPATCH_WORK_ITEM,
new IntPtr(SignalW),
new IntPtr(SignalL));
public void DispatchWorkItem() => Signaled?.Invoke();
public event Action? Signaled;
public event Action? Timer;
public void FireTimer() => Timer?.Invoke();
public void UpdateTimer(long? dueTimeInMs)
{
if (dueTimeInMs == null)
{
KillTimer(_messageWindow, (IntPtr)Win32Platform.TIMERID_DISPATCHER);
}
else
{
var interval = (uint)Math.Min(int.MaxValue - 10, Math.Max(1, dueTimeInMs.Value - Now));
SetTimer(
_messageWindow,
(IntPtr)Win32Platform.TIMERID_DISPATCHER,
interval,
null!);
}
}
public bool CanQueryPendingInput => true;
public bool HasPendingInput
{
get
{
// We need to know if there is any pending input in the Win32
// queue because we want to only process Avalon "background"
// items after Win32 input has been processed.
//
// Win32 provides the GetQueueStatus API -- but it has a major
// drawback: it only counts "new" input. This means that
// sometimes it could return false, even if there really is input
// that needs to be processed. This results in very hard to
// find bugs.
//
// Luckily, Win32 also provides the MsgWaitForMultipleObjectsEx
// API. While more awkward to use, this API can return queue
// status information even if the input is "old". The various
// flags we use are:
//
// QS_INPUT
// This represents any pending input - such as mouse moves, or
// key presses. It also includes the new GenericInput messages.
//
// QS_EVENT
// This is actually a private flag that represents the various
// events that can be queued in Win32. Some of these events
// can cause input, but Win32 doesn't include them in the
// QS_INPUT flag. An example is WM_MOUSELEAVE.
//
// QS_POSTMESSAGE
// If there is already a message in the queue, we need to process
// it before we can process input.
//
// MWMO_INPUTAVAILABLE
// This flag indicates that any input (new or old) is to be
// reported.
//
return MsgWaitForMultipleObjectsEx(0, null, 0,
QueueStatusFlags.QS_INPUT | QueueStatusFlags.QS_EVENT | QueueStatusFlags.QS_POSTMESSAGE,
MsgWaitForMultipleObjectsFlags.MWMO_INPUTAVAILABLE) == 0;
}
}
public void RunLoop(CancellationToken cancellationToken)
{
var result = 0;
while (!cancellationToken.IsCancellationRequested
&& (result = GetMessage(out var msg, IntPtr.Zero, 0, 0)) > 0)
{
TranslateMessage(ref msg);
DispatchMessage(ref msg);
}
if (result < 0)
{
Logging.Logger.TryGet(Logging.LogEventLevel.Error, Logging.LogArea.Win32Platform)
?.Log(this, "Unmanaged error in {0}. Error Code: {1}", nameof(RunLoop), Marshal.GetLastWin32Error());
}
}
public long Now => _clock.ElapsedMilliseconds;
}