Browse Source

Merge pull request #472 from PixiEditor/mouse-input-thread

Separate thread for the global mouse hook
Egor Mozgovoy 2 years ago
parent
commit
6c54094980

+ 64 - 103
src/PixiEditor/Helpers/GlobalMouseHook.cs

@@ -5,7 +5,9 @@ using System.Diagnostics.CodeAnalysis;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 using System.Windows;
 using System.Windows;
 using System.Windows.Input;
 using System.Windows.Input;
+using System.Windows.Interop;
 using System.Windows.Threading;
 using System.Windows.Threading;
+using PixiEditor.Views;
 
 
 namespace PixiEditor.Helpers;
 namespace PixiEditor.Helpers;
 
 
@@ -13,136 +15,95 @@ public delegate void MouseUpEventHandler(object sender, Point p, MouseButton but
 
 
 // see https://stackoverflow.com/questions/22659925/how-to-capture-mouseup-event-outside-the-wpf-window
 // see https://stackoverflow.com/questions/22659925/how-to-capture-mouseup-event-outside-the-wpf-window
 [ExcludeFromCodeCoverage]
 [ExcludeFromCodeCoverage]
-internal static class GlobalMouseHook
+internal class GlobalMouseHook
 {
 {
-    private const int WH_MOUSE_LL = 14;
-    private const int WM_LBUTTONUP = 0x0202;
-    private const int WM_MBUTTONUP = 0x0208;
-    private const int WM_RBUTTONUP = 0x0205;
+    private static readonly Lazy<GlobalMouseHook> lazy = new Lazy<GlobalMouseHook>(() => new GlobalMouseHook());
+    public static GlobalMouseHook Instance => lazy.Value;
 
 
-    private static int mouseHookHandle;
-    private static HookProc mouseDelegate;
+    public event MouseUpEventHandler OnMouseUp;
 
 
-    private delegate int HookProc(int nCode, int wParam, IntPtr lParam);
+    private int mouseHookHandle;
+    private Win32.HookProc mouseDelegate;
 
 
-    public static event MouseUpEventHandler OnMouseUp
+    private Thread mouseHookWindowThread;
+    private IntPtr mainWindowHandle;
+    private IntPtr childWindowHandle;
+
+    private GlobalMouseHook() { }
+
+    public void Initilize(MainWindow window)
     {
     {
-        add
-        {
-            // disable low-level hook in debug to prevent mouse lag when pausing in debugger
-#if !DEBUG
-                Subscribe();
+        // disable low-level hook in debug to prevent mouse lag when pausing in debugger
+#if DEBUG
+        return;
 #endif
 #endif
-            MouseUp += value;
-        }
+        mainWindowHandle = new WindowInteropHelper(window).Handle;
+        if (mainWindowHandle == IntPtr.Zero)
+            throw new InvalidOperationException();
 
 
-        remove
+        window.Closed += (_, _) =>
         {
         {
-            MouseUp -= value;
-#if !DEBUG
-                Unsubscribe();
-#endif
-        }
-    }
-
-    private static event MouseUpEventHandler MouseUp;
-
-    public static void RaiseMouseUp()
-    {
-        MouseUp?.Invoke(default, default, default);
-    }
+            if (childWindowHandle != IntPtr.Zero)
+                Win32.PostMessage(childWindowHandle, Win32.WM_CLOSE, 0, 0);
+        };
 
 
-    private static void Unsubscribe()
-    {
-        if (mouseHookHandle != 0)
+        mouseHookWindowThread = new Thread(StartMouseHook)
         {
         {
-            int result = UnhookWindowsHookEx(mouseHookHandle);
-            mouseHookHandle = 0;
-            mouseDelegate = null;
-            if (result == 0)
-            {
-                int errorCode = Marshal.GetLastWin32Error();
-                throw new Win32Exception(errorCode);
-            }
-        }
+            Name = $"{nameof(GlobalMouseHook)} Thread"
+        };
+        mouseHookWindowThread.Start();
     }
     }
 
 
-    private static void Subscribe()
+    private void StartMouseHook()
     {
     {
+        LowLevelWindow window = new LowLevelWindow(nameof(GlobalMouseHook), mainWindowHandle);
+        childWindowHandle = window.WindowHandle;
+
+        mouseDelegate = MouseHookProc;
+        mouseHookHandle = Win32.SetWindowsHookEx(
+            Win32.WH_MOUSE_LL,
+            mouseDelegate,
+            Win32.GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName),
+            0);
         if (mouseHookHandle == 0)
         if (mouseHookHandle == 0)
         {
         {
-            mouseDelegate = MouseHookProc;
-            mouseHookHandle = SetWindowsHookEx(
-                WH_MOUSE_LL,
-                mouseDelegate,
-                GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName),
-                0);
-            if (mouseHookHandle == 0)
-            {
-                int errorCode = Marshal.GetLastWin32Error();
-                throw new Win32Exception(errorCode);
-            }
+            int errorCode = Marshal.GetLastWin32Error();
+            throw new Win32Exception(errorCode);
         }
         }
+
+        window.RunEventLoop();
     }
     }
 
 
-    private static int MouseHookProc(int nCode, int wParam, IntPtr lParam)
+    //private void Unsubscribe()
+    //{
+    //    int result = Win32.UnhookWindowsHookEx(mouseHookHandle);
+    //    mouseHookHandle = 0;
+    //    mouseDelegate = null;
+    //    if (result == 0)
+    //    {
+    //        int errorCode = Marshal.GetLastWin32Error();
+    //        throw new Win32Exception(errorCode);
+    //    }
+    //}
+
+    private int MouseHookProc(int nCode, int wParam, IntPtr lParam)
     {
     {
         if (nCode >= 0)
         if (nCode >= 0)
         {
         {
-            MSLLHOOKSTRUCT mouseHookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
-            if (wParam == WM_LBUTTONUP || wParam == WM_MBUTTONUP || wParam == WM_RBUTTONUP)
+            Win32.MSLLHOOKSTRUCT mouseHookStruct = (Win32.MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(Win32.MSLLHOOKSTRUCT));
+            if (wParam == Win32.WM_LBUTTONUP || wParam == Win32.WM_MBUTTONUP || wParam == Win32.WM_RBUTTONUP)
             {
             {
-                if (MouseUp != null)
+                if (OnMouseUp is not null)
                 {
                 {
 
 
-                    MouseButton button = wParam == WM_LBUTTONUP ? MouseButton.Left
-                        : wParam == WM_MBUTTONUP ? MouseButton.Middle : MouseButton.Right;
-                    Dispatcher.CurrentDispatcher.BeginInvoke(() =>
-                        MouseUp.Invoke(null, new Point(mouseHookStruct.Pt.X, mouseHookStruct.Pt.Y), button));
+                    MouseButton button = wParam == Win32.WM_LBUTTONUP ? MouseButton.Left
+                        : wParam == Win32.WM_MBUTTONUP ? MouseButton.Middle : MouseButton.Right;
+                    Application.Current?.Dispatcher.BeginInvoke(() =>
+                        OnMouseUp.Invoke(null, new Point(mouseHookStruct.Pt.X, mouseHookStruct.Pt.Y), button));
                 }
                 }
             }
             }
         }
         }
 
 
-        return CallNextHookEx(mouseHookHandle, nCode, wParam, lParam);
-    }
-
-    [DllImport(
-        "user32.dll",
-        CharSet = CharSet.Auto,
-        CallingConvention = CallingConvention.StdCall,
-        SetLastError = true)]
-    private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
-
-    [DllImport(
-        "user32.dll",
-        CharSet = CharSet.Auto,
-        CallingConvention = CallingConvention.StdCall,
-        SetLastError = true)]
-    private static extern int UnhookWindowsHookEx(int idHook);
-
-    [DllImport(
-        "user32.dll",
-        CharSet = CharSet.Auto,
-        CallingConvention = CallingConvention.StdCall)]
-    private static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam);
-
-    [DllImport("kernel32.dll")]
-    private static extern IntPtr GetModuleHandle(string name);
-
-    [StructLayout(LayoutKind.Sequential)]
-    private struct POINT
-    {
-        public int X;
-        public int Y;
-    }
-
-    [StructLayout(LayoutKind.Sequential)]
-    private struct MSLLHOOKSTRUCT
-    {
-        public POINT Pt;
-        public uint MouseData;
-        public uint Flags;
-        public uint Time;
-        public IntPtr DwExtraInfo;
+        return Win32.CallNextHookEx(mouseHookHandle, nCode, wParam, lParam);
     }
     }
 }
 }

