This is an archived cached-text copy of the developerWorks article. Please consider viewing the original article at: IBM developerWorks



Skip to main content

skip to main content

developerWorks  >  Open source  >

ThinkPad aerobics: Rotate and shake your laptop to control applications

Start with a laptop containing HDAPS, add Linux and a little Perl, and voila! A giant Wii-like controller for your applications

developerWorks
Document options

Document options requiring JavaScript are not displayed

Sample code


Rate this page

Help us improve this content


Level: Intermediate

Nathan Harrington (harrington.nathan@gmail.com), Programmer, IBM 

11 Mar 2008

Use synthetic X Window System events and embedded accelerometers to control applications by the movement of a laptop computer. Translate gestures, such as shaking, into mode-switching commands with detection algorithms to interact with applications in new ways. Develop tools to help build the next generation of interfaces that use accelerometers, such as applications for laptops and iPhones.

Innovative haptic interface systems are allowing a new level of application interaction. Inexpensive video game peripherals with embedded accelerometers and PC devices ranging in size from laptops to hand-held phones are enabling new methods for application control. Applications frequently lag behind in their interfaces for control, restricting their functionality to keyboard and mouse events through a lack of APIs.

This article demonstrates techniques and code you'll need to connect accelerometer-enabled devices to applications in a traditional desktop computing context.

Requirements

Hardware

Many IBM® ThinkPads manufactured in 2003 and later sport Hard Disk Active Protection System (HDAPS) hardware. If you are unsure of your hardware configuration, check out the technical details on the Lenovo Web site.

Software

You need a Linux® distribution with kernel support for HDAPS. The HDAPS driver must be included in the kernel to enable access to the accelerometers. Newer kernel distributions, including Red Hat, Debian, Gentoo, and Ubuntu, include HDAPS drivers. Consult the HDAPS-related articles in the Resources section for more information about how to get started with HDAPS kernel drivers.

You'll also need the Time::HiRes module from CPAN to provide subsecond timing control when recording data. In addition, the X11::GUITest module is required for sending synthetic X events to applications (see Resources).

Note that the techniques and algorithms described here should work with any notebook computer that has on-board accelerometers. Those with MacBooks or ThinkPads running an OS other than Linux should be able to adapt the code presented here without extraordinary difficulty. Hand-held devices and other accelerometer-enabled systems can also be used to employ the translation between tips, tilts, and commands. There are many methods for sending synthetic keyboard and mouse events to applications that may prove useful if operating on other operating systems. If you are using Microsoft® Windows®, consider using the SendKeys class of commands for application access.



Back to top


Synthetic X events using xorg components

Control of application-specific features is most often accomplished with functional hooks at the API level. However, many applications do not have a full set of APIs and require mouse and keyboard input to manipulate the application fully. Other applications have no API and provide sole interactivity through traditional human computer-interface devices. This article will demonstrate components and algorithms that can be linked together to control applications without a D-Bus or other API.

Sending synthetic X events to applications is the key method to enabling full control of D-Bus and non-API applications.

X11::GUITest

You can spend a great amount of time integrating XTest library function calls into your application. You can specify key release rates and find all the nuances in identifying windows and keeping track of the keyboard and mouse state. Or you can let X11::GUITest do the work for you. The X11::GUITest module provides a robust wrapper around the XTest library for much more efficient handling of events.

Many applications that respond to user-keyboard and mouse events perform in unexpected ways when using synthetic events. You may have to alter your keyboard repeat rate and delay, or find a new method altogether, than standard input mechanisms. For example, Google Earth does not always respond in a useful way to key-down events regardless of the repeat rate and speed. An alternate method for equivalent synthetic input is used for this article: holding down the center control stick of the navigation compass. You may find similar inaccuracies or limitations in other programs when trying to create synthetic X events that perform the same functions as their real-world alternatives. When gluing together applications using programmatically triggered keyboard and mouse events, you may need to experiment to find the input method that provides the best usability experience.



Back to top


Translating HDAPS sensor data to application control

Our sensors have been enabled, and our controlling framework set up, so the next step is to integrate them in a Perl program to translate movement into action. Listing 1 shows the header and variable declaration of the hdapsGoogleEarth.pl script.


