Implement XTest extension and xte demo
authorIan Osgood <iano@quirkster.com>
Mon, 24 Apr 2006 21:16:38 +0000 (14:16 -0700)
committerIan Osgood <iano@quirkster.com>
Mon, 24 Apr 2006 21:16:38 +0000 (14:16 -0700)
app/xte/.gitignore [new file with mode: 0644]
app/xte/Makefile [new file with mode: 0644]
app/xte/xte.c [new file with mode: 0644]

diff --git a/app/xte/.gitignore b/app/xte/.gitignore
new file mode 100644 (file)
index 0000000..8bca2cc
--- /dev/null
@@ -0,0 +1,2 @@
+xte
+!Makefile
diff --git a/app/xte/Makefile b/app/xte/Makefile
new file mode 100644 (file)
index 0000000..b602e80
--- /dev/null
@@ -0,0 +1,6 @@
+pkgs = xcb xcb-keysyms
+CFLAGS = `pkg-config --cflags $(pkgs)` -g -Wall -Wpointer-arith -Wstrict-prototypes
+LIBS = `pkg-config --libs $(pkgs)` -lXCBxtest
+
+xte:   xte.c
+       $(CC) $(CFLAGS) xte.c $(LIBS) -o xte
diff --git a/app/xte/xte.c b/app/xte/xte.c
new file mode 100644 (file)
index 0000000..6dfbb5f
--- /dev/null
@@ -0,0 +1,359 @@
+/*
+ *  
+ *  Copyright (c) 2002 Steve Slaven, All Rights Reserved.
+ *  
+ *  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., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307 USA
+ *  
+*/
+
+/* Generates an X event, like keypress/mouseclick/move/etc
+   like a little man in your computer.  :) */
+
+#include <stdio.h>
+#define X_H   /* make sure we aren't using symbols from X.h */
+#include <X11/XCB/xcb.h>
+#include <X11/XCB/xcb_keysyms.h>
+#include <X11/XCB/xtest.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+/*#include "debug.h"*/
+#define dmsg(a,b,...)
+
+#define IS_CMD( x, y ) strncmp( x, y, strlen( y ) ) == 0
+
+/* NOTE: demo only supports US keyboards, but could also support German; */
+/*  see original configure.in */
+/*#include "kbd.h"*/
+#define KBDMAP "us"
+
+/* TODO: this is normally defined in configure.in */
+#define VERSION "0.97"
+
+#define XK_Shift_L                       0xffe1  /* Left shift */
+
+XCBKeySymbols *syms = NULL;
+
+BYTE thing_to_keycode( XCBConnection *c, char *thing ) {
+  XCBKEYCODE kc;
+  XCBKEYSYM ks;
+  
+#if 0   /* There is no XCB equivalent to XStringToKeysym */
+  ks = XStringToKeysym( thing );
+  if( ks == NoSymbol ){
+    fprintf( stderr, "Unable to resolve keysym for '%s'\n", thing );
+    return( thing_to_keycode( c, "space" ) );
+  }
+#else
+  /* For now, assume thing[0] == Latin-1 keysym */
+  ks.id = (BYTE)thing[0];
+#endif  
+
+  kc = XCBKeySymbolsGetKeycode( syms, ks );
+
+  dmsg( 1, "String '%s' maps to keysym '%d'\n", thing, ks );
+  dmsg( 1, "String '%s' maps to keycode '%d'\n", thing, kc );
+
+  return( kc.id );
+}
+
+/* XCBTestFakeInput(type,detail,time,window,x,y,device) */
+
+static void
+fake_input(XCBConnection *c, BYTE type, BYTE detail)
+{
+  XCBWINDOW none = { XCBNone };
+
+  XCBTestFakeInput( c, type, detail, 0, none, 0, 0, 0 );
+}
+
+static void
+fake_motion(XCBConnection *c, BOOL relative, CARD16 x, CARD16 y)
+{
+  XCBWINDOW window = { XCBNone };
+
+  if (!relative) {
+    window = XCBConnSetupSuccessRepRootsIter(XCBGetSetup(c)).data->root;
+  }
+  XCBTestFakeInput( c, XCBMotionNotify, relative, 0, window, x, y, 0 );
+}
+
+void send_key( XCBConnection *c, char *thing ) {
+  static XCBKEYSYM shift = { XK_Shift_L };
+  BYTE code, wrap_code = 0;
+
+  dmsg( 1, "Sending key '%s'\n", thing );
+
+#if 0
+  int probidx;
+  /* Catch some common problem characters (thanks Martin Pirker) */
+  for( probidx = 0; problems[ probidx ] != NULL; probidx += 3 ) {
+    if( strcmp( thing, problems[ probidx ] ) == 0 ) {
+      /*wrap_key = problems[ probidx + 1 ]; */
+      if (problems[ probidx + 1 ] != NULL) {
+        wrap_code = XCBKeySymbolsGetKeycode( syms, shift ).id;
+      }
+      thing = problems[ probidx + 2 ];
+      break;
+    }
+  }
+#else
+  /* no XStringToKeysym support: do by hand */
+/*const char *low = "`1234567890-=[]\\;',./";*/
+  const char *cap = "~!@#$%^&*()_+{}|:\"<>?";
+  
+  if (thing[0] >= 'A' && thing[0] <= 'Z')
+    wrap_code = XCBKeySymbolsGetKeycode( syms, shift ).id;
+  else if (strchr(cap, thing[0]) != NULL)
+    wrap_code = XCBKeySymbolsGetKeycode( syms, shift ).id;
+#endif
+  code = thing_to_keycode( c, thing );
+
+  if( wrap_code )
+    fake_input( c, XCBKeyPress, wrap_code );
+
+  fake_input( c, XCBKeyPress, code );
+  fake_input( c, XCBKeyRelease, code );
+
+  if( wrap_code )
+    fake_input( c, XCBKeyRelease, wrap_code );
+}
+
+void mouse_click( XCBConnection *c, int button ) {
+  dmsg( 1, "Clicking mouse button %d\n", button );
+  fake_input( c, XCBButtonPress, button );
+  fake_input( c, XCBButtonRelease, button );
+}
+
+void mouse_move( XCBConnection *c, int x, int y ) {
+  dmsg( 1, "Moving mouse to %c,%d\n", x, y );
+  fake_motion( c, 0, x, y );
+}
+
+void mouse_rel_move( XCBConnection *c, int x, int y ) {
+  dmsg( 1, "Moving mouse relatively by %c,%d\n", x, y );
+  fake_motion( c, 1, x, y );
+}
+
+void process_command( XCBConnection *c, const char *cmd ) {
+  /* Process a command */
+  int tmpx,tmpy;
+  char str[ 128 ];
+
+  bzero( str, 128 );
+  if( IS_CMD( cmd, "mouseclick " ) ) {
+    sscanf( cmd, "mouseclick %d", &tmpx );
+    tmpx = tmpx<1 ? 1 : (tmpx>5 ? 5 : tmpx);
+    mouse_click( c, tmpx );
+  }else if( IS_CMD( cmd, "key " ) ) {
+    strncpy( str, &cmd[ 4 ], 128 );
+    send_key( c, str );
+  }else if( IS_CMD( cmd, "keydown " ) ) {
+    strncpy( str, &cmd[ 8 ], 128 );
+    fake_input( c, XCBKeyPress, thing_to_keycode( c, str ) );
+  }else if( IS_CMD( cmd, "keyup " ) ) {
+    strncpy( str, &cmd[ 6 ], 128 );
+    fake_input( c, XCBKeyRelease, thing_to_keycode( c, str ) );
+  }else if( IS_CMD( cmd, "mousemove " ) ) {
+    sscanf( cmd, "mousemove %d %d", &tmpx, &tmpy );
+    mouse_move( c, tmpx, tmpy );
+  }else if( IS_CMD( cmd, "mousermove " ) ) {
+    sscanf( cmd, "mousermove %d %d", &tmpx, &tmpy );
+    mouse_rel_move( c, tmpx, tmpy );
+  }else if( IS_CMD( cmd, "sleep " ) ) {
+    sscanf( cmd, "sleep %d", &tmpx );
+    dmsg( 1, "sleep %d\n", tmpx );
+    sleep( tmpx );
+  }else if( IS_CMD( cmd, "usleep " ) ) {
+    sscanf( cmd, "usleep %d", &tmpx );
+    dmsg( 1, "usleep %d\n", tmpx );
+    usleep( tmpx );
+  }else if( IS_CMD( cmd, "mousedown " ) ) {
+    sscanf( cmd, "mousedown %d", &tmpx );
+    tmpx = tmpx<1 ? 1 : (tmpx>5 ? 5 : tmpx);
+    fake_input( c, XCBButtonPress, tmpx );
+  }else if( IS_CMD( cmd, "mouseup " ) ) {
+    sscanf( cmd, "mouseup %d", &tmpx );
+    tmpx = tmpx<1 ? 1 : (tmpx>5 ? 5 : tmpx);
+    fake_input( c, XCBButtonRelease, tmpx );
+  }else if( IS_CMD( cmd, "str " ) ) {
+    cmd += 4;
+    while( cmd[ 0 ] != 0 ) {
+      str[ 0 ] = cmd[ 0 ];
+      send_key( c, str );
+      cmd++;
+    }
+  /* in the absence of XStringToKeysym, allow sending hex syms directly */
+  }else if( IS_CMD( cmd, "sym " ) ) {
+    XCBKEYSYM sym;
+    XCBKEYCODE code;
+    sscanf( str, "sym %x", &sym.id );
+    code = XCBKeySymbolsGetKeycode( syms, sym );
+    fake_input( c, XCBKeyPress, code.id );
+    fake_input( c, XCBKeyRelease, code.id );
+  }else if( IS_CMD( cmd, "symdown " ) ) {
+    XCBKEYSYM sym;
+    sscanf( str, "symdown %x", &sym.id );
+    fake_input( c, XCBKeyPress, XCBKeySymbolsGetKeycode( syms, sym ).id );
+  }else if( IS_CMD( cmd, "symup " ) ) {
+    XCBKEYSYM sym;
+    sscanf( str, "symup %x", &sym.id );
+    fake_input( c, XCBKeyRelease, XCBKeySymbolsGetKeycode( syms, sym ).id );
+  }else{
+    fprintf( stderr, "Unknown command '%s'\n", cmd );
+  }
+
+  XCBFlush( c );
+}
+
+int main( int argc, char *argv[] ) {
+  XCBConnection *c = NULL;
+  int cnt;  /*, tmp_i; */
+  char *buf, *display = NULL;
+  int opt;
+  while( ( opt = getopt( argc, argv, "hx:" ) ) != EOF ) {  /* "hd:x: */
+    switch( opt ) {
+    case 'h':
+      printf( "xte v" VERSION "\n"
+             "Generates fake input using the XTest extension, more reliable than xse\n"
+             "Author: Steve Slaven - http://hoopajoo.net\n"
+             "Ported to XCB: Ian Osgood\n"
+             "Current keyboard map: " KBDMAP "\n"
+             "\n"
+             "usage: %s [-h] [-x display] [arg ..]\n"
+             "\n"
+             "  -h  this help\n"
+             "  -x  send commands to remote X server.  Note that some commands\n"
+             "      may not work correctly unless the display is on the console,\n"
+             "      e.g. the display is currently controlled by the keyboard and\n"
+             "      mouse and not in the background.  This seems to be a limitation\n"
+             "      of the XTest extension.\n"
+             "  arg args instructing the little man on what to do (see below)\n"
+             "      if no args are passec, commands are read from stdin separated\n"
+             "      by newlines, to allow a batch mode\n"
+             "\n"
+             " Commands:\n"
+             "  key k          Press and release key k\n"
+             "  keydown k      Press key k down\n"
+             "  keyup k        Release key k\n"
+             "  str string     Do a bunch of key X events for each char in string\n"
+             "  mouseclick i   Click mouse button i\n"
+             "  mousemove x y  Move mouse to screen position x,y\n"
+             "  mousermove x y Move mouse relative from current location by x,y\n"
+             "  mousedown i    Press mouse button i down\n"
+             "  mouseup i      Release mouse button i\n"
+             "  sleep x        Sleep x seconds\n"
+             "  usleep x       uSleep x microseconds\n"
+             "\n"
+             "Some useful keys (case sensitive)\n"
+             "  Home\n"
+             "  Left\n"
+             "  Up\n"
+             "  Right\n"
+             "  Down\n"
+             "  Page_Up\n"
+             "  Page_Down\n"
+             "  End\n"
+             "  Return\n"
+             "  Backspace\n"
+             "  Tab\n"
+             "  Escape\n"
+             "  Delete\n"
+             "  Shift_L\n"
+             "  Shift_R\n"
+             "  Control_L\n"
+             "  Control_R\n"
+             "  Meta_L\n"
+             "  Meta_R\n"
+             "  Alt_L\n"
+             "  Alt_R\n"
+             "\n"
+             "Sample, drag from 100,100 to 200,200 using mouse1:\n"
+             "  xte 'mousemove 100 100' 'mousedown 1' 'mousemove 200 200' 'mouseup 1'\n"
+             "\n"
+             , argv[ 0 ] );
+      exit( 0 );
+      break;
+#if 0      
+    case 'd':
+      sscanf( optarg, "%d", &tmp_i );
+      dmsg( 2, "Debug set to %d\n", tmp_i );
+      debug_level( tmp_i );
+      break;
+#endif
+    case 'x':
+      display = optarg;
+      break;
+
+    case '?':
+      fprintf( stderr, "Unknown option '%c'\n", optopt );
+      break;
+      
+    default:
+      fprintf( stderr, "Unhandled option '%c'\n", opt );
+      break;
+    }
+  }
+
+  c = XCBConnect( display, NULL );
+  if( c == NULL ) {
+    fprintf( stderr, "Unable to open display '%s'\n", display == NULL ? "default" : display );
+    exit( 1 );
+  }
+  
+  /* do XTest init and version check (need 2.1) */
+  /* XCBTestInit( c );   required? none of the other extension demos do this */
+  
+  XCBTestGetVersionCookie cookie = XCBTestGetVersion( c, 2, 1 );
+
+  XCBGenericError *e = NULL;
+  XCBTestGetVersionRep *xtest_reply = XCBTestGetVersionReply ( c, cookie, &e );
+  if (xtest_reply) {
+    fprintf( stderr, "XTest version %u.%u\n",
+      (unsigned int)xtest_reply->major_version,
+      (unsigned int)xtest_reply->minor_version );
+    free(xtest_reply);
+  }
+  if (e) {
+    fprintf( stderr, "XTest version error: %d", (int)e->error_code );
+    free(e);
+  }
+  
+  
+  /* prep for keysym-->keycode conversion */
+  syms = XCBKeySymbolsAlloc( c );
+
+  if( argc - optind >= 1 ) {
+    /* Arg mode */
+    for( cnt = optind; cnt < argc; cnt++ ) {
+      process_command( c, argv[ cnt ] );
+    }
+  }else{
+    /* STDIN mode */
+    buf = (char *)malloc( 128 );
+    while( fgets( buf, 128, stdin ) ) {
+      buf[ strlen( buf ) - 1 ] = 0; /* Chop \n */
+      process_command( c, buf );
+    }
+  }
+  
+  XCBKeySymbolsFree( syms );
+
+  XCBDisconnect( c );
+  exit( 0 );
+}