+ 2 - 41
src/PixiEditor/Helpers/InputKeyHelpers.cs

@@ -28,10 +28,10 @@ internal static class InputKeyHelpers
         int virtualKey = KeyInterop.VirtualKeyFromKey(key);
         int virtualKey = KeyInterop.VirtualKeyFromKey(key);
         byte[] keyboardState = new byte[256];
         byte[] keyboardState = new byte[256];
 
 
-        uint scanCode = MapVirtualKeyExW((uint)virtualKey, MapType.MAPVK_VK_TO_VSC, culture.KeyboardLayoutId);
+        uint scanCode = Win32.MapVirtualKeyExW((uint)virtualKey, Win32.MapType.MAPVK_VK_TO_VSC, culture.KeyboardLayoutId);
         StringBuilder stringBuilder = new(3);
         StringBuilder stringBuilder = new(3);
 
 
-        int result = ToUnicode((uint)virtualKey, scanCode, keyboardState, stringBuilder, stringBuilder.Capacity, 0);
+        int result = Win32.ToUnicode((uint)virtualKey, scanCode, keyboardState, stringBuilder, stringBuilder.Capacity, 0);
 
 
         string stringResult;
         string stringResult;
 
 
@@ -44,43 +44,4 @@ internal static class InputKeyHelpers
 
 
         return stringResult;
         return stringResult;
     }
     }
