1 /* xdotool
2  *
3  * command line interface to the xdo library
4  *
5  * getwindowfocus contributed by Lee Pumphret
6  * keyup/down contributed by Lee Pumphret
7  *
8  * vim:expandtab shiftwidth=2 softtabstop=2
9  */
10 
11 #define _GNU_SOURCE 1
12 #ifndef __USE_BSD
13 #define __USE_BSD /* for strdup on linux/glibc */
14 #endif /* __USE_BSD */
15 
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <unistd.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <getopt.h>
22 #include <string.h>
23 #include <strings.h>
24 #include <errno.h>
25 #include <ctype.h>
26 #include <stdarg.h>
27 
28 #include "xdo.h"
29 #include "xdotool.h"
30 
31 static int script_main(int argc, char **argv);
32 static int args_main(int argc, char **argv);
33 int context_execute(context_t *context);
34 void consume_args(context_t *context, int argc);
35 void window_save(context_t *context, Window window);
36 void window_list(context_t *context, const char *window_arg,
37                  Window **windowlist_ret, int *nwindows_ret,
38                  const int add_to_list);
39 int window_get_arg(context_t *context, int min_arg, int window_arg_pos,
40                    const char **window_arg);
41 int window_is_valid(context_t *context, const char *window_arg);
42 int is_command(char* cmd);
43 void xdotool_debug(context_t *context, const char *format, ...);
44 void xdotool_output(context_t *context, const char *format, ...);
45 
consume_args(context_t * context,int argc)46 void consume_args(context_t *context, int argc) {
47   if (argc > context->argc) {
48     fprintf(stderr,
49             "Can't consume %d args; are only %d available. This is a bug.\n",
50             argc, context->argc);
51     context->argv += context->argc;
52     context->argc = 0;
53     return;
54   }
55 
56   context->argv += argc;
57   context->argc -= argc;
58 } /* void consume_args(context_t *, int) */
59 
window_save(context_t * context,Window window)60 void window_save(context_t *context, Window window) {
61   if (context->windows != NULL) {
62     free(context->windows);
63   }
64 
65   context->windows = calloc(1, sizeof(Window));
66   context->nwindows = 1;
67   context->windows[0] = window;
68 } /* void window_save(context_t *, Window) */
69 
window_is_valid(context_t * context,const char * window_arg)70 int window_is_valid(context_t *context, const char *window_arg) {
71   if (window_arg == NULL) {
72     return True;
73   }
74 
75   if (window_arg[0] != '%') {
76     return True;
77   }
78 
79   /* Selected a window with %N or %@, but are there windows on the stack? */
80   if (context->nwindows == 0) {
81     fprintf(stderr, "There are no windows in the stack\n");
82     return False;
83   }
84 
85   if (window_arg[1] == '\0') {
86     fprintf(stderr, "Invalid window stack selection '%s'\n", window_arg);
87     return False;
88   }
89 
90   if (window_arg[1] == '@') {
91     return True;
92   }
93 
94   int window_index = atoi(window_arg + 1);
95   if (abs(window_index - 1) >= context->nwindows || (window_index == 0)) {
96     fprintf(stderr, "Invalid window stack selection '%s' (out of range)\n", window_arg);
97     return False;
98   }
99 
100   return True;
101 } /* int window_is_valid(context_t *, const char *) */
102 
window_get_arg(context_t * context,int min_arg,int window_arg_pos,const char ** window_arg)103 int window_get_arg(context_t *context, int min_arg, int window_arg_pos,
104                    const char **window_arg) {
105   if (context->argc < min_arg) {
106     fprintf(stderr, "Too few arguments (got %d, minimum is %d)\n",
107             context->argc, min_arg);
108     return False;
109   } else if (context->argc == min_arg) {
110     /* nothing, keep default */
111   } else if (context->argc > min_arg) {
112     if (is_command(context->argv[min_arg])) {
113       /* keep default */
114     } else {
115       /* got enough args, let's use the window you asked for */
116       *window_arg = context->argv[window_arg_pos];
117       consume_args(context, 1);
118     }
119   }
120 
121   if (!window_is_valid(context, *window_arg)) {
122     fprintf(stderr, "Invalid window '%s'\n", *window_arg);
123     return False;
124   }
125 
126   return True;
127 } /* int window_get_arg(context_t *, int, int, char **, int *) */
128 
window_list(context_t * context,const char * window_arg,Window ** windowlist_ret,int * nwindows_ret,const int add_to_list)129 void window_list(context_t *context, const char *window_arg,
130                  Window **windowlist_ret, int *nwindows_ret,
131                  const int add_to_list) {
132   /* If window_arg is NULL and we have windows in the list, use the list.
133    * If window_arg is "%@" and we have windows in the list, use the list.
134    * If window_arg is "%N" and we have windows in the list, use Nth window.
135    *   'N' above must be a positive number.
136    * Otherwise, assume it's a window id.
137    *
138    * TODO(sissel): Not implemented yet:
139    * If window_arg is "%r" it means the root window of the current screen.
140    * If window_arg is "%q" it means we will wait for you to select a window
141    *   by clicking on it. (May not be necessary since we have 'selectwindow')
142    * If window_arg is "%c" it means the currently-active window.
143    */
144 
145   *nwindows_ret = 0;
146   *windowlist_ret = NULL;
147 
148   if (window_arg != NULL && window_arg[0] == '%') {
149     if (context->nwindows == 0) {
150       fprintf(stderr, "There are no windows on the stack, Can't continue.\n");
151       return;
152     }
153 
154     if (strlen(window_arg) < 2) {
155       fprintf(stderr, "Invalid window selection '%s'\n", window_arg);
156       return;
157     }
158 
159     /* options.
160      * %N selects the Nth window. %1, %2, %-1 (last), %-2, etc.
161      * %@ selects all
162      */
163     if (window_arg[1] == '@') {
164       *windowlist_ret = context->windows;
165       *nwindows_ret = context->nwindows;
166     } else if (window_arg[1] == 'q') {
167       /* TODO(sissel): Wait for you to click on the window. */
168     } else if (window_arg[1] == 'r') {
169       /* TODO(sissel): Get the root window of the current screen */
170     } else if (window_arg[1] == 'c') {
171       /* TODO(sissel): Get the current window */
172     } else {
173       /* Otherwise assume %N */
174       int window_index = atoi(window_arg + 1);
175       if (window_index < 0) {
176         /* negative offset */
177         window_index = context->nwindows + window_index;
178       }
179 
180       if (window_index > context->nwindows || window_index <= 0) {
181         fprintf(stderr, "%d is out of range (only %d windows in list)\n",
182                 window_index, context->nwindows);
183         return;
184       }
185 
186       /* Subtract 1 since %1 is the first window in the list */
187       context->window_placeholder[0] = context->windows[window_index - 1];
188       *windowlist_ret = context->window_placeholder;
189       *nwindows_ret = 1;
190     }
191   } else {
192     /* Otherwise, window_arg is either invalid or null. Default to CURRENTWINDOW
193      */
194 
195     /* We can't return a pointer to a piece of the stack in this function,
196      * so we'll store the window in the context_t and return a pointer
197      * to that.
198      */
199     Window window = CURRENTWINDOW;
200     if (window_arg != NULL) {
201       window = (Window)strtol(window_arg, NULL, 0);
202     }
203 
204     context->window_placeholder[0] = window;
205     *nwindows_ret = 1;
206     *windowlist_ret = context->window_placeholder;
207   }
208 
209   if (add_to_list) {
210     /* save the window to the windowlist */
211   }
212 }
213 
214 
215 struct dispatch {
216   const char *name;
217   int (*func)(context_t *context);
218 } dispatch[] = {
219   /* Query functions */
220   { "getactivewindow", cmd_getactivewindow, },
221   { "getwindowfocus", cmd_getwindowfocus, },
222   { "getwindowname", cmd_getwindowname, },
223   { "getwindowclassname", cmd_getwindowclassname},
224   { "getwindowpid", cmd_getwindowpid, },
225   { "getwindowgeometry", cmd_getwindowgeometry, },
226   { "getdisplaygeometry", cmd_get_display_geometry, },
227   { "search", cmd_search, },
228   { "selectwindow", cmd_window_select, },
229 
230   /* Help me! */
231   { "help", cmd_help, },
232   { "version", cmd_version, },
233 
234   /* Action functions */
235   { "behave", cmd_behave, },
236   { "behave_screen_edge", cmd_behave_screen_edge, },
237   { "click", cmd_click, },
238   { "getmouselocation", cmd_getmouselocation, },
239   { "key", cmd_key, },
240   { "keydown", cmd_key, },
241   { "keyup", cmd_key, },
242   { "mousedown", cmd_mousedown, },
243   { "mousemove", cmd_mousemove, },
244   { "mousemove_relative", cmd_mousemove_relative, },
245   { "mouseup", cmd_mouseup, },
246   { "set_window", cmd_set_window, },
247   { "type", cmd_type, },
248   { "windowactivate", cmd_windowactivate, },
249   { "windowfocus", cmd_windowfocus, },
250   { "windowkill", cmd_windowkill, },
251   { "windowclose", cmd_windowclose, },
252   { "windowquit", cmd_windowquit, },
253   { "windowmap", cmd_windowmap, },
254   { "windowminimize", cmd_windowminimize, },
255   { "windowmove", cmd_windowmove, },
256   { "windowraise", cmd_windowraise, },
257   { "windowreparent", cmd_windowreparent, },
258   { "windowsize", cmd_windowsize, },
259   { "windowstate", cmd_windowstate, },
260   { "windowunmap", cmd_windowunmap, },
261 
262   { "set_num_desktops", cmd_set_num_desktops, },
263   { "get_num_desktops", cmd_get_num_desktops, },
264   { "set_desktop", cmd_set_desktop, },
265   { "get_desktop", cmd_get_desktop, },
266   { "set_desktop_for_window", cmd_set_desktop_for_window, },
267   { "get_desktop_for_window", cmd_get_desktop_for_window, },
268   { "get_desktop_viewport", cmd_get_desktop_viewport, },
269   { "set_desktop_viewport", cmd_set_desktop_viewport, },
270 
271   { "exec", cmd_exec, },
272   { "sleep", cmd_sleep, },
273 
274   { NULL, NULL, },
275 };
276 
is_command(char * cmd)277 int is_command(char* cmd) {
278   int i;
279   for (i = 0; dispatch[i].name != NULL; i++) {
280       if (!strcasecmp(dispatch[i].name, cmd)) {
281         return 1;
282       }
283     }
284   return 0;
285 }
286 
main(int argc,char ** argv)287 int main(int argc, char **argv) {
288   return xdotool_main(argc, argv);
289 }
290 
xdotool_main(int argc,char ** argv)291 int xdotool_main(int argc, char **argv) {
292 
293   /* If argv[1] is a file or "-", read commands from file or stdin,
294    * else use commands from argv.
295    */
296 
297   struct stat data;
298   int stat_ret;
299 
300   if (argc >= 2) {
301     /* See if the first argument is an existing file */
302     stat_ret = stat(argv[1], &data);
303     int i = 0;
304     int argv1_is_command= 0;
305 
306     for (i = 0; dispatch[i].name != NULL; i++) {
307       if (!strcasecmp(dispatch[i].name, argv[1])) {
308         argv1_is_command = 1;
309         break;
310       }
311     }
312 
313     if (!argv1_is_command && (strcmp(argv[1], "-") == 0 || stat_ret == 0)) {
314       return script_main(argc, argv);
315     }
316   }
317   return args_main(argc, argv);
318 }
319 
script_main(int argc,char ** argv)320 int script_main(int argc, char **argv) {
321   /* Tokenize the input file while expanding positional parameters and
322    * environment variables. Pass the resulting argument list to
323    * args_main().
324    */
325 
326   FILE *input = NULL;
327   const char *path = argv[1];
328   char buffer[4096];
329 
330   char **script_argv = (char **) calloc(1, sizeof(char *));
331   int script_argc = 0;
332   int script_argc_max = 0;
333 
334   /* determine whether reading from a file or from stdin */
335   if (!strcmp(path, "-")) {
336     input = fdopen(0, "r");
337   } else {
338     input = fopen(path, "r");
339     if (input == NULL) {
340       fprintf(stderr, "Failure opening '%s': %s\n", path, strerror(errno));
341       return EXIT_FAILURE;
342     }
343   }
344 
345   context_t context;
346   context.xdo = xdo_new(NULL);
347   context.prog = *argv;
348   context.windows = NULL;
349   context.nwindows = 0;
350   context.have_last_mouse = False;
351   context.debug = (getenv("DEBUG") != NULL);
352 
353   if (context.xdo == NULL) {
354     fprintf(stderr, "Failed creating new xdo instance\n");
355     return 1;
356   }
357   context.xdo->debug = context.debug;
358 
359   /* read input... */
360   int pos;
361   char *token;
362   int result = XDO_SUCCESS;
363 
364   while (fgets(buffer, 4096, input) != NULL) {
365     char *line = buffer;
366     token = NULL;
367 
368     /* Ignore leading whitespace */
369     line += strspn(line, " \t");
370 
371     /* blanklines or line comment are ignored, too */
372     if (line[0] == '\n' || line[0] == '#') {
373       continue;
374     }
375 
376     /* replace newline with null */
377     if (line[strlen(line)-1] == '\n')
378       line[strlen(line)-1] = '\0';
379 
380     /* tokenize line into script_argv... */
381     while (strlen(line)) {
382       token = NULL;
383 
384       /* modify line to contain the current token. Tokens are
385        * separated by whitespace, or quoted with single/double quotes.
386        */
387       if (line[0] == '"') {
388         line++;
389         line[strcspn(line, "\"")] = '\0';
390       }
391       else if (line[0] == '\'') {
392         line++;
393         line[strcspn(line, "\'")] = '\0';
394       }
395       else {
396         line[strcspn(line, " \t")] = '\0';
397       }
398 
399       /* if a token begins with "$", append the corresponding
400        * positional parameter or environment variable to
401        * script_argv...
402       */
403       if (line[0] == '$') {
404         /* ignore dollar sign */
405         line++;
406 
407         if (isdigit(line[0])) {
408           /* get the position of this parameter in argv */
409           pos = atoi(line) + 1; /* $1 is actually index 2 in the argv array */
410 
411           /* bail if no argument was given for this parameter */
412           if (pos >= argc) {
413             fprintf (stderr, "%s: error: `%s' needs at least %d %s; only %d given\n",
414                      argv[0], argv[1], pos - 1, pos == 2 ? "argument" : "arguments",
415                      argc - 2);
416             return EXIT_FAILURE;
417           }
418           /* use command line argument */
419           token = argv[pos];
420         }
421         else {
422           /* use environment variable */
423           token = getenv(line);
424           if (token == NULL) {
425             /* since it's not clear what we should do if this env var is not
426              * present, let's abort */
427             fprintf(stderr, "%s: error: environment variable $%s is not set.\n",
428                     argv[0], line);
429             return EXIT_FAILURE;
430           }
431         }
432       }
433       else {
434         /* use the verbatim token */
435         token = line;
436       }
437 
438       /* append token */
439       if (token != NULL) {
440 
441         if(script_argc + 1 > script_argc_max){
442           script_argv = realloc(script_argv, (script_argc + 1) * sizeof(char *));
443           script_argc_max++;
444         }
445 
446         if (script_argv == NULL) {
447           fprintf(stderr, "%s: error: failed to allocate memory while parsing `%s'.\n",
448                   argv[0], argv[1]);
449           exit(EXIT_FAILURE);
450         }
451         script_argv[script_argc] = (char *) calloc(strlen(token) + 1, sizeof(char));
452 
453         //printf("arg %d: %s\n", script_argc, token);
454         strcpy(script_argv[script_argc], token);
455         script_argc++;
456       }
457 
458       /* advance line to the next token */
459       line += strlen(line) + 1;
460       line += strspn(line, " \t");
461     } /* while line being tokenized */
462 
463     /*
464      * Add NULL at the end and reallocate memory if necessary.
465      */
466     if(script_argc_max <= script_argc){
467       script_argv = realloc(script_argv, (script_argc+1) * sizeof(char *));
468       /* TODO(sissel): STOPPED HERE */
469       script_argc_max++;
470     }
471     *(script_argv + script_argc) = NULL;
472 
473     if(script_argc > 0){
474       context.argc = script_argc;
475       context.argv = script_argv;
476       result = context_execute(&context);
477 
478       /*
479        * Free the allocated memory for tokens.
480        */
481       for(int j = 0; j < script_argc; j++){
482         if(*(script_argv + j) != NULL){
483           free(*(script_argv + j));
484         }
485       }
486 
487       script_argc = 0;
488       *script_argv = NULL;
489     }
490   }
491   fclose(input);
492 
493 
494   xdo_free(context.xdo);
495   if (context.windows != NULL) {
496     free(context.windows);
497   }
498 
499   for(int i=0; i<script_argc+1; ++i) {
500       free(script_argv[i]);
501   }
502   free(script_argv);
503   return result;
504 }
505 
args_main(int argc,char ** argv)506 int args_main(int argc, char **argv) {
507   int ret = 0;
508   int opt;
509   int option_index;
510 
511   const char *usage = "Usage: %s <cmd> <args>\n";
512   static struct option long_options[] = {
513     { "help", no_argument, NULL, 'h' },
514     { "version", no_argument, NULL, 'v' },
515     { 0, 0, 0, 0 }
516   };
517 
518   if (argc < 2) {
519     fprintf(stderr, usage, argv[0]);
520     cmd_help(NULL);
521     exit(1);
522   }
523 
524   if (!strcasecmp(argv[1], "help")) {
525     cmd_help(NULL);
526     exit(EXIT_SUCCESS);
527   } else if (!strcasecmp(argv[1], "version")) {
528     cmd_version(NULL);
529     exit(EXIT_SUCCESS);
530   }
531 
532   while ((opt = getopt_long_only(argc, argv, "++hv", long_options, &option_index)) != -1) {
533     switch (opt) {
534       case 'h':
535         cmd_help(NULL);
536         exit(EXIT_SUCCESS);
537       case 'v':
538         cmd_version(NULL);
539         exit(EXIT_SUCCESS);
540       default:
541         fprintf(stderr, usage, argv[0]);
542         exit(EXIT_FAILURE);
543     }
544   }
545 
546   context_t context;
547   context.xdo = xdo_new(NULL);
548   context.prog = *argv;
549   argv++; argc--;
550   context.argc = argc;
551   context.argv = argv;
552   context.windows = NULL;
553   context.nwindows = 0;
554   context.have_last_mouse = False;
555   context.debug = (getenv("DEBUG") != NULL);
556 
557   if (context.xdo == NULL) {
558     fprintf(stderr, "Failed creating new xdo instance.\n");
559     return 1;
560   }
561   context.xdo->debug = context.debug;
562 
563   ret = context_execute(&context);
564 
565   xdo_free(context.xdo);
566   if (context.windows != NULL) {
567     free(context.windows);
568   }
569 
570   return ret;
571 } /* int args_main(int, char **) */
572 
context_execute(context_t * context)573 int context_execute(context_t *context) {
574   int cmd_found = 0;
575   int i = 0;
576   char *cmd = NULL;
577   int ret = XDO_SUCCESS;
578 
579   /* Loop until all argv is consumed. */
580   while (context->argc > 0 && ret == XDO_SUCCESS) {
581     cmd = context->argv[0];
582     cmd_found = 0;
583     for (i = 0; dispatch[i].name != NULL && !cmd_found; i++) {
584       if (!strcasecmp(dispatch[i].name, cmd)) {
585         cmd_found = 1;
586         optind = 0;
587         if (context->debug) {
588           fprintf(stderr, "command: %s\n", cmd);
589         }
590         ret = dispatch[i].func(context);
591       }
592     }
593 
594     if (!cmd_found) {
595       fprintf(stderr, "%s: Unknown command: %s\n", context->prog, cmd);
596       fprintf(stderr, "Run '%s help' if you want a command list\n", context->prog);
597       ret = 1;
598     }
599   } /* while ... */
600   return ret;
601 } /* int args_main(int, char **) */
602 
cmd_help(context_t * context)603 int cmd_help(context_t *context) {
604   int i;
605   printf("Available commands:\n");
606   for (i = 0; dispatch[i].name != NULL; i++)
607     printf("  %s\n", dispatch[i].name);
608 
609   /* "help" can be invoked on errors, like when xdotool is given no arguments,
610    * so let's make sure we only consume if we have a context */
611   if (context != NULL) {
612     consume_args(context, 1);
613   }
614 
615   return 0;
616 }
617 
cmd_version(context_t * context)618 int cmd_version(context_t *context) {
619   xdotool_output(context, "xdotool version %s", xdo_version());
620   if (context != NULL) {
621     consume_args(context, 1);
622   }
623 
624   return 0;
625 }
626 
xdotool_debug(context_t * context,const char * format,...)627 void xdotool_debug(context_t *context, const char *format, ...) {
628   va_list args;
629 
630   va_start(args, format);
631   if (context->debug) {
632     vfprintf(stderr, format, args);
633     fprintf(stderr, "\n");
634   }
635 } /* xdotool_debug */
636 
xdotool_output(context_t * context,const char * format,...)637 void xdotool_output(context_t *context, const char *format, ...) {
638   context = context; /* Do something with context to avoid warnings */
639   va_list args;
640 
641   va_start(args, format);
642   vfprintf(stdout, format, args);
643   fprintf(stdout, "\n");
644   fflush(stdout);
645 } /* xdotool_output */
646