sl@0: using System;
sl@0: using System.Windows.Forms;
sl@0: using System.Runtime.InteropServices;
sl@0: using System.Diagnostics;
sl@23: using System.Text;
sl@24: using Microsoft.Win32.SafeHandles;
sl@23:
sl@8: using Hid.UsageTables;
sl@9: using Win32;
sl@0:
sl@23:
sl@24:
sl@6: namespace Devices.RemoteControl
sl@0: {
sl@6:
sl@0: public enum InputDevice
sl@0: {
sl@0: Key,
sl@0: Mouse,
sl@0: OEM
sl@0: }
sl@0:
sl@0:
sl@0: public enum RemoteControlButton
sl@0: {
sl@0: Clear,
sl@0: Down,
sl@0: Left,
sl@0: Digit0,
sl@0: Digit1,
sl@0: Digit2,
sl@0: Digit3,
sl@0: Digit4,
sl@0: Digit5,
sl@0: Digit6,
sl@0: Digit7,
sl@0: Digit8,
sl@0: Digit9,
sl@0: Enter,
sl@0: Right,
sl@0: Up,
sl@0:
sl@0: Back,
sl@0: ChannelDown,
sl@0: ChannelUp,
sl@0: FastForward,
sl@0: VolumeMute,
sl@0: Pause,
sl@0: Play,
sl@0: PlayPause,
sl@0: Record,
sl@0: PreviousTrack,
sl@0: Rewind,
sl@0: NextTrack,
sl@0: Stop,
sl@0: VolumeDown,
sl@0: VolumeUp,
sl@0:
sl@0: RecordedTV,
sl@0: Guide,
sl@0: LiveTV,
sl@12: MoreInfo,
sl@12: Print,
sl@0: DVDMenu,
sl@0: DVDAngle,
sl@0: DVDAudio,
sl@0: DVDSubtitle,
sl@0: MyMusic,
sl@0: MyPictures,
sl@0: MyVideos,
sl@0: MyTV,
sl@0: OEM1,
sl@0: OEM2,
sl@0: StandBy,
sl@0: TVJump,
sl@0:
sl@0: Unknown
sl@0: }
sl@0:
sl@0:
sl@0: #region RemoteControlEventArgs
sl@0:
sl@0: public class RemoteControlEventArgs : EventArgs
sl@0: {
sl@3: RemoteControlButton _rcb;
sl@0: InputDevice _device;
sl@3: MceButton iMceButton;
sl@19: ConsumerControl iConsumerControl;
sl@0:
sl@3: public RemoteControlEventArgs(RemoteControlButton rcb, InputDevice device)
sl@0: {
sl@19: SetNullButtons();
sl@19: //
sl@0: _rcb = rcb;
sl@19: _device = device;
sl@0: }
sl@0:
sl@19: public RemoteControlEventArgs(ConsumerControl aConsumerControl, InputDevice device)
sl@19: {
sl@19: SetNullButtons();
sl@19: //
sl@19: iConsumerControl = aConsumerControl;
sl@19: _device = device;
sl@19: }
sl@19:
sl@19:
sl@3: public RemoteControlEventArgs(MceButton mce, InputDevice device)
sl@3: {
sl@19: SetNullButtons();
sl@19: //
sl@19: iMceButton = mce;
sl@19: _device = device;
sl@19: }
sl@19:
sl@19: private void SetNullButtons()
sl@19: {
sl@19: iConsumerControl = ConsumerControl.Null;
sl@19: iMceButton = MceButton.Null;
sl@3: _rcb = RemoteControlButton.Unknown;
sl@3: }
sl@0:
sl@0: public RemoteControlEventArgs()
sl@0: {
sl@3: iMceButton = MceButton.Null;
sl@0: _rcb = RemoteControlButton.Unknown;
sl@0: _device = InputDevice.Key;
sl@0: }
sl@0:
sl@0: public RemoteControlButton Button
sl@0: {
sl@0: get { return _rcb; }
sl@0: set { _rcb = value; }
sl@0: }
sl@0:
sl@3: public MceButton MceButton
sl@3: {
sl@3: get { return iMceButton; }
sl@3: set { iMceButton = value; }
sl@3: }
sl@3:
sl@19: public ConsumerControl ConsumerControl
sl@19: {
sl@19: get { return iConsumerControl; }
sl@19: set { iConsumerControl = value; }
sl@19: }
sl@19:
sl@0: public InputDevice Device
sl@0: {
sl@0: get { return _device; }
sl@0: set { _device = value; }
sl@0: }
sl@0: }
sl@0:
sl@0: #endregion RemoteControlEventArgs
sl@0:
sl@0:
sl@0: public sealed class RemoteControlDevice
sl@0: {
sl@19: public delegate bool RemoteControlDeviceEventHandler(object sender, RemoteControlEventArgs e);
sl@0: public event RemoteControlDeviceEventHandler ButtonPressed;
sl@0:
sl@19: ///
sl@19: /// Return true if the usage was processed.
sl@19: ///
sl@19: ///
sl@19: ///
sl@19: public delegate bool HidUsageHandler(ushort aUsage);
sl@12:
sl@12:
sl@0: //-------------------------------------------------------------
sl@0: // constructors
sl@0: //-------------------------------------------------------------
sl@0:
sl@15: public RemoteControlDevice(IntPtr aHWND)
sl@0: {
sl@0: // Register the input device to receive the commands from the
sl@0: // remote device. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwmt/html/remote_control.asp
sl@0: // for the vendor defined usage page.
sl@0:
sl@22: RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[5];
sl@0:
sl@21: int i = 0;
sl@21: rid[i].usUsagePage = (ushort)Hid.UsagePage.MceRemote;
sl@21: rid[i].usUsage = (ushort)Hid.UsageIdMce.MceRemote;
sl@21: rid[i].dwFlags = Const.RIDEV_EXINPUTSINK;
sl@21: rid[i].hwndTarget = aHWND;
sl@0:
sl@21: i++;
sl@21: rid[i].usUsagePage = (ushort)Hid.UsagePage.Consumer;
sl@21: rid[i].usUsage = (ushort)Hid.UsageIdConsumer.ConsumerControl;
sl@21: rid[i].dwFlags = Const.RIDEV_EXINPUTSINK;
sl@21: rid[i].hwndTarget = aHWND;
sl@0:
sl@21: i++;
sl@21: rid[i].usUsagePage = (ushort)Hid.UsagePage.Consumer;
sl@21: rid[i].usUsage = (ushort)Hid.UsageIdConsumer.Selection;
sl@21: rid[i].dwFlags = Const.RIDEV_EXINPUTSINK;
sl@21: rid[i].hwndTarget = aHWND;
sl@21:
sl@22: i++;
sl@22: rid[i].usUsagePage = (ushort)Hid.UsagePage.GenericDesktopControl;
sl@22: rid[i].usUsage = (ushort)Hid.UsageIdGenericDesktop.SystemControl;
sl@22: rid[i].dwFlags = Const.RIDEV_EXINPUTSINK;
sl@22: rid[i].hwndTarget = aHWND;
sl@21:
sl@22: i++;
sl@22: rid[i].usUsagePage = (ushort)Hid.UsagePage.GenericDesktopControl;
sl@22: rid[i].usUsage = (ushort)Hid.UsageIdGenericDesktop.Keyboard;
sl@22: rid[i].dwFlags = Const.RIDEV_EXINPUTSINK;
sl@22: rid[i].hwndTarget = aHWND;
sl@21:
sl@21: //i++;
sl@21: //rid[i].usUsagePage = (ushort)Hid.UsagePage.GenericDesktopControl;
sl@21: //rid[i].usUsage = (ushort)Hid.UsageIdGenericDesktop.Mouse;
sl@21: //rid[i].dwFlags = Const.RIDEV_EXINPUTSINK;
sl@21: //rid[i].hwndTarget = aHWND;
sl@21:
sl@0:
sl@15: if (!Function.RegisterRawInputDevices(rid,(uint) rid.Length,(uint) Marshal.SizeOf(rid[0])))
sl@0: {
sl@15: throw new ApplicationException("Failed to register raw input devices: " + Marshal.GetLastWin32Error().ToString());
sl@0: }
sl@0: }
sl@0:
sl@0:
sl@0: //-------------------------------------------------------------
sl@0: // methods
sl@0: //-------------------------------------------------------------
sl@0:
sl@0: public void ProcessMessage(Message message)
sl@0: {
sl@0: switch (message.Msg)
sl@0: {
sl@15: case Const.WM_KEYDOWN:
sl@15: ProcessKeyDown(message.WParam);
sl@0: break;
sl@15: case Const.WM_INPUT:
sl@19: //Returning zero means we processed that message.
sl@19: message.Result = new IntPtr(0);
sl@0: ProcessInputCommand(ref message);
sl@0: break;
sl@0: }
sl@0:
sl@0: }
sl@0:
sl@0:
sl@0: //-------------------------------------------------------------
sl@0: // methods (helpers)
sl@0: //-------------------------------------------------------------
sl@0:
sl@15: private void ProcessKeyDown(IntPtr wParam)
sl@0: {
sl@0: RemoteControlButton rcb = RemoteControlButton.Unknown;
sl@0:
sl@15: switch (wParam.ToInt32())
sl@0: {
sl@0: case (int) Keys.Escape:
sl@0: rcb = RemoteControlButton.Clear;
sl@22: break;
sl@22: case (int)Keys.Up:
sl@22: rcb = RemoteControlButton.Up;
sl@22: break;
sl@0: case (int) Keys.Down:
sl@0: rcb = RemoteControlButton.Down;
sl@0: break;
sl@0: case (int) Keys.Left:
sl@0: rcb = RemoteControlButton.Left;
sl@22: break;
sl@22: case (int)Keys.Right:
sl@22: rcb = RemoteControlButton.Right;
sl@22: break;
sl@22: case (int)Keys.Enter:
sl@22: rcb = RemoteControlButton.Enter;
sl@22: break;
sl@0: case (int) Keys.D0:
sl@0: rcb = RemoteControlButton.Digit0;
sl@0: break;
sl@0: case (int) Keys.D1:
sl@0: rcb = RemoteControlButton.Digit1;
sl@0: break;
sl@0: case (int) Keys.D2:
sl@0: rcb = RemoteControlButton.Digit2;
sl@0: break;
sl@0: case (int) Keys.D3:
sl@0: rcb = RemoteControlButton.Digit3;
sl@0: break;
sl@0: case (int) Keys.D4:
sl@0: rcb = RemoteControlButton.Digit4;
sl@0: break;
sl@0: case (int) Keys.D5:
sl@0: rcb = RemoteControlButton.Digit5;
sl@0: break;
sl@0: case (int) Keys.D6:
sl@0: rcb = RemoteControlButton.Digit6;
sl@0: break;
sl@0: case (int) Keys.D7:
sl@0: rcb = RemoteControlButton.Digit7;
sl@0: break;
sl@0: case (int) Keys.D8:
sl@0: rcb = RemoteControlButton.Digit8;
sl@0: break;
sl@0: case (int) Keys.D9:
sl@0: rcb = RemoteControlButton.Digit9;
sl@22: break;
sl@0: }
sl@0:
sl@22: if (this.ButtonPressed != null && rcb != RemoteControlButton.Unknown)
sl@22: {
sl@22: Debug.WriteLine("KeyDown: " + rcb.ToString());
sl@20: this.ButtonPressed(this, new RemoteControlEventArgs(rcb, InputDevice.Key));
sl@22: }
sl@0: }
sl@0:
sl@0:
sl@12: ///
sl@12: ///
sl@12: ///
sl@12: ///
sl@19: private bool HidConsumerDeviceHandler(ushort aUsage)
sl@12: {
sl@12: if (aUsage == 0)
sl@12: {
sl@12: //Just skip those
sl@19: return false;
sl@12: }
sl@12:
sl@12: if (Enum.IsDefined(typeof(ConsumerControl), aUsage) && aUsage != 0) //Our button is a known consumer control
sl@12: {
sl@12: if (this.ButtonPressed != null)
sl@12: {
sl@19: return this.ButtonPressed(this, new RemoteControlEventArgs((ConsumerControl)aUsage, InputDevice.OEM));
sl@12: }
sl@19: return false;
sl@12: }
sl@12: else
sl@12: {
sl@12: Debug.WriteLine("Unknown Consumer Control!");
sl@19: return false;
sl@12: }
sl@12: }
sl@12:
sl@12: ///
sl@12: ///
sl@12: ///
sl@12: ///
sl@19: private bool HidMceRemoteHandler(ushort aUsage)
sl@12: {
sl@12: if (aUsage == 0)
sl@12: {
sl@12: //Just skip those
sl@19: return false;
sl@12: }
sl@12:
sl@12:
sl@12: if (Enum.IsDefined(typeof(MceButton), aUsage) && aUsage != 0) //Our button is a known MCE button
sl@12: {
sl@12: if (this.ButtonPressed != null)
sl@12: {
sl@19: return this.ButtonPressed(this, new RemoteControlEventArgs((MceButton)aUsage, InputDevice.OEM));
sl@12: }
sl@19: return false;
sl@12: }
sl@12: else
sl@12: {
sl@12: Debug.WriteLine("Unknown MCE button!");
sl@19: return false;
sl@12: }
sl@12: }
sl@12:
sl@0:
sl@0: private void ProcessInputCommand(ref Message message)
sl@0: {
sl@15: //We received a WM_INPUT message
sl@7: Debug.WriteLine("================WM_INPUT================");
sl@6:
sl@15: //Check if we received this message while in background or foreground
sl@15: if (Macro.GET_RAWINPUT_CODE_WPARAM(message.WParam) == Const.RIM_INPUT)
sl@15: {
sl@15: Debug.WriteLine("================FOREGROUND");
sl@15: }
sl@15: else if (Macro.GET_RAWINPUT_CODE_WPARAM(message.WParam) == Const.RIM_INPUTSINK)
sl@15: {
sl@15: Debug.WriteLine("================BACKGROUND");
sl@15: }
sl@0:
sl@17: //Declare some pointers
sl@10: IntPtr rawInputBuffer = IntPtr.Zero;
sl@17: //My understanding is that this is basically our HID descriptor
sl@17: IntPtr preParsedData = IntPtr.Zero;
sl@0:
sl@10: try
sl@10: {
sl@10: //Fetch raw input
sl@11: RAWINPUT rawInput = new RAWINPUT();
sl@11: if (!RawInput.GetRawInputData(message.LParam, ref rawInput, ref rawInputBuffer))
sl@6: {
sl@6: return;
sl@6: }
sl@6:
sl@23:
sl@23:
sl@10: //Fetch device info
sl@10: RID_DEVICE_INFO deviceInfo = new RID_DEVICE_INFO();
sl@11: if (!RawInput.GetDeviceInfo(rawInput.header.hDevice, ref deviceInfo))
sl@10: {
sl@10: return;
sl@10: }
sl@22:
sl@24: //Debug
sl@24: string deviceName = RawInput.GetDeviceName(rawInput.header.hDevice);
sl@24: Debug.WriteLine("Device name: " + deviceName);
sl@24:
sl@24: //Open our device from the device name/path
sl@24: SafeFileHandle handle=Win32.Function.CreateFile(deviceName,
sl@24: Win32.FileAccess.NONE,
sl@24: Win32.FileShare.FILE_SHARE_READ|Win32.FileShare.FILE_SHARE_WRITE,
sl@24: IntPtr.Zero,
sl@24: Win32.CreationDisposition.OPEN_EXISTING,
sl@24: Win32.FileFlagsAttributes.FILE_FLAG_OVERLAPPED,
sl@24: IntPtr.Zero
sl@24: );
sl@24:
sl@24: if (handle.IsInvalid)
sl@24: {
sl@24: Debug.WriteLine("Failed to CreateFile from device name " + Marshal.GetLastWin32Error().ToString());
sl@24: }
sl@24: else
sl@24: {
sl@24: //Get manufacturer string
sl@24: StringBuilder manufacturerString = new StringBuilder(256);
sl@24: bool returnStatus = Win32.Function.HidD_GetManufacturerString(handle, manufacturerString, manufacturerString.Capacity);
sl@24: if (returnStatus)
sl@24: {
sl@24: Debug.WriteLine("Manufacturer: " + manufacturerString.ToString());
sl@24: }
sl@24:
sl@24: //Get product string
sl@24: StringBuilder productString = new StringBuilder(256);
sl@24: returnStatus = Win32.Function.HidD_GetProductString(handle, productString, productString.Capacity);
sl@24: if (returnStatus)
sl@24: {
sl@24: Debug.WriteLine("Product: " + productString.ToString());
sl@24: }
sl@24:
sl@24: handle.Close();
sl@24:
sl@24: }
sl@11:
sl@6:
sl@24:
sl@11: if (rawInput.header.dwType == Const.RIM_TYPEHID) //Check that our raw input is HID
sl@6: {
sl@11: Debug.WriteLine("WM_INPUT source device is HID.");
sl@11: //Get Usage Page and Usage
sl@13: Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage ID: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
sl@10:
sl@17:
sl@17: preParsedData = RawInput.GetPreParsedData(rawInput.header.hDevice);
sl@17:
sl@12: //
sl@13: HidUsageHandler usagePageHandler=null;
sl@12:
sl@14: //Check if this an MCE remote HID message
sl@21: if (deviceInfo.hid.usUsagePage == (ushort)Hid.UsagePage.MceRemote && deviceInfo.hid.usUsage == (ushort)Hid.UsageIdMce.MceRemote)
sl@12: {
sl@13: usagePageHandler = HidMceRemoteHandler;
sl@12: }
sl@14: //Check if this is a consumer control HID message
sl@21: else if (deviceInfo.hid.usUsagePage == (ushort)Hid.UsagePage.Consumer && deviceInfo.hid.usUsage == (ushort)Hid.UsageIdConsumer.ConsumerControl)
sl@12: {
sl@13: usagePageHandler = HidConsumerDeviceHandler;
sl@12: }
sl@14: //Unknown HID message
sl@12: else
sl@6: {
sl@14: Debug.WriteLine("Unknown HID message.");
sl@6: return;
sl@6: }
sl@0:
sl@11: 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: && rawInput.hid.dwCount > 0)) //Check that we have at least one HID msg
sl@3: {
sl@11: return;
sl@11: }
sl@11:
sl@11:
sl@11: //Allocate a buffer for one HID input
sl@17: byte[] hidInputReport = new byte[rawInput.hid.dwSizeHid];
sl@11:
sl@18: Debug.WriteLine("Raw input contains " + rawInput.hid.dwCount + " HID input report(s)");
sl@18:
sl@18: //For each HID input report in our raw input
sl@11: for (int i = 0; i < rawInput.hid.dwCount; i++)
sl@11: {
sl@11: //Compute the address from which to copy our HID input
sl@11: int hidInputOffset = 0;
sl@11: unsafe
sl@3: {
sl@11: byte* source = (byte*)rawInputBuffer;
sl@11: source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + (rawInput.hid.dwSizeHid * i);
sl@11: hidInputOffset = (int)source;
sl@11: }
sl@11:
sl@11: //Copy HID input into our buffer
sl@17: Marshal.Copy(new IntPtr(hidInputOffset), hidInputReport, 0, (int)rawInput.hid.dwSizeHid);
sl@11:
sl@18: //Print HID input report in our debug output
sl@18: string hidDump = "HID input report: ";
sl@17: foreach (byte b in hidInputReport)
sl@11: {
sl@11: hidDump += b.ToString("X2");
sl@11: }
sl@11: Debug.WriteLine(hidDump);
sl@11:
sl@17: //Proper parsing now
sl@18: uint usageCount = 1; //Assuming a single usage per input report. Is that correct?
sl@17: Win32.USAGE_AND_PAGE[] usages = new Win32.USAGE_AND_PAGE[usageCount];
sl@17: Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, preParsedData, hidInputReport, (uint)hidInputReport.Length);
sl@17: if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
sl@17: {
sl@17: Debug.WriteLine("Could not parse HID data!");
sl@17: }
sl@17: else
sl@17: {
sl@17: Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4"));
sl@17: Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4"));
sl@18: //Call on our Usage Page handler
sl@18: usagePageHandler(usages[0].Usage);
sl@17: }
sl@3: }
sl@11:
sl@10: }
sl@11: else if (rawInput.header.dwType == Const.RIM_TYPEMOUSE)
sl@10: {
sl@11: Debug.WriteLine("WM_INPUT source device is Mouse.");
sl@10: // do mouse handling...
sl@10: }
sl@11: else if (rawInput.header.dwType == Const.RIM_TYPEKEYBOARD)
sl@10: {
sl@11: Debug.WriteLine("WM_INPUT source device is Keyboard.");
sl@10: // do keyboard handling...
sl@22: Debug.WriteLine("Type: " + deviceInfo.keyboard.dwType.ToString());
sl@22: Debug.WriteLine("SubType: " + deviceInfo.keyboard.dwSubType.ToString());
sl@22: Debug.WriteLine("Mode: " + deviceInfo.keyboard.dwKeyboardMode.ToString());
sl@22: Debug.WriteLine("Number of function keys: " + deviceInfo.keyboard.dwNumberOfFunctionKeys.ToString());
sl@22: Debug.WriteLine("Number of indicators: " + deviceInfo.keyboard.dwNumberOfIndicators.ToString());
sl@23: Debug.WriteLine("Number of keys total: " + deviceInfo.keyboard.dwNumberOfKeysTotal.ToString());
sl@10: }
sl@10: }
sl@10: finally
sl@10: {
sl@10: //Always executed when leaving our try block
sl@10: Marshal.FreeHGlobal(rawInputBuffer);
sl@17: Marshal.FreeHGlobal(preParsedData);
sl@10: }
sl@0: }
sl@0: }
sl@0: }