RemoteControlDevice.cs
author sl
Tue, 04 Nov 2014 22:11:01 +0100
changeset 4 eed66dfd9848
parent 3 db8e6a25d6bc
child 5 b2ed6fc37d95
permissions -rw-r--r--
Cleaning and adding GetRawInputDeviceInfo definition.
We will need to use it to get the Usage Page of HID messages.
     1 using System;
     2 using System.Windows.Forms;
     3 using System.Runtime.InteropServices;
     4 using System.Diagnostics;
     5 
     6 
     7 namespace BruceThomas.Devices.RemoteControl
     8 {
     9 	public enum InputDevice
    10 	{
    11 		Key,
    12 		Mouse,
    13 		OEM
    14 	}
    15 
    16 
    17 	public enum RemoteControlButton
    18 	{
    19 		Clear,
    20 		Down,
    21 		Left,
    22 		Digit0,
    23 		Digit1,
    24 		Digit2,
    25 		Digit3,
    26 		Digit4,
    27 		Digit5,
    28 		Digit6,
    29 		Digit7,
    30 		Digit8,
    31 		Digit9,
    32 		Enter,
    33 		Right,
    34 		Up,
    35 
    36 		Back,
    37 		ChannelDown,
    38 		ChannelUp,
    39 		FastForward,
    40 		VolumeMute,
    41 		Pause,
    42 		Play,
    43         PlayPause,
    44 		Record,
    45 		PreviousTrack,
    46 		Rewind,
    47 		NextTrack,
    48 		Stop,
    49 		VolumeDown,
    50 		VolumeUp,
    51 
    52 		RecordedTV,
    53 		Guide,
    54 		LiveTV,
    55 		Details,
    56 		DVDMenu,
    57 		DVDAngle,
    58 		DVDAudio,
    59 		DVDSubtitle,
    60 		MyMusic,
    61 		MyPictures,
    62 		MyVideos,
    63 		MyTV,
    64 		OEM1,
    65 		OEM2,
    66 		StandBy,
    67 		TVJump,
    68 
    69 		Unknown
    70 	}
    71 
    72     /// <summary>
    73     /// 
    74     /// </summary>
    75     public enum MceButton
    76     {
    77         Null                    =   0x00, //Not defined by the specs
    78         GreenStart              =   0x0D,
    79         ClosedCaptioning        =   0x2B,
    80         Teletext                =   0x5A,
    81         TeletextRed             =   0x5B,
    82         TeletextGreen           =   0x5C,
    83         TeletextYellow          =   0x5D,
    84         TeletextBlue            =   0x5E,
    85         LiveTv                  =   0x25,
    86         Music                   =   0x47,
    87         RecordedTv              =   0x48,
    88         Pictures                =   0x49,
    89         Videos                  =   0x4A,
    90         FmRadio                 =   0x50,
    91         Extras                  =   0x3C,
    92         ExtrasApp               =   0x3D,
    93         DvdMenu                 =   0x24,
    94         DvdAngle                =   0x4B,
    95         DvdAudio                =   0x4C,
    96         DvdSubtitle             =   0x4D,
    97         Eject                   =   0x28,
    98         DvdTopMenu              =   0x43,
    99         Ext0                    =   0x32,
   100         Ext1                    =   0x33,
   101         Ext2                    =   0x34,
   102         Ext3                    =   0x35,
   103         Ext4                    =   0x36,
   104         Ext5                    =   0x37,
   105         Ext6                    =   0x38,
   106         Ext7                    =   0x39,
   107         Ext8                    =   0x3A,
   108         Ext9                    =   0x80,
   109         Ext10                   =   0x81,
   110         Ext11                   =   0x6F,
   111         Zoom                    =   0x27,
   112         ChannelInput            =   0x42,
   113         SubAudio                =   0x2D,
   114         Channel10               =   0x3E,
   115         Channel11               =   0x3F,
   116         Channel12               =   0x40,
   117         Display                 =   0x4F,
   118         Kiosk                   =   0x6A,
   119         NetworkSelection        =   0x2C,
   120         BlueRayTool             =   0x78,
   121         ChannelInfo             =   0x41,
   122         VideoSelection          =   0x61                
   123     }
   124 
   125 
   126 	#region RemoteControlEventArgs
   127 
   128 	public class RemoteControlEventArgs : EventArgs
   129 	{
   130         RemoteControlButton _rcb;
   131 		InputDevice _device;
   132         MceButton iMceButton;
   133 
   134         public RemoteControlEventArgs(RemoteControlButton rcb, InputDevice device)
   135 		{
   136             iMceButton = MceButton.Null;
   137 			_rcb = rcb;
   138 			_device = device;
   139 		}
   140 
   141         public RemoteControlEventArgs(MceButton mce, InputDevice device)
   142         {
   143             iMceButton = mce;
   144             _rcb = RemoteControlButton.Unknown;
   145             _device = device;
   146         }
   147 
   148 		public RemoteControlEventArgs()
   149 		{
   150             iMceButton = MceButton.Null;
   151 			_rcb = RemoteControlButton.Unknown;
   152 			_device = InputDevice.Key;
   153 		}
   154 
   155 		public RemoteControlButton Button
   156 		{
   157 			get { return _rcb;  }
   158 			set { _rcb = value; }
   159 		}
   160 
   161         public MceButton MceButton
   162         {
   163             get { return iMceButton; }
   164             set { iMceButton = value; }
   165         }
   166 
   167 		public InputDevice Device
   168 		{
   169 			get { return _device;  }
   170 			set { _device = value; }
   171 		}
   172 	}
   173 
   174 	#endregion RemoteControlEventArgs
   175 
   176 
   177 	public sealed class RemoteControlDevice
   178 	{
   179 
   180         [StructLayout(LayoutKind.Sequential, Pack = 1)]
   181 		internal struct RAWINPUTDEVICE
   182 		{
   183 			[MarshalAs(UnmanagedType.U2)]
   184 			public ushort usUsagePage;
   185 			[MarshalAs(UnmanagedType.U2)]
   186 			public ushort usUsage;
   187 			[MarshalAs(UnmanagedType.U4)]
   188 			public int	 dwFlags;
   189 			public IntPtr hwndTarget;
   190 		}
   191 
   192 
   193         [StructLayout(LayoutKind.Sequential, Pack = 1)]
   194 		internal struct RAWINPUTHEADER
   195 		{
   196 			[MarshalAs(UnmanagedType.U4)]
   197 			public int dwType;
   198 			[MarshalAs(UnmanagedType.U4)]
   199 			public int dwSize;
   200             public IntPtr hDevice;
   201 			[MarshalAs(UnmanagedType.U4)]
   202 			public int wParam;
   203 		}
   204 
   205 
   206         [StructLayout(LayoutKind.Sequential, Pack = 1)]
   207 		internal struct RAWHID
   208 		{
   209 			[MarshalAs(UnmanagedType.U4)]
   210 			public int dwSizeHid;
   211 			[MarshalAs(UnmanagedType.U4)]
   212 			public int dwCount;
   213             //
   214             //BYTE  bRawData[1];
   215 		}
   216 
   217 
   218         [StructLayout(LayoutKind.Sequential, Pack = 1)]
   219 		internal struct BUTTONSSTR
   220 		{
   221 			[MarshalAs(UnmanagedType.U2)]
   222 			public ushort usButtonFlags;
   223 			[MarshalAs(UnmanagedType.U2)]
   224 			public ushort usButtonData;
   225 		}
   226 
   227 
   228         [StructLayout(LayoutKind.Explicit, Pack = 1)]
   229 		internal struct RAWMOUSE
   230 		{
   231 			[MarshalAs(UnmanagedType.U2)]
   232 			[FieldOffset (0)] public ushort usFlags;
   233 			[MarshalAs(UnmanagedType.U4)]
   234 			[FieldOffset (4)] public uint ulButtons;
   235 			[FieldOffset (4)] public BUTTONSSTR buttonsStr;
   236 			[MarshalAs(UnmanagedType.U4)]
   237 			[FieldOffset (8)] public uint ulRawButtons;
   238             [MarshalAs(UnmanagedType.U4)]
   239             [FieldOffset (12)] public int lLastX;
   240             [MarshalAs(UnmanagedType.U4)]
   241             [FieldOffset (16)] public int lLastY;
   242 			[MarshalAs(UnmanagedType.U4)]
   243 			[FieldOffset (20)] public uint ulExtraInformation;
   244 		}
   245 
   246         [StructLayout(LayoutKind.Sequential, Pack = 1)]
   247 		internal struct RAWKEYBOARD
   248 		{
   249 			[MarshalAs(UnmanagedType.U2)]
   250 			public ushort MakeCode;
   251 			[MarshalAs(UnmanagedType.U2)]
   252 			public ushort Flags;
   253 			[MarshalAs(UnmanagedType.U2)]
   254 			public ushort Reserved;
   255 			[MarshalAs(UnmanagedType.U2)]
   256 			public ushort VKey;
   257 			[MarshalAs(UnmanagedType.U4)]
   258 			public uint Message;
   259 			[MarshalAs(UnmanagedType.U4)]
   260 			public uint ExtraInformation;
   261 		}
   262 
   263 
   264 		[StructLayout(LayoutKind.Explicit, Pack=1)]
   265 		internal struct RAWINPUT
   266 		{
   267 			[FieldOffset  (0)] public RAWINPUTHEADER header;
   268 			[FieldOffset (16)] public RAWMOUSE mouse;
   269 			[FieldOffset (16)] public RAWKEYBOARD keyboard;
   270 			[FieldOffset (16)] public RAWHID hid;
   271 		}
   272 
   273 
   274 		[DllImport("User32.dll")]
   275 		extern static bool RegisterRawInputDevices(RAWINPUTDEVICE[] pRawInputDevice, uint uiNumDevices, uint cbSize);
   276 
   277 		[DllImport("User32.dll")]
   278 		extern static uint GetRawInputData(IntPtr hRawInput, uint uiCommand, IntPtr pData, ref uint pcbSize, uint cbSizeHeader);
   279 
   280    		[DllImport("User32.dll")]
   281 		extern static uint GetRawInputDeviceInfo(IntPtr hDevice, uint uiCommand, IntPtr pData, ref uint pcbSize);
   282 
   283 
   284 		private const int WM_KEYDOWN	= 0x0100;
   285 		private const int WM_APPCOMMAND	= 0x0319;
   286 		private const int WM_INPUT		= 0x00FF;
   287 
   288 		private const int APPCOMMAND_BROWSER_BACKWARD   = 1;
   289 		private const int APPCOMMAND_VOLUME_MUTE        = 8;
   290 		private const int APPCOMMAND_VOLUME_DOWN        = 9;
   291 		private const int APPCOMMAND_VOLUME_UP          = 10;
   292 		private const int APPCOMMAND_MEDIA_NEXTTRACK    = 11;
   293 		private const int APPCOMMAND_MEDIA_PREVIOUSTRACK = 12;
   294 		private const int APPCOMMAND_MEDIA_STOP         = 13;
   295 		private const int APPCOMMAND_MEDIA_PLAY_PAUSE   = 14;
   296 		private const int APPCOMMAND_MEDIA_PLAY         = 46;
   297 		private const int APPCOMMAND_MEDIA_PAUSE        = 47;
   298 		private const int APPCOMMAND_MEDIA_RECORD       = 48;
   299 		private const int APPCOMMAND_MEDIA_FAST_FORWARD = 49;
   300 		private const int APPCOMMAND_MEDIA_REWIND       = 50;
   301 		private const int APPCOMMAND_MEDIA_CHANNEL_UP   = 51;
   302 		private const int APPCOMMAND_MEDIA_CHANNEL_DOWN = 52;
   303 
   304 		private const int RIM_TYPEMOUSE					= 0;
   305 		private const int RIM_TYPEKEYBOARD				= 1;
   306 		private const int RIM_TYPEHID					= 2;
   307 
   308 		private const int RID_INPUT						= 0x10000003;
   309 		private const int RID_HEADER					= 0x10000005;
   310 
   311 		private const int FAPPCOMMAND_MASK				= 0xF000;
   312 		private const int FAPPCOMMAND_MOUSE				= 0x8000;
   313 		private const int FAPPCOMMAND_KEY				= 0;
   314 		private const int FAPPCOMMAND_OEM				= 0x1000;
   315 
   316 		public delegate void RemoteControlDeviceEventHandler(object sender, RemoteControlEventArgs e);
   317 		public event RemoteControlDeviceEventHandler ButtonPressed;
   318 
   319 
   320 		//-------------------------------------------------------------
   321 		// constructors
   322 		//-------------------------------------------------------------
   323 
   324 		public RemoteControlDevice()
   325 		{
   326 			// Register the input device to receive the commands from the
   327 			// remote device. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwmt/html/remote_control.asp
   328 			// for the vendor defined usage page.
   329 
   330 			RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[3];
   331 
   332 			rid[0].usUsagePage = 0xFFBC;
   333 			rid[0].usUsage = 0x88;
   334 			rid[0].dwFlags = 0;
   335 
   336 			rid[1].usUsagePage = 0x0C;
   337 			rid[1].usUsage = 0x01;
   338 			rid[1].dwFlags = 0;
   339 
   340 			rid[2].usUsagePage = 0x0C;
   341 			rid[2].usUsage = 0x80;
   342 			rid[2].dwFlags = 0;
   343 
   344 			if (!RegisterRawInputDevices(rid,
   345 				(uint) rid.Length,
   346 				(uint) Marshal.SizeOf(rid[0]))
   347 				)
   348 			{
   349 				throw new ApplicationException("Failed to register raw input devices.");
   350 			}
   351 		}
   352 
   353 
   354 		//-------------------------------------------------------------
   355 		// methods
   356 		//-------------------------------------------------------------
   357 
   358 		public void ProcessMessage(Message message)
   359 		{
   360 			int param;
   361 
   362 			switch (message.Msg)
   363 			{
   364 				case WM_KEYDOWN:
   365 					param = message.WParam.ToInt32();
   366 					ProcessKeyDown(param);
   367 					break;
   368 				case WM_APPCOMMAND:
   369 					param = message.LParam.ToInt32();
   370 					ProcessAppCommand(param);
   371 					break;
   372 				case WM_INPUT:
   373 					ProcessInputCommand(ref message);
   374                     message.Result = new IntPtr(0);
   375 					break;
   376 			}
   377 
   378 		}
   379 
   380 
   381 		//-------------------------------------------------------------
   382 		// methods (helpers)
   383 		//-------------------------------------------------------------
   384 
   385 		private void ProcessKeyDown(int param)
   386 		{
   387 			RemoteControlButton rcb = RemoteControlButton.Unknown;
   388 
   389 			switch (param)
   390 			{
   391 				case (int) Keys.Escape:
   392 					rcb = RemoteControlButton.Clear;
   393 					break;
   394 				case (int) Keys.Down:
   395 					rcb = RemoteControlButton.Down;
   396 					break;
   397 				case (int) Keys.Left:
   398 					rcb = RemoteControlButton.Left;
   399 					break;
   400 				case (int) Keys.D0:
   401 					rcb = RemoteControlButton.Digit0;
   402 					break;
   403 				case (int) Keys.D1:
   404 					rcb = RemoteControlButton.Digit1;
   405 					break;
   406 				case (int) Keys.D2:
   407 					rcb = RemoteControlButton.Digit2;
   408 					break;
   409 				case (int) Keys.D3:
   410 					rcb = RemoteControlButton.Digit3;
   411 					break;
   412 				case (int) Keys.D4:
   413 					rcb = RemoteControlButton.Digit4;
   414 					break;
   415 				case (int) Keys.D5:
   416 					rcb = RemoteControlButton.Digit5;
   417 					break;
   418 				case (int) Keys.D6:
   419 					rcb = RemoteControlButton.Digit6;
   420 					break;
   421 				case (int) Keys.D7:
   422 					rcb = RemoteControlButton.Digit7;
   423 					break;
   424 				case (int) Keys.D8:
   425 					rcb = RemoteControlButton.Digit8;
   426 					break;
   427 				case (int) Keys.D9:
   428 					rcb = RemoteControlButton.Digit9;
   429 					break;
   430 				case (int) Keys.Enter:
   431 					rcb = RemoteControlButton.Enter;
   432 					break;
   433 				case (int) Keys.Right:
   434 					rcb = RemoteControlButton.Right;
   435 					break;
   436 				case (int) Keys.Up:
   437 					rcb = RemoteControlButton.Up;
   438 					break;
   439 			}
   440 
   441 			if (this.ButtonPressed != null && rcb != RemoteControlButton.Unknown)
   442 				this.ButtonPressed(this, new RemoteControlEventArgs(rcb, GetDevice(param)));
   443 		}
   444 
   445 
   446 		private void ProcessAppCommand(int param)
   447 		{
   448 			RemoteControlButton rcb = RemoteControlButton.Unknown;
   449 
   450 			int cmd	= (int) (((ushort) (param >> 16)) & ~FAPPCOMMAND_MASK);
   451 
   452 			switch (cmd)
   453 			{
   454 				case APPCOMMAND_BROWSER_BACKWARD:
   455 					rcb = RemoteControlButton.Back;
   456 					break;
   457 				case APPCOMMAND_MEDIA_CHANNEL_DOWN:
   458 					rcb = RemoteControlButton.ChannelDown;
   459 					break;
   460 				case APPCOMMAND_MEDIA_CHANNEL_UP:
   461 					rcb = RemoteControlButton.ChannelUp;
   462 					break;
   463 				case APPCOMMAND_MEDIA_FAST_FORWARD:
   464 					rcb = RemoteControlButton.FastForward;
   465 					break;
   466 				case APPCOMMAND_VOLUME_MUTE:
   467 					rcb = RemoteControlButton.VolumeMute;
   468 					break;
   469 				case APPCOMMAND_MEDIA_PAUSE:
   470 					rcb = RemoteControlButton.Pause;
   471 					break;
   472 				case APPCOMMAND_MEDIA_PLAY:
   473 					rcb = RemoteControlButton.Play;
   474 					break;
   475                 case APPCOMMAND_MEDIA_PLAY_PAUSE:
   476                     rcb = RemoteControlButton.PlayPause;
   477                     break;
   478 				case APPCOMMAND_MEDIA_RECORD:
   479 					rcb = RemoteControlButton.Record;
   480 					break;
   481 				case APPCOMMAND_MEDIA_PREVIOUSTRACK:
   482 					rcb = RemoteControlButton.PreviousTrack;
   483 					break;
   484 				case APPCOMMAND_MEDIA_REWIND:
   485 					rcb = RemoteControlButton.Rewind;
   486 					break;
   487 				case APPCOMMAND_MEDIA_NEXTTRACK:
   488 					rcb = RemoteControlButton.NextTrack;
   489 					break;
   490 				case APPCOMMAND_MEDIA_STOP:
   491 					rcb = RemoteControlButton.Stop;
   492 					break;
   493 				case APPCOMMAND_VOLUME_DOWN:
   494 					rcb = RemoteControlButton.VolumeDown;
   495 					break;
   496 				case APPCOMMAND_VOLUME_UP:
   497 					rcb = RemoteControlButton.VolumeUp;
   498 					break;
   499 			}
   500 
   501 			if (this.ButtonPressed != null && rcb != RemoteControlButton.Unknown)
   502 				this.ButtonPressed(this, new RemoteControlEventArgs(rcb, GetDevice(param)));
   503 		}
   504 
   505 
   506 		private void ProcessInputCommand(ref Message message)
   507 		{
   508 			RemoteControlButton rcb = RemoteControlButton.Unknown;
   509 			uint dwSize = 0;
   510 
   511             uint sizeOfHeader=(uint)Marshal.SizeOf(typeof(RAWINPUTHEADER));
   512 
   513             //Get the size of our raw input data.
   514 			GetRawInputData(message.LParam,	RID_INPUT, IntPtr.Zero,	ref dwSize,	sizeOfHeader);
   515 
   516             //Allocate a large enough buffer
   517 			IntPtr buffer = Marshal.AllocHGlobal((int) dwSize);
   518 			try
   519 			{
   520 				if(buffer == IntPtr.Zero)
   521 					return;
   522 
   523                 //Now read our RAWINPUT data
   524 				if (GetRawInputData(message.LParam,	RID_INPUT, buffer, ref dwSize, (uint) Marshal.SizeOf(typeof(RAWINPUTHEADER))) != dwSize)
   525 				{
   526 					return;
   527 				}
   528 
   529                 //Cast our buffer
   530                 RAWINPUT raw = (RAWINPUT)Marshal.PtrToStructure(buffer, typeof(RAWINPUT));
   531 
   532                 //Check that our raw input is HID
   533                 if (raw.header.dwType == RIM_TYPEHID && raw.hid.dwSizeHid>0)
   534 				{
   535                     //Allocate a buffer for one HID message
   536 					byte[] bRawData = new byte[raw.hid.dwSizeHid];
   537 
   538                     //Compute the address from which to copy our HID message
   539                     int pRawData = 0;
   540                     unsafe
   541                     {
   542                         byte* source = (byte*)buffer;
   543                         source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID);
   544                         pRawData = (int)source;
   545                     }
   546 
   547                     //Copy HID message into our buffer
   548                     Marshal.Copy(new IntPtr(pRawData), bRawData, 0, raw.hid.dwSizeHid);
   549                     //bRawData[0] //Not sure what's the meaning of the code at offset 0
   550                     //TODO: check size before access
   551                     int rawData = bRawData[1]; //Get button code
   552                     //Print HID codes in our debug output
   553                     Debug.WriteLine("HID " + raw.hid.dwCount + "/" + raw.hid.dwSizeHid + ":" + bRawData[0].ToString("X2") + bRawData[1].ToString("X2"));
   554 
   555                     if (Enum.IsDefined(typeof(MceButton), rawData) && rawData!=0) //Our button is a known MCE button
   556                     {
   557                         if (this.ButtonPressed != null) //What's that?
   558                         {
   559                             this.ButtonPressed(this, new RemoteControlEventArgs((MceButton)rawData, GetDevice(message.LParam.ToInt32())));
   560                         }
   561                     }
   562 				}
   563 				else if(raw.header.dwType == RIM_TYPEMOUSE)
   564 				{
   565 					// do mouse handling...
   566 				}
   567 				else if(raw.header.dwType == RIM_TYPEKEYBOARD)
   568 				{
   569 					// do keyboard handling...
   570 				}
   571 			}
   572 			finally
   573 			{
   574 				Marshal.FreeHGlobal(buffer);
   575 			}
   576 		}
   577 
   578 
   579 		private InputDevice GetDevice(int param)
   580 		{
   581 			InputDevice inputDevice;
   582 
   583 			switch ((int) (((ushort) (param >> 16)) & FAPPCOMMAND_MASK))
   584 			{
   585 				case FAPPCOMMAND_OEM:
   586 					inputDevice = InputDevice.OEM;
   587 					break;
   588 				case FAPPCOMMAND_MOUSE:
   589 					inputDevice = InputDevice.Mouse;
   590 					break;
   591 				default:
   592 					inputDevice = InputDevice.Key;
   593 					break;
   594 			}
   595 
   596 			return inputDevice;
   597 		}
   598 	}
   599 }