Initial implementation of raw mouse input on Windows.

This commit is contained in:
the_fiddler 2010-10-22 13:41:42 +00:00
parent 687594db4c
commit ef6c910d30
4 changed files with 166 additions and 204 deletions

View file

@ -851,6 +851,9 @@ namespace OpenTK.Platform.Windows
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern BOOL BringWindowToTop(HWND hWnd); public static extern BOOL BringWindowToTop(HWND hWnd);
[DllImport("user32.dll", SetLastError = true)]
public static extern BOOL SetParent(HWND child, HWND newParent);
#endregion #endregion
#region Display settings #region Display settings
@ -1353,17 +1356,6 @@ namespace OpenTK.Platform.Windows
/// <remarks> /// <remarks>
/// GetRawInputData gets the raw input one RawInput structure at a time. In contrast, GetRawInputBuffer gets an array of RawInput structures. /// GetRawInputData gets the raw input one RawInput structure at a time. In contrast, GetRawInputBuffer gets an array of RawInput structures.
/// </remarks> /// </remarks>
[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] [System.Security.SuppressUnmanagedCodeSecurity]
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
internal static extern INT GetRawInputData( internal static extern INT GetRawInputData(
@ -1395,17 +1387,6 @@ namespace OpenTK.Platform.Windows
/// <remarks> /// <remarks>
/// GetRawInputData gets the raw input one RawInput structure at a time. In contrast, GetRawInputBuffer gets an array of RawInput structures. /// GetRawInputData gets the raw input one RawInput structure at a time. In contrast, GetRawInputBuffer gets an array of RawInput structures.
/// </remarks> /// </remarks>
[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] [System.Security.SuppressUnmanagedCodeSecurity]
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
internal static extern INT GetRawInputData( internal static extern INT GetRawInputData(
@ -1416,6 +1397,16 @@ namespace OpenTK.Platform.Windows
INT SizeHeader 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 #endregion
#region IntPtr NextRawInputStructure(IntPtr data) #region IntPtr NextRawInputStructure(IntPtr data)
@ -1487,7 +1478,7 @@ namespace OpenTK.Platform.Windows
#region --- Constants --- #region --- Constants ---
internal struct Constants static class Constants
{ {
// Found in winuser.h // Found in winuser.h
internal const int KEYBOARD_OVERRUN_MAKE_CODE = 0xFF; internal const int KEYBOARD_OVERRUN_MAKE_CODE = 0xFF;
@ -1574,6 +1565,8 @@ namespace OpenTK.Platform.Windows
// (found in winuser.h) // (found in winuser.h)
internal const int ENUM_REGISTRY_SETTINGS = -2; internal const int ENUM_REGISTRY_SETTINGS = -2;
internal const int ENUM_CURRENT_SETTINGS = -1; internal const int ENUM_CURRENT_SETTINGS = -1;
internal static readonly IntPtr MESSAGE_ONLY = new IntPtr(-3);
} }
#endregion #endregion
@ -2231,25 +2224,11 @@ namespace OpenTK.Platform.Windows
/// <para>To get device specific information, call GetRawInputDeviceInfo with the hDevice from RAWINPUTHEADER.</para> /// <para>To get device specific information, call GetRawInputDeviceInfo with the hDevice from RAWINPUTHEADER.</para>
/// <para>Raw input is available only when the application calls RegisterRawInputDevices with valid device specifications.</para> /// <para>Raw input is available only when the application calls RegisterRawInputDevices with valid device specifications.</para>
/// </remarks> /// </remarks>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct RawInput struct RawInput
{ {
internal RawInputHeader Header; public RawInputHeader Header;
internal RawInputData Data; public 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;
}
}
}
} }
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
@ -2350,38 +2329,9 @@ namespace OpenTK.Platform.Windows
/// <summary> /// <summary>
/// Contains information about the state of the mouse. /// Contains information about the state of the mouse.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential, Pack=1)] [StructLayout(LayoutKind.Explicit)]
internal struct RawMouse 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
/// <summary>
/// Reserved.
/// </summary>
//ULONG Buttons;
internal USHORT buttonFlags;
/// <summary>
/// If usButtonFlags is RI_MOUSE_WHEEL, this member is a signed value that specifies the wheel delta.
/// </summary>
internal USHORT ButtonData;// { get { return (USHORT)((Buttons & 0xFFFF0000) >> 16); } }
/// <summary>
/// Raw state of the mouse buttons.
/// </summary>
internal ULONG RawButtons;
/// <summary>
/// Motion in the X direction. This is signed relative motion or absolute motion, depending on the value of usFlags.
/// </summary>
internal LONG LastX;
/// <summary>
/// Motion in the Y direction. This is signed relative motion or absolute motion, depending on the value of usFlags.
/// </summary>
internal LONG LastY;
/// <summary>
/// Device-specific additional information for the event.
/// </summary>
internal ULONG ExtraInformation;
/// <summary> /// <summary>
/// Mouse state. This member can be any reasonable combination of the following. /// Mouse state. This member can be any reasonable combination of the following.
/// MOUSE_ATTRIBUTES_CHANGED /// MOUSE_ATTRIBUTES_CHANGED
@ -2393,12 +2343,34 @@ namespace OpenTK.Platform.Windows
/// MOUSE_VIRTUAL_DESKTOP /// MOUSE_VIRTUAL_DESKTOP
/// Mouse coordinates are mapped to the virtual desktop (for a multiple monitor system). /// Mouse coordinates are mapped to the virtual desktop (for a multiple monitor system).
/// </summary> /// </summary>
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;
/// <summary> /// <summary>
/// Transition state of the mouse buttons. /// If usButtonFlags is RI_MOUSE_WHEEL, this member is a signed value that specifies the wheel delta.
/// </summary> /// </summary>
internal RawInputMouseState ButtonFlags { get { return (RawInputMouseState)(buttonFlags); } } [FieldOffset(6)] public USHORT ButtonData;
/// <summary>
/// Raw state of the mouse buttons.
/// </summary>
[FieldOffset(8)] public ULONG RawButtons;
/// <summary>
/// Motion in the X direction. This is signed relative motion or absolute motion, depending on the value of usFlags.
/// </summary>
[FieldOffset(12)] public LONG LastX;
/// <summary>
/// Motion in the Y direction. This is signed relative motion or absolute motion, depending on the value of usFlags.
/// </summary>
[FieldOffset(16)] public LONG LastY;
/// <summary>
/// Device-specific additional information for the event.
/// </summary>
[FieldOffset(20)] public ULONG ExtraInformation;
} }
#endregion #endregion
@ -2483,10 +2455,11 @@ namespace OpenTK.Platform.Windows
/// Number of HID inputs in bRawData. /// Number of HID inputs in bRawData.
/// </summary> /// </summary>
internal DWORD Count; internal DWORD Count;
/// <summary> // The RawData field must be marshalled manually.
/// Raw input data as an array of bytes. ///// <summary>
/// </summary> ///// Raw input data as an array of bytes.
internal BYTE RawData; ///// </summary>
//internal IntPtr RawData;
} }
#endregion #endregion
@ -3239,6 +3212,7 @@ namespace OpenTK.Platform.Windows
/// <summary> /// <summary>
/// Mouse indicator flags (found in winuser.h). /// Mouse indicator flags (found in winuser.h).
/// </summary> /// </summary>
[Flags]
internal enum RawMouseFlags : ushort internal enum RawMouseFlags : ushort
{ {
/// <summary> /// <summary>

View file

@ -83,7 +83,10 @@ namespace OpenTK.Platform.Windows
public virtual OpenTK.Input.IMouseDriver CreateMouseDriver() 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 #endregion

View file

@ -38,9 +38,10 @@ namespace OpenTK.Platform.Windows
Debug.Indent(); Debug.Indent();
AssignHandle(parent.WindowHandle); AssignHandle(parent.WindowHandle);
WinWindowInfo win = new WinWindowInfo(this.Handle, parent);
Debug.Print("Input window attached to parent {0}", parent); Debug.Print("Input window attached to parent {0}", parent);
keyboardDriver = new WinRawKeyboard(this.Handle); keyboardDriver = new WinRawKeyboard(this.Handle);
mouseDriver = new WinRawMouse(this.Handle); mouseDriver = new WinRawMouse();
Debug.Unindent(); Debug.Unindent();
@ -93,9 +94,7 @@ namespace OpenTK.Platform.Windows
return; return;
case RawInputDeviceType.MOUSE: case RawInputDeviceType.MOUSE:
if (!mouseDriver.ProcessEvent(data)) throw new NotSupportedException();
Functions.DefRawInputProc(ref data, 1, (uint)API.RawInputHeaderSize);
return;
case RawInputDeviceType.HID: case RawInputDeviceType.HID:
Functions.DefRawInputProc(ref data, 1, (uint)API.RawInputHeaderSize); Functions.DefRawInputProc(ref data, 1, (uint)API.RawInputHeaderSize);

View file

@ -6,12 +6,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using OpenTK.Input;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Microsoft.Win32; using Microsoft.Win32;
using System.Drawing; using OpenTK.Input;
namespace OpenTK.Platform.Windows namespace OpenTK.Platform.Windows
{ {
@ -19,54 +18,64 @@ namespace OpenTK.Platform.Windows
/// <summary> /// <summary>
/// Contains methods to register for and process mouse WM_INPUT messages. /// Contains methods to register for and process mouse WM_INPUT messages.
/// </summary> /// </summary>
internal class WinRawMouse : IMouseDriver, IDisposable internal class WinRawMouse : IMouseDriver
{ {
private List<MouseDevice> mice = new List<MouseDevice>(); List<MouseState> mice;
private IntPtr window; Dictionary<ContextHandle, int> rawids; // ContextHandle instead of IntPtr for fast dictionary access
readonly INativeWindow native;
#region --- Constructors --- readonly IntPtr window;
readonly WindowProcedure WndProc;
readonly IntPtr OldWndProc;
internal WinRawMouse() internal WinRawMouse()
: this(IntPtr.Zero)
{
}
internal WinRawMouse(IntPtr windowHandle)
{ {
Debug.WriteLine("Initializing mouse driver (WinRawMouse)."); Debug.WriteLine("Initializing mouse driver (WinRawMouse).");
Debug.Indent(); 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(); Debug.Unindent();
} }
#endregion #region IMouseDriver Members
#region --- IMouseDriver Members --- public IList<MouseDevice> Mouse { get { throw new NotImplementedException(); } }
public IList<MouseDevice> Mouse
{
get { return mice; }
}
public MouseState GetState() public MouseState GetState()
{ {
throw new NotImplementedException(); native.ProcessEvents();
if (mice.Count > 0)
return mice[0];
else
return new MouseState();
} }
public MouseState GetState(int index) 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<MouseState> mice, out Dictionary<ContextHandle, int> rawids)
{ {
int count = WinRawInput.DeviceCount; int count = WinRawInput.DeviceCount;
mice = new List<MouseState>();
rawids = new Dictionary<ContextHandle, int>();
RawInputDeviceList[] ridl = new RawInputDeviceList[count]; RawInputDeviceList[] ridl = new RawInputDeviceList[count];
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
ridl[i] = new RawInputDeviceList(); ridl[i] = new RawInputDeviceList();
@ -114,22 +123,14 @@ namespace OpenTK.Platform.Windows
if (!String.IsNullOrEmpty(deviceClass) && deviceClass.ToLower().Equals("mouse")) if (!String.IsNullOrEmpty(deviceClass) && deviceClass.ToLower().Equals("mouse"))
{ {
OpenTK.Input.MouseDevice mouse = new OpenTK.Input.MouseDevice(); // Register the device:
mouse.Description = deviceDesc;
// Register the keyboard:
RawInputDeviceInfo info = new RawInputDeviceInfo(); RawInputDeviceInfo info = new RawInputDeviceInfo();
int devInfoSize = API.RawInputDeviceInfoSize; int devInfoSize = API.RawInputDeviceInfoSize;
Functions.GetRawInputDeviceInfo(ridl[i].Device, RawInputDeviceInfoEnum.DEVICEINFO, Functions.GetRawInputDeviceInfo(ridl[i].Device, RawInputDeviceInfoEnum.DEVICEINFO,
info, ref devInfoSize); info, ref devInfoSize);
mouse.NumberOfButtons = info.Device.Mouse.NumberOfButtons; mice.Add(RegisterRawDevice(deviceDesc, window));
mouse.NumberOfWheels = info.Device.Mouse.HasHorizontalWheel ? 1 : 0; rawids.Add(new ContextHandle(ridl[i].Device), mice.Count - 1);
mouse.DeviceID = ridl[i].Device;//(IntPtr)info.Device.Mouse.Id;
this.RegisterRawDevice(mouse);
mice.Add(mouse);
} }
} }
} }
@ -137,14 +138,9 @@ namespace OpenTK.Platform.Windows
return count; return count;
} }
#endregion static MouseState RegisterRawDevice(string device, IntPtr window)
#endregion
#region internal void RegisterRawDevice(OpenTK.Input.Mouse mouse)
internal void RegisterRawDevice(OpenTK.Input.MouseDevice mouse)
{ {
MouseState state = new MouseState();
RawInputDevice[] rid = new RawInputDevice[1]; RawInputDevice[] rid = new RawInputDevice[1];
// Mouse is 1/2 (page/id). See http://www.microsoft.com/whdc/device/input/HID_HWID.mspx // Mouse is 1/2 (page/id). See http://www.microsoft.com/whdc/device/input/HID_HWID.mspx
rid[0] = new RawInputDevice(); rid[0] = new RawInputDevice();
@ -164,106 +160,96 @@ namespace OpenTK.Platform.Windows
} }
else else
{ {
Debug.Print("Registered mouse {0}", mouse.ToString()); Debug.Print("Registered mouse {0}", device);
Point p = new Point(); Point p = new Point();
if (Functions.GetCursorPos(ref p)) if (Functions.GetCursorPos(ref p))
mouse.Position = p; {
state.X = p.X;
state.Y = p.Y;
}
} }
return state;
} }
#endregion IntPtr WindowProcedure(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
#region internal bool ProcessEvent(API.RawInput rin)
/// <summary>
/// Processes raw input events.
/// </summary>
/// <param name="rin"></param>
/// <returns></returns>
internal bool ProcessEvent(RawInput rin)
{ {
//MouseDevice mouse = mice.Find(delegate(MouseDevice m) switch (message)
//{
// return m.DeviceID == rin.Header.Device;
//});
MouseDevice mouse;
if (mice.Count > 0) mouse = mice[0];
else return false;
switch (rin.Header.Type)
{ {
case RawInputDeviceType.MOUSE: case WindowMessage.INPUT:
if ((rin.Data.Mouse.ButtonFlags & RawInputMouseState.LEFT_BUTTON_DOWN) != 0) mouse[MouseButton.Left] = true; int expected_size = 0, real_size = 0;
if ((rin.Data.Mouse.ButtonFlags & RawInputMouseState.LEFT_BUTTON_UP) != 0) mouse[MouseButton.Left] = false; RawInput data = new RawInput();
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;
if ((rin.Data.Mouse.ButtonFlags & RawInputMouseState.WHEEL) != 0) // Get the size of the input buffer
mouse.Wheel += (short)rin.Data.Mouse.ButtonData / 120; Functions.GetRawInputData(lParam, GetRawInputDataEnum.INPUT,
IntPtr.Zero, ref expected_size, API.RawInputHeaderSize);
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()));
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: default:
throw new ApplicationException("WinRawMouse driver received invalid data."); return Functions.CallWindowProc(OldWndProc, handle, message, wParam, lParam);
} }
} }
#endregion bool ProcessEvent(IntPtr device, RawMouse raw)
#region public void Poll()
public void Poll()
{ {
} 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() if ((raw.Flags & RawMouseFlags.MOUSE_MOVE_ABSOLUTE) != 0)
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool manual)
{
if (!disposed)
{ {
if (manual) mouse.X = raw.LastX;
{ mouse.Y = raw.LastY;
mice.Clear();
}
disposed = true;
} }
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
} }
} }