Simplify-ing

brucethomas

aBlog.Navigation()

Archives By Date

Post Categories

My Pictures

Related Sites

Sites and Blogs (Favorites)


Programming for the '10 Foot Experience' via Remote Control (Part 1 of 2)

Could your application benefit from a different Human Input Device (HID)?  Recently, I have been thinking about how I can build applications that take advantage of different input devices.  Well, beginning with Service Pack 1, Windows XP added support for infared remote control devices.  These input devices are typically used in home entertainment systems but there is nothing that limits their use to this type of application.  Imagine providing an alternative way for that technology-challenged CEO to browse the company's business intelligence (BI) system, or issue commands via remote to a production control system.

Our focus here is on what Microsoft has label as “10 Foot Experience“.  The 10 Foot Experience (10FX) refers to applications that are designed to be navigated and accept input via a distance greater then traditional “2 Foot Experience“ of a user sitting directly in front of a computer using a keyboard and mouse.


For this article, I am using a Microsoft Windows XP Media Center Remote Control device connect via a USB port. As I previously mentioned, this device requires Service Pack 1 or later to function properly.  To start using the device in an application, I developed a RemoteControlDevice class to process the messages from the device when a button is pressed.  This remote control device generates three different messages: WM_KEYDOWN, WM_APPCOMMAND, and  WM_INPUT.  Each message type contains the command  value for the button that was pressed in either the WParam or LParam property of the Message class.  The messages are captured by overriding the WndProc method of the Form class.  This MSDN article describes the type of message generated by each button on the remote.

// some form...
RemoteControlDevice _remote;

protected override void WndProc(ref Message message)
{
 _remote.ProcessMessage(message);   
 base.WndProc(ref message);
}
...

// use to define which button was pressed
public enum RemoteControlButton
 {
  Clear,
  Down,
  Left,
  Digit0,
  Digit1,
  Digit2,
  Digit3,
  Digit4,
  Digit5,
  Digit6,
  Digit7,
  Digit8,
  Digit9,
  Enter,
  Right,
  Up,
  Back,
  ChannelDown,
  ChannelUp,
  FastForward,
  VolumeMute,
  Pause,
  Play,
  Record,
  PreviousTrack,
  Rewind,
  NextTrack,
  Stop,
  VolumeDown,
  VolumeUp,
  RecordedTV,
  Guide,
  LiveTV,
  Details,
  DVDMenu,
  DVDAngle,
  DVDAudio,
  DVDSubtitle,
  MyMusic,
  MyPictures,
  MyVideos,
  MyTV,
  OEM1,
  OEM2,
  StandBy,
  TVJump,
  Unknown
 }

 public sealed class RemoteControlDevice
 {
  private const int WM_KEYDOWN = 0x0100;
  private const int WM_APPCOMMAND = 0x0319;
  private const int WM_INPUT  = 0x00FF;

  public RemoteControlDevice()
  {
   ...
  }

  public void ProcessMessage(Message message)
  {
   int param;

   switch (message.Msg)
   {
    case WM_KEYDOWN:
     param = message.WParam.ToInt32();
     ProcessKeyDown(param);
     break;
    case WM_APPCOMMAND:
     param = message.LParam.ToInt32();
     ProcessAppCommand(param);
     break;
    case WM_INPUT:
     ProcessInputCommand(ref message);
     break;
   }
  }
   ...
 }

The WM_KEYDOWN message is the easiest message to process because buttons that generate this message represents their direct keyboard equivalent.  The button value is passed in the WParam property of the Message class.

  private void ProcessKeyDown(int param)
  {
   RemoteControlButton rcb = RemoteControlButton.Unknown;

   switch (param)
   {
    case (int) Keys.Escape:
     rcb = RemoteControlButton.Clear;
     break;
    case (int) Keys.Down:
     rcb = RemoteControlButton.Down;
     break;
     ...
    case (int) Keys.D0:
     rcb = RemoteControlButton.Digit0;
     break;
    case (int) Keys.D1:
     rcb = RemoteControlButton.Digit1;
     break;
    ...
    case (int) Keys.Enter:
     rcb = RemoteControlButton.Enter;
     break;
   }

  }

The APP_COMMAND message requires a little more processing to get the appropriate value for buttons that generate this message.  The button for this message is passed via the Message class LParam property.  However, to get the correct button value from the LParam, we must perform some bit shifting and a bitwise operation using the FAPPCOMMAND_MASK value.

  private void ProcessAppCommand(int param)
  {
   RemoteControlButton rcb = RemoteControlButton.Unknown;

   int cmd = (int) (((ushort) (param >> 16)) & ~FAPPCOMMAND_MASK);

   switch (cmd)
   {
    case APPCOMMAND_BROWSER_BACKWARD:
     rcb = RemoteControlButton.Back;
     break;
    case APPCOMMAND_MEDIA_CHANNEL_DOWN:
     rcb = RemoteControlButton.ChannelDown;
     break;
    case APPCOMMAND_MEDIA_CHANNEL_UP:
     rcb = RemoteControlButton.ChannelUp;
     break;
    case APPCOMMAND_MEDIA_FAST_FORWARD:
     rcb = RemoteControlButton.FastForward;
     break;
    case APPCOMMAND_VOLUME_MUTE:
     rcb = RemoteControlButton.VolumeMute;
     break;
    ...     
   }
  }

In Part 2 of this post, I'll discuss how to capture the more complicated WM_INPUT message for the remaining buttons on the remote.

posted on Tuesday, January 25, 2005 12:15 PM