1 /*----------------------------------------------------------------------------
3 ** Utilty to send clicks to Wine Windows
5 ** See usage() for usage instructions.
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.
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.
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.
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
26 **--------------------------------------------------------------------------*/
35 #define APP_NAME "wclickat"
36 #define DEFAULT_DELAY 500
37 #define DEFAULT_REPEAT 1000
39 #define ARRAY_LENGTH(array) (sizeof(array)/sizeof((array)[0]))
41 static const WCHAR STATIC_CLASS
[]={'s','t','a','t','i','c','\0'};
43 /*----------------------------------------------------------------------------
45 **--------------------------------------------------------------------------*/
49 #define RC_INVALID_ARGUMENTS 1
50 #define RC_NODISPLAY 2
62 static action_type g_action
= ACTION_INVALID
;
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
;
71 static long g_dragto_x
= -1;
72 static long g_dragto_y
= -1;
73 static long g_disabled
= 0;
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;
83 * Provide some basic debugging support.
86 #define __PRINTF_ATTR(fmt,args) __attribute__((format (printf,fmt,args)))
88 #define __PRINTF_ATTR(fmt,args)
90 static int debug_on
=0;
91 static int init_debug()
93 char* str
=getenv("CXTEST_DEBUG");
94 if (str
&& strstr(str
, "+wclickat"))
99 static void cxlog(const char* format
, ...) __PRINTF_ATTR(1,2);
100 static void cxlog(const char* format
, ...)
106 va_start(valist
, format
);
107 vfprintf(stderr
, format
, valist
);
112 /*----------------------------------------------------------------------------
114 **--------------------------------------------------------------------------*/
115 static void usage(void)
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");
152 static const WCHAR
* my_strstriW(const WCHAR
* haystack
, const WCHAR
* needle
)
160 /* Special case the first character because
161 * we will be doing a lot of comparisons with it.
163 first
=towlower(*needle
);
167 while (towlower(*haystack
)!=first
&& *haystack
)
172 while (towlower(*h
)==towlower(*n
) && *h
)
184 static BOOL CALLBACK
find_control(HWND hwnd
, LPARAM lParam
)
189 if (!GetClassNameW(hwnd
, str
, ARRAY_LENGTH(str
)) ||
190 lstrcmpiW(str
, g_control_class
))
193 if (g_control_caption
)
195 if (!GetWindowTextW(hwnd
, str
, ARRAY_LENGTH(str
)) ||
196 !my_strstriW(str
, g_control_caption
))
199 if (g_control_id
&& g_control_id
!= GetWindowLong(hwnd
, GWL_ID
))
202 /* Check that the control is visible and active */
205 DWORD style
= GetWindowStyle(hwnd
);
206 if (!(style
& WS_VISIBLE
) || (style
& WS_DISABLED
))
210 pcontrol
= (HWND
*)lParam
;
215 static BOOL CALLBACK
find_top_window(HWND hwnd
, LPARAM lParam
)
220 if (!GetClassNameW(hwnd
, str
, ARRAY_LENGTH(str
)) ||
221 lstrcmpiW(str
, g_window_class
))
224 if (!GetWindowTextW(hwnd
, str
, ARRAY_LENGTH(str
)) ||
225 lstrcmpiW(str
, g_window_title
))
228 /* Check that the window is visible and active */
231 DWORD style
= GetWindowStyle(hwnd
);
232 if (!(style
& WS_VISIBLE
) || (style
& WS_DISABLED
))
236 /* See if we find the control we want */
240 EnumChildWindows(hwnd
, find_control
, (LPARAM
)&control
);
246 pwindow
= (HWND
*)lParam
;
251 static HWND
find_window()
256 EnumWindows(find_top_window
, (LPARAM
)&hwnd
);
260 static void do_click(HWND window
, DWORD down
, DWORD up
)
262 WINDOWINFO window_info
;
265 SetForegroundWindow(GetParent(window
));
266 window_info
.cbSize
=sizeof(window_info
);
267 GetWindowInfo(window
, &window_info
);
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.
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
);
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
);
283 mouse_event(MOUSEEVENTF_ABSOLUTE
| MOUSEEVENTF_MOVE
, x
, y
, 0, 0);
285 mouse_event(MOUSEEVENTF_ABSOLUTE
| down
, x
, y
, 0, 0);
286 if ((g_dragto_x
> 0) && (g_dragto_y
> 0)) {
289 long step_per_x
, step_per_y
;
290 long dragto_x
, dragto_y
;
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
);
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
);
302 dx
= g_dragto_x
- g_x
;
303 dy
= g_dragto_y
- g_y
;
306 for (i
= 0; i
< 4; i
++) {
307 mouse_event(MOUSEEVENTF_MOVE
, step_per_x
, step_per_y
, 0, 0);
314 mouse_event(MOUSEEVENTF_ABSOLUTE
| up
, x
, y
, 0, 0);
317 static void CALLBACK
ClickProc(HWND hwnd
, UINT uMsg
, UINT_PTR idEvent
, DWORD dwTime
)
319 HWND window
= find_window();
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.
333 cxlog("The window has disappeared!\n");
343 cxlog("Sending left click\n");
344 do_click(window
, MOUSEEVENTF_LEFTDOWN
, MOUSEEVENTF_LEFTUP
);
347 cxlog("Sending middle click\n");
348 do_click(window
, MOUSEEVENTF_MIDDLEDOWN
, MOUSEEVENTF_MIDDLEUP
);
351 cxlog("Sending right click\n");
352 do_click(window
, MOUSEEVENTF_RIGHTDOWN
, MOUSEEVENTF_RIGHTUP
);
354 fprintf(stderr
, "error: unknown action %d\n", g_action
);
361 static void CALLBACK
DelayProc(HWND hwnd
, UINT uMsg
, UINT_PTR idEvent
, DWORD dwTime
)
363 KillTimer(NULL
, timer_id
);
367 cxlog("Setting up a timer for --repeat\n");
368 timer_id
=SetTimer(NULL
, 0, g_repeat
, ClickProc
);
371 ClickProc(NULL
, 0, 0, 0);
374 static void CALLBACK
FindWindowProc(HWND hwnd
, UINT uMsg
, UINT_PTR idEvent
, DWORD dwTime
)
376 HWND window
= find_window();
380 cxlog("Found the window\n");
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);
390 DelayProc(NULL
, 0, 0, 0);
394 static void CALLBACK
TimeoutProc(HWND hwnd
, UINT uMsg
, UINT_PTR idEvent
, DWORD dwTime
)
399 /*----------------------------------------------------------------------------
401 **--------------------------------------------------------------------------*/
402 static int arg_get_long(const char** *argv
, const char* name
, long* value
)
406 fprintf(stderr
, "error: missing argument for '%s'\n", name
);
413 fprintf(stderr
, "error: invalid argument '%s' for '%s'\n",
422 static int arg_get_utf8(const char** *argv
, const char* name
, WCHAR
* *value
)
428 fprintf(stderr
, "error: missing argument for '%s'\n", name
);
432 len
= MultiByteToWideChar(CP_UTF8
, 0, **argv
, -1, NULL
, 0);
433 *value
= HeapAlloc(GetProcessHeap(), 0, len
*sizeof(WCHAR
));
436 fprintf(stderr
, "error: memory allocation error\n");
440 MultiByteToWideChar(CP_UTF8
, 0, **argv
, -1, *value
, len
);
445 static int parse_arguments(int argc
, const char** argv
)
458 if (g_action
!= ACTION_INVALID
)
460 fprintf(stderr
, "error: '%s' an action has already been specified\n", arg
);
463 else if (strcmp(arg
, "click") == 0 || strcmp(arg
, "lclick") == 0)
465 g_action
= ACTION_LCLICK
;
467 else if (strcmp(arg
, "mclick") == 0)
469 g_action
= ACTION_MCLICK
;
471 else if (strcmp(arg
, "rclick") == 0)
473 g_action
= ACTION_RCLICK
;
475 else if (strncmp(arg
, "button", 6) == 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'.
484 if (r
!=1 && (r
!=2 || extra
!='\0'))
486 fprintf(stderr
, "error: invalid argument '%s' for '%s'\n",
490 else if (button
<1 || button
>3)
492 fprintf(stderr
, "error: unknown button '%s'\n", arg
);
497 /* Just to remain compatible with the enum */
498 g_action
=button
+ACTION_LCLICK
-1;
501 else if (strcmp(arg
, "find") == 0)
503 g_action
= ACTION_FIND
;
507 fprintf(stderr
, "error: unknown action '%s'\n", arg
);
511 else if (strcmp(arg
, "--winclass") == 0)
513 rc
|=arg_get_utf8(&argv
, arg
, &g_window_class
);
515 else if (strcmp(arg
, "--wintitle") == 0)
517 rc
|=arg_get_utf8(&argv
,arg
, &g_window_title
);
519 else if (strcmp(arg
, "--ctrlclass") == 0)
521 rc
|=arg_get_utf8(&argv
, arg
, &g_control_class
);
523 else if (strcmp(arg
, "--ctrlid") == 0)
525 rc
|=arg_get_long(&argv
, arg
, &g_control_id
);
527 else if (strcmp(arg
, "--ctrlcaption") == 0)
529 rc
|=arg_get_utf8(&argv
, arg
, &g_control_caption
);
531 else if (strcmp(arg
, "--position") == 0)
535 fprintf(stderr
, "error: missing argument for '%s'\n", arg
);
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'.
546 if (r
!=2 && (r
!=3 || extra
!='\0'))
548 fprintf(stderr
, "error: invalid argument '%s' for '%s'\n",
555 else if (strcmp(arg
, "--dragto") == 0)
559 fprintf(stderr
, "error: missing argument for '%s'\n", arg
);
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'.
570 if (r
!=2 && (r
!=3 || extra
!='\0'))
572 fprintf(stderr
, "error: invalid argument '%s' for '%s'\n",
579 else if (strcmp(arg
, "--allow-disabled") == 0)
583 else if (strcmp(arg
, "--delay") == 0)
585 rc
|=arg_get_long(&argv
, arg
, &g_delay
);
587 else if (strcmp(arg
, "--timeout") == 0)
589 rc
|=arg_get_long(&argv
, arg
, &g_timeout
);
591 else if (strcmp(arg
, "--repeat") == 0)
593 rc
|=arg_get_long(&argv
, arg
, &g_repeat
);
595 else if (strcmp(arg
, "--untildeath") == 0)
599 else if (strcmp(arg
, "--help") == 0)
605 if (g_action
== ACTION_INVALID
)
607 fprintf(stderr
, "error: you must specify an action type\n");
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.
616 if (g_action
== ACTION_FIND
)
621 g_repeat
=DEFAULT_REPEAT
;
626 fprintf(stderr
, "error: you must specify a --winclass parameter\n");
631 fprintf(stderr
, "error: you must specify a --wintitle parameter\n");
636 if (!g_control_id
&& !g_control_caption
)
638 fprintf(stderr
, "error: you must specify either the control id or its caption\n");
643 /*------------------------------------------------------------------------
644 ** Process environment variables
645 **----------------------------------------------------------------------*/
646 p
= getenv("CXTEST_TIME_MULTIPLE");
649 float g_multiple
= atof(p
);
650 g_delay
= (long) (((float) g_delay
) * g_multiple
);
651 g_timeout
= (long) (((float) g_timeout
) * g_multiple
);
657 int main(int argc
, const char** argv
)
663 status
= parse_arguments(argc
, argv
);
669 fprintf(stderr
, "Issue %s --help for usage.\n", *argv
);
670 return RC_INVALID_ARGUMENTS
;
672 cxlog("Entering message loop. action=%d\n", g_action
);
675 SetTimer(NULL
, 0, g_timeout
, TimeoutProc
);
676 timer_id
=SetTimer(NULL
, 0, 100, FindWindowProc
);
679 while (status
==RC_RUNNING
&& GetMessage(&msg
, NULL
, 0, 0)!=0)
681 TranslateMessage(&msg
);
682 DispatchMessage(&msg
);