-
-    private enum MapType : uint
-    {
-        /// <summary>
-        /// The uCode parameter is a virtual-key code and is translated into a scan code. If it is a virtual-key code that does not distinguish between left- and right-hand keys, the left-hand scan code is returned. If there is no translation, the function returns 0.
-        /// </summary>
-        MAPVK_VK_TO_VSC = 0x0,
-
-        /// <summary>
-        /// The uCode parameter is a scan code and is translated into a virtual-key code that does not distinguish between left- and right-hand keys. If there is no translation, the function returns 0.
-        /// </summary>
-        MAPVK_VSC_TO_VK = 0x1,
-
-        /// <summary>
-        /// The uCode parameter is a virtual-key code and is translated into an unshifted character value in the low order word of the return value. Dead keys (diacritics) are indicated by setting the top bit of the return value. If there is no translation, the function returns 0.
-        /// </summary>
-        MAPVK_VK_TO_CHAR = 0x2,
-
-        /// <summary>
-        /// The uCode parameter is a scan code and is translated into a virtual-key code that distinguishes between left- and right-hand keys. If there is no translation, the function returns 0.
-        /// </summary>
-        MAPVK_VSC_TO_VK_EX = 0x3,
-    }
-
-    [DllImport("user32.dll")]
-    private static extern int ToUnicode(
-        uint wVirtKey,
-        uint wScanCode,
-        byte[] lpKeyState,
-        [Out, MarshalAs(UnmanagedType.LPWStr, SizeParamIndex = 4)]
-        StringBuilder pwszBuff,
-        int cchBuff,
-        uint wFlags);
-
-    [DllImport("user32.dll")]
-    private static extern bool GetKeyboardState(byte[] lpKeyState);
-
-    [DllImport("user32.dll")]
-    private static extern uint MapVirtualKeyExW(uint uCode, MapType uMapType, int hkl);
 }
 }

+ 100 - 0
src/PixiEditor/Helpers/LowLevelWindow.cs