Listing 1. hdapsGoogleEarth.pl globals and includes
                
#!/usr/local/bin/perl -w
# hdapsGoogleEarth.pl - control google earth by the pitch and roll of a thinkpad
use strict;
use Time::HiRes  qw( usleep );
use X11::GUITest qw( :ALL );

my $threshMove = 3;    # lower limit indicating motion
my $shakMin = 20;      # amount of a shake required to indicate change in mode
my $timeThreshold = 1; # number of seconds between mode changes
my $maxMove = 25;      # maximum movement of mouse from center point
my @avgX = ();         # array of 10 recent hdaps X values for shake detection
my $lastTime = time;   # record time of last mode change
my $slptim = 25000;    # microseconds to pause between data reads
my $maxTip = 100;      # turn thinkpad on its' side to exit the program
my $restX = 0;         # `resting' positiong of X axis accelerometer
my $restY = 0;         # `resting' positiong of Y axis accelerometer
my @windows;           # array of window id's associated with google earth
my $hdapsFN = "/sys/devices/platform/hdaps/position"; # hdaps sensor

After including the required modules, global variables are set up to process the motion and maintain the input mode. This article demonstrates controlling Google Earth in either zoom or movement mode. Shaking the ThinkPad enables a quick switch between modes for more intuitive navigation. Listing 2 handles the identification and focusing of the Google Earth window.


Listing 2. Identification and focus of Google Earth window
                
@windows = FindWindowLike("Google Earth");
die "No google earth windows found" unless( scalar(@windows) > 0 );
my ( $geX, $geY, $geW, $geH, undef, undef ) = GetWindowPos( $windows[0] );

# rose center is left 110 from geX+geW, down 135 from geY
# zoom center is left 22 from geX+geW, down 135 from geY
my $cntrCompX = ($geX + $geW) - 110;
my $cntrCompY = $geY + 135;

my $centerZoomX = ($geX + $geW) - 22;
my $centerZoomY = $geY + 135;

my $maxZoomTop    = $centerZoomY - $maxMove;
my $maxZoomBottom = $centerZoomY + $maxMove;

my $maxLeft   = $cntrCompX - $maxMove;
my $maxRight  = $cntrCompX + $maxMove;
my $maxTop    = $cntrCompY - $maxMove;
my $maxBottom = $cntrCompY + $maxMove;

my $mouseX = $cntrCompX;
my $mouseY = $cntrCompY;

my $controlMode = "movement";

MoveMouseAbs( $cntrCompX, $cntrCompY );
PressMouseButton M_LEFT;

($restX, $restY ) = readPosition();

The first step is to use the X11::GUITest module to locate the Google Earth window using the FindWindowLike function. Multiple windows for Google Earth are sometimes identified, but the first window ID is usually the main window. Note that if the first window ID is not the main Google Earth window, the program will not process inputs correctly. You may run into problems if you machine does not specify the main Google Earth window as the first Google Earth window ID. If this is the case, experiment with window IDs at different positions in the @windows array until you find the correct ID for the main Google Earth window.

After determining the correct coordinates of the Google Earth input window and moving the mouse into place, a baseline for the accelerometers is determined through readPosition. Listing 3 shows the readPosition subroutine.


Listing 3. readPosition subroutine
                
sub readPosition {

  my ($posX, $posY) = "";

  open(FH," $hdapsFN") or die "can't open hdaps file";

    while(my $hdapsLine = <FH>)
    {
      $hdapsLine =~ s/(\(|\))//g; # remove parens
      ($posX, $posY) = split ",", $hdapsLine;
    }# while hdaps line read 

  close(FH);

  return( $posX, $posY );

}#readPosition

Reading data from the HDAPS sensor is a straightforward process requiring an open and close of the file at each read due to its nonseekable nature. (Check Resources for more information about working with the particulars of the HDAPS sensors.) With the base variable declarations and subroutine complete, the program moves on to the main program loop, shown below.


Listing 4. Main program loop, Part 1
                
