RemoteControlDevice.cs
author sl
Fri, 05 Dec 2014 23:18:13 +0100
changeset 18 24ac84ab9620
parent 17 8f7e35c3bfd1
child 19 d066e3999973
permissions -rw-r--r--
Consolidating our HID input report parsing.
     1 using System;
     2 using System.Windows.Forms;
     3 using System.Runtime.InteropServices;
     4 using System.Diagnostics;
     5 using Hid.UsageTables;
     6 using Win32;
     7 
     8 namespace Devices.RemoteControl
     9 {
    10 
    11 	public enum InputDevice
    12 	{
    13 		Key,
    14 		Mouse,
    15 		OEM
    16 	}
    17 
    18 
    19 	public enum RemoteControlButton
    20 	{
    21 		Clear,
    22 		Down,
    23 		Left,
    24 		Digit0,
    25 		Digit1,
    26 		Digit2,
    27 		Digit3,
    28 		Digit4,
    29 		Digit5,
    30 		Digit6,
    31 		Digit7,
    32 		Digit8,
    33 		Digit9,
    34 		Enter,
    35 		Right,
    36 		Up,
    37 
    38 		Back,
    39 		ChannelDown,
    40 		ChannelUp,
    41 		FastForward,
    42 		VolumeMute,
    43 		Pause,
    44 		Play,
    45         PlayPause,
    46 		Record,
    47 		PreviousTrack,
    48 		Rewind,
    49 		NextTrack,
    50 		Stop,
    51 		VolumeDown,
    52 		VolumeUp,
    53 
    54 		RecordedTV,
    55 		Guide,
    56 		LiveTV,
    57 		MoreInfo,
    58         Print,
    59 		DVDMenu,
    60 		DVDAngle,
    61 		DVDAudio,
    62 		DVDSubtitle,
    63 		MyMusic,
    64 		MyPictures,
    65 		MyVideos,
    66 		MyTV,
    67 		OEM1,
    68 		OEM2,
    69 		StandBy,
    70 		TVJump,
    71 
    72 		Unknown
    73 	}
    74 
    75 
    76 	#region RemoteControlEventArgs
    77 
    78 	public class RemoteControlEventArgs : EventArgs
    79 	{
    80         RemoteControlButton _rcb;
    81 		InputDevice _device;
    82         MceButton iMceButton;
    83 
    84         public RemoteControlEventArgs(RemoteControlButton rcb, InputDevice device)
    85 		{
    86             iMceButton = MceButton.Null;
    87 			_rcb = rcb;
    88 			_device = device;
    89 		}
    90 
    91         public RemoteControlEventArgs(MceButton mce, InputDevice device)
    92         {
    93             iMceButton = mce;
    94             _rcb = RemoteControlButton.Unknown;
    95             _device = device;
    96         }
    97 
    98 		public RemoteControlEventArgs()
    99 		{
   100             iMceButton = MceButton.Null;
   101 			_rcb = RemoteControlButton.Unknown;
   102 			_device = InputDevice.Key;
   103 		}
   104 
   105 		public RemoteControlButton Button
   106 		{
   107 			get { return _rcb;  }
   108 			set { _rcb = value; }
   109 		}
   110 
   111         public MceButton MceButton
   112         {
   113             get { return iMceButton; }
   114             set { iMceButton = value; }
   115         }
   116 
   117 		public InputDevice Device
   118 		{
   119 			get { return _device;  }
   120 			set { _device = value; }
   121 		}
   122 	}
   123 
   124 	#endregion RemoteControlEventArgs
   125 
   126 
   127 	public sealed class RemoteControlDevice
   128 	{
   129 		public delegate void RemoteControlDeviceEventHandler(object sender, RemoteControlEventArgs e);
   130 		public event RemoteControlDeviceEventHandler ButtonPressed;
   131 
   132         public delegate void HidUsageHandler(ushort aUsage);
   133         
   134 
   135 		//-------------------------------------------------------------
   136 		// constructors
   137 		//-------------------------------------------------------------
   138 
   139 		public RemoteControlDevice(IntPtr aHWND)
   140 		{
   141 			// Register the input device to receive the commands from the
   142 			// remote device. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwmt/html/remote_control.asp
   143 			// for the vendor defined usage page.
   144 
   145 			RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[3];
   146 
   147 			rid[0].usUsagePage = 0xFFBC;
   148 			rid[0].usUsage = 0x88;
   149             rid[0].dwFlags = Const.RIDEV_EXINPUTSINK;
   150             rid[0].hwndTarget = aHWND;
   151 
   152 			rid[1].usUsagePage = 0x0C;
   153 			rid[1].usUsage = 0x01;
   154             rid[1].dwFlags = Const.RIDEV_EXINPUTSINK;
   155             rid[1].hwndTarget = aHWND;
   156 
   157 			rid[2].usUsagePage = 0x0C;
   158 			rid[2].usUsage = 0x80;
   159             rid[2].dwFlags = Const.RIDEV_EXINPUTSINK;
   160             rid[2].hwndTarget = aHWND;
   161 
   162 			if (!Function.RegisterRawInputDevices(rid,(uint) rid.Length,(uint) Marshal.SizeOf(rid[0])))
   163 			{
   164                 throw new ApplicationException("Failed to register raw input devices: " + Marshal.GetLastWin32Error().ToString());
   165 			}
   166 		}
   167 
   168 
   169 		//-------------------------------------------------------------
   170 		// methods
   171 		//-------------------------------------------------------------
   172 
   173 		public void ProcessMessage(Message message)
   174 		{
   175 			switch (message.Msg)
   176 			{
   177 				case Const.WM_KEYDOWN:
   178                     ProcessKeyDown(message.WParam);
   179 					break;
   180                 case Const.WM_APPCOMMAND:
   181 					ProcessAppCommand(message.LParam);
   182 					break;
   183                 case Const.WM_INPUT:
   184 					ProcessInputCommand(ref message);
   185                     message.Result = new IntPtr(0);
   186 					break;
   187 			}
   188 
   189 		}
   190 
   191 
   192 		//-------------------------------------------------------------
   193 		// methods (helpers)
   194 		//-------------------------------------------------------------
   195 
   196 		private void ProcessKeyDown(IntPtr wParam)
   197 		{
   198 			RemoteControlButton rcb = RemoteControlButton.Unknown;
   199 
   200             switch (wParam.ToInt32())
   201 			{
   202 				case (int) Keys.Escape:
   203 					rcb = RemoteControlButton.Clear;
   204 					break;
   205 				case (int) Keys.Down:
   206 					rcb = RemoteControlButton.Down;
   207 					break;
   208 				case (int) Keys.Left:
   209 					rcb = RemoteControlButton.Left;
   210 					break;
   211 				case (int) Keys.D0:
   212 					rcb = RemoteControlButton.Digit0;
   213 					break;
   214 				case (int) Keys.D1:
   215 					rcb = RemoteControlButton.Digit1;
   216 					break;
   217 				case (int) Keys.D2:
   218 					rcb = RemoteControlButton.Digit2;
   219 					break;
   220 				case (int) Keys.D3:
   221 					rcb = RemoteControlButton.Digit3;
   222 					break;
   223 				case (int) Keys.D4:
   224 					rcb = RemoteControlButton.Digit4;
   225 					break;
   226 				case (int) Keys.D5:
   227 					rcb = RemoteControlButton.Digit5;
   228 					break;
   229 				case (int) Keys.D6:
   230 					rcb = RemoteControlButton.Digit6;
   231 					break;
   232 				case (int) Keys.D7:
   233 					rcb = RemoteControlButton.Digit7;
   234 					break;
   235 				case (int) Keys.D8:
   236 					rcb = RemoteControlButton.Digit8;
   237 					break;
   238 				case (int) Keys.D9:
   239 					rcb = RemoteControlButton.Digit9;
   240 					break;
   241 				case (int) Keys.Enter:
   242 					rcb = RemoteControlButton.Enter;
   243 					break;
   244 				case (int) Keys.Right:
   245 					rcb = RemoteControlButton.Right;
   246 					break;
   247 				case (int) Keys.Up:
   248 					rcb = RemoteControlButton.Up;
   249 					break;
   250 			}
   251 
   252 			if (this.ButtonPressed != null && rcb != RemoteControlButton.Unknown)
   253 				this.ButtonPressed(this, new RemoteControlEventArgs(rcb, GetDevice(wParam)));
   254 		}
   255 
   256 
   257 		private void ProcessAppCommand(IntPtr lParam)
   258 		{
   259 			RemoteControlButton rcb = RemoteControlButton.Unknown;
   260 
   261             int cmd = Macro.GET_APPCOMMAND_LPARAM(lParam);
   262                 //(int) (((ushort) (param >> 16)) & ~Const.FAPPCOMMAND_MASK);
   263 
   264 			switch (cmd)
   265 			{
   266                 case Const.APPCOMMAND_BROWSER_BACKWARD:
   267 					rcb = RemoteControlButton.Back;
   268 					break;
   269                 case Const.APPCOMMAND_MEDIA_CHANNEL_DOWN:
   270 					rcb = RemoteControlButton.ChannelDown;
   271 					break;
   272                 case Const.APPCOMMAND_MEDIA_CHANNEL_UP:
   273 					rcb = RemoteControlButton.ChannelUp;
   274 					break;
   275                 case Const.APPCOMMAND_MEDIA_FAST_FORWARD:
   276 					rcb = RemoteControlButton.FastForward;
   277 					break;
   278                 case Const.APPCOMMAND_VOLUME_MUTE:
   279 					rcb = RemoteControlButton.VolumeMute;
   280 					break;
   281                 case Const.APPCOMMAND_MEDIA_PAUSE:
   282 					rcb = RemoteControlButton.Pause;
   283 					break;
   284                 case Const.APPCOMMAND_MEDIA_PLAY:
   285 					rcb = RemoteControlButton.Play;
   286 					break;
   287                 case Const.APPCOMMAND_MEDIA_PLAY_PAUSE:
   288                     rcb = RemoteControlButton.PlayPause;
   289                     break;
   290                 case Const.APPCOMMAND_MEDIA_RECORD:
   291 					rcb = RemoteControlButton.Record;
   292 					break;
   293                 case Const.APPCOMMAND_MEDIA_PREVIOUSTRACK:
   294 					rcb = RemoteControlButton.PreviousTrack;
   295 					break;
   296                 case Const.APPCOMMAND_MEDIA_REWIND:
   297 					rcb = RemoteControlButton.Rewind;
   298 					break;
   299                 case Const.APPCOMMAND_MEDIA_NEXTTRACK:
   300 					rcb = RemoteControlButton.NextTrack;
   301 					break;
   302                 case Const.APPCOMMAND_MEDIA_STOP:
   303 					rcb = RemoteControlButton.Stop;
   304 					break;
   305                 case Const.APPCOMMAND_VOLUME_DOWN:
   306 					rcb = RemoteControlButton.VolumeDown;
   307 					break;
   308                 case Const.APPCOMMAND_VOLUME_UP:
   309 					rcb = RemoteControlButton.VolumeUp;
   310 					break;
   311 			}
   312 
   313 			if (this.ButtonPressed != null && rcb != RemoteControlButton.Unknown)
   314                 this.ButtonPressed(this, new RemoteControlEventArgs(rcb, GetDevice(lParam)));
   315 		}
   316 
   317         /// <summary>
   318         /// 
   319         /// </summary>
   320         /// <param name="aUsage"></param>
   321         private void HidConsumerDeviceHandler(ushort aUsage)
   322         {
   323             if (aUsage == 0)
   324             {
   325                 //Just skip those
   326                 return;
   327             }
   328 
   329             if (Enum.IsDefined(typeof(ConsumerControl), aUsage) && aUsage != 0) //Our button is a known consumer control
   330             {
   331                 if (this.ButtonPressed != null)
   332                 {
   333                     RemoteControlButton button=RemoteControlButton.Unknown;
   334                     if (aUsage == (ushort)ConsumerControl.AppCtrlProperties)
   335                     {
   336                         button = RemoteControlButton.MoreInfo;
   337                     }
   338                     else if (aUsage == (ushort)ConsumerControl.AppCtrlPrint)
   339                     {
   340                         button = RemoteControlButton.Print;
   341                     }
   342                     else if (aUsage == (ushort)ConsumerControl.MediaSelectProgramGuide)
   343                     {
   344                         button = RemoteControlButton.Guide;
   345                     }
   346                     this.ButtonPressed(this, new RemoteControlEventArgs(button, InputDevice.OEM));
   347                 }
   348             }
   349             else
   350             {
   351                 Debug.WriteLine("Unknown Consumer Control!");
   352             }
   353         }
   354 
   355         /// <summary>
   356         /// 
   357         /// </summary>
   358         /// <param name="aUsage"></param>
   359         private void HidMceRemoteHandler(ushort aUsage)
   360         {
   361             if (aUsage == 0)
   362             {
   363                 //Just skip those
   364                 return;
   365             }
   366 
   367 
   368             if (Enum.IsDefined(typeof(MceButton), aUsage) && aUsage != 0) //Our button is a known MCE button
   369             {
   370                 if (this.ButtonPressed != null)
   371                 {
   372                     this.ButtonPressed(this, new RemoteControlEventArgs((MceButton)aUsage, InputDevice.OEM));
   373                 }
   374             }
   375             else
   376             {
   377                 Debug.WriteLine("Unknown MCE button!");
   378             }
   379 
   380         }
   381 
   382 
   383 		private void ProcessInputCommand(ref Message message)
   384 		{
   385             //We received a WM_INPUT message
   386             Debug.WriteLine("================WM_INPUT================");
   387 
   388             //Check if we received this message while in background or foreground
   389             if (Macro.GET_RAWINPUT_CODE_WPARAM(message.WParam) == Const.RIM_INPUT)
   390             {
   391                 Debug.WriteLine("================FOREGROUND");
   392             }
   393             else if (Macro.GET_RAWINPUT_CODE_WPARAM(message.WParam) == Const.RIM_INPUTSINK)
   394             {
   395                 Debug.WriteLine("================BACKGROUND");
   396             }
   397 
   398             //Declare some pointers
   399             IntPtr rawInputBuffer = IntPtr.Zero;
   400             //My understanding is that this is basically our HID descriptor
   401             IntPtr preParsedData = IntPtr.Zero;
   402 
   403             try
   404             {
   405                 //Fetch raw input
   406                 RAWINPUT rawInput = new RAWINPUT();
   407                 if (!RawInput.GetRawInputData(message.LParam, ref rawInput, ref rawInputBuffer))
   408                 {
   409                     return;
   410                 }
   411 
   412                 //Fetch device info
   413                 RID_DEVICE_INFO deviceInfo = new RID_DEVICE_INFO();
   414                 if (!RawInput.GetDeviceInfo(rawInput.header.hDevice, ref deviceInfo))
   415                 {
   416                     return;
   417                 }
   418                
   419 
   420                 if (rawInput.header.dwType == Const.RIM_TYPEHID)  //Check that our raw input is HID                        
   421                 {
   422                     Debug.WriteLine("WM_INPUT source device is HID.");
   423                     //Get Usage Page and Usage
   424                     Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage ID: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
   425 
   426 
   427                     preParsedData = RawInput.GetPreParsedData(rawInput.header.hDevice);
   428 
   429                     //
   430                     HidUsageHandler usagePageHandler=null;
   431 
   432                     //Check if this an MCE remote HID message
   433                     if (deviceInfo.hid.usUsagePage == (ushort)Hid.UsagePage.MceRemote && deviceInfo.hid.usUsage == (ushort)Hid.UsageId.MceRemoteUsage)
   434                     {                        
   435                         usagePageHandler = HidMceRemoteHandler;
   436                     }
   437                     //Check if this is a consumer control HID message
   438                     else if (deviceInfo.hid.usUsagePage == (ushort)Hid.UsagePage.Consumer && deviceInfo.hid.usUsage == (ushort)Hid.UsageId.ConsumerControl)
   439                     {
   440                         usagePageHandler = HidConsumerDeviceHandler;
   441                     }
   442                     //Unknown HID message
   443                     else
   444                     {
   445                         Debug.WriteLine("Unknown HID message.");
   446                         return;
   447                     }
   448 
   449                     if (!(rawInput.hid.dwSizeHid > 1     //Make sure our HID msg size more than 1. In fact the first ushort is irrelevant to us for now
   450                         && rawInput.hid.dwCount > 0))    //Check that we have at least one HID msg
   451                     {
   452                         return;
   453                     }
   454 
   455 
   456                     //Allocate a buffer for one HID input
   457                     byte[] hidInputReport = new byte[rawInput.hid.dwSizeHid];
   458 
   459                     Debug.WriteLine("Raw input contains " + rawInput.hid.dwCount + " HID input report(s)");
   460 
   461                     //For each HID input report in our raw input
   462                     for (int i = 0; i < rawInput.hid.dwCount; i++)
   463                     {
   464                         //Compute the address from which to copy our HID input
   465                         int hidInputOffset = 0;
   466                         unsafe
   467                         {
   468                             byte* source = (byte*)rawInputBuffer;
   469                             source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + (rawInput.hid.dwSizeHid * i);
   470                             hidInputOffset = (int)source;
   471                         }
   472 
   473                         //Copy HID input into our buffer
   474                         Marshal.Copy(new IntPtr(hidInputOffset), hidInputReport, 0, (int)rawInput.hid.dwSizeHid);
   475 
   476                         //Print HID input report in our debug output
   477                         string hidDump = "HID input report: ";
   478                         foreach (byte b in hidInputReport)
   479                         {
   480                             hidDump += b.ToString("X2");
   481                         }
   482                         Debug.WriteLine(hidDump);
   483                         
   484                         //Proper parsing now
   485                         uint usageCount = 1; //Assuming a single usage per input report. Is that correct?
   486                         Win32.USAGE_AND_PAGE[] usages = new Win32.USAGE_AND_PAGE[usageCount];
   487                         Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, preParsedData, hidInputReport, (uint)hidInputReport.Length);
   488                         if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
   489                         {
   490                             Debug.WriteLine("Could not parse HID data!");
   491                         }
   492                         else
   493                         {
   494                             Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4"));
   495                             Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4"));
   496                             //Call on our Usage Page handler
   497                             usagePageHandler(usages[0].Usage);
   498                         }
   499                     }
   500 
   501                 }
   502                 else if (rawInput.header.dwType == Const.RIM_TYPEMOUSE)
   503                 {
   504                     Debug.WriteLine("WM_INPUT source device is Mouse.");
   505                     // do mouse handling...
   506                 }
   507                 else if (rawInput.header.dwType == Const.RIM_TYPEKEYBOARD)
   508                 {
   509                     Debug.WriteLine("WM_INPUT source device is Keyboard.");
   510                     // do keyboard handling...
   511 
   512                 }
   513             }
   514             finally
   515             {
   516                 //Always executed when leaving our try block
   517                 Marshal.FreeHGlobal(rawInputBuffer);
   518                 Marshal.FreeHGlobal(preParsedData);
   519             }
   520 		}
   521 
   522 
   523 		private InputDevice GetDevice(IntPtr lParam)
   524 		{
   525 			InputDevice inputDevice;
   526 
   527             switch (Macro.GET_DEVICE_LPARAM(lParam))
   528 			{
   529 				case Const.FAPPCOMMAND_OEM:
   530 					inputDevice = InputDevice.OEM;
   531 					break;
   532 				case Const.FAPPCOMMAND_MOUSE:
   533 					inputDevice = InputDevice.Mouse;
   534 					break;
   535 				default:
   536 					inputDevice = InputDevice.Key;
   537 					break;
   538 			}
   539 
   540 			return inputDevice;
   541 		}
   542 	}
   543 }