@@ -0,0 +1,100 @@
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace PixiEditor.Helpers;
+internal class LowLevelWindow
+{
+    private bool disposed;
+    private Win32.WndProc wndProcDelegate;
+
+    public IntPtr WindowHandle { get; private set; }
+
+    public LowLevelWindow(string uniqueWindowName, IntPtr parentWindow)
+    {
+        if (string.IsNullOrEmpty(uniqueWindowName))
+            throw new ArgumentException(nameof(uniqueWindowName));
+
+        wndProcDelegate = CustomWndProc;
+
+        // Create WNDCLASS
+        Win32.WNDCLASS windowParams = new Win32.WNDCLASS();
+        windowParams.lpszClassName = uniqueWindowName;
+        windowParams.lpfnWndProc = Marshal.GetFunctionPointerForDelegate(wndProcDelegate);
+
+        ushort classAtom = Win32.RegisterClassW(ref windowParams);
+
+        int lastError = Marshal.GetLastWin32Error();
+        if (classAtom == 0 && lastError != Win32.ERROR_CLASS_ALREADY_EXISTS)
+            throw new Win32Exception("Could not register window class");
+
+        // Create window
+        WindowHandle = Win32.CreateWindowExW(
+            0,
+            uniqueWindowName,
+            String.Empty,
+            Win32.WS_CHILD, //| Win32.WS_OVERLAPPEDWINDOW
+            0,
+            0,
+            0,
+            0,
+            parentWindow,
+            IntPtr.Zero,
+            IntPtr.Zero,
+            IntPtr.Zero
+        );
+
+        if (WindowHandle == 0)
+            throw new Win32Exception("Could not create window");
+
+        //Win32.ShowWindow(WindowHandle, 1);
+        //Win32.UpdateWindow(WindowHandle);
+    }
+
+    public void RunEventLoop()
+    {
+        while (true)
+        {
+            var bRet = Win32.GetMessage(out Win32.MSG msg, WindowHandle, 0, 0);
+            if (bRet == 0 || msg.message == Win32.WM_CLOSE)
+                return;
+
+            if (bRet == -1)
+            {
+                // handle the error and possibly exit
+            }
+            else
+            {
+                Win32.TranslateMessage(ref msg);
+                Win32.DispatchMessage(ref msg);
+            }
+        }
+    }
+
+    private static IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
+    {
+        return Win32.DefWindowProcW(hWnd, msg, wParam, lParam);
+    }
+
+    public void Dispose()
+    {
+        Dispose(true);
+        GC.SuppressFinalize(this);
+    }
+
+    private void Dispose(bool disposing)
+    {
+        if (!disposed)
+        {
+            // Dispose unmanaged resources
+            if (WindowHandle != IntPtr.Zero)
+            {
+                Win32.DestroyWindow(WindowHandle);
+                WindowHandle = IntPtr.Zero;
+            }
+            disposed = true;
+        }
+    }
+
+    ~LowLevelWindow() => Dispose(false);
+}

+ 258 - 0
src/PixiEditor/Helpers/Win32.cs

