/*---------------------------------------------------------------------------- ** clickat.c ** Utilty to send clicks to X Windows ** ** See usage() for usage instructions. ** **--------------------------------------------------------------------------- ** Copyright 2003 by CodeWeavers, Inc. ** ** 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 ** **--------------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rgb_image.h" /*---------------------------------------------------------------------------- ** Error codes **--------------------------------------------------------------------------*/ #define SUCCESS 0 #define ERR_INVALID_ARGUMENTS 1 #define ERR_NODISPLAY 2 #define ERR_TIMEOUT 3 #define ERR_BAD_MALLOC 4 #define ERR_BAD_FILE 5 /*---------------------------------------------------------------------------- ** Global variables **--------------------------------------------------------------------------*/ long g_delay = -1; long g_timeout = -1; long g_repeat = -1; char *g_window = NULL; char *g_position = NULL; char *g_dragto = NULL; char *g_untilwindow = NULL; int g_untildeath_flag = 0; int g_timed_flag = 0; int g_noup_flag = 0; int g_nodown_flag = 0; int g_untildeath_wait_window = -1 ; int g_diffwid = -1; Window g_wid = 0; int g_x = -1; int g_y = -1; int g_dragto_x = -1; int g_dragto_y = -1; int g_xbutton = -1; float g_multiple = -1; char *g_keybuf = NULL; char *g_image = NULL; int g_err_margin = 7; int g_delay_between_keystrokes = 0; int g_print_flag = 1; /* Moving the mouse normally, I registered ~ 100 moves / second */ #define TYPICAL_MOUSE_SPEED 10 enum action_type { ACTION_INVALID, ACTION_CLICK, ACTION_FIND, ACTION_KEY, ACTION_SAVE_FOCUS, ACTION_RESTORE_FOCUS}; enum action_type g_action = ACTION_INVALID; typedef int (*x_error_handler_type)(Display *, XErrorEvent *); x_error_handler_type g_x_error_handler = NULL; #define SLEEP_INTERVAL 1000 /*---------------------------------------------------------------------------- ** usage **--------------------------------------------------------------------------*/ void usage(char *argv0) { char *bname; bname = basename(argv0); printf("%s - Utility to send clicks to X Windows.\n", bname); printf("----------------------------------------------\n"); printf("Usage:\n"); printf(" %s action [--timeout ms] [--window name] [--position XxY [--dragto XxY]\n", bname); printf(" %*.*s [--untilwindow name] [--untildeath wait|nowait]\n", (int) strlen(bname) + 3, (int) strlen(bname) + 3, ""); printf(" %*.*s [--keystroke keys] [--delay-between-keystrokes ms]\n", (int) strlen(bname) + 3, (int) strlen(bname) + 3, ""); printf(" %*.*s [--image file.png] [--err-margin num]\n", (int) strlen(bname) + 3, (int) strlen(bname) + 3, ""); printf(" %*.*s [--repeat ms] [--delay ms] [--noprint]\n", (int) strlen(bname) + 3, (int) strlen(bname) + 3, ""); printf("Where action can be one of:\n"); printf(" button Send a click with the given X button number\n"); printf(" move Synonym for button0, which is a move with no click\n"); printf(" click|lclick Synonym for button1 (left click)\n"); printf(" rclick Synonym for button3 (right click)\n"); printf(" mclick Synonym for button2 (middle click)\n"); printf(" find Find a window\n"); printf(" save Save focus information\n"); printf(" restore Restore focus information\n"); printf(" key Send keystrokes consisting of the remainder of the arguments\n"); printf(" Keystrokes are normal text, and can also include:\n"); printf(" \\b (backspace), \\t (tab), \\r (return),\n"); printf(" \\e (escape), \\d (delete), \\n, or \\\\ (backslash)\n"); printf(" \\[some-name] (e.g. \\[F1]; use xev to figure it out).\n"); printf(" Control, Alt, and Shift can also be emulated.\n"); printf(" The general form is \\XM where X can be either\n"); printf(" s (left shift), c (left control), a (left alt)\n"); printf(" S (right shift), C (right control), A (right alt),\n"); printf(" R (ISO_Level3_Shift, often AltGr)\n"); printf(" and M is either '+' (press down) or '-' (release).\n"); printf(" Hence, Control-Q is written: \\c+q\\c-\n"); printf(" WARNING: Misplacing the \\X- will likely screw up your keyboard\n"); printf("\n"); printf("The options are as follows:\n"); printf(" --timeout ms How long to wait before failing with a code of %d\n", ERR_TIMEOUT); printf(" --window name Send clicks to the specified window\n"); printf(" --wid wid Specify window by id, not name\n"); printf(" --position Specify where to send the clicks to in x/y coordinates\n"); printf(" --dragto If given, then position specifies start click, and\n"); printf(" dragto specifies release coords.\n"); printf(" --repeat ms If set, makes clickat repeatedly click with ms\n"); printf(" delay between clicks.\n"); printf(" --delay ms Milliseconds to delay after finding our window\n"); printf(" before sending any clicks.\n"); printf(" --timed Perform the operation for timeout period.\n"); printf(" --noup Just push the mouse button down; no up click.\n"); printf(" --nodown Just push the mouse button up; no down click.\n"); printf(" --untilwindow nm Will keep clicking until a Window of 'nm' appears\n"); printf(" --untildeath Keeps clicking until the window given in --window\n"); printf(" goes away\n"); printf(" nowait If window doesn't exist from the beginning, we won't wait for it to appear\n"); printf(" wait Wait for window to appear and then until it disappears\n"); printf(" --diffwid wid Clicks in window with specified name only if it does\n"); printf(" not match the window id wid\n"); printf(" --keystroke Keys that will be send to the destination window\n"); printf(" --delay-between.. Delay between single keystrokes defined in --keystroke\n"); printf(" --image file.png Finds the subimage file.png in either a window, specified\n"); printf(" by --window name, or in the root window. If the image is\n"); printf(" found, then a click is sent to the center of the image's\n"); printf(" location on the screen. If --position is specified, then\n"); printf(" the click is sent to that offset within the subimage.\n"); printf(" Multiple images can be specified by separating each filename\n"); printf(" with commas and no spaces (ie. --image f1.png,f2.png,f3.png)\n"); printf(" --err-margin num Specifies the error margin for the subimage find, from 0-100,\n"); printf(" where 0 is no error margin and 100 produces no errors.\n"); printf(" --noprint Don't print the window id.\n"); printf("%s returns %d on success\n", bname, SUCCESS); printf("\n"); printf("Environment variable overrides:"); printf(" CXTEST_TIME_MULTIPLE Specifies a floating multiplier applied to any\n"); printf(" delay and timeout parameters.\n"); } /*---------------------------------------------------------------------------- ** parse_arguments **--------------------------------------------------------------------------*/ int parse_arguments(int argc, char *argv[]) { int rc; int longindex = 0; char *p; char *untildeath_type = NULL; enum option_types { OPTION_DELAY, OPTION_TIMEOUT, OPTION_WINDOW, OPTION_POSITION, OPTION_DRAGTO, OPTION_REPEAT, OPTION_UNTILWINDOW, OPTION_UNTILDEATH, OPTION_TIMED, OPTION_NOUP, OPTION_NODOWN, OPTION_HELP, OPTION_DIFFWID, OPTION_WID, OPTION_IMAGE, OPTION_ERR_MARGIN, OPTION_KEY, OPTION_KEY_DELAY, OPTION_NOPRINT}; static struct option long_options[] = { {"delay", 1, 0, OPTION_DELAY }, {"timeout", 1, 0, OPTION_TIMEOUT }, {"window", 1, 0, OPTION_WINDOW }, {"position", 1, 0, OPTION_POSITION }, {"dragto", 1, 0, OPTION_DRAGTO}, {"repeat", 1, 0, OPTION_REPEAT }, {"untilwindow", 1, 0, OPTION_UNTILWINDOW }, {"untildeath", 1, 0, OPTION_UNTILDEATH }, {"timed", 0, 0, OPTION_TIMED }, {"noup", 0, 0, OPTION_NOUP}, {"nodown", 0, 0, OPTION_NODOWN}, {"help", 0, 0, OPTION_HELP}, {"diffwid", 1, 0, OPTION_DIFFWID}, {"wid", 1, 0, OPTION_WID}, {"image", 1, 0, OPTION_IMAGE}, {"err-margin", 1, 0, OPTION_ERR_MARGIN}, {"keystroke", 1, 0, OPTION_KEY}, {"delay-between-keystrokes", 1, 0, OPTION_KEY_DELAY}, {"noprint", 0, 0, OPTION_NOPRINT}, {0, 0, 0, 0} }; while (1) { rc = getopt_long_only(argc, argv, "", long_options, &longindex); if (rc == -1) break; switch (rc) { case OPTION_DELAY: g_delay = atol(optarg); break; case OPTION_TIMEOUT: g_timeout = atol(optarg); break; case OPTION_WINDOW: g_window = strdup(optarg); break; case OPTION_REPEAT: g_repeat = atol(optarg); break; case OPTION_POSITION: g_position = strdup(optarg); g_x = atoi(g_position); p = strchr(g_position, 'x'); if (p) g_y = atoi(p + 1); break; case OPTION_DRAGTO: g_dragto = strdup(optarg); g_dragto_x = atoi(g_dragto); p = strchr(g_dragto, 'x'); if (p) g_dragto_y = atoi(p + 1); break; case OPTION_UNTILWINDOW: g_untilwindow = strdup(optarg); break; case OPTION_TIMED: g_timed_flag = 1; break; case OPTION_NOUP: g_noup_flag = 1; break; case OPTION_NODOWN: g_nodown_flag = 1; break; case OPTION_NOPRINT: g_print_flag = 0; break; case OPTION_UNTILDEATH: g_untildeath_flag = 1; untildeath_type = strdup(optarg); if(strcmp(untildeath_type,"wait") == 0) { g_untildeath_wait_window = 1; } else if(strcmp(untildeath_type,"nowait") == 0) { g_untildeath_wait_window = 0; } else { fprintf(stderr, "Error: --untildeath requires a parameter(\"wait\"|\"nowait\")\n"); return(1); } break; case OPTION_DIFFWID: g_diffwid = atoi(optarg); if ( !g_diffwid) g_diffwid = strtol(optarg, (char **) NULL, 16); break; case OPTION_WID: g_wid = atoi(optarg); if ( !g_wid) g_wid = strtol(optarg, (char **) NULL, 16); break; case OPTION_IMAGE: g_image = strdup(optarg); break; case OPTION_ERR_MARGIN: g_err_margin = atoi(optarg); break; case OPTION_KEY: g_keybuf = strdup(optarg); break; case OPTION_KEY_DELAY: g_delay_between_keystrokes = strtol(optarg, (char **) NULL, 10); break; case OPTION_HELP: return(2); } } if (optind > argc - 1) { fprintf(stderr, "Error: You must specify an action type\n"); return(1); } if (strcmp(argv[optind], "key") == 0) { g_action = ACTION_KEY; } if (optind < argc - 1 && g_action != ACTION_KEY) { fprintf(stderr, "Error: too many parameters\n"); return(1); } if (strcmp(argv[optind], "move") == 0) { g_action = ACTION_CLICK; g_xbutton = 0; } if (strcmp(argv[optind], "click") == 0 || strcmp(argv[optind], "lclick") == 0) { g_action = ACTION_CLICK; g_xbutton = 1; } if (strcmp(argv[optind], "rclick") == 0) { g_action = ACTION_CLICK; g_xbutton = 3; } if (strcmp(argv[optind], "mclick") == 0) { g_action = ACTION_CLICK; g_xbutton = 2; } if (memcmp(argv[optind], "button", 6) == 0) { g_action = ACTION_CLICK; g_xbutton = atoi(argv[optind] + 6); } if (strcmp(argv[optind], "save") == 0) g_action = ACTION_SAVE_FOCUS; if (strcmp(argv[optind], "restore") == 0) g_action = ACTION_RESTORE_FOCUS; if (strcmp(argv[optind], "find") == 0) g_action = ACTION_FIND; if (g_action == ACTION_INVALID) { fprintf(stderr, "Error: [%s] is not a valid action type\n", argv[optind]); return(1); } /*------------------------------------------------------------------------ ** Make sure that if we are looking for an image, the error margin is ** set properly (between 0 - 100) **----------------------------------------------------------------------*/ if (g_image != NULL && (g_err_margin < 0 || g_err_margin > 100)) { fprintf(stderr, "Error: [%d] is not a valid subimage find margin of error\n", g_err_margin); return(1); } /*------------------------------------------------------------------------ ** Additionally, don't allow image find and a focus action **----------------------------------------------------------------------*/ if (g_image != NULL && (g_action == ACTION_SAVE_FOCUS || g_action == ACTION_RESTORE_FOCUS)) { fprintf(stderr, "Error: save/restore focus not valid actions with image search\n"); return(1); } /*------------------------------------------------------------------------ ** Process environment variables **----------------------------------------------------------------------*/ p = getenv("CXTEST_TIME_MULTIPLE"); if (p) { g_multiple = atof(p); if(g_multiple > 0) { if(g_delay != -1) g_delay = (long) (((float) g_delay) * g_multiple); if(g_timeout != -1) g_timeout = (long) (((float) g_timeout) * g_multiple); if(g_repeat != -1) g_repeat = (long) (((float) g_repeat) * g_multiple); } } return(0); } /*---------------------------------------------------------------------------- ** manage_focus **--------------------------------------------------------------------------*/ void manage_focus(Display *display, Window *current_focus, Window new_focus) { int revert; XGetInputFocus(display, current_focus, &revert); if (new_focus) XSetInputFocus(display, new_focus, revert, CurrentTime); } /*---------------------------------------------------------------------------- ** find_it_tool ** Recursable routine for window IsViewable windows **--------------------------------------------------------------------------*/ Window find_it_tool(Display *display, char *name, Window window, int level, int only_zero, int require_viewable) { unsigned int count = 0, childnum=0; Window root = 0, parent = 0, *children = NULL, win = 0; XWindowAttributes xwinatt; if(!level) return win; count = 0; XQueryTree(display, window, &root, &parent, &children, &count); if(!children) return win; //fprintf(stderr,"%d windows\n",count); // ***w/windows having the same name*** // with the for loop backward, the most recently focussed window is clicked // with the for loop forward, the least recently focussed window is clicked for(; count>0; count--) { childnum = count - 1; char *str = NULL; Status rc; rc = XFetchName(display, children[childnum], &str); if (! str) { XTextProperty tp; if (XGetWMName(display, children[childnum], &tp)) { if (tp.nitems > 0) { int count = 0, ret; char **list = NULL; ret = XmbTextPropertyToTextList(display, &tp, &list, &count); if((ret == Success || ret > 0) && list != NULL && count == 1) { str = XtNewString(list[0]); } else { printf("value: %s\n", tp.value); } XFreeStringList(list); } } } XGetWindowAttributes(display, children[childnum], &xwinatt); // make sure window has correct name, fits 'childnum' condition, and IsViewable if( str && !strcmp(name, str) && ( (only_zero && !childnum) || !only_zero) && (!require_viewable || xwinatt.map_state == IsViewable) && ( g_diffwid == -1 || (g_diffwid > -1 && g_diffwid != children[childnum])) ) { win = children[childnum]; //fprintf(stderr, "ok: window %d is >%s< --> lvl: %d, win: %d\n",childnum,str,level,(int)win); break; } if (str) XFree(str); win = find_it_tool(display, name, children[childnum], level-1, only_zero, require_viewable); if(win) break; } XFree(children); return win; } /*---------------------------------------------------------------------------- ** find_it ** Find a window of a given name that is viewable. ** ** Returns 0 on failure **--------------------------------------------------------------------------*/ Window find_it(Display *display, char *name, Window window, int level, int require_viewable) { Window win = 0; // first, check for i = 0 win = find_it_tool(display, name, window, level, 1, require_viewable); // if that doesn't work, expand search criteria if ( !win ) { win = find_it_tool(display, name, window, level, 0, require_viewable); } return win; } /*---------------------------------------------------------------------------- ** CalculateAbsoluteCoordinates ** Transform relative windows coordinates into absolute ones ** Parameters: ** 1. display X display ** 2. win Window ** 3-4 x,y Where to click in the window. These coordinates will ** be changed during recursive calls. And at the end they ** will represent absolute coordinates on the display **--------------------------------------------------------------------------*/ void CalculateAbsoluteCoordinates(Display *display, Window win,int* x,int* y) { Window parent_window, root_window, *children_return = NULL; XWindowAttributes win_atr; unsigned int nchildren_return; int status; // fprintf(stderr, "CalculateAbsoluteCoordinates: X,Y %d,%d\n",*x,*y); status = XQueryTree(display, win, &root_window, &parent_window, &children_return,&nchildren_return); if (!status) { // We most likely hit the BadWindow error, do not continue in the recursion return; } if (children_return != NULL) XFree(children_return); if (win == root_window) { return; } else { status = XGetWindowAttributes(display, win, &win_atr); // The x and y coordinates define the location of the drawable. For a window, // these coordinates specify the upper-left outer corner relative to its parent's origin. *x += win_atr.x; *y += win_atr.y; return CalculateAbsoluteCoordinates(display, parent_window,x,y); } } /*---------------------------------------------------------------------------- ** send_click ** Send a click to a given window. ** ** Parameters: ** 1. display X display ** 2. win Window to click to; if 0, send to root ** 3. type Button type (1-5, 1 is left click, 3 is right click) ** 4-5 x,y Where in the window to click; note that these can ** be negative ** **--------------------------------------------------------------------------*/ void send_click(Display *display, Window win, int type, int x, int y, int drag, int dragx, int dragy) { //Window discard; //int revert_state; int x_abs = x; int y_abs = y; if (! win) win = DefaultRootWindow(display); CalculateAbsoluteCoordinates(display,win,&x_abs,&y_abs); XTestFakeMotionEvent(display, 0, x_abs, y_abs, CurrentTime); if (type == 0) return; //XGetInputFocus(display, &discard, &revert_state); //XSetInputFocus(display, win, revert_state, CurrentTime); /* Push the button down */ if (! g_nodown_flag) XTestFakeButtonEvent(display, type, True, TYPICAL_MOUSE_SPEED); /*------------------------------------------------------------------------ ** If they've asked us to 'drag' the mouse, then we have to do it over ** time, mostly because most apps look for steps and time between ** them to watch out for 'false' drags **----------------------------------------------------------------------*/ if (drag) { int i; int dx; int dy; int step_per_x; int step_per_y; int delay = TYPICAL_MOUSE_SPEED; dx = dragx - x; dy = dragy - y; step_per_x = dx / 10; step_per_y = dy / 10; if (g_delay >= 0) delay = g_delay; for (i = 0; i < 10; i++) { XTestFakeRelativeMotionEvent(display, step_per_x, step_per_y, delay); dx -= step_per_x; dy -= step_per_y; } if (dx || dy) XTestFakeRelativeMotionEvent(display, dx, dy, delay); } /* Push the button up */ if (! g_noup_flag) XTestFakeButtonEvent(display, type, False, TYPICAL_MOUSE_SPEED); } /*---------------------------------------------------------------------------- ** send_key ** Send the given keycode to the current focused window ** **--------------------------------------------------------------------------*/ void send_key(Display *display, unsigned int k, int on, int off) { if (on) XTestFakeKeyEvent(display, k, True, CurrentTime + g_delay_between_keystrokes); if (off) XTestFakeKeyEvent(display, k, False, CurrentTime); } /*---------------------------------------------------------------------------- ** send_string ** Send a given string to either the current focused window or to an ** optionally specified window at an optionally specified position. ** **--------------------------------------------------------------------------*/ int send_string(Display *display, Window win, char *position, int x, int y, const unsigned char *str) { Window discard; int revert_state; const unsigned char *p, *q; unsigned char *r; unsigned int k; unsigned int c; int i; int on, off; Window dummy_win; int dummy_int; int shifted; int turn_lock_on = 0; int turn_shift_on = 0; int rc = -1; KeySym lower, upper; int go_low, go_high; unsigned int mask; struct { unsigned char escape_char; unsigned int keysym; int requires_flag; } escape_array[] = { { 't', XK_Tab , 0 }, { 'b', XK_BackSpace , 0 }, { 'e', XK_Escape , 0 }, { 'd', XK_Delete , 0 }, { 'n', XK_Linefeed , 0 }, { 'r', XK_Return , 0 }, { '\\', XK_backslash , 0 }, { 's', XK_Shift_L , 1 }, { 'S', XK_Shift_R , 1 }, { 'c', XK_Control_L , 1 }, { 'C', XK_Control_R , 1 }, { 'a', XK_Alt_L , 1 }, { 'A', XK_Alt_R , 1 }, { 'R', XK_ISO_Level3_Shift, 1 }, }; if (str == NULL) return 0; /*------------------------------------------------------------------------ ** If they specified a Window, set it up as the focus window, ** optionally moving the pointer too **----------------------------------------------------------------------*/ if (win) { if (position) { int x_abs = x; int y_abs = y; CalculateAbsoluteCoordinates(display, win, &x_abs, &y_abs); XTestFakeMotionEvent(display, 0, x_abs, y_abs, CurrentTime); } XGetInputFocus(display, &discard, &revert_state); XSetInputFocus(display, win, revert_state, CurrentTime); } /*------------------------------------------------------------------------ ** Retrieve the current mask; we have to muck with the shift ** state and we should restore things as we go **----------------------------------------------------------------------*/ XQueryPointer(display, DefaultRootWindow(display), &dummy_win, &dummy_win, &dummy_int, &dummy_int, &dummy_int, &dummy_int, &mask); if (mask & LockMask) { turn_lock_on = 1; send_key(display, XKeysymToKeycode(display, XK_Caps_Lock), 0, 1); } shifted = mask & ShiftMask; if (shifted) turn_shift_on = 1; /*------------------------------------------------------------------------ ** Loop through the string, decoding it as we go. **----------------------------------------------------------------------*/ for (p = str; *p; p++) { c = *p; k = NoSymbol; r = NULL; on = off = 1; if (*p == '\\' && *(p + 1)) { p++; /*---------------------------------------------------------------- ** Break out symbols of the type '\[keyname]' **--------------------------------------------------------------*/ if (*p == '[') { q = strchr(p, ']'); if (q && q > p + 1) { r = malloc(q - p); if (r) { memset(r, '\0', q - p); memcpy(r, p + 1, q - p - 1); c = XStringToKeysym(r); } } else { fprintf(stderr, "Error: malformed keyname '%s'\n", p); goto ERREXIT; } p = q; } else { /*------------------------------------------------------------ ** Translate any escape codes of the type '\t', '\b', etc **----------------------------------------------------------*/ for (i = 0; i < sizeof(escape_array) / sizeof(escape_array[0]); i++) { if (escape_array[i].escape_char == *p) { if (escape_array[i].requires_flag) { if (*(p + 1) == '+') off = 0; else if (*(p + 1) == '-') on = 0; else { fprintf(stderr, "Error: \\%c requires a following + or - (for shifted on or off)\n", *p); goto ERREXIT; } p++; } c = escape_array[i].keysym; break; } } if (i >= sizeof(escape_array) / sizeof(escape_array[0])) { fprintf(stderr, "Error: \\%c not a recognized code\n", *p); goto ERREXIT; } } } /*-------------------------------------------------------------------- ** Determine if this is a key which requires shift key ** changes; if so, put the shift key in the right place ** FIXME: This logic looks kludgey, at very best **------------------------------------------------------------------*/ XConvertCase((KeySym) c, &lower, &upper); go_low = go_high = 0; if (lower != upper) { if ((KeySym) c == lower) go_low = 1; else if ((KeySym) c == upper) go_high = 1; } if (!go_low && !go_high) { if (strchr("`1234567890-=\\][';/.,", c)) go_low = 1; else if (strchr("~!@#$%^&*()_+|}{\":?><", c)) go_high = 1; } if (go_low && shifted) { send_key(display, XKeysymToKeycode(display, XK_Shift_L), 0, 1); shifted = 0; } else if (go_high && !shifted) { send_key(display, XKeysymToKeycode(display, XK_Shift_L), 1, 0); shifted = 1; } /*-------------------------------------------------------------------- ** Look up the key code that matches the key symbol **------------------------------------------------------------------*/ k = XKeysymToKeycode(display, (KeySym) c); if (k == NoSymbol) { if (r) fprintf(stderr, "Error converting [%s] to a Keysym\n", r); else fprintf(stderr, "Error converting 0x%x('%c') to a Keysym\n", c, c); } else { send_key(display, k, on, off); } if (r) free(r); } /*------------------------------------------------------------------------ ** Restore shift state **----------------------------------------------------------------------*/ rc = 0; ERREXIT: if (shifted && ! turn_shift_on) send_key(display, XKeysymToKeycode(display, XK_Shift_L), 0, 1); if (! shifted && turn_shift_on) send_key(display, XKeysymToKeycode(display, XK_Shift_L), 1, 0); if (turn_lock_on) send_key(display, XKeysymToKeycode(display, XK_Caps_Lock), 1, 0); return(rc); } /*---------------------------------------------------------------------------- ** handle_xerrors ** There is no way for us to discover that our window has disappeared ** without risking a 'BadWindow' or 'BadMatch' error. Trap it and discard those; ** relay any other errors that come through. **--------------------------------------------------------------------------*/ int handle_xerrors(Display *display, XErrorEvent *event) { char error_message[255]; XGetErrorText(display, event->error_code, error_message, 255); fprintf(stderr, "Xlib error: %s\n", error_message); if (event->error_code == BadWindow || event->error_code == BadMatch) return(0); if (g_x_error_handler) return((*g_x_error_handler)(display, event)); return(0); } /*---------------------------------------------------------------------------- ** relax ** Delay for a while; generally this is going to be the ** time given by SLEEP_PERIOD, unless we're trying to click ** repeatedly faster than that. **--------------------------------------------------------------------------*/ void relax(void) { struct timespec timer; long sleep_period = SLEEP_INTERVAL; if (g_repeat > 0 && g_repeat < sleep_period) sleep_period = g_repeat; timer.tv_sec = sleep_period / 1000; timer.tv_nsec = (sleep_period % 1000) * 1000 * 1000; nanosleep(&timer, NULL); } /*---------------------------------------------------------------------------- ** mstime ** Return time in milliseconds **--------------------------------------------------------------------------*/ long mstime(void) { struct timeval tv; gettimeofday(&tv, NULL); return (tv.tv_sec * 1000 + (tv.tv_usec / 1000)); } /*---------------------------------------------------------------------------- ** parse_multimages ** Parses string of image files separated by commas w/out spaces, ** allocates the appropriate memory for a rgb_image array, and then reads ** the png files into rgb_image format. ** ** parameters: ** *image_count -- the number of images contained in g_image, can be a ** partial count if not all memory allocated properly ** *rc -- the return code for the function ** 0 - success ** !0 - failure **--------------------------------------------------------------------------*/ rgb_image** parse_multimages(int *image_count, int *rc) { int i, h_count=0, icount=0, slen; char *holster=NULL; rgb_image **rimage = NULL; *rc = SUCCESS; /*-------------------------------------------------------------------------- ** Count the number of images in g_image -- count stored in icount **------------------------------------------------------------------------*/ slen = strlen(g_image); h_count=0; for(i=0; i < slen; i++) { if(h_count > 0 && ((i+1) == slen || g_image[i] == ',')) { icount++; h_count = 0; } else h_count++; } /*-------------------------------------------------------------------------- ** Allocate memory for the rgb_image array **------------------------------------------------------------------------*/ rimage = malloc(sizeof(rgb_image*)*icount); if(rimage == NULL) { fprintf(stderr,"Unable to allocate memory for rgb_image**\n"); *rc = ERR_BAD_MALLOC; goto EXIT; } /*-------------------------------------------------------------------------- ** Initialize the rgb_image structures -- set the *image_count, used to ** deallocate memory for each rgb_image structure **------------------------------------------------------------------------*/ for(i=0; i < icount; i++) { rimage[i] = NULL; rimage[i] = rgb_image_init(); if(rimage[i] == NULL) { fprintf(stderr,"Unable to allocate memory for rgb_image*\n"); *rc = ERR_BAD_MALLOC; *image_count = i; goto EXIT; } } *image_count = icount; /*-------------------------------------------------------------------------- ** Allocate a string to store each image files name **------------------------------------------------------------------------*/ holster = malloc(sizeof(char)*(slen+1)); if(holster == NULL) { fprintf(stderr,"Unable to allocate memory for string\n"); *rc = ERR_BAD_MALLOC; goto EXIT; } /*-------------------------------------------------------------------------- ** Convert the png images to rgb_images **------------------------------------------------------------------------*/ h_count = icount = 0; for(i=0; i < slen; i++) { /*------------------------------------------------------------------------ ** Convert the png file if at the end of the file name **----------------------------------------------------------------------*/ if(h_count > 0 && ((i+1) == slen || g_image[i] == ',')) { /*---------------------------------------------------------------------- ** Remember to add a '\0' to the end of the string **--------------------------------------------------------------------*/ if((i+1) == slen && g_image[i] != ',') holster[h_count++] = g_image[i]; holster[h_count] = '\0'; /*---------------------------------------------------------------------- ** Convert the png file and evaluate its success **--------------------------------------------------------------------*/ switch(png_to_rgb_image(holster, rimage[icount])) { case 0: icount++; h_count = 0; holster[0] = '\0'; break; case 1: *rc = ERR_BAD_FILE; goto EXIT; case 2: *rc = ERR_BAD_MALLOC; goto EXIT; } } else /*---------------------------------------------------------------------- ** If not at the end of the filename, keep appending **--------------------------------------------------------------------*/ holster[h_count++] = g_image[i]; } EXIT: /*-------------------------------------------------------------------------- ** Remember to free the memory allocated for the string holster **------------------------------------------------------------------------*/ if(holster != NULL) free(holster); return(rimage); } int main(int argc, char *argv[]) { Display * display; Window my_window = 0; int rc; clock_t start_ms; clock_t now; long start_delay = -1; int done_click = 0; long next_click_at = 0; int been_there = 0; win_image *wimage = NULL; rgb_image **subimage = NULL, *image = NULL; int image_count = 0, i; /*------------------------------------------------------------------------ ** Parse arguments **----------------------------------------------------------------------*/ rc = parse_arguments(argc, argv); if (rc) { if (rc == 2) usage(argv[0]); else fprintf(stderr, "Issue %s --help for usage.\n", argv[0]); exit(ERR_INVALID_ARGUMENTS); } /*------------------------------------------------------------------------ ** Open the Display **----------------------------------------------------------------------*/ display = XOpenDisplay(NULL); if (! display) { fprintf(stderr, "Error: could not open display\n"); return(ERR_NODISPLAY); } g_x_error_handler = XSetErrorHandler(handle_xerrors); /*------------------------------------------------------------------------ ** Handle requests to report the focused window **----------------------------------------------------------------------*/ if (g_action == ACTION_SAVE_FOCUS) { Window win; manage_focus(display, &win, 0); printf("--wid 0x%lx\n", win); rc = SUCCESS; goto ERREXIT; } /*------------------------------------------------------------------------ ** Set up an override, if one is given **----------------------------------------------------------------------*/ if (g_wid) my_window = g_wid; /*------------------------------------------------------------------------ ** Allocate memory for rgb_image and win_image structures if image find ** and convert the png image to an rgb image for the struct subimage **----------------------------------------------------------------------*/ if (g_image != NULL) { wimage = win_image_init(); if(wimage == NULL) { rc = ERR_BAD_MALLOC; goto ERREXIT; } image = rgb_image_init(); if(image == NULL) { rc = ERR_BAD_MALLOC; goto ERREXIT; } subimage = parse_multimages(&image_count, &rc); if(rc != SUCCESS) goto ERREXIT; } /*------------------------------------------------------------------------ ** Loop until we're done **----------------------------------------------------------------------*/ for (now = start_ms = mstime(); 1 ; now = mstime()) { /*-------------------------------------------------------------------- ** Test our timeout **------------------------------------------------------------------*/ if (g_timeout > 0) { if (now > start_ms + g_timeout) { rc = ERR_TIMEOUT; goto ERREXIT; } } /*-------------------------------------------------------------------- ** Okay, if we're only supposed to do 1 click, and it's done, ** saddle up and head home... **------------------------------------------------------------------*/ if (done_click && ! g_timed_flag && ! g_untilwindow && g_untildeath_flag == 0) { rc = SUCCESS; goto ERREXIT; } /*-------------------------------------------------------------------- ** If we're supposed to act on a window, let's find it. **------------------------------------------------------------------*/ if (! my_window && g_window) { my_window = find_it(display, g_window, DefaultRootWindow(display), 3, g_action == ACTION_RESTORE_FOCUS ? 0 : 1); /*---------------------------------------------------------------- ** If they want a post find delay, start that clock ticking... **--------------------------------------------------------------*/ if (my_window && g_delay) { start_delay = now; } } else if (! my_window && ! g_window && g_image) { /*---------------------------------------------------------------- ** Set my_window to be the root window **--------------------------------------------------------------*/ my_window = DefaultRootWindow(display); } /*-------------------------------------------------------------------- ** If we're restoring focus, let's do that and get out **------------------------------------------------------------------*/ if (g_action == ACTION_RESTORE_FOCUS && my_window) { Window win; manage_focus(display, &win, my_window); XMapRaised(display, my_window); rc = SUCCESS; goto ERREXIT; } /*-------------------------------------------------------------------- ** If we're just finding, and we have one, we're done **------------------------------------------------------------------*/ if (my_window && g_action == ACTION_FIND && g_untildeath_flag == 0 && g_image == NULL) { rc = SUCCESS; goto ERREXIT; } /*-------------------------------------------------------------------- ** If we have set --untildeath with nowait flag and we don't find ** our window, we're done **------------------------------------------------------------------*/ if (!my_window && g_untildeath_flag && g_untildeath_wait_window == 0) { rc = SUCCESS; goto ERREXIT; } /*-------------------------------------------------------------------- ** If our window dies, and they gave --untildeath, bail **------------------------------------------------------------------*/ if (my_window && g_untildeath_flag) { if (g_image && done_click) { /*-------------------------------------------------------------- ** If the window is not the root window and is no longer ** present, then success **------------------------------------------------------------*/ if (my_window != DefaultRootWindow(display) && find_it(display, g_window, DefaultRootWindow(display), 3, 1) != my_window) { rc = SUCCESS; goto ERREXIT; } /*-------------------------------------------------------------- ** See if any of the images are still present, if not success **------------------------------------------------------------*/ else { for(i = 0; i < image_count; i++) if(!rgb_image_find_subimage(image, subimage[i], g_err_margin, &g_x, &g_y)) break; if(i == image_count) { rc = SUCCESS; goto ERREXIT; } } } else if (!g_image) { if (find_it(display, g_window, DefaultRootWindow(display), 3, 1) != my_window) { rc = SUCCESS; goto ERREXIT; } } } /*-------------------------------------------------------------------- ** If they were waiting until a *different* window appears, then ** check for that... **------------------------------------------------------------------*/ if (g_untilwindow) { if (find_it(display, g_untilwindow, DefaultRootWindow(display), 3, 1)) { rc = SUCCESS; goto ERREXIT; } } /*-------------------------------------------------------------------- ** If this isn't the first time through the loop, let's relax so as to ** not chew too much cpu. **------------------------------------------------------------------*/ if (been_there) relax(); else been_there = 1; /*-------------------------------------------------------------------- ** If there is a post find delay, then reloop until that expires **------------------------------------------------------------------*/ if (start_delay > 0) { if (now > start_delay + g_delay) start_delay = 0; else { continue; } } /*-------------------------------------------------------------------- ** If we're waiting for a window, and it isn't here, we can't ** do anything **------------------------------------------------------------------*/ if (g_window && ! my_window) continue; /*-------------------------------------------------------------------- ** Send clicks and keystrokes, if any **------------------------------------------------------------------*/ if ( g_image != NULL && (! done_click || (g_repeat > 0 && now >= next_click_at))) { /*-------------------------------------------------------------------- ** Get the ximage for my_window **------------------------------------------------------------------*/ if( my_window) { if(win_image_set_ximage(wimage, display, my_window)) { rc = ERR_BAD_MALLOC; goto ERREXIT; } if(win_image_to_rgb_image(wimage, image)) { rc = ERR_BAD_MALLOC; goto ERREXIT; } /*------------------------------------------------------------------ ** Search image with each image in subimage array **----------------------------------------------------------------*/ for(i = 0; i < image_count; i++) { switch(rgb_image_find_subimage(image, subimage[i], g_err_margin, &g_x, &g_y)) { case 1: /* subimage not found */ break; case 2: /* error in parameters */ rc = ERR_INVALID_ARGUMENTS; goto ERREXIT; case 0: /* subimage found */ if (g_action == ACTION_CLICK && g_xbutton >= 0) send_click(display, my_window, g_xbutton, g_x, g_y, g_dragto != NULL, g_dragto_x, g_dragto_y); if (g_action == ACTION_KEY) { rc = send_string(display, my_window, g_position, g_x, g_y, g_keybuf); if (rc != SUCCESS) goto ERREXIT; } done_click = 1; break; default: /* unrecognized return value */ rc = ERR_INVALID_ARGUMENTS; goto ERREXIT; } } /*------------------------------------------------------------------ ** No subimages found, no timeout specified, so exit **----------------------------------------------------------------*/ if(!done_click && g_timeout == -1) { rc = ERR_TIMEOUT; goto ERREXIT; } } if (g_repeat > 0) next_click_at = now + g_repeat; } else if (! done_click || (g_repeat > 0 && now >= next_click_at)) { if (g_action == ACTION_CLICK && g_xbutton >= 0 && (! g_window || my_window)) send_click(display, my_window, g_xbutton, g_x, g_y, g_dragto != NULL, g_dragto_x, g_dragto_y); if (g_action == ACTION_KEY) { rc = send_string(display, my_window, g_position, g_x, g_y, g_keybuf); if (rc != SUCCESS) goto ERREXIT; } if (g_repeat > 0) next_click_at = now + g_repeat; done_click = 1; } } rc = ERR_TIMEOUT; ERREXIT: if (g_print_flag) printf("%d\n", (int) my_window); win_image_destroy(wimage); rgb_image_destroy(image); /*------------------------------------------------------------------------ ** Free rgb_image array **----------------------------------------------------------------------*/ if(subimage != NULL) { for(i = 0; i < image_count; i++) rgb_image_destroy(subimage[i]); free(subimage); } XCloseDisplay(display); exit(rc); }