/* usbhotkey: Turn USB keyboard events into X11 keyboard events. */
/* Copyright (C) 2007 Lars Krueger */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 2 of the License, or */
/* (at your option) any later version. */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program; if not, write to the Free Software */
/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
/* Portions of this code (overlay image) are ported from xteddy-2.0.1.
*
* The ruby specific code is taken from various tutorials and forum entries
* from all over the net. */
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>
#include <config.h>
/* Undef the symbols that may collide with ruby. */
#undef PACKAGE_NAME
#undef PACKAGE_VERSION
#undef PACKAGE_STRING
#undef PACKAGE_TARNAME
#include <hid.h>
#include <ruby.h>
#include <X11/Xlib.h>
#include <X11/extensions/XTest.h>
#include "constants.h"
/* Uncomment this to enable packet debugging. */
/* #define DEBUG_PACKET */
/*################################# variables ################################*/
/*=========================== HID device interface ===========================*/
/** @name HID device interface
*
*/
/*@{*/
/** Handle for opened Human Interface Device */
static HIDInterface * theHid;
/** Set non-zero if ruby script called the "open device" function */
static unsigned theHidIsOpen=0;
/** Set by signal handler to quit the program. */
static unsigned thePrgShouldQuit=0;
/** Number of the device endpoint. */
static unsigned theEndPoint;
/*@}*/
/*=========================== USB keyboard packets ===========================*/
/** @name USB keyboard packets
* We hardcode the packet length here, although it is theoretically possible
* that a USB keyboard sends other packets.
*
* Each USB packet consists of a byte indicating the state of the shift, ctrl,
* alt, and windows key. The next byte is reserved.
* Then 6 bytes follow that hold the the ids of the currently pressed keys.
* Such a packet is sent every time a key is pressed or released.
*/
/*@{*/
/** Number of usable bytes for keycode */
#define thePacketLen 6
/** Storage space for 2 packets: the last one seen and that of the current
* event */
static char thePktBuf[2][2+thePacketLen];
/** Points to current packet */
static char * thePacket=thePktBuf[0];
/** Points to packet of last event. */
static char * theLastPacket=thePktBuf[1];
/** The time of the last event. */
static struct timeval theLastTime;
/*@}*/
/*====================== ruby variables and callback ids =====================*/
/** @name ruby access
*
* In order to access ruby from C, we need to store function ids and pointer
* to variables.
*/
/*@{*/
/** Id of the ruby function "UHK_keyup" */
static ID theKeyUpFcn;
/** Id of the ruby function "UHK_keydn" */
static ID theKeyDnFcn;
/** Id of the ruby function "UHK_mark" */
static ID theMarkFcn=0;
/*@}*/
/*==================================== X11 ===================================*/
/** @name X11
*
*/
/*@{*/
/** X11 server connection */
static Display * the_display=NULL;
/** Number of milliseconds to wait after a keypress was sent. */
#define INTERKEY_DELAY 0
/*@}*/
/*############################## infrastructure ##############################*/
/** @name Infrastructure
*
*/
/*@{*/
/** Displays error message and help. */
static void
errorHelp( const char * msg)
{
fprintf( stderr, "usbhotkey: %s\n", msg);
fputs(
"usbhotkey: Intercept USB keyboard events and send them to X.\n"
"USAGE: usbhotkey [OPTIONS] <script>\n"
"\n"
"Options:\n"
" -h\n"
" --help -- Display help.\n"
"\n"
"Parameters:\n"
" <script> -- Ruby script containing the code to run on keypress.\n"
, stderr);
/* " -- %%%\n" */
exit( EXIT_FAILURE);
}
/** Signal handler, set a flag to quit. */
static void
quitMe(
int sigNr)
{
thePrgShouldQuit=1;
}
/** Check for a ruby exception and print some help. */
static void
handle_ruby_error(
int status)
{
if( status)
{
unsigned c;
VALUE lasterr = rb_gv_get("$!");
VALUE message = rb_obj_as_string(lasterr);
VALUE ary = rb_funcall( ruby_errinfo, rb_intern("backtrace"), 0);
if( ! strcmp(RSTRING(message)->ptr, "exit"))
{
quitMe(1);
return;
}
printf( "usbhotkey: %s\n", RSTRING(message)->ptr);
printf( "usbhotkey: Backtrace:\n");
for( c=0; c<RARRAY(ary)->len; c++)
printf( "usbhotkey: \tfrom %s\n", RSTRING(RARRAY(ary)->ptr[c])->ptr);
errorHelp( "Error executing start script\n");
}
}
/*@}*/
/*############################ HID device handling ###########################*/
/** @name HID device interface
*
*/
/*@{*/
/** Ruby callback to open a device given its vendor and product id. */
static VALUE
connectHid(
VALUE self,
VALUE vendorIdVal,
VALUE productIdVal,
VALUE endPointVal)
{
if( theHidIsOpen)
rb_raise( rb_eRuntimeError, "HID already connected.");
{
unsigned vendorId=NUM2UINT(vendorIdVal);
unsigned productId=NUM2UINT( productIdVal);
theEndPoint=NUM2UINT( endPointVal);
HIDInterfaceMatcher matcher = { vendorId, productId, NULL, NULL, 0 };
printf( "usbhotkey: Connecting to %04x:%04x\n", vendorId, productId);
hid_return ret=hid_force_open( theHid, 0, &matcher, 3);
if( ret!=HID_RET_SUCCESS)
rb_raise( rb_eRuntimeError, "Can't connect HID.");
/* hid_dump_tree( stdout, theHid);
*/
theHidIsOpen=1;
}
return self;
}
/** Closes HID device and X11 connection. */
static void
closeAll()
{
if( !theHid)
return;
if( theHidIsOpen)
{
hid_close( theHid);
theHidIsOpen=0;
}
hid_delete_HIDInterface( &theHid);
hid_cleanup();
if( the_display)
{
XCloseDisplay( the_display);
the_display=NULL;
}
}
/*@}*/
/*########################### USB packet processing ##########################*/
/** Check all keys in test that are not in set and call ruby function for
* them. */
static void
process_USB_keys(
char * test,
char * set,
ID fcn)
{
unsigned i, j;
test+=2;
set+=2;
for( i=0; i<thePacketLen; ++i)
{
if( test[i])
{
unsigned found=0;
for( j=0; j<thePacketLen; ++j)
{
if( test[i]==set[j])
{
found=1;
break;
}
}
if( !found)
rb_funcall( rb_mKernel, fcn, 1, INT2FIX( test[i]));
}
}
}
/** Check all shift key bits in test that are not in set and call ruby
* function for them. */
static void
process_USB_shift_keys(
unsigned char test,
unsigned char set,
ID fcn)
{
unsigned char mask;
int i;
for( i=0; i<8; ++i)
{
mask = 1 << i;
if( test & mask)
{
if( (set & mask) == 0)
rb_funcall( rb_mKernel, fcn, 1, INT2FIX( -i-1));
}
}
}
/** Process the current packet. This function calls ruby functions which might
* raise an exception. In this case the remainder of the function is not
* executed. */
static VALUE
handlePacket(
VALUE args)
{
/* Get the time since the last event. */
struct timeval thisTime;
int timeElapsed;
gettimeofday( &thisTime, NULL);
timeElapsed=1000*(thisTime.tv_sec-theLastTime.tv_sec)+(thisTime.tv_usec-theLastTime.tv_usec)/1000;
/* Call the mark function indicating the start of the release sequence. */
rb_funcall( rb_mKernel, theMarkFcn, 2, INT2FIX( MARK_START_RELEASE), INT2FIX( timeElapsed));
/* Call the key-up function for those keys that are down, but not in
* packet anymore. */
process_USB_keys( theLastPacket, thePacket, theKeyUpFcn);
/* Check the release of shift keys. */
process_USB_shift_keys( theLastPacket[0], thePacket[0], theKeyUpFcn);
/* Call the mark function indicating the end of the release sequence. */
rb_funcall( rb_mKernel, theMarkFcn, 2, INT2FIX( MARK_END_RELEASE_START_PUSH), INT2FIX( timeElapsed));
/* Check the press of shift keys. */
process_USB_shift_keys( thePacket[0], theLastPacket[0], theKeyDnFcn);
/* Call the key-down function for the keys that are down. */
process_USB_keys( thePacket, theLastPacket, theKeyDnFcn);
/* Call the mark function indicating the end of the push sequence. */
rb_funcall( rb_mKernel, theMarkFcn, 2, INT2FIX( MARK_END_PUSH), INT2FIX( timeElapsed));
theLastTime=thisTime;
return 0;
}
/*############################ X11 key generation ############################*/
/** Send X11 key press event. */
static VALUE
x11_keydn(
VALUE self,
VALUE keycode)
{
unsigned kc=FIX2UINT( keycode);
#ifdef DEBUG_RUBY_IF
printf( "usbhotkey: x11_keydn_norep( %d)\n", kc);
#endif
XTestFakeKeyEvent( the_display, XKeysymToKeycode( the_display, kc), True, INTERKEY_DELAY);
return 0;
}
/** Send X11 key release event. */
static VALUE
x11_keyup(
VALUE self,
VALUE keycode)
{
unsigned kc=FIX2UINT( keycode);
#ifdef DEBUG_RUBY_IF
printf( "usbhotkey: x11_keyup( %d)\n", kc);
#endif
XTestFakeKeyEvent( the_display, XKeysymToKeycode( the_display, kc), False, INTERKEY_DELAY);
return 0;
}
/*################################### Main ###################################*/
int
main(
int argc,
char * * argv)
{
int status=0;
char * script;
hid_return ret;
/* Process commandline */
int opti=1;
while( opti<argc)
{
if( !strcmp( argv[opti], "-h") || !strcmp( argv[opti], "--help"))
errorHelp( "Display help text.");
else
break;
}
if( opti>=argc)
errorHelp( "No script given.");
script=argv[opti];
/* Init libhid */
hid_init();
hid_set_debug(HID_DEBUG_NONE);
hid_set_debug_stream(stderr);
hid_set_usb_debug(1000);
theHid=hid_new_HIDInterface();
atexit( closeAll);
signal( SIGINT, quitMe);
signal( SIGTERM, quitMe);
memset( theLastPacket, 0, thePacketLen+2);
/* Init X11 connection and Test extension. */
the_display=XOpenDisplay( NULL);
if( !the_display)
errorHelp( "Can't open X11 connection.");
/* Init ruby */
ruby_init();
ruby_init_loadpath();
/* Provide additional methods. */
rb_define_global_function( "UHK_connect", connectHid, 3);
rb_define_global_function( "UHK_keydn", x11_keydn, 1);
rb_define_global_function( "UHK_keyup", x11_keyup, 1);
theKeyUpFcn=rb_intern( "UHK_USB_keyup");
theKeyDnFcn=rb_intern( "UHK_USB_keydn");
theMarkFcn= rb_intern( "UHK_USB_mark");
create_mark_constants();
create_usb_constants();
create_win_constants();
/* Load ruby script */
printf( "usbhotkey: Loading script %s\n", script);
rb_load_protect(rb_str_new2(script), 0, &status);
handle_ruby_error( status);
/* Perform USB and X11 event processing. */
if( theHidIsOpen)
{
printf( "usbhotkey: Starting event loop\n");
gettimeofday( &theLastTime, NULL);
theLastTime.tv_sec-=10;
/* Event loop. */
while( !thePrgShouldQuit)
{
memset( thePacket, 0, thePacketLen+2);
/* Read a packet. */
ret = hid_interrupt_read( theHid, theEndPoint, thePacket, thePacketLen+2, 200);
if( ret == HID_RET_SUCCESS )
{
XTestGrabControl( the_display,True);
#ifdef DEBUG_PACKET
unsigned i;
printf( "usbhotkey: Packet received: ");
for( i=0; i<thePacketLen+2; ++i)
printf( "%02x ", thePacket[i]);
printf( "\n");
#endif
/* Find set differences and call ruby functions. */
rb_protect( handlePacket, 0, &status);
handle_ruby_error( status);
/* Switch roles of packet and last packet. */
{
char * h=thePacket;
thePacket=theLastPacket;
theLastPacket=h;
}
XTestGrabControl( the_display, False);
XSync( the_display, False);
}
else
#ifdef LIBHID_HAS_TIMEOUT
if( ret==HID_RET_TIMEOUT)
#endif
{
/* Call the mark function indicating an idle event. */
{
/* Get the time since the last event. */
struct timeval thisTime;
int timeElapsed;
gettimeofday( &thisTime, NULL);
timeElapsed=1000*(thisTime.tv_sec-theLastTime.tv_sec)+(thisTime.tv_usec-theLastTime.tv_usec)/1000;
XTestGrabControl( the_display,True);
rb_funcall( rb_mKernel, theMarkFcn, 2, INT2FIX( MARK_IDLE), INT2FIX( timeElapsed));
XTestGrabControl( the_display, False);
XSync( the_display, False);
theLastTime=thisTime;
}
}
#ifdef LIBHID_HAS_TIMEOUT
else
break;
#endif
}
}
else
{
printf( "usbhotkey: No device opened by script. Exiting.\n");
}
closeAll();
return EXIT_SUCCESS;
}
/* else if( !strcmp( argv[opti], "-%%%")) */
/* { */
/* ++opti; */
/* %%% */
/* } */
/* else if( !strcmp( argv[opti], "-%%%")) */
/* { */
/* ++opti; */
/* if( opti>=argc) */
/* errorHelp( "Missing argument after -%%%"); */
/* */
/* %%% = %%% argv[opti++] %%% ; */
/* } */
syntax highlighted by Code2HTML, v. 0.9.1