fix include file case
[reactos.git] / rosapps / tests / wclickat / wclickat.c
1 /*----------------------------------------------------------------------------
2 ** wclickat.c
3 ** Utilty to send clicks to Wine Windows
4 **
5 ** See usage() for usage instructions.
6 **
7 **---------------------------------------------------------------------------
8 ** Copyright 2004 Jozef Stefanka for CodeWeavers, Inc.
9 ** Copyright 2005 Dmitry Timoshkov for CodeWeavers, Inc.
10 ** Copyright 2005 Francois Gouget for CodeWeavers, Inc.
11 **
12 ** This program is free software; you can redistribute it and/or modify
13 ** it under the terms of the GNU General Public License as published by
14 ** the Free Software Foundation; either version 2 of the License, or
15 ** (at your option) any later version.
16 **
17 ** This program is distributed in the hope that it will be useful,
18 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ** GNU General Public License for more details.
21 **
22 ** You should have received a copy of the GNU General Public License
23 ** along with this program; if not, write to the Free Software
24 ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 **
26 **--------------------------------------------------------------------------*/
27
28
29 #include <windows.h>
30 #include <windowsx.h>
31 #include <stdio.h>
32 #include <ctype.h>
33
34
35 #define APP_NAME "wclickat"
36 #define DEFAULT_DELAY 500
37 #define DEFAULT_REPEAT 1000
38
39 #define ARRAY_LENGTH(array) (sizeof(array)/sizeof((array)[0]))
40
41 static const WCHAR STATIC_CLASS[]={'s','t','a','t','i','c','\0'};
42
43 /*----------------------------------------------------------------------------
44 ** Global variables
45 **--------------------------------------------------------------------------*/
46
47 #define RC_RUNNING -1
48 #define RC_SUCCESS 0
49 #define RC_INVALID_ARGUMENTS 1
50 #define RC_NODISPLAY 2
51 #define RC_TIMEOUT 3
52 static int status;
53
54 typedef enum
55 {
56 ACTION_INVALID,
57 ACTION_FIND,
58 ACTION_LCLICK,
59 ACTION_MCLICK,
60 ACTION_RCLICK
61 } action_type;
62 static action_type g_action = ACTION_INVALID;
63
64 static WCHAR* g_window_class = NULL;
65 static WCHAR* g_window_title = NULL;
66 static long g_control_id = 0;
67 static WCHAR* g_control_class = NULL;
68 static WCHAR* g_control_caption = NULL;
69 static long g_x = -1;
70 static long g_y = -1;
71 static long g_dragto_x = -1;
72 static long g_dragto_y = -1;
73 static long g_disabled = 0;
74
75 static long g_delay = DEFAULT_DELAY;
76 static long g_timeout = 0;
77 static long g_repeat = 0;
78 static long g_untildeath = 0;
79 static UINT timer_id;
80
81
82 /*
83 * Provide some basic debugging support.
84 */
85 #ifdef __GNUC__
86 #define __PRINTF_ATTR(fmt,args) __attribute__((format (printf,fmt,args)))
87 #else
88 #define __PRINTF_ATTR(fmt,args)
89 #endif
90 static int debug_on=0;
91 static int init_debug()
92 {
93 char* str=getenv("CXTEST_DEBUG");
94 if (str && strstr(str, "+wclickat"))
95 debug_on=1;
96 return debug_on;
97 }
98
99 static void cxlog(const char* format, ...) __PRINTF_ATTR(1,2);
100 static void cxlog(const char* format, ...)
101 {
102 va_list valist;
103
104 if (debug_on)
105 {
106 va_start(valist, format);
107 vfprintf(stderr, format, valist);
108 va_end(valist);
109 }
110 }
111
112 /*----------------------------------------------------------------------------
113 ** usage
114 **--------------------------------------------------------------------------*/
115 static void usage(void)
116 {
117 fprintf(stderr, "%s - Utility to send clicks to Wine Windows.\n", APP_NAME);
118 fprintf(stderr, "----------------------------------------------\n");
119 fprintf(stderr, "Usage:\n");
120 fprintf(stderr, " %s action --winclass class --wintitle title [--timeout ms]\n",APP_NAME);
121 fprintf(stderr, " %*.*s [--ctrlclas class] [--ctrlcaption caption] [--ctrlid id]\n", strlen(APP_NAME) + 3, strlen(APP_NAME) + 3, "");
122 fprintf(stderr, " %*.*s [--position XxY] [--delay ms] [--untildeath] [--repeat ms]\n", strlen(APP_NAME) + 3, strlen(APP_NAME) + 3, "");
123 fprintf(stderr, "Where action can be one of:\n");
124 fprintf(stderr, " find Find the specified window or control\n");
125 fprintf(stderr, " button<n> Send a click with the given X button number\n");
126 fprintf(stderr, " click|lclick Synonym for button1 (left click)\n");
127 fprintf(stderr, " mclick Synonym for button2 (middle click)\n");
128 fprintf(stderr, " rclick Synonym for button3 (right click)\n");
129 fprintf(stderr, "\n");
130 fprintf(stderr, "The options are as follows:\n");
131 fprintf(stderr, " --timeout ms How long to wait before failing with a code of %d\n", RC_TIMEOUT);
132 fprintf(stderr, " --winclass class Class name of the top-level window of interest\n");
133 fprintf(stderr, " --wintitle title Title of the top-level window of interest\n");
134 fprintf(stderr, " --ctrlclass name Class name of the control of interest, if any\n");
135 fprintf(stderr, " --ctrlcaption cap A substring of the control's caption\n");
136 fprintf(stderr, " --ctrlid id Id of the control\n");
137 fprintf(stderr, " --position XxY Coordinates for the click, relative to the window / control\n");
138 fprintf(stderr, " --dragto If given, then position specifies start click, and\n");
139 fprintf(stderr, " dragto specifies release coords.\n");
140 fprintf(stderr, " --allow-disabled Match the window or control even hidden or disabled\n");
141 fprintf(stderr, " --delay ms Wait ms milliseconds before clicking. The default is %d\n", DEFAULT_DELAY);
142 fprintf(stderr, " --untildeath Wait until the window disappears\n");
143 fprintf(stderr, " --repeat ms Click every ms milliseconds. The default is %d\n", DEFAULT_REPEAT);
144 fprintf(stderr, "\n");
145 fprintf(stderr, "%s returns %d on success\n", APP_NAME, RC_SUCCESS);
146 fprintf(stderr, "\n");
147 fprintf(stderr, "Environment variable overrides:\n");
148 fprintf(stderr, " CXTEST_TIME_MULTIPLE Specifies a floating multiplier applied to any\n");
149 fprintf(stderr, " delay and timeout parameters.\n");
150 }
151
152 static const WCHAR* my_strstriW(const WCHAR* haystack, const WCHAR* needle)
153 {
154 const WCHAR *h,*n;
155 WCHAR first;
156
157 if (!*needle)
158 return haystack;
159
160 /* Special case the first character because
161 * we will be doing a lot of comparisons with it.
162 */
163 first=towlower(*needle);
164 needle++;
165 while (*haystack)
166 {
167 while (towlower(*haystack)!=first && *haystack)
168 haystack++;
169
170 h=haystack+1;
171 n=needle;
172 while (towlower(*h)==towlower(*n) && *h)
173 {
174 h++;
175 n++;
176 }
177 if (!*n)
178 return haystack;
179 haystack++;
180 }
181 return NULL;
182 }
183
184 static BOOL CALLBACK find_control(HWND hwnd, LPARAM lParam)
185 {
186 WCHAR str[1024];
187 HWND* pcontrol;
188
189 if (!GetClassNameW(hwnd, str, ARRAY_LENGTH(str)) ||
190 lstrcmpiW(str, g_control_class))
191 return TRUE;
192
193 if (g_control_caption)
194 {
195 if (!GetWindowTextW(hwnd, str, ARRAY_LENGTH(str)) ||
196 !my_strstriW(str, g_control_caption))
197 return TRUE;
198 }
199 if (g_control_id && g_control_id != GetWindowLong(hwnd, GWL_ID))
200 return TRUE;
201
202 /* Check that the control is visible and active */
203 if (!g_disabled)
204 {
205 DWORD style = GetWindowStyle(hwnd);
206 if (!(style & WS_VISIBLE) || (style & WS_DISABLED))
207 return TRUE;
208 }
209
210 pcontrol = (HWND*)lParam;
211 *pcontrol = hwnd;
212 return FALSE;
213 }
214
215 static BOOL CALLBACK find_top_window(HWND hwnd, LPARAM lParam)
216 {
217 WCHAR str[1024];
218 HWND* pwindow;
219
220 if (!GetClassNameW(hwnd, str, ARRAY_LENGTH(str)) ||
221 lstrcmpiW(str, g_window_class))
222 return TRUE;
223
224 if (!GetWindowTextW(hwnd, str, ARRAY_LENGTH(str)) ||
225 lstrcmpiW(str, g_window_title))
226 return TRUE;
227
228 /* Check that the window is visible and active */
229 if (!g_disabled)
230 {
231 DWORD style = GetWindowStyle(hwnd);
232 if (!(style & WS_VISIBLE) || (style & WS_DISABLED))
233 return TRUE;
234 }
235
236 /* See if we find the control we want */
237 if (g_control_class)
238 {
239 HWND control = NULL;
240 EnumChildWindows(hwnd, find_control, (LPARAM)&control);
241 if (!control)
242 return TRUE;
243 hwnd=control;
244 }
245
246 pwindow = (HWND*)lParam;
247 *pwindow = hwnd;
248 return FALSE;
249 }
250
251 static HWND find_window()
252 {
253 HWND hwnd;
254
255 hwnd=NULL;
256 EnumWindows(find_top_window, (LPARAM)&hwnd);
257 return hwnd;
258 }
259
260 static void do_click(HWND window, DWORD down, DWORD up)
261 {
262 WINDOWINFO window_info;
263 long x, y;
264
265 SetForegroundWindow(GetParent(window));
266 window_info.cbSize=sizeof(window_info);
267 GetWindowInfo(window, &window_info);
268
269 /* The calculations below convert the coordinates so they are absolute
270 * screen coordinates in 'Mickeys' as required by mouse_event.
271 * In mickeys the screen size is always 65535x65535.
272 */
273 x=window_info.rcWindow.left+g_x;
274 if (x<window_info.rcWindow.left || x>=window_info.rcWindow.right)
275 x=(window_info.rcWindow.right+window_info.rcWindow.left)/2;
276 x=(x << 16)/GetSystemMetrics(SM_CXSCREEN);
277
278 y=window_info.rcWindow.top+g_y;
279 if (y<window_info.rcWindow.top || y>=window_info.rcWindow.bottom)
280 y=(window_info.rcWindow.bottom+window_info.rcWindow.top)/2;
281 y=(y << 16)/GetSystemMetrics(SM_CYSCREEN);
282
283 mouse_event(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, x, y, 0, 0);
284 if (down) {
285 mouse_event(MOUSEEVENTF_ABSOLUTE | down, x, y, 0, 0);
286 if ((g_dragto_x > 0) && (g_dragto_y > 0)) {
287 int i;
288 long dx, dy;
289 long step_per_x, step_per_y;
290 long dragto_x, dragto_y;
291
292 dragto_x=window_info.rcWindow.left+g_dragto_x;
293 if (dragto_x<window_info.rcWindow.left || dragto_x>=window_info.rcWindow.right)
294 dragto_x=(window_info.rcWindow.right+window_info.rcWindow.left)/2;
295 dragto_x=(dragto_x << 16)/GetSystemMetrics(SM_CXSCREEN);
296
297 dragto_y=window_info.rcWindow.top+g_dragto_y;
298 if (dragto_y<window_info.rcWindow.top || dragto_y>=window_info.rcWindow.bottom)
299 dragto_y=(window_info.rcWindow.bottom+window_info.rcWindow.top)/2;
300 dragto_y=(dragto_y << 16)/GetSystemMetrics(SM_CYSCREEN);
301
302 dx = g_dragto_x - g_x;
303 dy = g_dragto_y - g_y;
304 step_per_x = dx / 4;
305 step_per_y = dy / 4;
306 for (i = 0; i < 4; i++) {
307 mouse_event(MOUSEEVENTF_MOVE, step_per_x, step_per_y, 0, 0);
308 }
309 x=dragto_x;
310 y=dragto_y;
311 }
312 }
313 if (up)
314 mouse_event(MOUSEEVENTF_ABSOLUTE | up, x, y, 0, 0);
315 }
316
317 static void CALLBACK ClickProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
318 {
319 HWND window = find_window();
320
321 if (!window)
322 {
323 if (g_untildeath)
324 {
325 /* FIXME: The window / control might just be disabled and if
326 * that's the case we should not exit yet. But I don't expect
327 * --untildeath to be used at all anyway so fixing this can
328 * wait until it becomes necessary.
329 */
330 status=RC_SUCCESS;
331 }
332 else
333 cxlog("The window has disappeared!\n");
334 return;
335 }
336
337 switch (g_action)
338 {
339 case ACTION_FIND:
340 /* Nothing to do */
341 break;
342 case ACTION_LCLICK:
343 cxlog("Sending left click\n");
344 do_click(window, MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP);
345 break;
346 case ACTION_MCLICK:
347 cxlog("Sending middle click\n");
348 do_click(window, MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP);
349 break;
350 case ACTION_RCLICK:
351 cxlog("Sending right click\n");
352 do_click(window, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP);
353 default:
354 fprintf(stderr, "error: unknown action %d\n", g_action);
355 break;
356 }
357 if (!g_repeat)
358 status=RC_SUCCESS;
359 }
360
361 static void CALLBACK DelayProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
362 {
363 KillTimer(NULL, timer_id);
364 timer_id=0;
365 if (g_repeat)
366 {
367 cxlog("Setting up a timer for --repeat\n");
368 timer_id=SetTimer(NULL, 0, g_repeat, ClickProc);
369 }
370
371 ClickProc(NULL, 0, 0, 0);
372 }
373
374 static void CALLBACK FindWindowProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
375 {
376 HWND window = find_window();
377 if (!window)
378 return;
379
380 cxlog("Found the window\n");
381 if (g_delay)
382 {
383 cxlog("Waiting for a bit\n");
384 KillTimer(NULL, timer_id);
385 timer_id=SetTimer(NULL, 0, g_delay, DelayProc);
386 do_click(window, 0,0);
387 }
388 else
389 {
390 DelayProc(NULL, 0, 0, 0);
391 }
392 }
393
394 static void CALLBACK TimeoutProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
395 {
396 status = RC_TIMEOUT;
397 }
398
399 /*----------------------------------------------------------------------------
400 ** parse_arguments
401 **--------------------------------------------------------------------------*/
402 static int arg_get_long(const char** *argv, const char* name, long* value)
403 {
404 if (!**argv)
405 {
406 fprintf(stderr, "error: missing argument for '%s'\n", name);
407 return 1;
408 }
409
410 *value=atol(**argv);
411 if (*value < 0)
412 {
413 fprintf(stderr, "error: invalid argument '%s' for '%s'\n",
414 **argv, name);
415 (*argv)++;
416 return 1;
417 }
418 (*argv)++;
419 return 0;
420 }
421
422 static int arg_get_utf8(const char** *argv, const char* name, WCHAR* *value)
423 {
424 int len;
425
426 if (!**argv)
427 {
428 fprintf(stderr, "error: missing argument for '%s'\n", name);
429 return 1;
430 }
431
432 len = MultiByteToWideChar(CP_UTF8, 0, **argv, -1, NULL, 0);
433 *value = HeapAlloc(GetProcessHeap(), 0, len*sizeof(WCHAR));
434 if (!*value)
435 {
436 fprintf(stderr, "error: memory allocation error\n");
437 (*argv)++;
438 return 1;
439 }
440 MultiByteToWideChar(CP_UTF8, 0, **argv, -1, *value, len);
441 (*argv)++;
442 return 0;
443 }
444
445 static int parse_arguments(int argc, const char** argv)
446 {
447 int rc;
448 const char* arg;
449 char* p;
450
451 rc=0;
452 argv++;
453 while (*argv)
454 {
455 arg=*argv++;
456 if (*arg!='-')
457 {
458 if (g_action != ACTION_INVALID)
459 {
460 fprintf(stderr, "error: '%s' an action has already been specified\n", arg);
461 rc=1;
462 }
463 else if (strcmp(arg, "click") == 0 || strcmp(arg, "lclick") == 0)
464 {
465 g_action = ACTION_LCLICK;
466 }
467 else if (strcmp(arg, "mclick") == 0)
468 {
469 g_action = ACTION_MCLICK;
470 }
471 else if (strcmp(arg, "rclick") == 0)
472 {
473 g_action = ACTION_RCLICK;
474 }
475 else if (strncmp(arg, "button", 6) == 0)
476 {
477 int button;
478 char extra='\0';
479 int r=sscanf(arg, "button%d%c", &button, &extra);
480 /* We should always get r==1 but due to a bug in Wine's
481 * msvcrt.dll implementation (at least up to 20050127)
482 * we may also get r==2 and extra=='\0'.
483 */
484 if (r!=1 && (r!=2 || extra!='\0'))
485 {
486 fprintf(stderr, "error: invalid argument '%s' for '%s'\n",
487 *argv, arg);
488 rc=1;
489 }
490 else if (button<1 || button>3)
491 {
492 fprintf(stderr, "error: unknown button '%s'\n", arg);
493 rc=1;
494 }
495 else
496 {
497 /* Just to remain compatible with the enum */
498 g_action=button+ACTION_LCLICK-1;
499 }
500 }
501 else if (strcmp(arg, "find") == 0)
502 {
503 g_action = ACTION_FIND;
504 }
505 else
506 {
507 fprintf(stderr, "error: unknown action '%s'\n", arg);
508 rc=1;
509 }
510 }
511 else if (strcmp(arg, "--winclass") == 0)
512 {
513 rc|=arg_get_utf8(&argv, arg, &g_window_class);
514 }
515 else if (strcmp(arg, "--wintitle") == 0)
516 {
517 rc|=arg_get_utf8(&argv,arg, &g_window_title);
518 }
519 else if (strcmp(arg, "--ctrlclass") == 0)
520 {
521 rc|=arg_get_utf8(&argv, arg, &g_control_class);
522 }
523 else if (strcmp(arg, "--ctrlid") == 0)
524 {
525 rc|=arg_get_long(&argv, arg, &g_control_id);
526 }
527 else if (strcmp(arg, "--ctrlcaption") == 0)
528 {
529 rc|=arg_get_utf8(&argv, arg, &g_control_caption);
530 }
531 else if (strcmp(arg, "--position") == 0)
532 {
533 if (!*argv)
534 {
535 fprintf(stderr, "error: missing argument for '%s'\n", arg);
536 rc=1;
537 }
538 else
539 {
540 char extra='\0';
541 int r=sscanf(*argv, "%ldx%ld%c", &g_x, &g_y, &extra);
542 /* We should always get r==2 but due to a bug in Wine's
543 * msvcrt.dll implementation (at least up to 20050127)
544 * we may also get r==3 and extra=='\0'.
545 */
546 if (r!=2 && (r!=3 || extra!='\0'))
547 {
548 fprintf(stderr, "error: invalid argument '%s' for '%s'\n",
549 *argv, arg);
550 rc=1;
551 }
552 argv++;
553 }
554 }
555 else if (strcmp(arg, "--dragto") == 0)
556 {
557 if (!*argv)
558 {
559 fprintf(stderr, "error: missing argument for '%s'\n", arg);
560 rc=1;
561 }
562 else
563 {
564 char extra='\0';
565 int r=sscanf(*argv, "%ldx%ld%c", &g_dragto_x, &g_dragto_y, &extra);
566 /* We should always get r==2 but due to a bug in Wine's
567 * * msvcrt.dll implementation (at least up to 20050127)
568 * * we may also get r==3 and extra=='\0'.
569 * */
570 if (r!=2 && (r!=3 || extra!='\0'))
571 {
572 fprintf(stderr, "error: invalid argument '%s' for '%s'\n",
573 *argv, arg);
574 rc=1;
575 }
576 argv++;
577 }
578 }
579 else if (strcmp(arg, "--allow-disabled") == 0)
580 {
581 g_disabled = 1;
582 }
583 else if (strcmp(arg, "--delay") == 0)
584 {
585 rc|=arg_get_long(&argv, arg, &g_delay);
586 }
587 else if (strcmp(arg, "--timeout") == 0)
588 {
589 rc|=arg_get_long(&argv, arg, &g_timeout);
590 }
591 else if (strcmp(arg, "--repeat") == 0)
592 {
593 rc|=arg_get_long(&argv, arg, &g_repeat);
594 }
595 else if (strcmp(arg, "--untildeath") == 0)
596 {
597 g_untildeath=1;
598 }
599 else if (strcmp(arg, "--help") == 0)
600 {
601 rc=2;
602 }
603 }
604
605 if (g_action == ACTION_INVALID)
606 {
607 fprintf(stderr, "error: you must specify an action type\n");
608 rc=1;
609 }
610 else
611 {
612 /* Adjust the default delay and repeat parameters depending on
613 * the operating mode so less needs to be specified on the command
614 * line, and so we can assume them to be set right.
615 */
616 if (g_action == ACTION_FIND)
617 g_delay=0;
618 if (!g_untildeath)
619 g_repeat=0;
620 else if (!g_repeat)
621 g_repeat=DEFAULT_REPEAT;
622 }
623
624 if (!g_window_class)
625 {
626 fprintf(stderr, "error: you must specify a --winclass parameter\n");
627 rc=1;
628 }
629 if (!g_window_title)
630 {
631 fprintf(stderr, "error: you must specify a --wintitle parameter\n");
632 rc=1;
633 }
634 if (g_control_class)
635 {
636 if (!g_control_id && !g_control_caption)
637 {
638 fprintf(stderr, "error: you must specify either the control id or its caption\n");
639 rc=1;
640 }
641 }
642
643 /*------------------------------------------------------------------------
644 ** Process environment variables
645 **----------------------------------------------------------------------*/
646 p = getenv("CXTEST_TIME_MULTIPLE");
647 if (p)
648 {
649 float g_multiple = atof(p);
650 g_delay = (long) (((float) g_delay) * g_multiple);
651 g_timeout = (long) (((float) g_timeout) * g_multiple);
652 }
653
654 return rc;
655 }
656
657 int main(int argc, const char** argv)
658 {
659 MSG msg;
660
661 init_debug();
662
663 status = parse_arguments(argc, argv);
664 if (status)
665 {
666 if (status == 2)
667 usage();
668 else
669 fprintf(stderr, "Issue %s --help for usage.\n", *argv);
670 return RC_INVALID_ARGUMENTS;
671 }
672 cxlog("Entering message loop. action=%d\n", g_action);
673
674 if (g_timeout>0)
675 SetTimer(NULL, 0, g_timeout, TimeoutProc);
676 timer_id=SetTimer(NULL, 0, 100, FindWindowProc);
677
678 status=RC_RUNNING;
679 while (status==RC_RUNNING && GetMessage(&msg, NULL, 0, 0)!=0)
680 {
681 TranslateMessage(&msg);
682 DispatchMessage(&msg);
683 }
684
685 return status;
686 }