while(1)
{   
  # get accelerometer values
  my ($currX, $currY) = readPosition();
  $currX -= $restX;  # adjust for rest data state
  $currY -= $restY;
    
  # exit loop if ThinkPad tipped past 'exit' range
  last if( abs($currX) > $maxTip || abs($currY) > $maxTip );

  my $testX = abs($currX);  # keep original values for trending
  my $testY = abs($currY);
  
  if( $controlMode eq "movement" ) 
  {
    #  stay at the maximum distance to move even if severely tipped
    if( $testX > $maxMove ){ $testX = $maxMove }
    if( $testY > $maxMove ){ $testY = $maxMove }
      
    $mouseX = ($cntrCompX - $testX) if( $currX < -$threshMove );
    $mouseX = ($cntrCompX + $testX) if( $currX > $threshMove );
    $mouseY = ($cntrCompY - $testY) if( $currY < -$threshMove );
    $mouseY = ($cntrCompY + $testY) if( $currY > $threshMove );
    
    if( $mouseX < $maxLeft )  {  $mouseX = $maxLeft   }
    if( $mouseX > $maxRight ) {  $mouseX = $maxRight  }
    if( $mouseY < $maxTop )   {  $mouseY = $maxTop    }
    if( $mouseY > $maxBottom ){  $mouseY = $maxBottom }

    # reset the mouse to the center if the current reading is not past
    # the threshold
    $mouseY = $cntrCompY if( $currY < $threshMove && $currY > -$threshMove );
    $mouseX = $cntrCompX if( $currX < $threshMove && $currX > -$threshMove );

Each pass of the main program loop reads new values from the accelerometer sensors. These values are subtracted from the reference state created earlier to transform values like '-379,-380' to a more useful '0,0' (when the ThinkPad is at rest). Next, the program checks to see if the ThinkPad has been tipped drastically from a rest state to indicate an exit condition. This is necessary as full keyboard and mouse control has been taken by the program and, therefore, a physical-orientation method of telling the program to exit is required.

If the program is in movement-processing mode, the next two lines set the upper movement-boundary limits to $maxMove. The next four lines move the mouse in the appropriate X and Y dimensions depending on how far the ThinkPad has been tipped past the threshold of movement ($threshMove). Very few people possess the ability to consistently hold their ThinkPads perfectly level, so it's useful to have a threshold of tip in each dimension for easier base-state detection.

The next four lines restrict the movement of the mouse to the maximum distance from the center in each dimension. Finally, the mouse position is reset to the center if the aforementioned threshold of movement has not been reached. Resetting the center position can create a jump movement upon tilt past a threshold, but is preferable in many applications to a slow drift or unknown initial state.

Listing 5 shows the comparatively simple zoom-control mode.


Listing 5. Main program loop, Part 2
                
  }else
  {

    # zoom control - move up and down only
    $mouseY = ( $centerZoomY - $testY ) if( $currY < -$threshMove );
    $mouseY = ( $centerZoomY + $testY ) if( $currY > $threshMove  );
    if( $mouseY < $maxZoomTop ){  $mouseY = $maxZoomTop }
    if( $mouseY > $maxZoomBottom ){  $mouseY = $maxZoomBottom }

    $mouseX = $centerZoomX;
    $mouseY = $centerZoomY if( $currY < $threshMove && $currY > -$threshMove )

  }#if in zoom mode

  MoveMouseAbs( $mouseX, $mouseY);  # move the mouse after coordinates computed

Similar to the movement mode above, the zoom-control mode moves the mouse in the Y dimension depending on how far the ThinkPad has been tipped past the movement threshold forward or back. The computed movement is then restricted to the maximum distance from the center of the zoom slider. Again, the mouse position is reset to the center if the movement threshold is not reached.

After computation of the mouse position, whether in zoom or movement mode, the mouse is moved to control the application.



Back to top


Gesture acquisition for mode switching

With the code above, you can control the movement and zoom modes of Google Earth by moving your ThinkPad. To switch between modes, an additional check is required in each pass of the main program loop. Listings 6 and 7 show the remainder of the main program logic and the shakeCheck subroutine that perform the gesture acquisition and mode change.


Listing 6. Main program loop, Part 3
                
  # mode switch by shaking, only if at least timeThreshold seconds have passed
  # since last mode switch
  if( shakeCheck( $currX ) == 1 && abs($lastTime - time) > $timeThreshold)
  {
    $lastTime = time;
    if( $controlMode eq "movement" )
    {
      $controlMode = "zoomRotate";
      $mouseX = $centerZoomX;
      $mouseY = $centerZoomY;
    }else
    {
      $controlMode = "movement";
      $mouseX = $cntrCompX;
      $mouseY = $cntrCompY;
    }#if movement or zoomRotate mode

    ReleaseMouseButton(M_LEFT) if( IsMouseButtonPressed(M_LEFT) );
    MoveMouseAbs( $mouseX, $mouseY);
    PressMouseButton( M_LEFT );

    print "$controlMode mode\n";

  }# if a large enough shake in X dimension detected

  usleep($slptim);

}#while

