RemoteControlDevice.cs
author sl
Fri, 07 Nov 2014 20:49:51 +0100
changeset 9 94850bfc12b5
parent 8 0d0c62b1df48
child 10 17f8421146ba
permissions -rw-r--r--
Cast fix and moving Win32 stuff in a dedicated file.
     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 		Details,
    58 		DVDMenu,
    59 		DVDAngle,
    60 		DVDAudio,
    61 		DVDSubtitle,
    62 		MyMusic,
    63 		MyPictures,
    64 		MyVideos,
    65 		MyTV,
    66 		OEM1,
    67 		OEM2,
    68 		StandBy,
    69 		TVJump,
    70 
    71 		Unknown
    72 	}
    73 
    74 
    75 	#region RemoteControlEventArgs
    76 
    77 	public class RemoteControlEventArgs : EventArgs
    78 	{
    79         RemoteControlButton _rcb;
    80 		InputDevice _device;
    81         MceButton iMceButton;
    82 
    83         public RemoteControlEventArgs(RemoteControlButton rcb, InputDevice device)
    84 		{
    85             iMceButton = MceButton.Null;
    86 			_rcb = rcb;
    87 			_device = device;
    88 		}
    89 
    90         public RemoteControlEventArgs(MceButton mce, InputDevice device)
    91         {
    92             iMceButton = mce;
    93             _rcb = RemoteControlButton.Unknown;
    94             _device = device;
    95         }
    96 
    97 		public RemoteControlEventArgs()
    98 		{
    99             iMceButton = MceButton.Null;
   100 			_rcb = RemoteControlButton.Unknown;
   101 			_device = InputDevice.Key;
   102 		}
   103 
   104 		public RemoteControlButton Button
   105 		{
   106 			get { return _rcb;  }
   107 			set { _rcb = value; }
   108 		}
   109 
   110         public MceButton MceButton
   111         {
   112             get { return iMceButton; }
   113             set { iMceButton = value; }
   114         }
   115 
   116 		public InputDevice Device
   117 		{
   118 			get { return _device;  }
   119 			set { _device = value; }
   120 		}
   121 	}
   122 
   123 	#endregion RemoteControlEventArgs
   124 
   125 
   126 	public sealed class RemoteControlDevice
   127 	{
   128 		private const int WM_KEYDOWN	= 0x0100;
   129 		private const int WM_APPCOMMAND	= 0x0319;
   130 		private const int WM_INPUT		= 0x00FF;
   131 
   132 		private const int APPCOMMAND_BROWSER_BACKWARD   = 1;
   133 		private const int APPCOMMAND_VOLUME_MUTE        = 8;
   134 		private const int APPCOMMAND_VOLUME_DOWN        = 9;
   135 		private const int APPCOMMAND_VOLUME_UP          = 10;
   136 		private const int APPCOMMAND_MEDIA_NEXTTRACK    = 11;
   137 		private const int APPCOMMAND_MEDIA_PREVIOUSTRACK = 12;
   138 		private const int APPCOMMAND_MEDIA_STOP         = 13;
   139 		private const int APPCOMMAND_MEDIA_PLAY_PAUSE   = 14;
   140 		private const int APPCOMMAND_MEDIA_PLAY         = 46;
   141 		private const int APPCOMMAND_MEDIA_PAUSE        = 47;
   142 		private const int APPCOMMAND_MEDIA_RECORD       = 48;
   143 		private const int APPCOMMAND_MEDIA_FAST_FORWARD = 49;
   144 		private const int APPCOMMAND_MEDIA_REWIND       = 50;
   145 		private const int APPCOMMAND_MEDIA_CHANNEL_UP   = 51;
   146 		private const int APPCOMMAND_MEDIA_CHANNEL_DOWN = 52;
   147 
   148 		private const int RID_INPUT						= 0x10000003;
   149 		private const int RID_HEADER					= 0x10000005;
   150 
   151 		private const int FAPPCOMMAND_MASK				= 0xF000;
   152 		private const int FAPPCOMMAND_MOUSE				= 0x8000;
   153 		private const int FAPPCOMMAND_KEY				= 0;
   154 		private const int FAPPCOMMAND_OEM				= 0x1000;
   155 
   156 
   157 
   158 		public delegate void RemoteControlDeviceEventHandler(object sender, RemoteControlEventArgs e);
   159 		public event RemoteControlDeviceEventHandler ButtonPressed;
   160 
   161 
   162 		//-------------------------------------------------------------
   163 		// constructors
   164 		//-------------------------------------------------------------
   165 
   166 		public RemoteControlDevice()
   167 		{
   168 			// Register the input device to receive the commands from the
   169 			// remote device. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwmt/html/remote_control.asp
   170 			// for the vendor defined usage page.
   171 
   172 			RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[3];
   173 
   174 			rid[0].usUsagePage = 0xFFBC;
   175 			rid[0].usUsage = 0x88;
   176 			rid[0].dwFlags = 0;
   177 
   178 			rid[1].usUsagePage = 0x0C;
   179 			rid[1].usUsage = 0x01;
   180 			rid[1].dwFlags = 0;
   181 
   182 			rid[2].usUsagePage = 0x0C;
   183 			rid[2].usUsage = 0x80;
   184 			rid[2].dwFlags = 0;
   185 
   186 			if (!Function.RegisterRawInputDevices(rid,
   187 				(uint) rid.Length,
   188 				(uint) Marshal.SizeOf(rid[0]))
   189 				)
   190 			{
   191 				throw new ApplicationException("Failed to register raw input devices.");
   192 			}
   193 		}
   194 
   195 
   196 		//-------------------------------------------------------------
   197 		// methods
   198 		//-------------------------------------------------------------
   199 
   200 		public void ProcessMessage(Message message)
   201 		{
   202 			int param;
   203 
   204 			switch (message.Msg)
   205 			{
   206 				case WM_KEYDOWN:
   207 					param = message.WParam.ToInt32();
   208 					ProcessKeyDown(param);
   209 					break;
   210 				case WM_APPCOMMAND:
   211 					param = message.LParam.ToInt32();
   212 					ProcessAppCommand(param);
   213 					break;
   214 				case WM_INPUT:
   215 					ProcessInputCommand(ref message);
   216                     message.Result = new IntPtr(0);
   217 					break;
   218 			}
   219 
   220 		}
   221 
   222 
   223 		//-------------------------------------------------------------
   224 		// methods (helpers)
   225 		//-------------------------------------------------------------
   226 
   227 		private void ProcessKeyDown(int param)
   228 		{
   229 			RemoteControlButton rcb = RemoteControlButton.Unknown;
   230 
   231 			switch (param)
   232 			{
   233 				case (int) Keys.Escape:
   234 					rcb = RemoteControlButton.Clear;
   235 					break;
   236 				case (int) Keys.Down:
   237 					rcb = RemoteControlButton.Down;
   238 					break;
   239 				case (int) Keys.Left:
   240 					rcb = RemoteControlButton.Left;
   241 					break;
   242 				case (int) Keys.D0:
   243 					rcb = RemoteControlButton.Digit0;
   244 					break;
   245 				case (int) Keys.D1:
   246 					rcb = RemoteControlButton.Digit1;
   247 					break;
   248 				case (int) Keys.D2:
   249 					rcb = RemoteControlButton.Digit2;
   250 					break;
   251 				case (int) Keys.D3:
   252 					rcb = RemoteControlButton.Digit3;
   253 					break;
   254 				case (int) Keys.D4:
   255 					rcb = RemoteControlButton.Digit4;
   256 					break;
   257 				case (int) Keys.D5:
   258 					rcb = RemoteControlButton.Digit5;
   259 					break;
   260 				case (int) Keys.D6:
   261 					rcb = RemoteControlButton.Digit6;
   262 					break;
   263 				case (int) Keys.D7:
   264 					rcb = RemoteControlButton.Digit7;
   265 					break;
   266 				case (int) Keys.D8:
   267 					rcb = RemoteControlButton.Digit8;
   268 					break;
   269 				case (int) Keys.D9:
   270 					rcb = RemoteControlButton.Digit9;
   271 					break;
   272 				case (int) Keys.Enter:
   273 					rcb = RemoteControlButton.Enter;
   274 					break;
   275 				case (int) Keys.Right:
   276 					rcb = RemoteControlButton.Right;
   277 					break;
   278 				case (int) Keys.Up:
   279 					rcb = RemoteControlButton.Up;
   280 					break;
   281 			}
   282 
   283 			if (this.ButtonPressed != null && rcb != RemoteControlButton.Unknown)
   284 				this.ButtonPressed(this, new RemoteControlEventArgs(rcb, GetDevice(param)));
   285 		}
   286 
   287 
   288 		private void ProcessAppCommand(int param)
   289 		{
   290 			RemoteControlButton rcb = RemoteControlButton.Unknown;
   291 
   292 			int cmd	= (int) (((ushort) (param >> 16)) & ~FAPPCOMMAND_MASK);
   293 
   294 			switch (cmd)
   295 			{
   296 				case APPCOMMAND_BROWSER_BACKWARD:
   297 					rcb = RemoteControlButton.Back;
   298 					break;
   299 				case APPCOMMAND_MEDIA_CHANNEL_DOWN:
   300 					rcb = RemoteControlButton.ChannelDown;
   301 					break;
   302 				case APPCOMMAND_MEDIA_CHANNEL_UP:
   303 					rcb = RemoteControlButton.ChannelUp;
   304 					break;
   305 				case APPCOMMAND_MEDIA_FAST_FORWARD:
   306 					rcb = RemoteControlButton.FastForward;
   307 					break;
   308 				case APPCOMMAND_VOLUME_MUTE:
   309 					rcb = RemoteControlButton.VolumeMute;
   310 					break;
   311 				case APPCOMMAND_MEDIA_PAUSE:
   312 					rcb = RemoteControlButton.Pause;
   313 					break;
   314 				case APPCOMMAND_MEDIA_PLAY:
   315 					rcb = RemoteControlButton.Play;
   316 					break;
   317                 case APPCOMMAND_MEDIA_PLAY_PAUSE:
   318                     rcb = RemoteControlButton.PlayPause;
   319                     break;
   320 				case APPCOMMAND_MEDIA_RECORD:
   321 					rcb = RemoteControlButton.Record;
   322 					break;
   323 				case APPCOMMAND_MEDIA_PREVIOUSTRACK:
   324 					rcb = RemoteControlButton.PreviousTrack;
   325 					break;
   326 				case APPCOMMAND_MEDIA_REWIND:
   327 					rcb = RemoteControlButton.Rewind;
   328 					break;
   329 				case APPCOMMAND_MEDIA_NEXTTRACK:
   330 					rcb = RemoteControlButton.NextTrack;
   331 					break;
   332 				case APPCOMMAND_MEDIA_STOP:
   333 					rcb = RemoteControlButton.Stop;
   334 					break;
   335 				case APPCOMMAND_VOLUME_DOWN:
   336 					rcb = RemoteControlButton.VolumeDown;
   337 					break;
   338 				case APPCOMMAND_VOLUME_UP:
   339 					rcb = RemoteControlButton.VolumeUp;
   340 					break;
   341 			}
   342 
   343 			if (this.ButtonPressed != null && rcb != RemoteControlButton.Unknown)
   344 				this.ButtonPressed(this, new RemoteControlEventArgs(rcb, GetDevice(param)));
   345 		}
   346 
   347 
   348 		private void ProcessInputCommand(ref Message message)
   349 		{
   350             Debug.WriteLine("================WM_INPUT================");
   351 
   352 			uint dwSize = 0;
   353 
   354             uint sizeOfHeader=(uint)Marshal.SizeOf(typeof(RAWINPUTHEADER));
   355 
   356             //Get the size of our raw input data.
   357 			Win32.Function.GetRawInputData(message.LParam,	RID_INPUT, IntPtr.Zero,	ref dwSize,	sizeOfHeader);
   358 
   359             //Allocate a large enough buffer
   360 			IntPtr rawInputBuffer = Marshal.AllocHGlobal((int) dwSize);
   361 			try
   362 			{
   363 				if(rawInputBuffer == IntPtr.Zero)
   364 					return;
   365 
   366                 //Now read our RAWINPUT data
   367                 if (Win32.Function.GetRawInputData(message.LParam, RID_INPUT, rawInputBuffer, ref dwSize, (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER))) != dwSize)
   368 				{
   369 					return;
   370 				}
   371 
   372                 //Cast our buffer
   373                 RAWINPUT raw = (RAWINPUT)Marshal.PtrToStructure(rawInputBuffer, typeof(RAWINPUT));
   374 
   375                 //Get Device Info
   376                 uint deviceInfoSize = (uint)Marshal.SizeOf(typeof(RID_DEVICE_INFO));
   377                 IntPtr deviceInfoBuffer = Marshal.AllocHGlobal((int)deviceInfoSize);
   378 
   379                 int res = Win32.Function.GetRawInputDeviceInfo(raw.header.hDevice, Const.RIDI_DEVICEINFO, deviceInfoBuffer, ref deviceInfoSize);
   380                 if (res <= 0)
   381                 {
   382                     Debug.WriteLine("WM_INPUT could not read device info: " + Marshal.GetLastWin32Error().ToString());
   383                     return;
   384                 }
   385 
   386                 //Cast our buffer
   387                 RID_DEVICE_INFO deviceInfo = (RID_DEVICE_INFO)Marshal.PtrToStructure(deviceInfoBuffer, typeof(RID_DEVICE_INFO));
   388 
   389                 //Check type of input device and quite if we don't like it
   390                 switch (deviceInfo.dwType)
   391                 {
   392                     case Const.RIM_TYPEHID:
   393                         Debug.WriteLine("WM_INPUT source device is HID.");
   394                         break;
   395                     case Const.RIM_TYPEMOUSE:
   396                         Debug.WriteLine("WM_INPUT source device is Mouse.");
   397                         return;
   398                     case Const.RIM_TYPEKEYBOARD:
   399                         Debug.WriteLine("WM_INPUT source device is Keyboard.");
   400                         return;
   401                     default:
   402                         Debug.WriteLine("WM_INPUT source device is Unknown.");
   403                         return;
   404                 }
   405 
   406                 //Get Usage Page and Usage
   407                 Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
   408 
   409                 //Check that our raw input is HID
   410                 if (raw.header.dwType == Const.RIM_TYPEHID && raw.hid.dwSizeHid>0)
   411 				{
   412                     //Allocate a buffer for one HID message
   413 					byte[] bRawData = new byte[raw.hid.dwSizeHid];
   414 
   415                     //Compute the address from which to copy our HID message
   416                     int pRawData = 0;
   417                     unsafe
   418                     {
   419                         byte* source = (byte*)rawInputBuffer;
   420                         source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID);
   421                         pRawData = (int)source;
   422                     }
   423 
   424                     //Copy HID message into our buffer
   425                     Marshal.Copy(new IntPtr(pRawData), bRawData, 0, raw.hid.dwSizeHid);
   426                     //bRawData[0] //Not sure what's the meaning of the code at offset 0
   427                     //TODO: check size before access
   428                     int rawData = bRawData[1]; //Get button code
   429                     //Print HID codes in our debug output
   430                     string hidDump = "HID " + raw.hid.dwCount + "/" + raw.hid.dwSizeHid + ":";
   431                     foreach (byte b in bRawData)
   432                     {
   433                         hidDump += b.ToString("X2");
   434                     }
   435                     Debug.WriteLine(hidDump);
   436 
   437                     //Make sure both usage page and usage are matching MCE remote
   438                     if (deviceInfo.hid.usUsagePage != (ushort)Hid.UsagePage.MceRemote || deviceInfo.hid.usUsage != (ushort)Hid.UsageId.MceRemoteUsage)
   439                     {
   440                         Debug.WriteLine("Not MCE remote page and usage.");
   441                         return;
   442                     }
   443 
   444                     if (Enum.IsDefined(typeof(MceButton), rawData) && rawData!=0) //Our button is a known MCE button
   445                     {
   446                         if (this.ButtonPressed != null) //What's that?
   447                         {
   448                             this.ButtonPressed(this, new RemoteControlEventArgs((MceButton)rawData, GetDevice(message.LParam.ToInt32())));
   449                         }
   450                     }
   451 				}
   452 				else if(raw.header.dwType == Const.RIM_TYPEMOUSE)
   453 				{
   454 					// do mouse handling...
   455 				}
   456 				else if(raw.header.dwType == Const.RIM_TYPEKEYBOARD)
   457 				{
   458 					// do keyboard handling...
   459 				}
   460 			}
   461 			finally
   462 			{
   463 				Marshal.FreeHGlobal(rawInputBuffer);
   464 			}
   465 		}
   466 
   467 
   468 		private InputDevice GetDevice(int param)
   469 		{
   470 			InputDevice inputDevice;
   471 
   472 			switch ((int) (((ushort) (param >> 16)) & FAPPCOMMAND_MASK))
   473 			{
   474 				case FAPPCOMMAND_OEM:
   475 					inputDevice = InputDevice.OEM;
   476 					break;
   477 				case FAPPCOMMAND_MOUSE:
   478 					inputDevice = InputDevice.Mouse;
   479 					break;
   480 				default:
   481 					inputDevice = InputDevice.Key;
   482 					break;
   483 			}
   484 
   485 			return inputDevice;
   486 		}
   487 	}
   488 }