From ef6c910d307f60aa91372e8b4b45d056973bf42c Mon Sep 17 00:00:00 2001 From: the_fiddler Date: Fri, 22 Oct 2010 13:41:42 +0000 Subject: [PATCH] Initial implementation of raw mouse input on Windows. --- Source/OpenTK/Platform/Windows/API.cs | 130 ++++------ Source/OpenTK/Platform/Windows/WinFactory.cs | 5 +- Source/OpenTK/Platform/Windows/WinRawInput.cs | 7 +- Source/OpenTK/Platform/Windows/WinRawMouse.cs | 228 ++++++++---------- 4 files changed, 166 insertions(+), 204 deletions(-) diff --git a/Source/OpenTK/Platform/Windows/API.cs b/Source/OpenTK/Platform/Windows/API.cs index 20cb38a7..94081054 100644 --- a/Source/OpenTK/Platform/Windows/API.cs +++ b/Source/OpenTK/Platform/Windows/API.cs @@ -851,6 +851,9 @@ namespace OpenTK.Platform.Windows [DllImport("user32.dll", SetLastError = true)] public static extern BOOL BringWindowToTop(HWND hWnd); + [DllImport("user32.dll", SetLastError = true)] + public static extern BOOL SetParent(HWND child, HWND newParent); + #endregion #region Display settings @@ -1353,17 +1356,6 @@ namespace OpenTK.Platform.Windows /// /// GetRawInputData gets the raw input one RawInput structure at a time. In contrast, GetRawInputBuffer gets an array of RawInput structures. /// - [CLSCompliant(false)] - [System.Security.SuppressUnmanagedCodeSecurity] - [DllImport("user32.dll", SetLastError = true)] - internal static extern UINT GetRawInputData( - HRAWINPUT RawInput, - GetRawInputDataEnum Command, - [Out] LPVOID Data, - [In, Out] ref UINT Size, - UINT SizeHeader - ); - [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("user32.dll", SetLastError = true)] internal static extern INT GetRawInputData( @@ -1395,17 +1387,6 @@ namespace OpenTK.Platform.Windows /// /// GetRawInputData gets the raw input one RawInput structure at a time. In contrast, GetRawInputBuffer gets an array of RawInput structures. /// - [CLSCompliant(false)] - [System.Security.SuppressUnmanagedCodeSecurity] - [DllImport("user32.dll", SetLastError = true)] - internal static extern UINT GetRawInputData( - HRAWINPUT RawInput, - GetRawInputDataEnum Command, - /*[MarshalAs(UnmanagedType.LPStruct)]*/ [Out] out RawInput Data, - [In, Out] ref UINT Size, - UINT SizeHeader - ); - [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("user32.dll", SetLastError = true)] internal static extern INT GetRawInputData( @@ -1416,6 +1397,16 @@ namespace OpenTK.Platform.Windows INT SizeHeader ); + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("user32.dll", SetLastError = true)] + unsafe internal static extern INT GetRawInputData( + HRAWINPUT RawInput, + GetRawInputDataEnum Command, + RawInput* Data, + [In, Out] ref INT Size, + INT SizeHeader + ); + #endregion #region IntPtr NextRawInputStructure(IntPtr data) @@ -1487,7 +1478,7 @@ namespace OpenTK.Platform.Windows #region --- Constants --- - internal struct Constants + static class Constants { // Found in winuser.h internal const int KEYBOARD_OVERRUN_MAKE_CODE = 0xFF; @@ -1574,6 +1565,8 @@ namespace OpenTK.Platform.Windows // (found in winuser.h) internal const int ENUM_REGISTRY_SETTINGS = -2; internal const int ENUM_CURRENT_SETTINGS = -1; + + internal static readonly IntPtr MESSAGE_ONLY = new IntPtr(-3); } #endregion @@ -2231,25 +2224,11 @@ namespace OpenTK.Platform.Windows /// To get device specific information, call GetRawInputDeviceInfo with the hDevice from RAWINPUTHEADER. /// Raw input is available only when the application calls RegisterRawInputDevices with valid device specifications. /// - [StructLayout(LayoutKind.Sequential)] - internal struct RawInput + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct RawInput { - internal RawInputHeader Header; - internal RawInputData Data; - - internal byte[] ToByteArray() - { - unsafe - { - byte[] dump = new byte[API.RawInputSize]; - fixed (RawInputDeviceType* ptr = &Header.Type) - { - for (int i = 0; i < API.RawInputSize; i++) - dump[i] = *((byte*)ptr + i); - return dump; - } - } - } + public RawInputHeader Header; + public RawInputData Data; } [StructLayout(LayoutKind.Explicit)] @@ -2350,38 +2329,9 @@ namespace OpenTK.Platform.Windows /// /// Contains information about the state of the mouse. /// - [StructLayout(LayoutKind.Sequential, Pack=1)] + [StructLayout(LayoutKind.Explicit)] internal struct RawMouse { - //internal RawMouseFlags Flags; // USHORT in winuser.h, but only INT works -- USHORT returns 0. - USHORT flags; - byte for_alignment; // Not used -- used for alignment - /// - /// Reserved. - /// - //ULONG Buttons; - internal USHORT buttonFlags; - /// - /// If usButtonFlags is RI_MOUSE_WHEEL, this member is a signed value that specifies the wheel delta. - /// - internal USHORT ButtonData;// { get { return (USHORT)((Buttons & 0xFFFF0000) >> 16); } } - /// - /// Raw state of the mouse buttons. - /// - internal ULONG RawButtons; - /// - /// Motion in the X direction. This is signed relative motion or absolute motion, depending on the value of usFlags. - /// - internal LONG LastX; - /// - /// Motion in the Y direction. This is signed relative motion or absolute motion, depending on the value of usFlags. - /// - internal LONG LastY; - /// - /// Device-specific additional information for the event. - /// - internal ULONG ExtraInformation; - /// /// Mouse state. This member can be any reasonable combination of the following. /// MOUSE_ATTRIBUTES_CHANGED @@ -2393,12 +2343,34 @@ namespace OpenTK.Platform.Windows /// MOUSE_VIRTUAL_DESKTOP /// Mouse coordinates are mapped to the virtual desktop (for a multiple monitor system). /// - internal RawMouseFlags Flags { get { return (RawMouseFlags)(flags); } } + [FieldOffset(0)] public RawMouseFlags Flags; // USHORT in winuser.h, but only INT works -- USHORT returns 0. + + [FieldOffset(4)] public RawInputMouseState ButtonFlags; /// - /// Transition state of the mouse buttons. + /// If usButtonFlags is RI_MOUSE_WHEEL, this member is a signed value that specifies the wheel delta. /// - internal RawInputMouseState ButtonFlags { get { return (RawInputMouseState)(buttonFlags); } } + [FieldOffset(6)] public USHORT ButtonData; + + /// + /// Raw state of the mouse buttons. + /// + [FieldOffset(8)] public ULONG RawButtons; + + /// + /// Motion in the X direction. This is signed relative motion or absolute motion, depending on the value of usFlags. + /// + [FieldOffset(12)] public LONG LastX; + + /// + /// Motion in the Y direction. This is signed relative motion or absolute motion, depending on the value of usFlags. + /// + [FieldOffset(16)] public LONG LastY; + + /// + /// Device-specific additional information for the event. + /// + [FieldOffset(20)] public ULONG ExtraInformation; } #endregion @@ -2483,10 +2455,11 @@ namespace OpenTK.Platform.Windows /// Number of HID inputs in bRawData. /// internal DWORD Count; - /// - /// Raw input data as an array of bytes. - /// - internal BYTE RawData; + // The RawData field must be marshalled manually. + ///// + ///// Raw input data as an array of bytes. + ///// + //internal IntPtr RawData; } #endregion @@ -3239,6 +3212,7 @@ namespace OpenTK.Platform.Windows /// /// Mouse indicator flags (found in winuser.h). /// + [Flags] internal enum RawMouseFlags : ushort { /// diff --git a/Source/OpenTK/Platform/Windows/WinFactory.cs b/Source/OpenTK/Platform/Windows/WinFactory.cs index 30b25b74..2616e770 100644 --- a/Source/OpenTK/Platform/Windows/WinFactory.cs +++ b/Source/OpenTK/Platform/Windows/WinFactory.cs @@ -83,7 +83,10 @@ namespace OpenTK.Platform.Windows public virtual OpenTK.Input.IMouseDriver CreateMouseDriver() { - throw new NotImplementedException(); + if (System.Environment.OSVersion.Version.Major >= 5) + return new WinRawMouse(); + else + return new WMInput(null); } #endregion diff --git a/Source/OpenTK/Platform/Windows/WinRawInput.cs b/Source/OpenTK/Platform/Windows/WinRawInput.cs index 3cd04ec6..4d4a2f77 100644 --- a/Source/OpenTK/Platform/Windows/WinRawInput.cs +++ b/Source/OpenTK/Platform/Windows/WinRawInput.cs @@ -38,9 +38,10 @@ namespace OpenTK.Platform.Windows Debug.Indent(); AssignHandle(parent.WindowHandle); + WinWindowInfo win = new WinWindowInfo(this.Handle, parent); Debug.Print("Input window attached to parent {0}", parent); keyboardDriver = new WinRawKeyboard(this.Handle); - mouseDriver = new WinRawMouse(this.Handle); + mouseDriver = new WinRawMouse(); Debug.Unindent(); @@ -93,9 +94,7 @@ namespace OpenTK.Platform.Windows return; case RawInputDeviceType.MOUSE: - if (!mouseDriver.ProcessEvent(data)) - Functions.DefRawInputProc(ref data, 1, (uint)API.RawInputHeaderSize); - return; + throw new NotSupportedException(); case RawInputDeviceType.HID: Functions.DefRawInputProc(ref data, 1, (uint)API.RawInputHeaderSize); diff --git a/Source/OpenTK/Platform/Windows/WinRawMouse.cs b/Source/OpenTK/Platform/Windows/WinRawMouse.cs index 46ab54f4..f242bd89 100644 --- a/Source/OpenTK/Platform/Windows/WinRawMouse.cs +++ b/Source/OpenTK/Platform/Windows/WinRawMouse.cs @@ -6,12 +6,11 @@ using System; using System.Collections.Generic; -using System.Text; -using OpenTK.Input; using System.Diagnostics; +using System.Drawing; using System.Runtime.InteropServices; using Microsoft.Win32; -using System.Drawing; +using OpenTK.Input; namespace OpenTK.Platform.Windows { @@ -19,54 +18,64 @@ namespace OpenTK.Platform.Windows /// /// Contains methods to register for and process mouse WM_INPUT messages. /// - internal class WinRawMouse : IMouseDriver, IDisposable + internal class WinRawMouse : IMouseDriver { - private List mice = new List(); - private IntPtr window; - - #region --- Constructors --- + List mice; + Dictionary rawids; // ContextHandle instead of IntPtr for fast dictionary access + readonly INativeWindow native; + readonly IntPtr window; + readonly WindowProcedure WndProc; + readonly IntPtr OldWndProc; internal WinRawMouse() - : this(IntPtr.Zero) - { - } - - internal WinRawMouse(IntPtr windowHandle) { Debug.WriteLine("Initializing mouse driver (WinRawMouse)."); Debug.Indent(); - this.window = windowHandle; + // Create a new message-only window to retrieve WM_INPUT messages. + native = new NativeWindow(); + window = (native.WindowInfo as WinWindowInfo).WindowHandle; + //Functions.SetParent(window, Constants.MESSAGE_ONLY); + // Subclass the window to retrieve the events we are interested in. + WndProc = WindowProcedure; + OldWndProc = Functions.SetWindowLong(window, WndProc); + native.ProcessEvents(); - RegisterDevices(); + RegisterDevices(window, out mice, out rawids); Debug.Unindent(); } - #endregion + #region IMouseDriver Members - #region --- IMouseDriver Members --- - - public IList Mouse - { - get { return mice; } - } + public IList Mouse { get { throw new NotImplementedException(); } } public MouseState GetState() { - throw new NotImplementedException(); + native.ProcessEvents(); + if (mice.Count > 0) + return mice[0]; + else + return new MouseState(); } public MouseState GetState(int index) { - throw new NotImplementedException(); + native.ProcessEvents(); + if (index < mice.Count) + return mice[index]; + else + return new MouseState(); } - #region public int RegisterDevices() + #endregion - public int RegisterDevices() + static int RegisterDevices(IntPtr window, out List mice, out Dictionary rawids) { int count = WinRawInput.DeviceCount; + mice = new List(); + rawids = new Dictionary(); + RawInputDeviceList[] ridl = new RawInputDeviceList[count]; for (int i = 0; i < count; i++) ridl[i] = new RawInputDeviceList(); @@ -114,22 +123,14 @@ namespace OpenTK.Platform.Windows if (!String.IsNullOrEmpty(deviceClass) && deviceClass.ToLower().Equals("mouse")) { - OpenTK.Input.MouseDevice mouse = new OpenTK.Input.MouseDevice(); - mouse.Description = deviceDesc; - - // Register the keyboard: + // Register the device: RawInputDeviceInfo info = new RawInputDeviceInfo(); int devInfoSize = API.RawInputDeviceInfoSize; Functions.GetRawInputDeviceInfo(ridl[i].Device, RawInputDeviceInfoEnum.DEVICEINFO, info, ref devInfoSize); - mouse.NumberOfButtons = info.Device.Mouse.NumberOfButtons; - mouse.NumberOfWheels = info.Device.Mouse.HasHorizontalWheel ? 1 : 0; - - mouse.DeviceID = ridl[i].Device;//(IntPtr)info.Device.Mouse.Id; - - this.RegisterRawDevice(mouse); - mice.Add(mouse); + mice.Add(RegisterRawDevice(deviceDesc, window)); + rawids.Add(new ContextHandle(ridl[i].Device), mice.Count - 1); } } } @@ -137,14 +138,9 @@ namespace OpenTK.Platform.Windows return count; } - #endregion - - #endregion - - #region internal void RegisterRawDevice(OpenTK.Input.Mouse mouse) - - internal void RegisterRawDevice(OpenTK.Input.MouseDevice mouse) + static MouseState RegisterRawDevice(string device, IntPtr window) { + MouseState state = new MouseState(); RawInputDevice[] rid = new RawInputDevice[1]; // Mouse is 1/2 (page/id). See http://www.microsoft.com/whdc/device/input/HID_HWID.mspx rid[0] = new RawInputDevice(); @@ -164,106 +160,96 @@ namespace OpenTK.Platform.Windows } else { - Debug.Print("Registered mouse {0}", mouse.ToString()); + Debug.Print("Registered mouse {0}", device); Point p = new Point(); if (Functions.GetCursorPos(ref p)) - mouse.Position = p; + { + state.X = p.X; + state.Y = p.Y; + } } + + return state; } - #endregion - - #region internal bool ProcessEvent(API.RawInput rin) - - /// - /// Processes raw input events. - /// - /// - /// - internal bool ProcessEvent(RawInput rin) + IntPtr WindowProcedure(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam) { - //MouseDevice mouse = mice.Find(delegate(MouseDevice m) - //{ - // return m.DeviceID == rin.Header.Device; - //}); - MouseDevice mouse; - if (mice.Count > 0) mouse = mice[0]; - else return false; - - switch (rin.Header.Type) + switch (message) { - case RawInputDeviceType.MOUSE: - if ((rin.Data.Mouse.ButtonFlags & RawInputMouseState.LEFT_BUTTON_DOWN) != 0) mouse[MouseButton.Left] = true; - if ((rin.Data.Mouse.ButtonFlags & RawInputMouseState.LEFT_BUTTON_UP) != 0) mouse[MouseButton.Left] = false; - if ((rin.Data.Mouse.ButtonFlags & RawInputMouseState.RIGHT_BUTTON_DOWN) != 0) mouse[MouseButton.Right] = true; - if ((rin.Data.Mouse.ButtonFlags & RawInputMouseState.RIGHT_BUTTON_UP) != 0) mouse[MouseButton.Right] = false; - if ((rin.Data.Mouse.ButtonFlags & RawInputMouseState.MIDDLE_BUTTON_DOWN) != 0) mouse[MouseButton.Middle] = true; - if ((rin.Data.Mouse.ButtonFlags & RawInputMouseState.MIDDLE_BUTTON_UP) != 0) mouse[MouseButton.Middle] = false; - if ((rin.Data.Mouse.ButtonFlags & RawInputMouseState.BUTTON_4_DOWN) != 0) mouse[MouseButton.Button1] = true; - if ((rin.Data.Mouse.ButtonFlags & RawInputMouseState.BUTTON_4_UP) != 0) mouse[MouseButton.Button1] = false; - if ((rin.Data.Mouse.ButtonFlags & RawInputMouseState.BUTTON_5_DOWN) != 0) mouse[MouseButton.Button2] = true; - if ((rin.Data.Mouse.ButtonFlags & RawInputMouseState.BUTTON_5_UP) != 0) mouse[MouseButton.Button2] = false; + case WindowMessage.INPUT: + int expected_size = 0, real_size = 0; + RawInput data = new RawInput(); - if ((rin.Data.Mouse.ButtonFlags & RawInputMouseState.WHEEL) != 0) - mouse.Wheel += (short)rin.Data.Mouse.ButtonData / 120; - - if ((rin.Data.Mouse.Flags & RawMouseFlags.MOUSE_MOVE_ABSOLUTE) != 0) - { - mouse.Position = new Point(rin.Data.Mouse.LastX, rin.Data.Mouse.LastY); - } - else - { // Seems like MOUSE_MOVE_RELATIVE is the default, unless otherwise noted. - mouse.Position = new Point(mouse.X + rin.Data.Mouse.LastX, - mouse.Y + rin.Data.Mouse.LastY); - } - - if ((rin.Data.Mouse.Flags & RawMouseFlags.MOUSE_VIRTUAL_DESKTOP) != 0) - Debug.WriteLine(String.Format("Mouse {0} defines MOUSE_VIRTUAL_DESKTOP flag, please report at http://www.opentk.com", mouse.ToString())); + // Get the size of the input buffer + Functions.GetRawInputData(lParam, GetRawInputDataEnum.INPUT, + IntPtr.Zero, ref expected_size, API.RawInputHeaderSize); - return true; + // Read the actual data + unsafe + { + real_size = Functions.GetRawInputData(lParam, GetRawInputDataEnum.INPUT, + &data, ref expected_size, API.RawInputHeaderSize); + } + + if (real_size == expected_size) + { + if (data.Header.Type == RawInputDeviceType.MOUSE) + { + if (ProcessEvent(data.Header.Device, data.Data.Mouse)) + { + return IntPtr.Zero; + } + } + } + // We didn't handle this message after all, give it back to the old WndProc. + goto default; default: - throw new ApplicationException("WinRawMouse driver received invalid data."); + return Functions.CallWindowProc(OldWndProc, handle, message, wParam, lParam); } } - #endregion - - #region public void Poll() - - public void Poll() + bool ProcessEvent(IntPtr device, RawMouse raw) { - } + if (mice.Count == 0) + return false; - #endregion + ContextHandle handle = new ContextHandle(device); + MouseState mouse; + if (rawids.ContainsKey(handle)) + mouse = mice[rawids[handle]]; + else + return false; - #region --- IDisposable Members --- + if ((raw.ButtonFlags & RawInputMouseState.LEFT_BUTTON_DOWN) != 0) mouse.EnableBit((int)MouseButton.Left); + if ((raw.ButtonFlags & RawInputMouseState.LEFT_BUTTON_UP) != 0) mouse.DisableBit((int)MouseButton.Left); + if ((raw.ButtonFlags & RawInputMouseState.RIGHT_BUTTON_DOWN) != 0) mouse.EnableBit((int)MouseButton.Right); + if ((raw.ButtonFlags & RawInputMouseState.RIGHT_BUTTON_UP) != 0) mouse.DisableBit((int)MouseButton.Right); + if ((raw.ButtonFlags & RawInputMouseState.MIDDLE_BUTTON_DOWN) != 0) mouse.EnableBit((int)MouseButton.Middle); + if ((raw.ButtonFlags & RawInputMouseState.MIDDLE_BUTTON_UP) != 0) mouse.DisableBit((int)MouseButton.Middle); + if ((raw.ButtonFlags & RawInputMouseState.BUTTON_4_DOWN) != 0) mouse.EnableBit((int)MouseButton.Button1); + if ((raw.ButtonFlags & RawInputMouseState.BUTTON_4_UP) != 0) mouse.DisableBit((int)MouseButton.Button1); + if ((raw.ButtonFlags & RawInputMouseState.BUTTON_5_DOWN) != 0) mouse.EnableBit((int)MouseButton.Button2); + if ((raw.ButtonFlags & RawInputMouseState.BUTTON_5_UP) != 0) mouse.DisableBit((int)MouseButton.Button2); - private bool disposed; + if ((raw.ButtonFlags & RawInputMouseState.WHEEL) != 0) + mouse.WheelPrecise += (short)raw.ButtonData / 120.0f; - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool manual) - { - if (!disposed) + if ((raw.Flags & RawMouseFlags.MOUSE_MOVE_ABSOLUTE) != 0) { - if (manual) - { - mice.Clear(); - } - disposed = true; + mouse.X = raw.LastX; + mouse.Y = raw.LastY; } + else + { // Seems like MOUSE_MOVE_RELATIVE is the default, unless otherwise noted. + mouse.X += raw.LastX; + mouse.Y += raw.LastY; + } + + mice[rawids[handle]] = mouse; + return true; } - ~WinRawMouse() - { - Dispose(false); - } - #endregion } }