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