1 /*
2  * tkMacOSXSend.c --
3  *
4  *	This file provides procedures that implement the "send" command,
5  *	allowing commands to be passed from interpreter to interpreter. This
6  *	current implementation for the Mac has most functionality stubbed out.
7  *
8  *	The current plan, which we have not had time to implement, is for the
9  *	first Wish app to create a gestalt of type 'WIsH'. This gestalt will
10  *	point to a table, in system memory, of Tk apps. Each Tk app, when it
11  *	starts up, will register their name, and process ID, in this table.
12  *	This will allow us to implement "tk appname".
13  *
14  *	Then the send command will look up the process id of the target app in
15  *	this table, and send an AppleEvent to that process. The AppleEvent
16  *	handler is much like the do script handler, except that you have to
17  *	specify the name of the tk app as well, since there may be many
18  *	interps in one wish app, and you need to send it to the right one.
19  *
20  *	Implementing this has been on our list of things to do, but what with
21  *	the demise of Tcl at Sun, and the lack of resources at Scriptics it
22  *	may not get done for awhile. So this sketch is offered for the brave
23  *	to attempt if they need the functionality...
24  *
25  * Copyright © 1989-1994 The Regents of the University of California.
26  * Copyright © 1994-1998 Sun Microsystems, Inc.
27  * Copyright © 2001-2009 Apple Inc.
28  * Copyright © 2005-2009 Daniel A. Steffen <das@users.sourceforge.net>
29  *
30  * See the file "license.terms" for information on usage and redistribution
31  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
32  */
33 
34 #include "tkMacOSXInt.h"
35 
36 /*
37  * The following structure is used to keep track of the interpreters
38  * registered by this process.
39  */
40 
41 typedef struct RegisteredInterp {
42     char *name;			/* Interpreter's name (malloc-ed). */
43     Tcl_Interp *interp;		/* Interpreter associated with name. */
44     struct RegisteredInterp *nextPtr;
45 				/* Next in list of names associated with
46 				 * interps in this process. NULL means end of
47 				 * list. */
48 } RegisteredInterp;
49 
50 /*
51  * A registry of all interpreters for a display is kept in a property
52  * "InterpRegistry" on the root window of the display. It is organized as a
53  * series of zero or more concatenated strings (in no particular order), each
54  * of the form:
55  *	window space name '\0'
56  * where "window" is the hex id of the comm. window to use to talk to an
57  * interpreter named "name".
58  *
59  * When the registry is being manipulated by an application (e.g. to add or
60  * remove an entry), it is loaded into memory using a structure of the
61  * following type:
62  */
63 
64 typedef struct NameRegistry {
65     TkDisplay *dispPtr;		/* Display from which the registry was
66 				 * read. */
67     int locked;			/* Non-zero means that the display was locked
68 				 * when the property was read in. */
69     int modified;		/* Non-zero means that the property has been
70 				 * modified, so it needs to be written out
71 				 * when the NameRegistry is closed. */
72     unsigned long propLength;	/* Length of the property, in bytes. */
73     char *property;		/* The contents of the property, or NULL if
74 				 * none. See format description above; this is
75 				 * *not* terminated by the first null
76 				 * character. Dynamically allocated. */
77     int allocedByX;		/* Non-zero means must free property with
78 				 * XFree; zero means use ckfree. */
79 } NameRegistry;
80 
81 static int initialized = 0;	/* A flag to denote if we have initialized
82 				 * yet. */
83 
84 static RegisteredInterp *interpListPtr = NULL;
85 				/* List of all interpreters registered by this
86 				 * process. */
87 
88 /*
89  * The information below is used for communication between processes during
90  * "send" commands. Each process keeps a private window, never even mapped,
91  * with one property, "Comm". When a command is sent to an interpreter, the
92  * command is appended to the comm property of the communication window
93  * associated with the interp's process. Similarly, when a result is returned
94  * from a sent command, it is also appended to the comm property.
95  *
96  * Each command and each result takes the form of ASCII text. For a command,
97  * the text consists of a zero character followed by several null-terminated
98  * ASCII strings. The first string consists of the single letter "c".
99  * Subsequent strings have the form "option value" where the following options
100  * are supported:
101  *
102  * -r commWindow serial
103  *
104  *	This option means that a response should be sent to the window whose X
105  *	identifier is "commWindow" (in hex), and the response should be
106  *	identified with the serial number given by "serial" (in decimal). If
107  *	this option isn't specified then the send is asynchronous and no
108  *	response is sent.
109  *
110  * -n name
111  *
112  *	"Name" gives the name of the application for which the command is
113  *	intended. This option must be present.
114  *
115  * -s script
116  *
117  *	"Script" is the script to be executed. This option must be present.
118  *
119  * The options may appear in any order. The -n and -s options must be present,
120  * but -r may be omitted for asynchronous RPCs. For compatibility with future
121  * releases that may add new features, there may be additional options
122  * present; as long as they start with a "-" character, they will be ignored.
123  *
124  *
125  * A result also consists of a zero character followed by several null-
126  * terminated ASCII strings. The first string consists of the single letter
127  * "r". Subsequent strings have the form "option value" where the following
128  * options are supported:
129  *
130  * -s serial
131  *
132  *	Identifies the command for which this is the result. It is the same as
133  *	the "serial" field from the -s option in the command. This option must
134  *	be present.
135  *
136  * -c code
137  *
138  *	"Code" is the completion code for the script, in decimal. If the code
139  *	is omitted it defaults to TCL_OK.
140  *
141  * -r result
142  *
143  *	"Result" is the result string for the script, which may be either a
144  *	result or an error message. If this field is omitted then it defaults
145  *	to an empty string.
146  *
147  * -i errorInfo
148  *
149  *	"ErrorInfo" gives a string with which to initialize the errorInfo
150  *	variable. This option may be omitted; it is ignored unless the
151  *	completion code is TCL_ERROR.
152  *
153  * -e errorCode
154  *
155  *	"ErrorCode" gives a string with with to initialize the errorCode
156  *	variable. This option may be omitted; it is ignored unless the
157  *	completion code is TCL_ERROR.
158  *
159  * Options may appear in any order, and only the -s option must be present. As
160  * with commands, there may be additional options besides these; unknown
161  * options are ignored.
162  */
163 
164 /*
165  * Maximum size property that can be read at one time by this module:
166  */
167 
168 #define MAX_PROP_WORDS 100000
169 
170 /*
171  * Forward declarations for procedures defined later in this file:
172  */
173 
174 static int SendInit(Tcl_Interp *interp);
175 
176 /*
177  *--------------------------------------------------------------
178  *
179  * Tk_SetAppName --
180  *
181  *	This procedure is called to associate an ASCII name with a Tk
182  *	application. If the application has already been named, the name
183  *	replaces the old one.
184  *
185  * Results:
186  *	The return value is the name actually given to the application. This
187  *	will normally be the same as name, but if name was already in use for
188  *	an application then a name of the form "name #2" will be chosen, with
189  *	a high enough number to make the name unique.
190  *
191  * Side effects:
192  *	Registration info is saved, thereby allowing the "send" command to be
193  *	used later to invoke commands in the application. In addition, the
194  *	"send" command is created in the application's interpreter. The
195  *	registration will be removed automatically if the interpreter is
196  *	deleted or the "send" command is removed.
197  *
198  *--------------------------------------------------------------
199  */
200 
201 const char *
Tk_SetAppName(Tk_Window tkwin,const char * name)202 Tk_SetAppName(
203     Tk_Window tkwin,		/* Token for any window in the application to
204 				 * be named: it is just used to identify the
205 				 * application and the display. */
206     const char *name)		/* The name that will be used to refer to the
207 				 * interpreter in later "send" commands. Must
208 				 * be globally unique. */
209 {
210     TkWindow *winPtr = (TkWindow *) tkwin;
211     Tcl_Interp *interp = winPtr->mainPtr->interp;
212     int i, suffix, offset, result;
213     RegisteredInterp *riPtr, *prevPtr;
214     const char *actualName;
215     Tcl_DString dString;
216     Tcl_Obj *resultObjPtr, *interpNamePtr;
217     char *interpName;
218 
219     if (!initialized) {
220 	SendInit(interp);
221     }
222 
223     /*
224      * See if the application is already registered; if so, remove its current
225      * name from the registry. The deletion of the command will take care of
226      * disposing of this entry.
227      */
228 
229     for (riPtr = interpListPtr, prevPtr = NULL; riPtr != NULL;
230 	    prevPtr = riPtr, riPtr = riPtr->nextPtr) {
231 	if (riPtr->interp == interp) {
232 	    if (prevPtr == NULL) {
233 		interpListPtr = interpListPtr->nextPtr;
234 	    } else {
235 		prevPtr->nextPtr = riPtr->nextPtr;
236 	    }
237 	    break;
238 	}
239     }
240 
241     /*
242      * Pick a name to use for the application. Use "name" if it's not already
243      * in use. Otherwise add a suffix such as " #2", trying larger and larger
244      * numbers until we eventually find one that is unique.
245      */
246 
247     actualName = name;
248     suffix = 1;
249     offset = 0;
250     Tcl_DStringInit(&dString);
251 
252     TkGetInterpNames(interp, tkwin);
253     resultObjPtr = Tcl_GetObjResult(interp);
254     Tcl_IncrRefCount(resultObjPtr);
255     for (i = 0; ; ) {
256 	result = Tcl_ListObjIndex(NULL, resultObjPtr, i, &interpNamePtr);
257 	if (result != TCL_OK || interpNamePtr == NULL) {
258 	    break;
259 	}
260 	interpName = Tcl_GetString(interpNamePtr);
261 	if (strcmp(actualName, interpName) == 0) {
262 	    if (suffix == 1) {
263 		Tcl_DStringAppend(&dString, name, -1);
264 		Tcl_DStringAppend(&dString, " #", 2);
265 		offset = Tcl_DStringLength(&dString);
266 		Tcl_DStringSetLength(&dString, offset + 10);
267 		actualName = Tcl_DStringValue(&dString);
268 	    }
269 	    suffix++;
270 	    sprintf(Tcl_DStringValue(&dString) + offset, "%d", suffix);
271 	    i = 0;
272 	} else {
273 	    i++;
274 	}
275     }
276 
277     Tcl_DecrRefCount(resultObjPtr);
278     Tcl_ResetResult(interp);
279 
280     /*
281      * We have found a unique name. Now add it to the registry.
282      */
283 
284     riPtr = (RegisteredInterp *)ckalloc(sizeof(RegisteredInterp));
285     riPtr->interp = interp;
286     riPtr->name = (char *)ckalloc(strlen(actualName) + 1);
287     riPtr->nextPtr = interpListPtr;
288     interpListPtr = riPtr;
289     strcpy(riPtr->name, actualName);
290 
291     /*
292      * TODO: DeleteProc
293      */
294 
295     Tcl_CreateObjCommand(interp, "send", Tk_SendObjCmd, riPtr, NULL);
296     if (Tcl_IsSafe(interp)) {
297 	Tcl_HideCommand(interp, "send", "send");
298     }
299     Tcl_DStringFree(&dString);
300 
301     return riPtr->name;
302 }
303 
304 /*
305  *--------------------------------------------------------------
306  *
307  * Tk_SendObjCmd --
308  *
309  *	This procedure is invoked to process the "send" Tcl command. See the
310  *	user documentation for details on what it does.
311  *
312  * Results:
313  *	A standard Tcl result.
314  *
315  * Side effects:
316  *	See the user documentation.
317  *
318  *--------------------------------------------------------------
319  */
320 
321 int
Tk_SendObjCmd(ClientData dummy,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])322 Tk_SendObjCmd(
323     ClientData dummy,	/* Not used */
324     Tcl_Interp *interp,		/* The interp we are sending from */
325     int objc,			/* Number of arguments */
326     Tcl_Obj *const objv[])	/* The arguments */
327 {
328     const char *const sendOptions[] = {"-async", "-displayof", "--", NULL};
329     char *stringRep, *destName;
330     /*int async = 0;*/
331     int i, index, firstArg;
332     RegisteredInterp *riPtr;
333     Tcl_Obj *listObjPtr;
334     int result = TCL_OK;
335     (void)dummy;
336 
337     for (i = 1; i < (objc - 1); ) {
338 	stringRep = Tcl_GetString(objv[i]);
339 	if (stringRep[0] == '-') {
340 	    if (Tcl_GetIndexFromObjStruct(interp, objv[i], sendOptions,
341 		    sizeof(char *), "option", 0, &index) != TCL_OK) {
342 		return TCL_ERROR;
343 	    }
344 	    if (index == 0) {
345 		/*async = 1;*/
346 		i++;
347 	    } else if (index == 1) {
348 		i += 2;
349 	    } else {
350 		i++;
351 	    }
352 	} else {
353 	    break;
354 	}
355     }
356 
357     if (objc < (i + 2)) {
358 	Tcl_WrongNumArgs(interp, 1, objv,
359 		"?-option value ...? interpName arg ?arg ...?");
360 	return TCL_ERROR;
361     }
362 
363     destName = Tcl_GetString(objv[i]);
364     firstArg = i + 1;
365 
366     /*
367      * See if the target interpreter is local. If so, execute the command
368      * directly without going through the DDE server. The only tricky thing is
369      * passing the result from the target interpreter to the invoking
370      * interpreter. Watch out: they could be the same!
371      */
372 
373     for (riPtr = interpListPtr; (riPtr != NULL)
374 	    && (strcmp(destName, riPtr->name)); riPtr = riPtr->nextPtr) {
375 	/*
376 	 * Empty loop body.
377 	 */
378     }
379 
380     if (riPtr != NULL) {
381 	/*
382 	 * This command is to a local interp. No need to go through the
383 	 * server.
384 	 */
385 
386 	Tcl_Interp *localInterp;
387 
388 	Tcl_Preserve(riPtr);
389 	localInterp = riPtr->interp;
390 	Tcl_Preserve(localInterp);
391 	if (firstArg == (objc - 1)) {
392 	    /*
393 	     * This might be one of those cases where the new parser is
394 	     * faster.
395 	     */
396 
397 	    result = Tcl_EvalObjEx(localInterp, objv[firstArg],
398 		    TCL_EVAL_DIRECT);
399 	} else {
400 	    listObjPtr = Tcl_NewListObj(0, NULL);
401 	    for (i = firstArg; i < objc; i++) {
402 		Tcl_ListObjAppendList(interp, listObjPtr, objv[i]);
403 	    }
404 	    Tcl_IncrRefCount(listObjPtr);
405 	    result = Tcl_EvalObjEx(localInterp, listObjPtr, TCL_EVAL_DIRECT);
406 	    Tcl_DecrRefCount(listObjPtr);
407 	}
408 	if (interp != localInterp) {
409 	    if (result == TCL_ERROR) {
410 		/* Tcl_Obj *errorObjPtr; */
411 
412 		/*
413 		 * An error occurred, so transfer error information from the
414 		 * destination interpreter back to our interpreter. Must clear
415 		 * interp's result before calling Tcl_AddErrorInfo, since
416 		 * Tcl_AddErrorInfo will store the interp's result in
417 		 * errorInfo before appending riPtr's $errorInfo; we've
418 		 * already got everything we need in riPtr's $errorInfo.
419 		 */
420 
421 		Tcl_ResetResult(interp);
422 		Tcl_AddErrorInfo(interp, Tcl_GetVar2(localInterp,
423 			"errorInfo", NULL, TCL_GLOBAL_ONLY));
424 		/* errorObjPtr = Tcl_GetObjVar2(localInterp, "errorCode", NULL,
425 			TCL_GLOBAL_ONLY);
426 		Tcl_SetObjErrorCode(interp, errorObjPtr); */
427 	    }
428 	    Tcl_SetObjResult(interp, Tcl_GetObjResult(localInterp));
429 	}
430 	Tcl_Release(riPtr);
431 	Tcl_Release(localInterp);
432     } else {
433 	/*
434 	 * TODO: This is a non-local request. Send the script to the server
435 	 * and poll it for a result.
436 	 */
437     }
438 
439     return result;
440 }
441 
442 /*
443  *----------------------------------------------------------------------
444  *
445  * TkGetInterpNames --
446  *
447  *	This procedure is invoked to fetch a list of all the interpreter names
448  *	currently registered for the display of a particular window.
449  *
450  * Results:
451  *	A standard Tcl return value. Interp->result will be set to hold a list
452  *	of all the interpreter names defined for tkwin's display. If an error
453  *	occurs, then TCL_ERROR is returned and interp->result will hold an
454  *	error message.
455  *
456  * Side effects:
457  *	None.
458  *
459  *----------------------------------------------------------------------
460  */
461 
462 int
TkGetInterpNames(Tcl_Interp * interp,Tk_Window tkwin)463 TkGetInterpNames(
464     Tcl_Interp *interp,		/* Interpreter for returning a result. */
465     Tk_Window tkwin)		/* Window whose display is to be used for the
466 				 * lookup. */
467 {
468     Tcl_Obj *listObjPtr;
469     RegisteredInterp *riPtr;
470     (void)tkwin;
471 
472     listObjPtr = Tcl_NewListObj(0, NULL);
473     riPtr = interpListPtr;
474     while (riPtr != NULL) {
475 	Tcl_ListObjAppendElement(interp, listObjPtr,
476 		Tcl_NewStringObj(riPtr->name, -1));
477 	riPtr = riPtr->nextPtr;
478     }
479 
480     Tcl_SetObjResult(interp, listObjPtr);
481     return TCL_OK;
482 }
483 
484 /*
485  *--------------------------------------------------------------
486  *
487  * SendInit --
488  *
489  *	This procedure is called to initialize the communication channels for
490  *	sending commands and receiving results.
491  *
492  * Results:
493  *	None.
494  *
495  * Side effects:
496  *	Sets up various data structures and windows.
497  *
498  *--------------------------------------------------------------
499  */
500 
501 static int
SendInit(Tcl_Interp * dummy)502 SendInit(
503     Tcl_Interp *dummy)		/* Not used */
504 {
505     (void)dummy;
506     return TCL_OK;
507 }
508 
509 /*
510  * Local Variables:
511  * mode: objc
512  * c-basic-offset: 4
513  * fill-column: 79
514  * coding: utf-8
515  * End:
516  */
517