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