From 095d3f26c00499f336ab8d26bfa114e560208eea Mon Sep 17 00:00:00 2001 From: Fraser Waters Date: Sun, 23 Mar 2014 16:26:24 +0000 Subject: [PATCH] Use GetMouseMovePointsEx to smooth mouse input. Uses GetMouseMovePointsEx to get mouse move history so mouse events aren't dropped even with low framerates. Fixes #76. --- Source/OpenTK/Platform/Windows/API.cs | 89 +++++++++++++++++++++++++++ Source/OpenTK/Platform/Windows/WinGLNative.cs | 85 +++++++++++++++++++++++-- 2 files changed, 170 insertions(+), 4 deletions(-) diff --git a/Source/OpenTK/Platform/Windows/API.cs b/Source/OpenTK/Platform/Windows/API.cs index e265afd..a29c677 100644 --- a/Source/OpenTK/Platform/Windows/API.cs +++ b/Source/OpenTK/Platform/Windows/API.cs @@ -111,6 +111,13 @@ namespace OpenTK.Platform.Windows internal static class Functions { + #region GetLastError + + [DllImport("Kernel32.dll")] + internal static extern uint GetLastError(); + + #endregion + #region Window functions #region SetWindowPos @@ -371,6 +378,21 @@ namespace OpenTK.Platform.Windows #endregion + #region GetMessageTime + + /// + /// Retrieves the message time for the last message retrieved by the + /// GetMessage function. The time is a long integer that specifies the + /// elapsed time, in milliseconds, from the time the system was started + /// to the time the message was created (that is, placed in the thread's + /// message queue). + /// + /// The return value specifies the message time. + [DllImport("User32.dll")] + internal static extern int GetMessageTime(); + + #endregion + #region SendMessage [DllImport("user32.dll", CharSet = CharSet.Auto)] @@ -1004,6 +1026,30 @@ namespace OpenTK.Platform.Windows [DllImport("user32.dll")] public static extern bool SetCursorPos(int X, int Y); + /// + /// Retrieves a history of up to 64 previous coordinates of the mouse or pen. + /// + /// The size, in bytes, of the MouseMovePoint structure. + /// + /// A pointer to a MOUSEMOVEPOINT structure containing valid mouse + /// coordinates (in screen coordinates). It may also contain a time + /// stamp. + /// + /// + /// A pointer to a buffer that will receive the points. It should be at + /// least cbSize * nBufPoints in size. + /// + /// The number of points to be retrieved. + /// + /// The resolution desired. This parameter can GMMP_USE_DISPLAY_POINTS + /// or GMMP_USE_HIGH_RESOLUTION_POINTS. + /// + /// + [DllImport("user32", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] + unsafe internal static extern int GetMouseMovePointsEx( + uint cbSize, MouseMovePoint* pointsIn, + MouseMovePoint* pointsBufferOut, int nBufPoints, uint resolution); + #region Async input #region GetCursorPos @@ -1633,6 +1679,19 @@ namespace OpenTK.Platform.Windows internal static readonly IntPtr MESSAGE_ONLY = new IntPtr(-3); internal static readonly IntPtr HKEY_LOCAL_MACHINE = new IntPtr(unchecked((int)0x80000002)); + + /// + /// Retrieves the points using the display resolution. + /// + internal const int GMMP_USE_DISPLAY_POINTS = 1; + + /// + /// Retrieves high resolution points. Points can range from zero to + /// 65,535 (0xFFFF) in both x and y coordinates. This is the resolution + /// provided by absolute coordinate pointing devices such as drawing + /// tablets. + /// + internal const int GMMP_USE_HIGH_RESOLUTION_POINTS = 2; } #endregion @@ -2917,6 +2976,36 @@ namespace OpenTK.Platform.Windows #endregion + #region MouseMovePoint + + /// + /// Contains information about the mouse's location in screen coordinates. + /// + [StructLayout(LayoutKind.Sequential)] + public struct MouseMovePoint + { + /// + /// The x-coordinate of the mouse. + /// + public int X; + /// + /// The y-coordinate of the mouse. + /// + public int Y; + /// + /// The time stamp of the mouse coordinate. + /// + public int Time; + /// + /// Additional information associated with this coordinate. + /// + public IntPtr ExtraInfo; + + public static readonly int SizeInBytes = Marshal.SizeOf(default(MouseMovePoint)); + } + + #endregion + #endregion #region --- Enums --- diff --git a/Source/OpenTK/Platform/Windows/WinGLNative.cs b/Source/OpenTK/Platform/Windows/WinGLNative.cs index aa84682..5bfd209 100644 --- a/Source/OpenTK/Platform/Windows/WinGLNative.cs +++ b/Source/OpenTK/Platform/Windows/WinGLNative.cs @@ -70,6 +70,7 @@ namespace OpenTK.Platform.Windows bool borderless_maximized_window_state = false; // Hack to get maximized mode with hidden border (not normally possible). bool focused; bool mouse_outside_window = true; + int mouse_last_timestamp = 0; bool invisible_since_creation; // Set by WindowsMessage.CREATE and consumed by Visible = true (calls BringWindowToFront). int suppress_resize; // Used in WindowBorder and WindowState in order to avoid rapid, consecutive resize events. bool is_in_modal_loop; // set to true whenever we enter the modal resize/move event loop @@ -403,10 +404,86 @@ namespace OpenTK.Platform.Windows void HandleMouseMove(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam) { - Point point = new Point( - (short)((uint)lParam.ToInt32() & 0x0000FFFF), - (short)(((uint)lParam.ToInt32() & 0xFFFF0000) >> 16)); - mouse.Position = point; + unsafe + { + Point point = new Point( + (short)((uint)lParam.ToInt32() & 0x0000FFFF), + (short)(((uint)lParam.ToInt32() & 0xFFFF0000) >> 16)); + + // GetMouseMovePointsEx works with screen coordinates + Point screenPoint = point; + Functions.ClientToScreen(handle, ref screenPoint); + int timestamp = Functions.GetMessageTime(); + + // & 0xFFFF to handle multiple monitors http://support.microsoft.com/kb/269743 + MouseMovePoint movePoint = new MouseMovePoint() + { + X = screenPoint.X & 0xFFFF, + Y = screenPoint.Y & 0xFFFF, + Time = timestamp, + }; + + // Max points GetMouseMovePointsEx can return is 64. + int numPoints = 64; + + MouseMovePoint* movePoints = stackalloc MouseMovePoint[numPoints]; + + // GetMouseMovePointsEx fills in movePoints so that the most + // recent events are at low indices in the array. + int points = Functions.GetMouseMovePointsEx( + (uint)MouseMovePoint.SizeInBytes, + &movePoint, movePoints, numPoints, + Constants.GMMP_USE_DISPLAY_POINTS); + + uint lastError = Functions.GetLastError(); + + // No points returned or search point not found + if (points == 0 || (points == -1 && lastError == 1171)) + { + // Just use the mouse move position + mouse.Position = point; + } + else if (points == -1) + { + throw new System.ComponentModel.Win32Exception((int)lastError); + } + else + { + // Exclude the current position. + Point currentScreenPosition = new Point(mouse.X, mouse.Y); + Functions.ClientToScreen(handle, ref currentScreenPosition); + + // Find the first move point we've already seen. + int i = 0; + for (i = 0; i < points; ++i) + { + if (movePoints[i].Time < mouse_last_timestamp) + break; + if (movePoints[i].Time == mouse_last_timestamp && + movePoints[i].X == currentScreenPosition.X && + movePoints[i].Y == currentScreenPosition.Y) + break; + } + + // Now move the mouse to each point before the one just found. + while (--i >= 0) + { + Point position = new Point(movePoints[i].X, movePoints[i].Y); + // Handle multiple monitors http://support.microsoft.com/kb/269743 + if (position.X > 32767) + { + position.X -= 65536; + } + if (position.Y > 32767) + { + position.Y -= 65536; + } + Functions.ScreenToClient(handle, ref position); + mouse.Position = position; + } + } + mouse_last_timestamp = timestamp; + } if (mouse_outside_window) { -- 2.7.4