Adding debug output formating.
2 using System.Windows.Forms;
3 using System.Runtime.InteropServices;
4 using System.Diagnostics;
7 namespace Devices.RemoteControl
10 public static class Hid
13 /// From USB HID usage tables.
14 /// http://www.usb.org/developers/hidpage#HID_Usage
15 /// http://www.usb.org/developers/devclass_docs/Hut1_12v2.pdf
17 public enum UsagePage : ushort
20 GenericDesktopControl,
22 VirtualRealityControl,
33 PhysicalInterfaceDevice = 0x0f,
35 AlphaNumericDisplay = 0x14,
36 MedicalInstruments = 0x40,
45 BarCodeScanner = 0x8c,
51 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb417079.aspx
53 TerraTecRemote = 0xffcc
56 public const ushort MceRemoteUsage = 0x88;
60 public enum InputDevice
68 public enum RemoteControlButton
126 public enum MceButton
129 /// Not defined by the Microsoft specs.
133 ClosedCaptioning = 0x2B,
136 TeletextGreen = 0x5C,
137 TeletextYellow = 0x5D,
152 /// First press action: Ejects a DVD drive.
154 /// Second press action: Repeats first press action.
156 /// Notably issued by XBOX360 remote as defined in irplus - Remote Control - Android application.
161 /// First press action: Generates EXTn HID message in the Media Center Vendor Specific
162 /// Collection (page 0xFFBC, usage 0x88).
164 /// Second press action: Repeats message.
168 /// Notably sent by the 'Visualization' button of HP Windows Media Center Remote (TSGH-IR08).
170 /// According to HP specs it displays visual imagery that is synchronized to the sound of your music tracks.
174 /// First press action: Generates EXTn HID message in the Media Center Vendor Specific
175 /// Collection (page 0xFFBC, usage 0x88).
177 /// Second press action: Repeats message.
181 /// Notably sent by the 'Slide Show' button of HP Windows Media Center Remote (TSGH-IR08).
183 /// According to HP specs it plays a slide show of all the pictures on your hard disk drive.
187 /// First press action: Generates EXTn HID message in the Media Center Vendor Specific
188 /// Collection (page 0xFFBC, usage 0x88).
190 /// Second press action: Repeats message.
194 /// Notably sent by the 'Eject' button of HP Windows Media Center Remote (TSGH-IR08).
195 /// Also interpreted as 'Eject' action by SoundGraph iMON Manager in MCE mode (OrigenAE VF310).
199 /// First press action: Generates EXTn HID message in the Media Center Vendor Specific
200 /// Collection (page 0xFFBC, usage 0x88).
202 /// Second press action: Repeats message.
206 /// Notably sent by the 'Input selection' button of HP Windows Media Center Remote (TSGH-IR08).
224 /// First press action: Generates OEM2 HID message in the Media Center Vendor Specific
225 /// Collection. This button is intended to control the front panel display of home entertainment
226 /// computers. When this button is pressed, the display could be turned on or off, or the display
227 /// mode could change.
229 /// Second press action: Repeats message.
233 /// Notably issued by XBOX360 remote as defined in irplus - Remote Control - Android application.
237 /// First press action: To be determined.
239 /// Second press action: Repeats message.
244 NetworkSelection = 0x2C,
247 VideoSelection = 0x61
250 public enum HpMceButton
253 /// Displays visual imagery that is synchronized to the sound of your music tracks.
255 /// Second press action: Repeats message.
259 /// Notably sent by the 'Visualization' button of HP Windows Media Center Remote (TSGH-IR08).
261 /// According to HP specs it displays visual imagery that is synchronized to the sound of your music tracks.
263 Visualization = MceButton.Ext0,
265 /// Plays a slide show of all the pictures on your hard disk drive.
267 /// Second press action: Repeats message.
271 /// Notably sent by the 'Slide Show' button of HP Windows Media Center Remote (TSGH-IR08).
273 /// According to HP specs it plays a slide show of all the pictures on your hard disk drive.
275 SlideShow = MceButton.Ext1,
277 /// Eject optical drive.
279 /// Second press action: Repeats message.
283 /// Notably sent by the 'Eject' button of HP Windows Media Center Remote (TSGH-IR08).
284 /// Also interpreted as 'Eject' action by SoundGraph iMON Manager in MCE mode (OrigenAE VF310).
286 Eject = MceButton.Ext2,
288 /// Not sure what this should do.
290 /// Second press action: Repeats message.
294 /// Notably sent by the 'Input selection' button of HP Windows Media Center Remote (TSGH-IR08).
296 InputSelection = MceButton.Ext3,
301 #region RemoteControlEventArgs
303 public class RemoteControlEventArgs : EventArgs
305 RemoteControlButton _rcb;
307 MceButton iMceButton;
309 public RemoteControlEventArgs(RemoteControlButton rcb, InputDevice device)
311 iMceButton = MceButton.Null;
316 public RemoteControlEventArgs(MceButton mce, InputDevice device)
319 _rcb = RemoteControlButton.Unknown;
323 public RemoteControlEventArgs()
325 iMceButton = MceButton.Null;
326 _rcb = RemoteControlButton.Unknown;
327 _device = InputDevice.Key;
330 public RemoteControlButton Button
333 set { _rcb = value; }
336 public MceButton MceButton
338 get { return iMceButton; }
339 set { iMceButton = value; }
342 public InputDevice Device
344 get { return _device; }
345 set { _device = value; }
349 #endregion RemoteControlEventArgs
352 public sealed class RemoteControlDevice
355 [StructLayout(LayoutKind.Sequential, Pack = 1)]
356 internal struct RAWINPUTDEVICE
358 [MarshalAs(UnmanagedType.U2)]
359 public ushort usUsagePage;
360 [MarshalAs(UnmanagedType.U2)]
361 public ushort usUsage;
362 [MarshalAs(UnmanagedType.U4)]
364 public IntPtr hwndTarget;
368 [StructLayout(LayoutKind.Sequential, Pack = 1)]
369 internal struct RAWINPUTHEADER
371 [MarshalAs(UnmanagedType.U4)]
373 [MarshalAs(UnmanagedType.U4)]
375 public IntPtr hDevice;
376 [MarshalAs(UnmanagedType.U4)]
381 [StructLayout(LayoutKind.Sequential, Pack = 1)]
382 internal struct RAWHID
384 [MarshalAs(UnmanagedType.U4)]
385 public int dwSizeHid;
386 [MarshalAs(UnmanagedType.U4)]
393 [StructLayout(LayoutKind.Sequential, Pack = 1)]
394 internal struct BUTTONSSTR
396 [MarshalAs(UnmanagedType.U2)]
397 public ushort usButtonFlags;
398 [MarshalAs(UnmanagedType.U2)]
399 public ushort usButtonData;
403 [StructLayout(LayoutKind.Explicit, Pack = 1)]
404 internal struct RAWMOUSE
406 [MarshalAs(UnmanagedType.U2)]
407 [FieldOffset (0)] public ushort usFlags;
408 [MarshalAs(UnmanagedType.U4)]
409 [FieldOffset (4)] public uint ulButtons;
410 [FieldOffset (4)] public BUTTONSSTR buttonsStr;
411 [MarshalAs(UnmanagedType.U4)]
412 [FieldOffset (8)] public uint ulRawButtons;
413 [MarshalAs(UnmanagedType.U4)]
414 [FieldOffset (12)] public int lLastX;
415 [MarshalAs(UnmanagedType.U4)]
416 [FieldOffset (16)] public int lLastY;
417 [MarshalAs(UnmanagedType.U4)]
418 [FieldOffset (20)] public uint ulExtraInformation;
421 [StructLayout(LayoutKind.Sequential, Pack = 1)]
422 internal struct RAWKEYBOARD
424 [MarshalAs(UnmanagedType.U2)]
425 public ushort MakeCode;
426 [MarshalAs(UnmanagedType.U2)]
428 [MarshalAs(UnmanagedType.U2)]
429 public ushort Reserved;
430 [MarshalAs(UnmanagedType.U2)]
432 [MarshalAs(UnmanagedType.U4)]
434 [MarshalAs(UnmanagedType.U4)]
435 public uint ExtraInformation;
439 [StructLayout(LayoutKind.Explicit, Pack=1)]
440 internal struct RAWINPUT
442 [FieldOffset (0)] public RAWINPUTHEADER header;
443 [FieldOffset (16)] public RAWMOUSE mouse;
444 [FieldOffset (16)] public RAWKEYBOARD keyboard;
445 [FieldOffset (16)] public RAWHID hid;
449 [StructLayout(LayoutKind.Sequential, Pack=1)]
450 internal struct RID_DEVICE_INFO_MOUSE
453 public uint dwNumberOfButtons;
454 public uint dwSampleRate;
455 public bool fHasHorizontalWheel;
459 [StructLayout(LayoutKind.Sequential, Pack=1)]
460 internal struct RID_DEVICE_INFO_KEYBOARD
463 public uint dwSubType;
464 public uint dwKeyboardMode;
465 public uint dwNumberOfFunctionKeys;
466 public uint dwNumberOfIndicators;
467 public uint dwNumberOfKeysTotal;
470 [StructLayout(LayoutKind.Sequential, Pack=1)]
471 internal struct RID_DEVICE_INFO_HID
473 public uint dwVendorId;
474 public uint dwProductId;
475 public uint dwVersionNumber;
476 public ushort usUsagePage;
477 public ushort usUsage;
480 [StructLayout(LayoutKind.Explicit, Pack=1)]
481 internal struct RID_DEVICE_INFO
488 public RID_DEVICE_INFO_MOUSE mouse;
490 public RID_DEVICE_INFO_KEYBOARD keyboard;
492 public RID_DEVICE_INFO_HID hid;
497 [DllImport("User32.dll")]
498 extern static bool RegisterRawInputDevices(RAWINPUTDEVICE[] pRawInputDevice, uint uiNumDevices, uint cbSize);
500 [DllImport("User32.dll")]
501 extern static uint GetRawInputData(IntPtr hRawInput, uint uiCommand, IntPtr pData, ref uint pcbSize, uint cbSizeHeader);
503 [DllImport("User32.dll", SetLastError=true)]
504 extern static int GetRawInputDeviceInfo(IntPtr hDevice, uint uiCommand, IntPtr pData, ref uint pcbSize);
507 private const int WM_KEYDOWN = 0x0100;
508 private const int WM_APPCOMMAND = 0x0319;
509 private const int WM_INPUT = 0x00FF;
511 private const int APPCOMMAND_BROWSER_BACKWARD = 1;
512 private const int APPCOMMAND_VOLUME_MUTE = 8;
513 private const int APPCOMMAND_VOLUME_DOWN = 9;
514 private const int APPCOMMAND_VOLUME_UP = 10;
515 private const int APPCOMMAND_MEDIA_NEXTTRACK = 11;
516 private const int APPCOMMAND_MEDIA_PREVIOUSTRACK = 12;
517 private const int APPCOMMAND_MEDIA_STOP = 13;
518 private const int APPCOMMAND_MEDIA_PLAY_PAUSE = 14;
519 private const int APPCOMMAND_MEDIA_PLAY = 46;
520 private const int APPCOMMAND_MEDIA_PAUSE = 47;
521 private const int APPCOMMAND_MEDIA_RECORD = 48;
522 private const int APPCOMMAND_MEDIA_FAST_FORWARD = 49;
523 private const int APPCOMMAND_MEDIA_REWIND = 50;
524 private const int APPCOMMAND_MEDIA_CHANNEL_UP = 51;
525 private const int APPCOMMAND_MEDIA_CHANNEL_DOWN = 52;
527 private const int RID_INPUT = 0x10000003;
528 private const int RID_HEADER = 0x10000005;
530 private const int FAPPCOMMAND_MASK = 0xF000;
531 private const int FAPPCOMMAND_MOUSE = 0x8000;
532 private const int FAPPCOMMAND_KEY = 0;
533 private const int FAPPCOMMAND_OEM = 0x1000;
536 /// GetRawInputDeviceInfo pData points to a string that contains the device name.
538 public const uint RIDI_DEVICENAME = 0x20000007;
540 /// GetRawInputDeviceInfo For this uiCommand only, the value in pcbSize is the character count (not the byte count).
542 public const uint RIDI_DEVICEINFO = 0x2000000b;
544 /// GetRawInputDeviceInfo pData points to an RID_DEVICE_INFO structure.
546 public const uint RIDI_PREPARSEDDATA = 0x20000005;
550 /// Data comes from a mouse.
552 public const uint RIM_TYPEMOUSE = 0;
554 /// Data comes from a keyboard.
556 public const uint RIM_TYPEKEYBOARD = 1;
558 /// Data comes from an HID that is not a keyboard or a mouse.
560 public const uint RIM_TYPEHID = 2;
566 public delegate void RemoteControlDeviceEventHandler(object sender, RemoteControlEventArgs e);
567 public event RemoteControlDeviceEventHandler ButtonPressed;
570 //-------------------------------------------------------------
572 //-------------------------------------------------------------
574 public RemoteControlDevice()
576 // Register the input device to receive the commands from the
577 // remote device. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwmt/html/remote_control.asp
578 // for the vendor defined usage page.
580 RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[3];
582 rid[0].usUsagePage = 0xFFBC;
583 rid[0].usUsage = 0x88;
586 rid[1].usUsagePage = 0x0C;
587 rid[1].usUsage = 0x01;
590 rid[2].usUsagePage = 0x0C;
591 rid[2].usUsage = 0x80;
594 if (!RegisterRawInputDevices(rid,
596 (uint) Marshal.SizeOf(rid[0]))
599 throw new ApplicationException("Failed to register raw input devices.");
604 //-------------------------------------------------------------
606 //-------------------------------------------------------------
608 public void ProcessMessage(Message message)
615 param = message.WParam.ToInt32();
616 ProcessKeyDown(param);
619 param = message.LParam.ToInt32();
620 ProcessAppCommand(param);
623 ProcessInputCommand(ref message);
624 message.Result = new IntPtr(0);
631 //-------------------------------------------------------------
633 //-------------------------------------------------------------
635 private void ProcessKeyDown(int param)
637 RemoteControlButton rcb = RemoteControlButton.Unknown;
641 case (int) Keys.Escape:
642 rcb = RemoteControlButton.Clear;
644 case (int) Keys.Down:
645 rcb = RemoteControlButton.Down;
647 case (int) Keys.Left:
648 rcb = RemoteControlButton.Left;
651 rcb = RemoteControlButton.Digit0;
654 rcb = RemoteControlButton.Digit1;
657 rcb = RemoteControlButton.Digit2;
660 rcb = RemoteControlButton.Digit3;
663 rcb = RemoteControlButton.Digit4;
666 rcb = RemoteControlButton.Digit5;
669 rcb = RemoteControlButton.Digit6;
672 rcb = RemoteControlButton.Digit7;
675 rcb = RemoteControlButton.Digit8;
678 rcb = RemoteControlButton.Digit9;
680 case (int) Keys.Enter:
681 rcb = RemoteControlButton.Enter;
683 case (int) Keys.Right:
684 rcb = RemoteControlButton.Right;
687 rcb = RemoteControlButton.Up;
691 if (this.ButtonPressed != null && rcb != RemoteControlButton.Unknown)
692 this.ButtonPressed(this, new RemoteControlEventArgs(rcb, GetDevice(param)));
696 private void ProcessAppCommand(int param)
698 RemoteControlButton rcb = RemoteControlButton.Unknown;
700 int cmd = (int) (((ushort) (param >> 16)) & ~FAPPCOMMAND_MASK);
704 case APPCOMMAND_BROWSER_BACKWARD:
705 rcb = RemoteControlButton.Back;
707 case APPCOMMAND_MEDIA_CHANNEL_DOWN:
708 rcb = RemoteControlButton.ChannelDown;
710 case APPCOMMAND_MEDIA_CHANNEL_UP:
711 rcb = RemoteControlButton.ChannelUp;
713 case APPCOMMAND_MEDIA_FAST_FORWARD:
714 rcb = RemoteControlButton.FastForward;
716 case APPCOMMAND_VOLUME_MUTE:
717 rcb = RemoteControlButton.VolumeMute;
719 case APPCOMMAND_MEDIA_PAUSE:
720 rcb = RemoteControlButton.Pause;
722 case APPCOMMAND_MEDIA_PLAY:
723 rcb = RemoteControlButton.Play;
725 case APPCOMMAND_MEDIA_PLAY_PAUSE:
726 rcb = RemoteControlButton.PlayPause;
728 case APPCOMMAND_MEDIA_RECORD:
729 rcb = RemoteControlButton.Record;
731 case APPCOMMAND_MEDIA_PREVIOUSTRACK:
732 rcb = RemoteControlButton.PreviousTrack;
734 case APPCOMMAND_MEDIA_REWIND:
735 rcb = RemoteControlButton.Rewind;
737 case APPCOMMAND_MEDIA_NEXTTRACK:
738 rcb = RemoteControlButton.NextTrack;
740 case APPCOMMAND_MEDIA_STOP:
741 rcb = RemoteControlButton.Stop;
743 case APPCOMMAND_VOLUME_DOWN:
744 rcb = RemoteControlButton.VolumeDown;
746 case APPCOMMAND_VOLUME_UP:
747 rcb = RemoteControlButton.VolumeUp;
751 if (this.ButtonPressed != null && rcb != RemoteControlButton.Unknown)
752 this.ButtonPressed(this, new RemoteControlEventArgs(rcb, GetDevice(param)));
756 private void ProcessInputCommand(ref Message message)
758 Debug.WriteLine("================WM_INPUT================");
762 uint sizeOfHeader=(uint)Marshal.SizeOf(typeof(RAWINPUTHEADER));
764 //Get the size of our raw input data.
765 GetRawInputData(message.LParam, RID_INPUT, IntPtr.Zero, ref dwSize, sizeOfHeader);
767 //Allocate a large enough buffer
768 IntPtr rawInputBuffer = Marshal.AllocHGlobal((int) dwSize);
771 if(rawInputBuffer == IntPtr.Zero)
774 //Now read our RAWINPUT data
775 if (GetRawInputData(message.LParam, RID_INPUT, rawInputBuffer, ref dwSize, (uint) Marshal.SizeOf(typeof(RAWINPUTHEADER))) != dwSize)
781 RAWINPUT raw = (RAWINPUT)Marshal.PtrToStructure(rawInputBuffer, typeof(RAWINPUT));
784 uint deviceInfoSize = (uint)Marshal.SizeOf(typeof(RID_DEVICE_INFO));
785 IntPtr deviceInfoBuffer = Marshal.AllocHGlobal((int)deviceInfoSize);
787 int res = GetRawInputDeviceInfo(raw.header.hDevice, RIDI_DEVICEINFO, deviceInfoBuffer, ref deviceInfoSize);
790 Debug.WriteLine("WM_INPUT could not read device info: " + Marshal.GetLastWin32Error().ToString());
795 RID_DEVICE_INFO deviceInfo = (RID_DEVICE_INFO)Marshal.PtrToStructure(deviceInfoBuffer, typeof(RID_DEVICE_INFO));
797 //Check type of input device and quite if we don't like it
798 switch (deviceInfo.dwType)
801 Debug.WriteLine("WM_INPUT source device is HID.");
804 Debug.WriteLine("WM_INPUT source device is Mouse.");
806 case RIM_TYPEKEYBOARD:
807 Debug.WriteLine("WM_INPUT source device is Keyboard.");
810 Debug.WriteLine("WM_INPUT source device is Unknown.");
814 //Get Usage Page and Usage
815 Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
817 //Check that our raw input is HID
818 if (raw.header.dwType == RIM_TYPEHID && raw.hid.dwSizeHid>0)
820 //Allocate a buffer for one HID message
821 byte[] bRawData = new byte[raw.hid.dwSizeHid];
823 //Compute the address from which to copy our HID message
827 byte* source = (byte*)rawInputBuffer;
828 source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID);
829 pRawData = (int)source;
832 //Copy HID message into our buffer
833 Marshal.Copy(new IntPtr(pRawData), bRawData, 0, raw.hid.dwSizeHid);
834 //bRawData[0] //Not sure what's the meaning of the code at offset 0
835 //TODO: check size before access
836 int rawData = bRawData[1]; //Get button code
837 //Print HID codes in our debug output
838 string hidDump = "HID " + raw.hid.dwCount + "/" + raw.hid.dwSizeHid + ":";
839 foreach (byte b in bRawData)
841 hidDump += b.ToString("X2");
843 Debug.WriteLine(hidDump);
845 //Make sure both usage page and usage are matching MCE remote
846 if (deviceInfo.hid.usUsagePage != (ushort)Hid.UsagePage.MceRemote || deviceInfo.hid.usUsage != (ushort)Hid.MceRemoteUsage)
848 Debug.WriteLine("Not MCE remote page and usage.");
852 if (Enum.IsDefined(typeof(MceButton), rawData) && rawData!=0) //Our button is a known MCE button
854 if (this.ButtonPressed != null) //What's that?
856 this.ButtonPressed(this, new RemoteControlEventArgs((MceButton)rawData, GetDevice(message.LParam.ToInt32())));
860 else if(raw.header.dwType == RIM_TYPEMOUSE)
862 // do mouse handling...
864 else if(raw.header.dwType == RIM_TYPEKEYBOARD)
866 // do keyboard handling...
871 Marshal.FreeHGlobal(rawInputBuffer);
876 private InputDevice GetDevice(int param)
878 InputDevice inputDevice;
880 switch ((int) (((ushort) (param >> 16)) & FAPPCOMMAND_MASK))
882 case FAPPCOMMAND_OEM:
883 inputDevice = InputDevice.OEM;
885 case FAPPCOMMAND_MOUSE:
886 inputDevice = InputDevice.Mouse;
889 inputDevice = InputDevice.Key;