ReleaseMouseButton M_LEFT;   # let go of button on exit

A true value from the shakeCheck subroutine, as well as an acceptable time difference between mode switches, will alternate between movement and zoom mode. $timeThreshold is easily configurable to require more or less time between mode shaking. Depending on how hyper-caffeinated you are, you may want to increase this parameter, as well as the shakMin variable shown in the shakeCheck subroutine, as shown in the globals definition section and Listing 7.


Listing 7. shakeCheck subroutine
                
sub shakeCheck
{
  my $xVal  = $_[0];
  push @avgX, $xVal;
  # build an array of 10 samples before attempting processing
  return(0) if( @avgX < 10 );

  my $currAvg = 0;
  for my $eachX ( @avgX ){ $currAvg += $eachX };
  $currAvg = $currAvg /10;
  shift @avgX;

  # if current value is a big enough deviation from average value
  return(1) if( abs($currAvg - $xVal) > $shakMin );
  return(0);

}#shakeCheck

The subroutine shakeCheck builds an average of the past 10 HDAPS sensor reads in the X dimension only. The current value of the X dimension is compared to the average, and if they diverge by at least $shakMin, a shake condition is set and the subroutine returns true. A rapid side-to-side shaking motion is easily detected because, although the movements are relatively small, the accelerations are large enough for useful gesture acquisition.



Back to top


hdapsGoogleEarth.pl and hdapsGoogleStreeView.pl usage

To run the hdapsGoogleEarth.pl program as built above, start the Google Earth application. When the default globe is displayed, start the synthetic X events translator with the command perl hdapsGoogleEarth.pl. You should see the Google Earth window brought into focus, with the mouse grabbing the movement-control knob in the upper right-hand corner. Pick up your ThinkPad and start navigating, and remember to tip your ThinkPad near vertical in either dimension to exit the program.

Check the code archive in the Resources section for another Perl script to control Google street view by translating ThinkPad tips and tilts to key presses. To use the hdapsGoogleStreetView.pl script, make sure you have a Firefox Web browser running with Google Maps open and in street-view mode.. Start the application with perl hdapsGoogleStreetView.pl, and you'll be able to navigate city streets with tilts and rolls. Examine the source code to see how the hdapsGoogleStreetView.pl application uses keystrokes instead of mouse moves for application control.



Back to top


Conclusion and further examples

Share this...

digg Digg this story
del.icio.us Post to del.icio.us
Slashdot Slashdot it!

Continuous input applications, such as mapping software, are excellent for applying intuitive navigation systems. With the code demonstrated here, you can apply this next level of navigational system to any application through the use of synthetic keyboard and mouse events.

Connect your accelerometer-enabled cell phone over Bluetooth to your PC, and control applications as you rotate your phone. Attach an inertial gyroscope to the ThinkPad and scan the map in rotate, as well as tilt and roll.




Back to top


Download

DescriptionNameSizeDownload method
Sample codeos-thinkpad-hdapsAppControl.zip4KBHTTP
Information about download methods


Resources

Learn

Get products and technologies
  • Grab the Time::HiRes module from CPAN.

  • Learn more about Linux kernel download mirrors for acquiring HDAPS.

  • Grab Perl from the source at Perl.org.

  • Innovate your next open source development project with IBM trial software, available for download or on DVD.

  • Download IBM product evaluation versions, and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.

Discuss


About the author

Nathan Harrington

Nathan Harrington is a programmer at IBM currently working with Linux and resource-locating technologies.




Rate this page


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top