@@ -0,0 +1,258 @@
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace PixiEditor.Helpers;
+internal class Win32
+{
+    public const uint MONITOR_DEFAULTTONEAREST = 0x00000002;
+    public const int WH_MOUSE_LL = 14;
+
+    public const int WM_GETMINMAXINFO = 0x0024;
+    public const int WM_LBUTTONUP = 0x0202;
+    public const int WM_MBUTTONUP = 0x0208;
+    public const int WM_RBUTTONUP = 0x0205;
+    public const int WM_CLOSE = 0x0010;
+    public const int WM_DESTROY = 0x0002;
+
+    public const int ERROR_CLASS_ALREADY_EXISTS = 1410;
+    public const int CW_USEDEFAULT = unchecked((int)0x80000000);
+
+    public const uint WS_CHILD = 0x40000000;
+    public const uint WS_CAPTION = 0x00C00000;
+    public const uint WS_OVERLAPPED = 0x00000000;
+    public const uint WS_SYSMENU = 0x00080000;
+    public const uint WS_THICKFRAME = 0x00040000;
+    public const uint WS_MINIMIZEBOX = 0x00020000;
+    public const uint WS_MAXIMIZEBOX = 0x00010000;
+    public const uint WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
+
+    public delegate int HookProc(int nCode, int wParam, IntPtr lParam);
+    public delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
+
+    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+    internal struct WNDCLASS
+    {
+        public uint style;
+        public IntPtr lpfnWndProc;
+        public int cbClsExtra;
+        public int cbWndExtra;
+        public IntPtr hInstance;
+        public IntPtr hIcon;
+        public IntPtr hCursor;
+        public IntPtr hbrBackground;
+        [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
+        public string lpszMenuName;
+        [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
+        public string lpszClassName;
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    internal struct MSG
+    {
+        public IntPtr hwnd;
+        public uint message;
+        public IntPtr wParam;
+        public IntPtr lParam;
+        public uint time;
+        public POINT pt;
+        public uint lPrivate;
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    public struct MSLLHOOKSTRUCT
+    {
+        public POINT Pt;
+        public uint MouseData;
+        public uint Flags;
+        public uint Time;
+        public IntPtr DwExtraInfo;
+    }
+
+    public enum MapType : uint
+    {
+        /// <summary>
+        /// The uCode parameter is a virtual-key code and is translated into a scan code. If it is a virtual-key code that does not distinguish between left- and right-hand keys, the left-hand scan code is returned. If there is no translation, the function returns 0.
+        /// </summary>
+        MAPVK_VK_TO_VSC = 0x0,
+
+        /// <summary>
+        /// The uCode parameter is a scan code and is translated into a virtual-key code that does not distinguish between left- and right-hand keys. If there is no translation, the function returns 0.
+        /// </summary>
+        MAPVK_VSC_TO_VK = 0x1,
+
+        /// <summary>
+        /// The uCode parameter is a virtual-key code and is translated into an unshifted character value in the low order word of the return value. Dead keys (diacritics) are indicated by setting the top bit of the return value. If there is no translation, the function returns 0.
+        /// </summary>
+        MAPVK_VK_TO_CHAR = 0x2,
+
+        /// <summary>
+        /// The uCode parameter is a scan code and is translated into a virtual-key code that distinguishes between left- and right-hand keys. If there is no translation, the function returns 0.
+        /// </summary>
+        MAPVK_VSC_TO_VK_EX = 0x3,
+    }
+
+    [Serializable]
+    [StructLayout(LayoutKind.Sequential)]
+    public struct RECT
+    {
+        public int Left;
+        public int Top;
+        public int Right;
+        public int Bottom;
+
+        public RECT(int left, int top, int right, int bottom)
+        {
+            this.Left = left;
+            this.Top = top;
+            this.Right = right;
+            this.Bottom = bottom;
+        }
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    public struct MONITORINFO
+    {
+        public int cbSize;
+        public RECT rcMonitor;
+        public RECT rcWork;
+        public uint dwFlags;
+    }
+
+    [Serializable]
+    [StructLayout(LayoutKind.Sequential)]
+    public struct POINT
+    {
+        public int X;
+        public int Y;
+
+        public POINT(int x, int y)
+        {
+            this.X = x;
+            this.Y = y;
+        }
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    public struct MINMAXINFO
+    {
+        public POINT ptReserved;
+        public POINT ptMaxSize;
+        public POINT ptMaxPosition;
+        public POINT ptMinTrackSize;
+        public POINT ptMaxTrackSize;
+    }
+
+
+    [DllImport("user32.dll")]
+    public static extern IntPtr MonitorFromWindow(IntPtr handle, uint flags);
+
+    [DllImport("user32.dll")]
+    public static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);
+
+    [DllImport("user32.dll")]
+    public static extern int ToUnicode(
+        uint wVirtKey,
+        uint wScanCode,
+        byte[] lpKeyState,
+        [Out, MarshalAs(UnmanagedType.LPWStr, SizeParamIndex = 4)]
+        StringBuilder pwszBuff,
+        int cchBuff,
+        uint wFlags);
+
+    [DllImport("user32.dll")]
+    public static extern bool GetKeyboardState(byte[] lpKeyState);
+
+    [DllImport("user32.dll")]
+    public static extern uint MapVirtualKeyExW(uint uCode, MapType uMapType, int hkl);
+
+    [DllImport(
+        "user32.dll",
+        CharSet = CharSet.Auto,
+        CallingConvention = CallingConvention.StdCall,
+        SetLastError = true)]
+    public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
+
+    [DllImport(
+        "user32.dll",
+        CharSet = CharSet.Auto,
+        CallingConvention = CallingConvention.StdCall,
+        SetLastError = true)]
+    public static extern int UnhookWindowsHookEx(int idHook);
+
+    [DllImport(
+        "user32.dll",
+        CharSet = CharSet.Auto,
+        CallingConvention = CallingConvention.StdCall)]
+    public static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam);
+
+    [DllImport("kernel32.dll")]
+    public static extern IntPtr GetModuleHandle(string name);
+
+    [DllImport("user32.dll", SetLastError = true)]
+    public static extern IntPtr CreateWindowExW(
+       UInt32 dwExStyle,
+       [MarshalAs(UnmanagedType.LPWStr)]
+       string lpClassName,
+       [MarshalAs(UnmanagedType.LPWStr)]
+       string lpWindowName,
+       UInt32 dwStyle,
+       Int32 x,
+       Int32 y,
+       Int32 nWidth,
+       Int32 nHeight,
+       IntPtr hWndParent,
+       IntPtr hMenu,
+       IntPtr hInstance,
+       IntPtr lpParam
+    );
+
+    [DllImport("user32.dll", SetLastError = true)]
+    public static extern IntPtr DefWindowProcW(
+        IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam
+    );
+
+    [DllImport("user32.dll", SetLastError = true)]
+    public static extern bool DestroyWindow(
+        IntPtr hWnd
+    );
+
+    [DllImport("user32.dll", SetLastError = true)]
+    public static extern bool ShowWindow(
+        IntPtr hWnd,
+        int nCmdShow
+    );
+
+    [DllImport("user32.dll", SetLastError = true)]
+    public static extern bool UpdateWindow(
+        IntPtr hWnd
+    );
+
+    [DllImport("user32.dll", SetLastError = true)]
+    public static extern int GetMessage(
+        out MSG lpMsg,
+        IntPtr hWnd,
+        uint wMsgFilterMin,
+        uint wMsgFilterMax
+    );
+
+    [DllImport("user32.dll", SetLastError = true)]
+    public static extern bool DispatchMessage(
+            [In] ref MSG lpMsg
+        );
+
+    [DllImport("user32.dll", SetLastError = true)]
+    public static extern bool TranslateMessage(
+            [In] ref MSG lpMsg
+        );
+
+    [DllImport("user32.dll", SetLastError = true)]
+    public static extern System.UInt16 RegisterClassW(
+        [In] ref WNDCLASS lpWndClass
+    );
+
+    [DllImport("Kernel32.dll", SetLastError = true)]
+    public static extern int GetCurrentThreadId();
+
+    [DllImport("user32.dll")]
+    public static extern bool PostMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);
+}

+ 9 - 70
src/PixiEditor/Helpers/WindowSizeHelper.cs

@@ -9,22 +9,22 @@ static class WindowSizeHelper
     {
     {
         // All windows messages (msg) can be found here
         // All windows messages (msg) can be found here
         // https://docs.microsoft.com/de-de/windows/win32/winmsg/window-notifications
         // https://docs.microsoft.com/de-de/windows/win32/winmsg/window-notifications
-        if (msg == WM_GETMINMAXINFO)
+        if (msg == Win32.WM_GETMINMAXINFO)
         {
         {
             // We need to tell the system what our size should be when maximized. Otherwise it will
             // We need to tell the system what our size should be when maximized. Otherwise it will
             // cover the whole screen, including the task bar.
             // cover the whole screen, including the task bar.
-            MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
+            Win32.MINMAXINFO mmi = (Win32.MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(Win32.MINMAXINFO));
 
 
             // Adjust the maximized size and position to fit the work area of the correct monitor
             // Adjust the maximized size and position to fit the work area of the correct monitor
-            IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
+            IntPtr monitor = Win32.MonitorFromWindow(hwnd, Win32.MONITOR_DEFAULTTONEAREST);
 
 
             if (monitor != IntPtr.Zero)
             if (monitor != IntPtr.Zero)
             {
             {
-                MONITORINFO monitorInfo = default;
-                monitorInfo.cbSize = Marshal.SizeOf(typeof(MONITORINFO));
-                GetMonitorInfo(monitor, ref monitorInfo);
-                RECT rcWorkArea = monitorInfo.rcWork;
-                RECT rcMonitorArea = monitorInfo.rcMonitor;
+                Win32.MONITORINFO monitorInfo = default;
+                monitorInfo.cbSize = Marshal.SizeOf(typeof(Win32.MONITORINFO));
+                Win32.GetMonitorInfo(monitor, ref monitorInfo);
+                Win32.RECT rcWorkArea = monitorInfo.rcWork;
+                Win32.RECT rcMonitorArea = monitorInfo.rcMonitor;
                 mmi.ptMaxPosition.X = Math.Abs(rcWorkArea.Left - rcMonitorArea.Left);
                 mmi.ptMaxPosition.X = Math.Abs(rcWorkArea.Left - rcMonitorArea.Left);
                 mmi.ptMaxPosition.Y = Math.Abs(rcWorkArea.Top - rcMonitorArea.Top);
                 mmi.ptMaxPosition.Y = Math.Abs(rcWorkArea.Top - rcMonitorArea.Top);
                 mmi.ptMaxSize.X = Math.Abs(rcWorkArea.Right - rcWorkArea.Left);
                 mmi.ptMaxSize.X = Math.Abs(rcWorkArea.Right - rcWorkArea.Left);
@@ -36,65 +36,4 @@ static class WindowSizeHelper
 
 
         return IntPtr.Zero;
         return IntPtr.Zero;
     }
     }
-
-    private const int WM_GETMINMAXINFO = 0x0024;
-
-    private const uint MONITOR_DEFAULTTONEAREST = 0x00000002;
-
-    [DllImport("user32.dll")]
-    private static extern IntPtr MonitorFromWindow(IntPtr handle, uint flags);
-
-    [DllImport("user32.dll")]
-    private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);
-
-    [Serializable]
-    [StructLayout(LayoutKind.Sequential)]
-    private struct RECT
-    {
-        public int Left;
-        public int Top;
-        public int Right;
-        public int Bottom;
-
-        public RECT(int left, int top, int right, int bottom)
-        {
-            this.Left = left;
-            this.Top = top;
-            this.Right = right;
-            this.Bottom = bottom;
-        }
-    }
-
-    [StructLayout(LayoutKind.Sequential)]
-    private struct MONITORINFO
-    {
-        public int cbSize;
-        public RECT rcMonitor;
-        public RECT rcWork;
-        public uint dwFlags;
-    }
-
-    [Serializable]
-    [StructLayout(LayoutKind.Sequential)]
-    private struct POINT
-    {
-        public int X;
-        public int Y;
-
-        public POINT(int x, int y)
-        {
-            this.X = x;
-            this.Y = y;
-        }
-    }
-
-    [StructLayout(LayoutKind.Sequential)]
-    private struct MINMAXINFO
-    {
-        public POINT ptReserved;
-        public POINT ptMaxSize;
-        public POINT ptMaxPosition;
-        public POINT ptMinTrackSize;
-        public POINT ptMaxTrackSize;
-    }
-}
+}

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs

@@ -32,7 +32,7 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
         MouseMoveCommand = new RelayCommand(mouseFilter.MouseMoveInlet);
         MouseMoveCommand = new RelayCommand(mouseFilter.MouseMoveInlet);
         MouseUpCommand = new RelayCommand(mouseFilter.MouseUpInlet);
         MouseUpCommand = new RelayCommand(mouseFilter.MouseUpInlet);
         PreviewMouseMiddleButtonCommand = new RelayCommand(OnPreviewMiddleMouseButton);
         PreviewMouseMiddleButtonCommand = new RelayCommand(OnPreviewMiddleMouseButton);
-        GlobalMouseHook.OnMouseUp += mouseFilter.MouseUpInlet;
+        GlobalMouseHook.Instance.OnMouseUp += mouseFilter.MouseUpInlet;
 
 
         InputManager.Current.PreProcessInput += Current_PreProcessInput;
         InputManager.Current.PreProcessInput += Current_PreProcessInput;
 
 

+ 6 - 0
src/PixiEditor/Views/MainWindow.xaml.cs

@@ -53,6 +53,7 @@ internal partial class MainWindow : Window
 
 
         DataContext.CloseAction = Close;
         DataContext.CloseAction = Close;
         Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
         Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
+        ContentRendered += MainWindow_ContentRendered;
 
 
         preferences.AddCallback<bool>("ImagePreviewInTaskbar", x =>
         preferences.AddCallback<bool>("ImagePreviewInTaskbar", x =>
         {
         {
@@ -62,6 +63,11 @@ internal partial class MainWindow : Window
         DataContext.DocumentManagerSubViewModel.ActiveDocumentChanged += DocumentChanged;
         DataContext.DocumentManagerSubViewModel.ActiveDocumentChanged += DocumentChanged;
     }
     }
 
 
+    private void MainWindow_ContentRendered(object sender, EventArgs e)
+    {
+        GlobalMouseHook.Instance.Initilize(this);
+    }
+
     public static MainWindow CreateWithDocuments(IEnumerable<(string? originalPath, byte[] dotPixiBytes)> documents)
     public static MainWindow CreateWithDocuments(IEnumerable<(string? originalPath, byte[] dotPixiBytes)> documents)
     {
     {
         MainWindow window = new();
         MainWindow window = new();