RemoteControlDevice.cs
author sl
Sat, 08 Nov 2014 12:40:05 +0100
changeset 12 ff7a8955f82d
parent 11 2ff6dbe0d356
child 13 6e4d62a4fed3
permissions -rw-r--r--
Now supporting some HID consumer codes.
Architecture to support multiple Usage Page.
     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 		private const int WM_KEYDOWN	= 0x0100;
   130 		private const int WM_APPCOMMAND	= 0x0319;
   131 		private const int WM_INPUT		= 0x00FF;
   132 
   133 		private const int APPCOMMAND_BROWSER_BACKWARD   = 1;
   134 		private const int APPCOMMAND_VOLUME_MUTE        = 8;
   135 		private const int APPCOMMAND_VOLUME_DOWN        = 9;
   136 		private const int APPCOMMAND_VOLUME_UP          = 10;
   137 		private const int APPCOMMAND_MEDIA_NEXTTRACK    = 11;
   138 		private const int APPCOMMAND_MEDIA_PREVIOUSTRACK = 12;
   139 		private const int APPCOMMAND_MEDIA_STOP         = 13;
   140 		private const int APPCOMMAND_MEDIA_PLAY_PAUSE   = 14;
   141 		private const int APPCOMMAND_MEDIA_PLAY         = 46;
   142 		private const int APPCOMMAND_MEDIA_PAUSE        = 47;
   143 		private const int APPCOMMAND_MEDIA_RECORD       = 48;
   144 		private const int APPCOMMAND_MEDIA_FAST_FORWARD = 49;
   145 		private const int APPCOMMAND_MEDIA_REWIND       = 50;
   146 		private const int APPCOMMAND_MEDIA_CHANNEL_UP   = 51;
   147 		private const int APPCOMMAND_MEDIA_CHANNEL_DOWN = 52;
   148 
   149 		private const int FAPPCOMMAND_MASK				= 0xF000;
   150 		private const int FAPPCOMMAND_MOUSE				= 0x8000;
   151 		private const int FAPPCOMMAND_KEY				= 0;
   152 		private const int FAPPCOMMAND_OEM				= 0x1000;
   153 
   154 
   155 
   156 		public delegate void RemoteControlDeviceEventHandler(object sender, RemoteControlEventArgs e);
   157 		public event RemoteControlDeviceEventHandler ButtonPressed;
   158 
   159         public delegate void HidUsageHandler(ushort aUsage);
   160         
   161 
   162 
   163 		//-------------------------------------------------------------
   164 		// constructors
   165 		//-------------------------------------------------------------
   166 
   167 		public RemoteControlDevice()
   168 		{
   169 			// Register the input device to receive the commands from the
   170 			// remote device. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwmt/html/remote_control.asp
   171 			// for the vendor defined usage page.
   172 
   173 			RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[3];
   174 
   175 			rid[0].usUsagePage = 0xFFBC;
   176 			rid[0].usUsage = 0x88;
   177 			rid[0].dwFlags = 0;
   178 
   179 			rid[1].usUsagePage = 0x0C;
   180 			rid[1].usUsage = 0x01;
   181 			rid[1].dwFlags = 0;
   182 
   183 			rid[2].usUsagePage = 0x0C;
   184 			rid[2].usUsage = 0x80;
   185 			rid[2].dwFlags = 0;
   186 
   187 			if (!Function.RegisterRawInputDevices(rid,
   188 				(uint) rid.Length,
   189 				(uint) Marshal.SizeOf(rid[0]))
   190 				)
   191 			{
   192 				throw new ApplicationException("Failed to register raw input devices.");
   193 			}
   194 		}
   195 
   196 
   197 		//-------------------------------------------------------------
   198 		// methods
   199 		//-------------------------------------------------------------
   200 
   201 		public void ProcessMessage(Message message)
   202 		{
   203 		int param;
   204 
   205 			switch (message.Msg)
   206 			{
   207 				case WM_KEYDOWN:
   208 					param = message.WParam.ToInt32();
   209 					ProcessKeyDown(param);
   210 					break;
   211 				case WM_APPCOMMAND:
   212 					param = message.LParam.ToInt32();
   213 					ProcessAppCommand(param);
   214 					break;
   215 				case WM_INPUT:
   216 					ProcessInputCommand(ref message);
   217                     message.Result = new IntPtr(0);
   218 					break;
   219 			}
   220 
   221 		}
   222 
   223 
   224 		//-------------------------------------------------------------
   225 		// methods (helpers)
   226 		//-------------------------------------------------------------
   227 
   228 		private void ProcessKeyDown(int param)
   229 		{
   230 			RemoteControlButton rcb = RemoteControlButton.Unknown;
   231 
   232 			switch (param)
   233 			{
   234 				case (int) Keys.Escape:
   235 					rcb = RemoteControlButton.Clear;
   236 					break;
   237 				case (int) Keys.Down:
   238 					rcb = RemoteControlButton.Down;
   239 					break;
   240 				case (int) Keys.Left:
   241 					rcb = RemoteControlButton.Left;
   242 					break;
   243 				case (int) Keys.D0:
   244 					rcb = RemoteControlButton.Digit0;
   245 					break;
   246 				case (int) Keys.D1:
   247 					rcb = RemoteControlButton.Digit1;
   248 					break;
   249 				case (int) Keys.D2:
   250 					rcb = RemoteControlButton.Digit2;
   251 					break;
   252 				case (int) Keys.D3:
   253 					rcb = RemoteControlButton.Digit3;
   254 					break;
   255 				case (int) Keys.D4:
   256 					rcb = RemoteControlButton.Digit4;
   257 					break;
   258 				case (int) Keys.D5:
   259 					rcb = RemoteControlButton.Digit5;
   260 					break;
   261 				case (int) Keys.D6:
   262 					rcb = RemoteControlButton.Digit6;
   263 					break;
   264 				case (int) Keys.D7:
   265 					rcb = RemoteControlButton.Digit7;
   266 					break;
   267 				case (int) Keys.D8:
   268 					rcb = RemoteControlButton.Digit8;
   269 					break;
   270 				case (int) Keys.D9:
   271 					rcb = RemoteControlButton.Digit9;
   272 					break;
   273 				case (int) Keys.Enter:
   274 					rcb = RemoteControlButton.Enter;
   275 					break;
   276 				case (int) Keys.Right:
   277 					rcb = RemoteControlButton.Right;
   278 					break;
   279 				case (int) Keys.Up:
   280 					rcb = RemoteControlButton.Up;
   281 					break;
   282 			}
   283 
   284 			if (this.ButtonPressed != null && rcb != RemoteControlButton.Unknown)
   285 				this.ButtonPressed(this, new RemoteControlEventArgs(rcb, GetDevice(param)));
   286 		}
   287 
   288 
   289 		private void ProcessAppCommand(int param)
   290 		{
   291 			RemoteControlButton rcb = RemoteControlButton.Unknown;
   292 
   293 			int cmd	= (int) (((ushort) (param >> 16)) & ~FAPPCOMMAND_MASK);
   294 
   295 			switch (cmd)
   296 			{
   297 				case APPCOMMAND_BROWSER_BACKWARD:
   298 					rcb = RemoteControlButton.Back;
   299 					break;
   300 				case APPCOMMAND_MEDIA_CHANNEL_DOWN:
   301 					rcb = RemoteControlButton.ChannelDown;
   302 					break;
   303 				case APPCOMMAND_MEDIA_CHANNEL_UP:
   304 					rcb = RemoteControlButton.ChannelUp;
   305 					break;
   306 				case APPCOMMAND_MEDIA_FAST_FORWARD:
   307 					rcb = RemoteControlButton.FastForward;
   308 					break;
   309 				case APPCOMMAND_VOLUME_MUTE:
   310 					rcb = RemoteControlButton.VolumeMute;
   311 					break;
   312 				case APPCOMMAND_MEDIA_PAUSE:
   313 					rcb = RemoteControlButton.Pause;
   314 					break;
   315 				case APPCOMMAND_MEDIA_PLAY:
   316 					rcb = RemoteControlButton.Play;
   317 					break;
   318                 case APPCOMMAND_MEDIA_PLAY_PAUSE:
   319                     rcb = RemoteControlButton.PlayPause;
   320                     break;
   321 				case APPCOMMAND_MEDIA_RECORD:
   322 					rcb = RemoteControlButton.Record;
   323 					break;
   324 				case APPCOMMAND_MEDIA_PREVIOUSTRACK:
   325 					rcb = RemoteControlButton.PreviousTrack;
   326 					break;
   327 				case APPCOMMAND_MEDIA_REWIND:
   328 					rcb = RemoteControlButton.Rewind;
   329 					break;
   330 				case APPCOMMAND_MEDIA_NEXTTRACK:
   331 					rcb = RemoteControlButton.NextTrack;
   332 					break;
   333 				case APPCOMMAND_MEDIA_STOP:
   334 					rcb = RemoteControlButton.Stop;
   335 					break;
   336 				case APPCOMMAND_VOLUME_DOWN:
   337 					rcb = RemoteControlButton.VolumeDown;
   338 					break;
   339 				case APPCOMMAND_VOLUME_UP:
   340 					rcb = RemoteControlButton.VolumeUp;
   341 					break;
   342 			}
   343 
   344 			if (this.ButtonPressed != null && rcb != RemoteControlButton.Unknown)
   345 				this.ButtonPressed(this, new RemoteControlEventArgs(rcb, GetDevice(param)));
   346 		}
   347 
   348         /// <summary>
   349         /// 
   350         /// </summary>
   351         /// <param name="aUsage"></param>
   352         private void HidConsumerDeviceHandler(ushort aUsage)
   353         {
   354             if (aUsage == 0)
   355             {
   356                 //Just skip those
   357                 return;
   358             }
   359 
   360             if (Enum.IsDefined(typeof(ConsumerControl), aUsage) && aUsage != 0) //Our button is a known consumer control
   361             {
   362                 if (this.ButtonPressed != null)
   363                 {
   364                     RemoteControlButton button=RemoteControlButton.Unknown;
   365                     if (aUsage== (ushort)ConsumerControl.AppCtrlProperties)
   366                     {
   367                         button = RemoteControlButton.MoreInfo;
   368                     }
   369                     else if (aUsage==(ushort)ConsumerControl.AppCtrlPrint)
   370                     {
   371                         button = RemoteControlButton.Print;
   372                     }
   373                     else if (aUsage==(ushort)ConsumerControl.MediaSelectProgramGuide)
   374                     {
   375                         button = RemoteControlButton.Guide;
   376                     }
   377                     this.ButtonPressed(this, new RemoteControlEventArgs(button, InputDevice.OEM));
   378                 }
   379             }
   380             else
   381             {
   382                 Debug.WriteLine("Unknown Consumer Control!");
   383             }
   384         }
   385 
   386         /// <summary>
   387         /// 
   388         /// </summary>
   389         /// <param name="aUsage"></param>
   390         private void HidMceRemoteHandler(ushort aUsage)
   391         {
   392             if (aUsage == 0)
   393             {
   394                 //Just skip those
   395                 return;
   396             }
   397 
   398 
   399             if (Enum.IsDefined(typeof(MceButton), aUsage) && aUsage != 0) //Our button is a known MCE button
   400             {
   401                 if (this.ButtonPressed != null)
   402                 {
   403                     this.ButtonPressed(this, new RemoteControlEventArgs((MceButton)aUsage, InputDevice.OEM));
   404                 }
   405             }
   406             else
   407             {
   408                 Debug.WriteLine("Unknown MCE button!");
   409             }
   410 
   411         }
   412 
   413 
   414 		private void ProcessInputCommand(ref Message message)
   415 		{
   416             Debug.WriteLine("================WM_INPUT================");
   417 
   418 
   419             //Declare a pointer
   420             IntPtr rawInputBuffer = IntPtr.Zero;
   421 
   422             try
   423             {
   424                 //Fetch raw input
   425                 RAWINPUT rawInput = new RAWINPUT();
   426                 if (!RawInput.GetRawInputData(message.LParam, ref rawInput, ref rawInputBuffer))
   427                 {
   428                     return;
   429                 }
   430 
   431                 //Fetch device info
   432                 RID_DEVICE_INFO deviceInfo = new RID_DEVICE_INFO();
   433                 if (!RawInput.GetDeviceInfo(rawInput.header.hDevice, ref deviceInfo))
   434                 {
   435                     return;
   436                 }
   437                
   438 
   439                 if (rawInput.header.dwType == Const.RIM_TYPEHID)  //Check that our raw input is HID                        
   440                 {
   441                     Debug.WriteLine("WM_INPUT source device is HID.");
   442                     //Get Usage Page and Usage
   443                     Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
   444 
   445                     //
   446                     HidUsageHandler handler=null;
   447 
   448                     //Make sure both usage page and usage are matching MCE remote
   449                     //TODO: handle more that just MCE usage page.
   450                     if (deviceInfo.hid.usUsagePage == (ushort)Hid.UsagePage.MceRemote || deviceInfo.hid.usUsage == (ushort)Hid.UsageId.MceRemoteUsage)
   451                     {                        
   452                         handler = HidMceRemoteHandler;
   453                     }
   454                     else if (deviceInfo.hid.usUsagePage == (ushort)Hid.UsagePage.Consumer || deviceInfo.hid.usUsage == (ushort)Hid.UsageId.ConsumerControl)
   455                     {
   456                         handler = HidConsumerDeviceHandler;
   457                     }
   458                     else
   459                     {
   460                         Debug.WriteLine("Not MCE remote page and usage.");
   461                         return;
   462                     }
   463 
   464                     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
   465                         && rawInput.hid.dwCount > 0))    //Check that we have at least one HID msg
   466                     {
   467                         return;
   468                     }
   469 
   470 
   471                     //Allocate a buffer for one HID input
   472                     byte[] hidInput = new byte[rawInput.hid.dwSizeHid];
   473 
   474                     //For each HID input in our raw input
   475                     for (int i = 0; i < rawInput.hid.dwCount; i++)
   476                     {
   477                         //Compute the address from which to copy our HID input
   478                         int hidInputOffset = 0;
   479                         unsafe
   480                         {
   481                             byte* source = (byte*)rawInputBuffer;
   482                             source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + (rawInput.hid.dwSizeHid * i);
   483                             hidInputOffset = (int)source;
   484                         }
   485 
   486                         //Copy HID input into our buffer
   487                         Marshal.Copy(new IntPtr(hidInputOffset), hidInput, 0, rawInput.hid.dwSizeHid);
   488 
   489                         //Print HID raw input in our debug output
   490                         string hidDump = "HID " + rawInput.hid.dwCount + "/" + rawInput.hid.dwSizeHid + ":";
   491                         foreach (byte b in hidInput)
   492                         {
   493                             hidDump += b.ToString("X2");
   494                         }
   495                         Debug.WriteLine(hidDump);
   496                         
   497                         ushort usage = 0;
   498                         //hidInput[0] //Not sure what's the meaning of the code at offset 0
   499                         if (hidInput.Length == 2)
   500                         {
   501                             //Single byte code
   502                             usage = hidInput[1]; //Get button code
   503                         }
   504                         else if (hidInput.Length > 2) //Defensive
   505                         {
   506                             //Assuming double bytes code
   507                             usage = (ushort)((hidInput[2] << 8) + hidInput[1]);
   508                         }
   509 
   510                         //
   511                         handler(usage);
   512                     }
   513 
   514                 }
   515                 else if (rawInput.header.dwType == Const.RIM_TYPEMOUSE)
   516                 {
   517                     Debug.WriteLine("WM_INPUT source device is Mouse.");
   518                     // do mouse handling...
   519                 }
   520                 else if (rawInput.header.dwType == Const.RIM_TYPEKEYBOARD)
   521                 {
   522                     Debug.WriteLine("WM_INPUT source device is Keyboard.");
   523                     // do keyboard handling...
   524 
   525                 }
   526             }
   527             finally
   528             {
   529                 //Always executed when leaving our try block
   530                 Marshal.FreeHGlobal(rawInputBuffer);
   531             }
   532 		}
   533 
   534 
   535 		private InputDevice GetDevice(int param)
   536 		{
   537 			InputDevice inputDevice;
   538 
   539 			switch ((int) (((ushort) (param >> 16)) & FAPPCOMMAND_MASK))
   540 			{
   541 				case FAPPCOMMAND_OEM:
   542 					inputDevice = InputDevice.OEM;
   543 					break;
   544 				case FAPPCOMMAND_MOUSE:
   545 					inputDevice = InputDevice.Mouse;
   546 					break;
   547 				default:
   548 					inputDevice = InputDevice.Key;
   549 					break;
   550 			}
   551 
   552 			return inputDevice;
   553 		}
   554 	}
   555 }