1 /*
2  * tkMacOSXTest.c --
3  *
4  *	Contains commands for platform specific tests for
5  *	the Macintosh platform.
6  *
7  * Copyright © 1996 Sun Microsystems, Inc.
8  * Copyright © 2001-2009 Apple Inc.
9  * Copyright © 2005-2009 Daniel A. Steffen <das@users.sourceforge.net>
10  *
11  * See the file "license.terms" for information on usage and redistribution
12  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
13  */
14 
15 #include "tkMacOSXPrivate.h"
16 #include "tkMacOSXConstants.h"
17 #include "tkMacOSXWm.h"
18 
19 
20 /*
21  * Forward declarations of procedures defined later in this file:
22  */
23 
24 #if MAC_OS_X_VERSION_MAX_ALLOWED < 1080
25 static int		DebuggerObjCmd (ClientData dummy, Tcl_Interp *interp,
26 					int objc, Tcl_Obj *const objv[]);
27 #endif
28 static int		PressButtonObjCmd (ClientData dummy, Tcl_Interp *interp,
29 					int objc, Tcl_Obj *const *objv);
30 static int		InjectKeyEventObjCmd (ClientData dummy, Tcl_Interp *interp,
31 					int objc, Tcl_Obj *const *objv);
32 static int		MenuBarHeightObjCmd (ClientData dummy, Tcl_Interp *interp,
33 					int objc, Tcl_Obj *const *objv);
34 
35 
36 /*
37  *----------------------------------------------------------------------
38  *
39  * TkplatformtestInit --
40  *
41  *	Defines commands that test platform specific functionality for
42  *	Unix platforms.
43  *
44  * Results:
45  *	A standard Tcl result.
46  *
47  * Side effects:
48  *	Defines new commands.
49  *
50  *----------------------------------------------------------------------
51  */
52 
53 int
TkplatformtestInit(Tcl_Interp * interp)54 TkplatformtestInit(
55     Tcl_Interp *interp)		/* Interpreter to add commands to. */
56 {
57     /*
58      * Add commands for platform specific tests on MacOS here.
59      */
60 
61 #if MAC_OS_X_VERSION_MAX_ALLOWED < 1080
62     Tcl_CreateObjCommand(interp, "debugger", DebuggerObjCmd, NULL, NULL);
63 #endif
64     Tcl_CreateObjCommand(interp, "pressbutton", PressButtonObjCmd, NULL, NULL);
65     Tcl_CreateObjCommand(interp, "injectkeyevent", InjectKeyEventObjCmd, NULL, NULL);
66     Tcl_CreateObjCommand(interp, "menubarheight", MenuBarHeightObjCmd, NULL, NULL);
67     return TCL_OK;
68 }
69 
70 /*
71  *----------------------------------------------------------------------
72  *
73  * DebuggerObjCmd --
74  *
75  *	This procedure simply calls the low level debugger, which was
76  *      deprecated in OSX 10.8.
77  *
78  * Results:
79  *	A standard Tcl result.
80  *
81  * Side effects:
82  *	None.
83  *
84  *----------------------------------------------------------------------
85  */
86 
87 #if MAC_OS_X_VERSION_MAX_ALLOWED < 1080
88 static int
DebuggerObjCmd(ClientData clientData,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])89 DebuggerObjCmd(
90     ClientData clientData,		/* Not used. */
91     Tcl_Interp *interp,			/* Not used. */
92     int objc,				/* Not used. */
93     Tcl_Obj *const objv[])			/* Not used. */
94 {
95     Debugger();
96     return TCL_OK;
97 }
98 #endif
99 
100 /*
101  *----------------------------------------------------------------------
102  *
103  * MenuBarHeightObjCmd --
104  *
105  *	This procedure calls [NSMenu menuBarHeight] and returns the result
106  *      as an integer.  Windows can never be placed to overlap the MenuBar,
107  *      so tests need to be aware of its size.
108  *
109  * Results:
110  *	A standard Tcl result.
111  *
112  * Side effects:
113  *	None.
114  *
115  *----------------------------------------------------------------------
116  */
117 
118 static int
MenuBarHeightObjCmd(TCL_UNUSED (void *),Tcl_Interp * interp,TCL_UNUSED (int),TCL_UNUSED (Tcl_Obj * const *))119 MenuBarHeightObjCmd(
120     TCL_UNUSED(void *),		/* Not used. */
121     Tcl_Interp *interp,			/* Not used. */
122     TCL_UNUSED(int),				/* Not used. */
123     TCL_UNUSED(Tcl_Obj *const *))		/* Not used. */
124 {
125     static int height = 0;
126     if (height == 0) {
127 	height = (int) [[NSApp mainMenu] menuBarHeight];
128     }
129     Tcl_SetObjResult(interp, Tcl_NewWideIntObj(height));
130     return TCL_OK;
131 }
132 
133 /*
134  *----------------------------------------------------------------------
135  *
136  * TkTestLogDisplay --
137  *
138  *      The test image display procedure calls this to determine whether it
139  *      should write a log message recording that it has being run.
140  *
141  * Results:
142  *      Returns true if and only if the NSView of the drawable is the
143  *      current focusView, which on 10.14 and newer systems can only be the
144  *      case when within [NSView drawRect].
145  *
146  * Side effects:
147  *	None
148  *
149  *----------------------------------------------------------------------
150  */
151 MODULE_SCOPE Bool
TkTestLogDisplay(Drawable drawable)152 TkTestLogDisplay(
153     Drawable drawable)
154 {
155     MacDrawable *macWin = (MacDrawable *)drawable;
156     NSWindow *win = nil;
157     if (macWin->toplevel && macWin->toplevel->winPtr &&
158 	macWin->toplevel->winPtr->wmInfoPtr &&
159 	macWin->toplevel->winPtr->wmInfoPtr->window) {
160 	win = macWin->toplevel->winPtr->wmInfoPtr->window;
161     } else if (macWin->winPtr && macWin->winPtr->wmInfoPtr &&
162 	       macWin->winPtr->wmInfoPtr->window) {
163 	win = macWin->winPtr->wmInfoPtr->window;
164     }
165     if (win) {
166 	return ([win contentView] == [NSView focusView]);
167     } else {
168 	return True;
169     }
170 }
171 
172 /*
173  *----------------------------------------------------------------------
174  *
175  * PressButtonObjCmd --
176  *
177  *	This Tcl command simulates a button press at a specific screen
178  *      location.  It injects NSEvents into the NSApplication event queue, as
179  *      opposed to adding events to the Tcl queue as event generate would do.
180  *      One application is for testing the grab command. These events have
181  *      their unused context property set to 1 as a signal indicating that they
182  *      should not be ignored by [NSApp tkProcessMouseEvent].
183  *
184  * Results:
185  *	A standard Tcl result.
186  *
187  * Side effects:
188  *	None.
189  *
190  *----------------------------------------------------------------------
191  */
192 
193 static int
PressButtonObjCmd(TCL_UNUSED (void *),Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])194 PressButtonObjCmd(
195     TCL_UNUSED(void *),
196     Tcl_Interp *interp,
197     int objc,
198     Tcl_Obj *const objv[])
199 {
200     int x = 0, y = 0, i, value;
201     NSInteger signal = -1;
202     CGPoint pt;
203     NSPoint loc;
204     NSEvent *motion, *press, *release;
205     NSArray *screens = [NSScreen screens];
206     CGFloat ScreenHeight = 0;
207     enum {X=1, Y};
208 
209     if (screens && [screens count]) {
210 	ScreenHeight = [[screens objectAtIndex:0] frame].size.height;
211     }
212 
213     if (objc != 3) {
214         Tcl_WrongNumArgs(interp, 1, objv, "x y");
215         return TCL_ERROR;
216     }
217     for (i = 1; i < objc; i++) {
218 	if (Tcl_GetIntFromObj(interp,objv[i],&value) != TCL_OK) {
219 	    return TCL_ERROR;
220 	}
221 	switch (i) {
222 	case X:
223 	    x = value;
224 	    break;
225 	case Y:
226 	    y = value;
227 	    break;
228 	default:
229 	    break;
230 	}
231     }
232     pt.x = loc.x = x;
233     pt.y = y;
234     loc.y = ScreenHeight - y;
235 
236     /*
237      *  We set the window number and the eventNumber to -1 as a signal to
238      *  processMouseEvent.
239      */
240 
241     CGWarpMouseCursorPosition(pt);
242     motion = [NSEvent mouseEventWithType:NSMouseMoved
243 	location:loc
244 	modifierFlags:0
245 	timestamp:GetCurrentEventTime()
246 	windowNumber:signal
247 	context:nil
248 	eventNumber:signal
249 	clickCount:1
250 	pressure:0.0];
251     [NSApp postEvent:motion atStart:NO];
252     press = [NSEvent mouseEventWithType:NSLeftMouseDown
253 	location:loc
254 	modifierFlags:0
255 	timestamp:GetCurrentEventTime()
256 	windowNumber:signal
257 	context:nil
258 	eventNumber:signal
259 	clickCount:1
260 	pressure:0.0];
261     [NSApp postEvent:press atStart:NO];
262     release = [NSEvent mouseEventWithType:NSLeftMouseUp
263 	location:loc
264 	modifierFlags:0
265 	timestamp:GetCurrentEventTime()
266 	windowNumber:signal
267 	context:nil
268 	eventNumber:signal
269 	clickCount:1
270 	pressure:-1.0];
271     [NSApp postEvent:release atStart:NO];
272     return TCL_OK;
273 }
274 
275 static int
InjectKeyEventObjCmd(TCL_UNUSED (void *),Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])276 InjectKeyEventObjCmd(
277     TCL_UNUSED(void *),
278     Tcl_Interp *interp,
279     int objc,
280     Tcl_Obj *const objv[])
281 {
282     static const char *const optionStrings[] = {
283 	"press", "release", "flagschanged", NULL};
284     NSUInteger types[3] = {NSKeyDown, NSKeyUp, NSFlagsChanged};
285     static const char *const argStrings[] = {
286 	"-shift", "-control", "-option", "-command", "-function", "-x", "-y", NULL};
287     enum args {KEYEVENT_SHIFT, KEYEVENT_CONTROL, KEYEVENT_OPTION, KEYEVENT_COMMAND,
288 	       KEYEVENT_FUNCTION, KEYEVENT_X, KEYEVENT_Y};
289     int i, index, keysym, mods = 0, x = 0, y = 0;
290     NSString *chars = nil, *unmod = nil, *upper, *lower;
291     NSEvent *keyEvent;
292     NSUInteger type;
293     MacKeycode macKC;
294 
295     if (objc < 3) {
296     wrongArgs:
297         Tcl_WrongNumArgs(interp, 1, objv, "option keysym ?arg?");
298         return TCL_ERROR;
299     }
300     if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "option", 0,
301             &index) != TCL_OK) {
302         return TCL_ERROR;
303     }
304     type = types[index];
305     if (Tcl_GetIntFromObj(interp, objv[2], &keysym) != TCL_OK) {
306 	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
307 			 "keysym must be an integer"));
308 	Tcl_SetErrorCode(interp, "TK", "TEST", "INJECT", "KEYSYM", NULL);
309 	return TCL_ERROR;
310     }
311     macKC.uint = XKeysymToKeycode(NULL, keysym);
312     for (i = 3; i < objc; i++) {
313 	if (Tcl_GetIndexFromObjStruct(interp, objv[i], argStrings,
314                 sizeof(char *), "option", TCL_EXACT, &index) != TCL_OK) {
315             return TCL_ERROR;
316         }
317         switch ((enum args) index) {
318 	case KEYEVENT_SHIFT:
319 	    mods |= NSShiftKeyMask;
320             break;
321 	case KEYEVENT_CONTROL:
322 	    mods |= NSControlKeyMask;
323             break;
324 	case KEYEVENT_OPTION:
325 	    mods |= NSAlternateKeyMask;
326             break;
327 	case KEYEVENT_COMMAND:
328 	    mods |= NSCommandKeyMask;
329             break;
330 	case KEYEVENT_FUNCTION:
331 	    mods |= NSFunctionKeyMask;
332             break;
333 	case KEYEVENT_X:
334 	    if (++i >= objc) {
335                 goto wrongArgs;
336             }
337 	    if (Tcl_GetIntFromObj(interp,objv[i], &x) != TCL_OK) {
338 		return TCL_ERROR;
339 	    }
340 	    break;
341 	case KEYEVENT_Y:
342 	    if (++i >= objc) {
343                 goto wrongArgs;
344             }
345 	    if (Tcl_GetIntFromObj(interp,objv[i], &y) != TCL_OK) {
346 		return TCL_ERROR;
347 	    }
348 	    break;
349 	}
350     }
351     if (type != NSFlagsChanged) {
352 	UniChar keychar = macKC.v.keychar;
353 	chars = [[NSString alloc] initWithCharacters: &keychar length:1];
354 	upper = [chars uppercaseString];
355 	lower = [chars lowercaseString];
356 	if (![upper isEqual: lower] && [chars isEqual: upper]) {
357 	    mods |= NSShiftKeyMask;
358 	}
359 	if (mods & NSShiftKeyMask) {
360 	    chars = upper;
361 	    unmod = lower;
362 	    macKC.v.o_s |= INDEX_SHIFT;
363 	} else {
364 	    unmod = chars;
365 	}
366 	if (macKC.v.o_s & INDEX_OPTION) {
367 	    mods |= NSAlternateKeyMask;
368 	}
369     }
370     keyEvent = [NSEvent keyEventWithType:type
371 	location:NSMakePoint(x, y)
372         modifierFlags:mods
373 	timestamp:GetCurrentEventTime()
374 	windowNumber:0
375 	context:nil
376 	characters:chars
377 	charactersIgnoringModifiers:unmod
378 	isARepeat:NO
379 	keyCode:macKC.v.virt];
380     [NSApp postEvent:keyEvent atStart:NO];
381     return TCL_OK;
382 }
383 /*
384  * Local Variables:
385  * mode: objc
386  * c-basic-offset: 4
387  * fill-column: 79
388  * coding: utf-8
389  * End:
390  */
391