// Copyright (c) The Perspex Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using Perspex.Input.Platform; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Reactive.Disposables; using System.Runtime.InteropServices; using System.Threading; using Perspex.Controls.Platform; using Perspex.Input; using Perspex.Platform; using Perspex.Shared.PlatformSupport; using Perspex.Win32.Input; using Perspex.Win32.Interop; namespace Perspex.Win32 { public class Win32Platform : IPlatformThreadingInterface, IPlatformSettings { private static readonly Win32Platform s_instance = new Win32Platform(); private static Thread _uiThread; private UnmanagedMethods.WndProc _wndProcDelegate; private IntPtr _hwnd; private readonly List _delegates = new List(); public Win32Platform() { CreateMessageWindow(); } public Size DoubleClickSize => new Size( UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXDOUBLECLK), UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYDOUBLECLK)); public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(UnmanagedMethods.GetDoubleClickTime()); public double RenderScalingFactor { get; } = 1; public double LayoutScalingFactor { get; } = 1; private static void InitializeInternal() { PerspexLocator.CurrentMutable .Bind().ToTransient() .Bind().ToSingleton() .Bind().ToConstant(CursorFactory.Instance) .Bind().ToConstant(WindowsKeyboardDevice.Instance) .Bind().ToConstant(WindowsMouseDevice.Instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) .Bind().ToSingleton(); SharedPlatform.Register(); _uiThread = Thread.CurrentThread; } public static void Initialize() { PerspexLocator.CurrentMutable.Bind().ToTransient(); InitializeInternal(); } public static void InitializeEmbedded() { PerspexLocator.CurrentMutable.Bind().ToTransient(); InitializeInternal(); } public bool HasMessages() { UnmanagedMethods.MSG msg; return UnmanagedMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0); } public void ProcessMessage() { UnmanagedMethods.MSG msg; UnmanagedMethods.GetMessage(out msg, IntPtr.Zero, 0, 0); UnmanagedMethods.TranslateMessage(ref msg); UnmanagedMethods.DispatchMessage(ref msg); } public void RunLoop(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { UnmanagedMethods.MSG msg; UnmanagedMethods.GetMessage(out msg, IntPtr.Zero, 0, 0); UnmanagedMethods.TranslateMessage(ref msg); UnmanagedMethods.DispatchMessage(ref msg); } } public IDisposable StartTimer(TimeSpan interval, Action callback) { UnmanagedMethods.TimerProc timerDelegate = (hWnd, uMsg, nIDEvent, dwTime) => callback(); IntPtr handle = UnmanagedMethods.SetTimer( IntPtr.Zero, IntPtr.Zero, (uint)interval.TotalMilliseconds, timerDelegate); // Prevent timerDelegate being garbage collected. _delegates.Add(timerDelegate); return Disposable.Create(() => { _delegates.Remove(timerDelegate); UnmanagedMethods.KillTimer(IntPtr.Zero, handle); }); } private static readonly int SignalW = unchecked((int) 0xdeadbeaf); private static readonly int SignalL = unchecked((int)0x12345678); public void Signal() { UnmanagedMethods.PostMessage( _hwnd, (int) UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM, new IntPtr(SignalW), new IntPtr(SignalL)); } public bool CurrentThreadIsLoopThread => _uiThread == Thread.CurrentThread; public event Action Signaled; [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) UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM && wParam.ToInt64() == SignalW && lParam.ToInt64() == SignalL) { Signaled?.Invoke(); } return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); } private void CreateMessageWindow() { // Ensure that the delegate doesn't get garbage collected by storing it as a field. _wndProcDelegate = new UnmanagedMethods.WndProc(WndProc); UnmanagedMethods.WNDCLASSEX wndClassEx = new UnmanagedMethods.WNDCLASSEX { cbSize = Marshal.SizeOf(typeof(UnmanagedMethods.WNDCLASSEX)), lpfnWndProc = _wndProcDelegate, hInstance = Marshal.GetHINSTANCE(GetType().Module), lpszClassName = "PerspexMessageWindow", }; ushort atom = UnmanagedMethods.RegisterClassEx(ref wndClassEx); if (atom == 0) { throw new Win32Exception(); } _hwnd = UnmanagedMethods.CreateWindowEx(0, atom, null, 0, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); if (_hwnd == IntPtr.Zero) { throw new Win32Exception(); } } } }