using System.Collections; using System.Runtime.InteropServices; using T3.Editor.App; // Drawn heavily from Emma Burrows code project sample // which closely follows the C++ MS WM_INPUT sample. namespace T3.Editor.Gui.Interaction.Camera; public sealed partial class SpaceMouse { public sealed class SpaceMouseDevice: IWindowsFormsMessageHandler { #region const definitions private const int RIDEV_INPUTSINK = 0x00000100; private const int RID_HEADER = 0x10000005; private const int RID_INPUT = 0x10000003; private const int HID_USAGE_GENERIC_MULTIAXIS_CONTROLLER = 0x08; private const int HID_USAGE_PAGE_GENERIC = 0x01; private const int LOGITECH_3DX_VID = 0x046d; private const int SIZEOF_STANDARD_REPORT = 15; private const int FAPPCOMMAND_MASK = 0xF000; private const int FAPPCOMMAND_MOUSE = 0x8000; private const int FAPPCOMMAND_OEM = 0x1000; private const int RIM_TYPEHID = 2; private const int RIDI_DEVICENAME = 0x20000007; // return length is the character length not the byte size private const int RIDI_DEVICEINFO = 0x2000000b; private const int WM_INPUT = 0x00FF; #endregion const definitions #region structs & enums public enum RAW3DxMouseEventType { TranslationVector = 1, RotationVector = 2, ButtonReport = 3 } #region Windows.h structure declarations [StructLayout(LayoutKind.Sequential)] internal struct RAWINPUTDEVICELIST { public IntPtr hDevice; [MarshalAs(UnmanagedType.U4)] public int dwType; } [StructLayout(LayoutKind.Sequential)] internal struct RAWINPUTHEADER { [MarshalAs(UnmanagedType.U4)] public int dwType; [MarshalAs(UnmanagedType.U4)] public int dwSize; public IntPtr hDevice; public int wParam; } [StructLayout(LayoutKind.Sequential)] internal struct RAWHID { [MarshalAs(UnmanagedType.U4)] public int dwSizHid; [MarshalAs(UnmanagedType.U4)] public int dwCount; } [StructLayout(LayoutKind.Sequential)] internal struct RID_DEVICE_INFO_HID { [MarshalAs(UnmanagedType.U4)] public int cbSize; [MarshalAs(UnmanagedType.U4)] public int dwType; [MarshalAs(UnmanagedType.U4)] public int dwVendorID; [MarshalAs(UnmanagedType.U4)] public int dwProductID; [MarshalAs(UnmanagedType.U4)] public int dwVersionNumber; [MarshalAs(UnmanagedType.U2)] public short usUsagePage; [MarshalAs(UnmanagedType.U2)] public short usUsage; [MarshalAs(UnmanagedType.U4)] // padding to get the size right public int unused1; [MarshalAs(UnmanagedType.U4)] public int unused2; } [StructLayout(LayoutKind.Sequential)] internal struct BUTTONSSTR { [MarshalAs(UnmanagedType.U2)] public ushort usButtonFlags; [MarshalAs(UnmanagedType.U2)] public ushort usButtonData; } [StructLayout(LayoutKind.Sequential)] internal struct RAW3DMOUSE_EVENTTYPE { [MarshalAs(UnmanagedType.U4)] public int dwSizeHid; // byte size of each report [MarshalAs(UnmanagedType.U4)] public int dwCount; // number of input packed [MarshalAs(UnmanagedType.U1)] public byte eventType; // 1 for translation vector, 2 for rot vector, 3 for buttons } [StructLayout(LayoutKind.Sequential)] internal struct RAW3DMOUSEMOTION_T { [MarshalAs(UnmanagedType.U4)] public int dwSizeHid; // byte size of each report [MarshalAs(UnmanagedType.U4)] public int dwCount; // number of input packed [MarshalAs(UnmanagedType.U1)] public byte eventType; // 1 for translation vector [MarshalAs(UnmanagedType.U1)] public byte X_lb; [MarshalAs(UnmanagedType.U1)] public byte X_hb; [MarshalAs(UnmanagedType.U1)] public byte Y_lb; [MarshalAs(UnmanagedType.U1)] public byte Y_hb; [MarshalAs(UnmanagedType.U1)] public byte Z_lb; [MarshalAs(UnmanagedType.U1)] public byte Z_hb; } [StructLayout(LayoutKind.Sequential)] internal struct RAW3DMOUSEMOTION_TR_COMBINED { [MarshalAs(UnmanagedType.U4)] public int dwSizeHid; // byte size of each report [MarshalAs(UnmanagedType.U4)] public int dwCount; // number of input packed [MarshalAs(UnmanagedType.U1)] public byte eventType; // 1 for translation vector [MarshalAs(UnmanagedType.U1)] public byte X_lb; [MarshalAs(UnmanagedType.U1)] public byte X_hb; [MarshalAs(UnmanagedType.U1)] public byte Y_lb; [MarshalAs(UnmanagedType.U1)] public byte Y_hb; [MarshalAs(UnmanagedType.U1)] public byte Z_lb; [MarshalAs(UnmanagedType.U1)] public byte Z_hb; [MarshalAs(UnmanagedType.U1)] public byte RX_lb; [MarshalAs(UnmanagedType.U1)] public byte RX_hb; [MarshalAs(UnmanagedType.U1)] public byte RY_lb; [MarshalAs(UnmanagedType.U1)] public byte RY_hb; [MarshalAs(UnmanagedType.U1)] public byte RZ_lb; [MarshalAs(UnmanagedType.U1)] public byte RZ_hb; } [StructLayout(LayoutKind.Sequential)] internal struct RAW3DMOUSEMOTION_R { [MarshalAs(UnmanagedType.U4)] public int dwSizeHid; // byte size of each report [MarshalAs(UnmanagedType.U4)] public int dwCount; // number of input packed [MarshalAs(UnmanagedType.U1)] public byte eventType; // 2 for rotation vector public byte X_lb; [MarshalAs(UnmanagedType.U1)] public byte X_hb; [MarshalAs(UnmanagedType.U1)] public byte Y_lb; [MarshalAs(UnmanagedType.U1)] public byte Y_hb; [MarshalAs(UnmanagedType.U1)] public byte Z_lb; [MarshalAs(UnmanagedType.U1)] public byte Z_hb; } [StructLayout(LayoutKind.Sequential)] internal struct RAW3DMOUSEBUTTONS { [MarshalAs(UnmanagedType.U4)] public int dwSizeHid; // byte size of each report [MarshalAs(UnmanagedType.U4)] public int dwCount; // number of input packed [MarshalAs(UnmanagedType.U1)] public byte eventType; // 3 for buttons report [MarshalAs(UnmanagedType.U1)] public byte b1; [MarshalAs(UnmanagedType.U1)] public byte b2; [MarshalAs(UnmanagedType.U1)] public byte b3; [MarshalAs(UnmanagedType.U1)] public byte b4; } [StructLayout(LayoutKind.Sequential)] internal struct RAWINPUTDEVICE { [MarshalAs(UnmanagedType.U2)] public ushort usUsagePage; [MarshalAs(UnmanagedType.U2)] public ushort usUsage; [MarshalAs(UnmanagedType.U4)] public int dwFlags; public IntPtr hwndTarget; } #endregion Windows.h structure declarations /// /// Class encapsulating the information about a /// keyboard event, including the device it /// originated with and what key was pressed /// public class DeviceInfo { public string deviceName; public IntPtr deviceHandle; } /// /// ButtonMask from device /// public sealed class ButtonMask { public ButtonMask(byte b1, byte b2, byte b3, byte b4) { this.mask = (System.UInt32)(b1 + (b2 << 8) + (b3 << 16) + (b4 << 24)); } public UInt32 Mask { get { return mask; } set { mask = value; } } public UInt32 Pressed { get { return mask; } } private System.UInt32 mask; } /// /// Translation Vector from device /// public sealed class TranslationVector { public TranslationVector(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } public TranslationVector(byte xl, byte xh, byte yl, byte yh, byte zl, byte zh) { this.x = (int)(xl + (System.Int16)((System.Int16)xh << 8)); this.y = (int)(yl + (System.Int16)((System.Int16)yh << 8)); this.z = (int)(zl + (System.Int16)((System.Int16)zh << 8)); } public int X { get { return x; } set { x = value; } } public int Y { get { return y; } set { y = value; } } public int Z { get { return z; } set { z = value; } } private int x; private int y; private int z; } /// /// Rotation Vector from device /// public sealed class RotationVector { public RotationVector(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } public RotationVector(byte xl, byte xh, byte yl, byte yh, byte zl, byte zh) { this.x = (int)(xl + (System.Int16)((System.Int16)xh << 8)); this.y = (int)(yl + (System.Int16)((System.Int16)yh << 8)); this.z = (int)(zl + (System.Int16)((System.Int16)zh << 8)); } public int X { get { return x; } set { x = value; } } public int Y { get { return y; } set { y = value; } } public int Z { get { return z; } set { z = value; } } private int x; private int y; private int z; } #endregion structs & enums #region DllImports [DllImport("User32.dll")] private static extern uint GetRawInputDeviceList(IntPtr pRawInputDeviceList, ref uint uiNumDevices, uint cbSize); [DllImport("User32.dll")] private static extern uint GetRawInputDeviceInfo(IntPtr hDevice, uint uiCommand, IntPtr pData, ref uint pcbSize); [DllImport("User32.dll")] private static extern bool RegisterRawInputDevices(RAWINPUTDEVICE[] pRawInputDevice, uint uiNumDevices, uint cbSize); [DllImport("User32.dll")] private static extern uint GetRawInputData(IntPtr hRawInput, uint uiCommand, IntPtr pData, ref uint pcbSize, uint cbSizeHeader); #endregion DllImports #region Variables and event handling /// /// List of 3Dx devices /// Key: the device handle /// Value: the device info class /// private readonly Hashtable _deviceList = new(); //Event and delegate public delegate void MotionEventHandler(object sender, MotionEventArgs e); public event MotionEventHandler MotionEvent; public delegate void ButtonEventHandler(object sender, ButtonEventArgs e); public event ButtonEventHandler ButtonEvent; /// /// Arguments provided by the handler for a motion event /// public sealed class MotionEventArgs : EventArgs { private DeviceInfo _deviceInfo; private TranslationVector _translationVector; private RotationVector _rotationVector; public MotionEventArgs(DeviceInfo dInfo, TranslationVector translationVector, RotationVector rotationVector) { _deviceInfo = dInfo; _translationVector = translationVector; _rotationVector = rotationVector; } public MotionEventArgs(DeviceInfo dInfo, TranslationVector translationVector) { _deviceInfo = dInfo; _translationVector = translationVector; } public MotionEventArgs(DeviceInfo dInfo, RotationVector rotationVector) { _deviceInfo = dInfo; _rotationVector = rotationVector; } public MotionEventArgs() { } public TranslationVector TranslationVector { get { return _translationVector; } set { _translationVector = value; } } public RotationVector RotationVector { get { return _rotationVector; } set { _rotationVector = value; } } public DeviceInfo DeviceInfo { get { return _deviceInfo; } set { _deviceInfo = value; } } } /// /// Arguments provided by the handler for a button event /// public sealed class ButtonEventArgs : EventArgs { private DeviceInfo _deviceInfo; private ButtonMask _buttonMask; public ButtonEventArgs(DeviceInfo dInfo, ButtonMask buttonMask) { _deviceInfo = dInfo; _buttonMask = buttonMask; } public ButtonEventArgs() { } public ButtonMask ButtonMask { get { return _buttonMask; } set { _buttonMask = value; } } public DeviceInfo DeviceInfo { get { return _deviceInfo; } set { _deviceInfo = value; } } } #endregion Variables and event handling #region SpaceMouse( IntPtr hwnd ) /// /// InputDevice constructor; registers the raw input devices /// for the calling window. /// /// Handle of the window listening for key presses public SpaceMouseDevice(IntPtr hwnd) { RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1]; rid[0].usUsagePage = HID_USAGE_PAGE_GENERIC; rid[0].usUsage = HID_USAGE_GENERIC_MULTIAXIS_CONTROLLER; rid[0].dwFlags = RIDEV_INPUTSINK; rid[0].hwndTarget = hwnd; // Don't register each physical device, just the type (MultiAxis Controller) if (!RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0]))) { throw new ApplicationException("Failed to register raw input device(s)."); } } #endregion SpaceMouse( IntPtr hwnd ) #region int EnumerateDevices() /// /// Iterates through the list provided by GetRawInputDeviceList, /// counting 3Dx devices and adding them to deviceList. /// /// The number of 3Dx devices found. public int EnumerateDevices() { int NumberOfDevices = 0; uint deviceCount = 0; int dwSize = (Marshal.SizeOf(typeof(RAWINPUTDEVICELIST))); if (GetRawInputDeviceList(IntPtr.Zero, ref deviceCount, (uint)dwSize) == 0) { IntPtr pRawInputDeviceList = Marshal.AllocHGlobal((int)(dwSize * deviceCount)); GetRawInputDeviceList(pRawInputDeviceList, ref deviceCount, (uint)dwSize); for (int i = 0; i < deviceCount; i++) { uint pcbSize = 0; RAWINPUTDEVICELIST rid = (RAWINPUTDEVICELIST)Marshal.PtrToStructure( new IntPtr((pRawInputDeviceList.ToInt64() + (dwSize * i))), typeof(RAWINPUTDEVICELIST)); GetRawInputDeviceInfo(rid.hDevice, RIDI_DEVICENAME, IntPtr.Zero, ref pcbSize); if (pcbSize > 0) { IntPtr pData = Marshal.AllocHGlobal((int)pcbSize); GetRawInputDeviceInfo(rid.hDevice, RIDI_DEVICENAME, pData, ref pcbSize); string deviceName = Marshal.PtrToStringAnsi(pData); //If the device is identified as a HID device with usagePage 1, usage 8, //create a DeviceInfo object to store information about it if (rid.dwType == RIM_TYPEHID) { DeviceInfo dInfo = new DeviceInfo(); // Create an object to set the size in the struct. Then marshall it to a ptr to pass to RI. RID_DEVICE_INFO_HID hidInfo = new RID_DEVICE_INFO_HID(); int dwHidInfoSize = Marshal.SizeOf(hidInfo); hidInfo.cbSize = dwHidInfoSize; IntPtr pHIDData = Marshal.AllocHGlobal(dwHidInfoSize); Marshal.StructureToPtr(hidInfo, pHIDData, true); uint uHidInfoSize = (uint)dwHidInfoSize; GetRawInputDeviceInfo(rid.hDevice, RIDI_DEVICEINFO, pHIDData, ref uHidInfoSize); // Marshal back to an object to retrieve info hidInfo = (RID_DEVICE_INFO_HID)Marshal.PtrToStructure(pHIDData, typeof(RID_DEVICE_INFO_HID)); dInfo.deviceName = Marshal.PtrToStringAnsi(pData); dInfo.deviceHandle = rid.hDevice; //If it is a 3Dx device and it isn't already in the list, //add it to the deviceList hashtable and increase the //NumberOfDevices count if (Is3DxDevice(hidInfo) && !_deviceList.Contains(rid.hDevice)) { Console.WriteLine("Using 3Dx device: " + deviceName); NumberOfDevices++; _deviceList.Add(rid.hDevice, dInfo); } Marshal.FreeHGlobal(pHIDData); } Marshal.FreeHGlobal(pData); } } Marshal.FreeHGlobal(pRawInputDeviceList); return NumberOfDevices; } else { throw new ApplicationException("Error!"); } } private bool Is3DxDevice(RID_DEVICE_INFO_HID hidInfo) { return hidInfo.dwVendorID == LOGITECH_3DX_VID && hidInfo.usUsagePage == HID_USAGE_PAGE_GENERIC && hidInfo.usUsage == HID_USAGE_GENERIC_MULTIAXIS_CONTROLLER; } #endregion EnumerateDevices() #region ProcessInputCommand( Message message ) /// /// Processes WM_INPUT messages to retrieve information about any /// keyboard events that occur. /// /// The WM_INPUT message to process. private void ProcessInputCommand(System.Windows.Forms.Message message) { uint dwSize = 0; // First call to GetRawInputData sets the value of dwSize // dwSize can then be used to allocate the appropriate amount of memory, // storing the pointer in "buffer". GetRawInputData(message.LParam, RID_HEADER, IntPtr.Zero, ref dwSize, (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER))); IntPtr headerBuffer = Marshal.AllocHGlobal((int)dwSize); try { // Check that buffer points to something, and if so, // call GetRawInputData again to fill the allocated memory // with information about the input if (headerBuffer != IntPtr.Zero && GetRawInputData(message.LParam, RID_HEADER, headerBuffer, ref dwSize, (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER))) == dwSize) { RAWINPUTHEADER header = (RAWINPUTHEADER)Marshal.PtrToStructure(headerBuffer, typeof(RAWINPUTHEADER)); if (header.dwType == RIM_TYPEHID) { DeviceInfo dInfo = null; if (_deviceList.Contains(header.hDevice)) { dInfo = (DeviceInfo)_deviceList[header.hDevice]; } else { // Device not in list. Reenumerate all of them again. Could warn the code with some sort of Connect/Disconnect event. EnumerateDevices(); dInfo = (DeviceInfo)_deviceList[header.hDevice]; } // The header tells us the size of the actual event IntPtr eventBuffer = Marshal.AllocHGlobal(header.dwSize); uint eventSize = (uint)header.dwSize; if (eventBuffer != IntPtr.Zero && GetRawInputData(message.LParam, RID_INPUT, eventBuffer, ref eventSize, (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER))) == header.dwSize) { RAW3DMOUSE_EVENTTYPE eventType = (RAW3DMOUSE_EVENTTYPE)Marshal.PtrToStructure(new IntPtr(eventBuffer.ToInt64() + Marshal.SizeOf(typeof(RAWINPUTHEADER))), typeof(RAW3DMOUSE_EVENTTYPE)); switch (eventType.eventType) { case (byte)RAW3DxMouseEventType.TranslationVector: if (header.dwSize == Marshal.SizeOf(typeof(RAWINPUTHEADER)) + SIZEOF_STANDARD_REPORT) // standard length T report { RAW3DMOUSEMOTION_T t = (RAW3DMOUSEMOTION_T) Marshal.PtrToStructure(new IntPtr(eventBuffer.ToInt64() + Marshal.SizeOf(typeof(RAWINPUTHEADER))), typeof(RAW3DMOUSEMOTION_T)); TranslationVector tv = new TranslationVector(t.X_lb, t.X_hb, t.Y_lb, t.Y_hb, t.Z_lb, t.Z_hb); // Console.Write("Motion Event = {0} {1} {2}", tv.X, tv.Y, tv.Z); MotionEvent?.Invoke(this, new MotionEventArgs(dInfo, tv)); } else // "High Speed" firmware version includes both T and R vector in the same report { RAW3DMOUSEMOTION_TR_COMBINED tr = (RAW3DMOUSEMOTION_TR_COMBINED) Marshal.PtrToStructure(new IntPtr(eventBuffer.ToInt64() + Marshal.SizeOf(typeof(RAWINPUTHEADER))), typeof(RAW3DMOUSEMOTION_TR_COMBINED)); TranslationVector tv = new TranslationVector(tr.X_lb, tr.X_hb, tr.Y_lb, tr.Y_hb, tr.Z_lb, tr.Z_hb); RotationVector rv = new RotationVector(tr.RX_lb, tr.RX_hb, tr.RY_lb, tr.RY_hb, tr.RZ_lb, tr.RZ_hb); // Console.WriteLine("6DOF Motion Event = {0} {1} {2} {3} {4} {5}", tv.X, tv.Y, tv.Z, rv.X, rv.Y, rv.Z); MotionEvent?.Invoke(this, new MotionEventArgs(dInfo, tv, rv)); } break; case (byte)RAW3DxMouseEventType.RotationVector: { RAW3DMOUSEMOTION_R r = (RAW3DMOUSEMOTION_R) Marshal.PtrToStructure(new IntPtr(eventBuffer.ToInt64() + Marshal.SizeOf(typeof(RAWINPUTHEADER))), typeof(RAW3DMOUSEMOTION_R)); RotationVector rv = new RotationVector(r.X_lb, r.X_hb, r.Y_lb, r.Y_hb, r.Z_lb, r.Z_hb); // Console.WriteLine(" {0} {1} {2}", rv.X, rv.Y, rv.Z); MotionEvent?.Invoke(this, new MotionEventArgs(dInfo, rv)); } break; case (byte)RAW3DxMouseEventType.ButtonReport: RAW3DMOUSEBUTTONS b = (RAW3DMOUSEBUTTONS) Marshal.PtrToStructure(new IntPtr(eventBuffer.ToInt64() + Marshal.SizeOf(typeof(RAWINPUTHEADER))), typeof(RAW3DMOUSEBUTTONS)); ButtonMask bm = new ButtonMask(b.b1, b.b2, b.b3, b.b4); Console.WriteLine("raw.buttons = {0:X}", bm.Pressed); ButtonEvent?.Invoke(this, new ButtonEventArgs(dInfo, bm)); break; } } } } } finally { Marshal.FreeHGlobal(headerBuffer); } } #endregion ProcessInputCommand( Message message ) #region ProcessMessage(Message message) /// /// Filters Windows messages for WM_INPUT messages and calls /// ProcessInputCommand if necessary. /// /// The Windows message. public void ProcessMessage(System.Windows.Forms.Message message) { switch (message.Msg) { case WM_INPUT: { ProcessInputCommand(message); } break; } } #endregion ProcessMessage( Message message ) } }