]> www.wagner.pp.ru Git - oss/ck.git/blob - ckRecorder.c
Ck console graphics toolkit
[oss/ck.git] / ckRecorder.c
1 /* 
2  * ckRecorder.c --
3  *
4  *      This file provides a simple event recorder.
5  *
6  * Copyright (c) 1996-1999 Christian Werner
7  *
8  * See the file "license.terms" for information on usage and redistribution
9  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10  */
11
12 #include "ckPort.h"
13 #include "ck.h"
14
15 /*
16  * There is one structure of the following type for the global data
17  * of the recorder.
18  */
19
20 typedef struct {
21     CkWindow *mainPtr;
22     Tcl_Interp *interp;
23     int timerRunning;
24     Tk_TimerToken timer;
25 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
26     struct timeval lastEvent;
27     FILE *record;
28     FILE *replay;
29 #else
30     Tcl_Time lastEvent;
31     Tcl_Channel record;
32     Tcl_Channel replay;
33 #endif
34     int withDelay;
35     CkEvent event;
36 } Recorder;
37
38 static Recorder *ckRecorder = NULL;
39
40 /*
41  *   Internal procedures.
42  */
43
44 static int      RecorderInput _ANSI_ARGS_((ClientData clientData,
45                     CkEvent *eventPtr));
46 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
47 static int      DStringGets _ANSI_ARGS_((FILE *filePtr, Tcl_DString *dsPtr));
48 #else
49 static int      DStringGets _ANSI_ARGS_((Tcl_Channel chan,
50                     Tcl_DString *dsPtr));
51 #endif
52 static void     DeliverEvent _ANSI_ARGS_((ClientData clientData));
53 static void     RecorderReplay _ANSI_ARGS_((ClientData clientData));
54 \f
55 /*
56  *----------------------------------------------------------------------
57  *
58  * RecorderInput --
59  *
60  *      This procedure is installed as generic event handler.
61  *      For certain events it adds lines to the recorder file.
62  *
63  *----------------------------------------------------------------------
64  */
65
66 static int
67 RecorderInput(clientData, eventPtr)
68     ClientData clientData;
69     CkEvent *eventPtr;
70 {
71     Recorder *recPtr = (Recorder *) clientData;
72     int hadEvent = 0, type = eventPtr->any.type;
73 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
74     struct timeval now;
75 #else
76     Tcl_Time now;
77     extern void TclpGetTime _ANSI_ARGS_((Tcl_Time *timePtr));
78 #endif
79     char buffer[64];
80     char *keySym, *barCode, *result;
81     char *argv[16];
82
83     if (recPtr->record == NULL) {
84         Ck_DeleteGenericHandler(RecorderInput, clientData);
85         return 0;
86     }
87
88     if (type != CK_EV_KEYPRESS && type != CK_EV_BARCODE &&
89         type != CK_EV_MOUSE_UP && type != CK_EV_MOUSE_DOWN)
90         return 0;
91         
92 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
93     gettimeofday(&now, (struct timezone *) NULL);
94     if (recPtr->withDelay && recPtr->lastEvent.tv_sec != 0 &&
95         recPtr->lastEvent.tv_usec != 0) {
96         double diff;
97
98         diff = now.tv_sec * 1000 + now.tv_usec / 1000;
99         diff -= recPtr->lastEvent.tv_sec * 1000 +
100             recPtr->lastEvent.tv_usec / 1000;
101         if (diff > 50) {
102             if (diff > 3600000)
103                 diff = 3600000;
104             fprintf(recPtr->record, "<Delay> %d\n", (int) diff);
105             hadEvent++;
106         }
107     }
108 #else
109     TclpGetTime(&now);
110     if (recPtr->withDelay && recPtr->lastEvent.sec != 0 &&
111         recPtr->lastEvent.usec != 0) {
112         double diff;
113         char string[100];
114
115         diff = now.sec * 1000 + now.usec / 1000;
116         diff -= recPtr->lastEvent.sec * 1000 +
117             recPtr->lastEvent.usec / 1000;
118         if (diff > 50) {
119             if (diff > 3600000)
120                 diff = 3600000;
121             sprintf(string, "<Delay> %d\n", (int) diff);
122             Tcl_Write(recPtr->record, string, strlen(string));
123             hadEvent++;
124         }
125     }
126 #endif
127
128     switch (type) {
129         case CK_EV_KEYPRESS:
130             argv[2] = NULL;
131             keySym = CkKeysymToString(eventPtr->key.keycode, 1);
132             if (strcmp(keySym, "NoSymbol") != 0)
133                 argv[2] = keySym;
134             else if (eventPtr->key.keycode > 0 &&
135                 eventPtr->key.keycode < 256) {
136                 /* Unsafe, ie not portable */
137                 sprintf(buffer, "0x%2x", eventPtr->key.keycode);
138                 argv[2] = buffer;
139             }
140             if (argv[2] != NULL) {
141                 argv[0] = "<Key>";
142                 argv[1] = eventPtr->key.winPtr == NULL ? "" :
143                     eventPtr->key.winPtr->pathName;
144                 result = Tcl_Merge(3, argv);
145 printPctSNL:
146 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
147                 fprintf(recPtr->record, "%s\n", result);
148 #else
149                 Tcl_Write(recPtr->record, result, strlen(result));
150                 Tcl_Write(recPtr->record, "\n", 1);
151 #endif
152                 ckfree(result);
153                 hadEvent++;
154             }
155             break;
156
157         case CK_EV_BARCODE:
158             barCode = CkGetBarcodeData(recPtr->mainPtr->mainPtr);
159             if (barCode != NULL) {
160                 argv[0] = "<BarCode>";
161                 argv[1] = eventPtr->key.winPtr == NULL ? "" :
162                     eventPtr->key.winPtr->pathName;
163                 argv[2] = barCode;
164                 result = Tcl_Merge(3, argv);
165                 goto printPctSNL;
166             }
167             break;
168
169         case CK_EV_MOUSE_UP:
170         case CK_EV_MOUSE_DOWN:
171             {
172                 char bbuf[16], xbuf[16], ybuf[16], rxbuf[16], rybuf[16];
173
174                 argv[0] = type == CK_EV_MOUSE_DOWN ?
175                     "<ButtonPress>" : "<ButtonRelease>";
176                 argv[1] = eventPtr->mouse.winPtr == NULL ? "" :
177                     eventPtr->mouse.winPtr->pathName;
178                 sprintf(bbuf, "%d", eventPtr->mouse.button);
179                 argv[2] = bbuf;
180                 sprintf(xbuf, "%d", eventPtr->mouse.x);
181                 argv[3] = xbuf;
182                 sprintf(ybuf, "%d", eventPtr->mouse.y);
183                 argv[4] = ybuf;
184                 sprintf(rxbuf, "%d", eventPtr->mouse.rootx);
185                 argv[5] = rxbuf;
186                 sprintf(rybuf, "%d", eventPtr->mouse.rooty);
187                 argv[6] = rybuf;
188                 result = Tcl_Merge(7, argv);
189                 goto printPctSNL;
190             }
191             break;
192     }
193
194     if (hadEvent) {
195 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
196         fflush(recPtr->record);
197 #else
198         Tcl_Flush(recPtr->record);
199 #endif
200         recPtr->lastEvent = now;
201     }
202
203     return 0;
204 }
205 \f
206 /*
207  *----------------------------------------------------------------------
208  *
209  * DStringGets --
210  *
211  *      Similar to the fgets library routine, a dynamic string is
212  *      read from a file. Can deal with backslash-newline continuation.
213  *      lines.
214  *
215  * Results:
216  *      A standard Tcl result.
217  *
218  *----------------------------------------------------------------------
219  */
220
221 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
222 static int
223 DStringGets(filePtr, dsPtr)
224     FILE *filePtr;
225     Tcl_DString *dsPtr;
226 {
227     int count, c, p = EOF;
228     char buf;
229
230     for (count = 0;;) {
231         c = getc(filePtr);
232         if (c == EOF)
233             return count ? TCL_OK : TCL_ERROR;
234         else if (c == '\n') {
235             if (p == '\\')
236                 c = ' ';
237             else
238                 return TCL_OK;
239         }
240         buf = c;
241         Tcl_DStringAppend(dsPtr, &buf, 1);
242         p = c;
243     }
244     /* Not reached. */  
245 }
246 #else
247 static int
248 DStringGets(chan, dsPtr)
249     Tcl_Channel chan;
250     Tcl_DString *dsPtr;
251 {
252     char *p;
253     int length, code;
254
255     for (;;) {
256         code = Tcl_Gets(chan, dsPtr);
257         length = Tcl_DStringLength(dsPtr);
258         if (code == -1)
259             return length == 0 ? TCL_ERROR : TCL_OK;
260         if (length > 0) {
261             p = Tcl_DStringValue(dsPtr) + length - 1;
262             if (*p != '\\')
263                 return TCL_OK;
264             *p = ' ';
265         } else {
266             return TCL_OK;
267         }
268     }
269     /* Not reached. */  
270 }
271 #endif
272 \f
273 /*
274  *----------------------------------------------------------------------
275  *
276  * DeliverEvent --
277  *
278  *      Call by do-when-idle mechanism, dispatched by replay handler.
279  *      Deliver event, but first reschedule replay handler. This order
280  *      is essential !
281  *
282  *----------------------------------------------------------------------
283  */
284
285 static void
286 DeliverEvent(clientData)
287     ClientData clientData;
288 {
289     Recorder *recPtr = (Recorder *) clientData;
290
291     Tk_DoWhenIdle(RecorderReplay, (ClientData) recPtr);
292     Ck_HandleEvent(recPtr->mainPtr->mainPtr, &recPtr->event);
293 }
294 \f
295 /*
296  *----------------------------------------------------------------------
297  *
298  * RecorderReplay --
299  *
300  *      Replay handler, called by the do-when-idle mechanism or by a
301  *      timer's expiration.
302  *
303  * Results:
304  *      None.
305  *
306  *----------------------------------------------------------------------
307  */
308
309 static void
310 RecorderReplay(clientData)
311     ClientData clientData;
312 {
313     Recorder *recPtr = (Recorder *) clientData;
314     Tcl_DString input;
315     char *p;
316     int getsResult, delayValue = 0, doidle = 1;
317
318     recPtr->timerRunning = 0;
319     if (recPtr->replay == NULL)
320         return;
321
322     Tcl_DStringInit(&input);
323     while ((getsResult = DStringGets(recPtr->replay, &input)) == TCL_OK) {
324         p = Tcl_DStringValue(&input);
325         while (*p == ' ' || *p == '\t')
326             ++p;
327         if (*p == '#') {
328             Tcl_DStringTrunc(&input, 0);
329             continue;
330         }
331         if (*p == '<') {
332             CkEvent event;
333             int cmdError = TCL_OK, deliver = 0;
334             int argc;
335             char **argv;
336
337             if (Tcl_SplitList(recPtr->interp, p, &argc, &argv) != TCL_OK) {
338                 Tk_BackgroundError(recPtr->interp);
339                 getsResult = TCL_ERROR;
340                 break;
341             }
342             if (strcmp(argv[0], "<Delay>") == 0) {
343                 if (argc != 2) {
344 badNumArgs:
345                     Tcl_AppendResult(recPtr->interp,
346                         "wrong # args for ", argv[0], (char *) NULL);
347                     cmdError = TCL_ERROR;
348                 } else
349                     cmdError = Tcl_GetInt(recPtr->interp, argv[1],
350                         &delayValue);
351             } else if (strcmp(argv[0], "<Key>") == 0) {
352                 int keySym;
353
354                 if (argc != 3)
355                     goto badNumArgs;
356                 event.any.type = CK_EV_KEYPRESS;
357                 if (argv[1][0] == '\0')
358                     event.any.winPtr = NULL;
359                 else if ((event.any.winPtr = Ck_NameToWindow(recPtr->interp,
360                     argv[1], recPtr->mainPtr)) == NULL)
361                     cmdError = TCL_ERROR;
362                 else if (strncmp(argv[2], "Control-", 8) == 0 &&
363                     strlen(argv[2]) == 9) {
364                     event.key.keycode = argv[2][8] - 0x40;
365                     if (event.key.keycode > 0x20)
366                         event.key.keycode -= 0x20;
367                     deliver++;
368                 } else if (strncmp(argv[2], "0x", 2) == 0 &&
369                     strlen(argv[2]) == 4) {
370                     sscanf(&argv[2][2], "%x", &event.key.keycode);
371                     deliver++;
372                 } else if ((keySym = CkStringToKeysym(argv[2])) != NoSymbol) {
373                     event.key.keycode = keySym;
374                     deliver++;
375                 }
376             } else if (strcmp(argv[0], "<BarCode>") == 0) {
377                 if (argc != 3)
378                     goto badNumArgs;
379
380             } else if (strcmp(argv[0], "<ButtonPress>") == 0) {
381                 if (argc != 7)
382                     goto badNumArgs;
383                 event.any.type = CK_EV_MOUSE_DOWN;
384 doMouse:
385                 if (argv[1][0] == '\0')
386                     event.any.winPtr = NULL;
387                 else if ((event.any.winPtr = Ck_NameToWindow(recPtr->interp,
388                     argv[1], recPtr->mainPtr)) == NULL)
389                     cmdError = TCL_ERROR;
390                 else {
391                     cmdError |= Tcl_GetInt(recPtr->interp, argv[2],
392                         &event.mouse.button);
393                     cmdError |= Tcl_GetInt(recPtr->interp, argv[3],
394                         &event.mouse.x);
395                     cmdError |= Tcl_GetInt(recPtr->interp, argv[4],
396                         &event.mouse.y);
397                     cmdError |= Tcl_GetInt(recPtr->interp, argv[5],
398                         &event.mouse.rootx);
399                     cmdError |= Tcl_GetInt(recPtr->interp, argv[6],
400                         &event.mouse.rooty);
401                     if (cmdError == TCL_OK)
402                         deliver++;
403                 }
404             } else if (strcmp(argv[0], "<ButtonRelease>") == 0) {
405                 if (argc != 7)
406                     goto badNumArgs;
407                 event.any.type = CK_EV_MOUSE_UP;
408                 goto doMouse;
409             }
410             ckfree((char *) argv);
411             if (cmdError != TCL_OK) {
412                 Tk_BackgroundError(recPtr->interp);
413                 getsResult = cmdError;
414             } else if (deliver) {
415                 doidle = delayValue = 0;
416                 recPtr->event = event;
417                 Tk_DoWhenIdle(DeliverEvent, (ClientData) recPtr);
418             }
419             break;
420         } else if (Tcl_GlobalEval(recPtr->interp, p) != TCL_OK) {
421             Tk_BackgroundError(recPtr->interp);
422             getsResult = TCL_ERROR;
423             break;
424         }
425         Tcl_DStringTrunc(&input, 0);
426     }
427     if (getsResult != TCL_OK) {
428 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
429         fclose(recPtr->replay);
430 #else
431         Tcl_Close(NULL, recPtr->replay);
432 #endif
433         recPtr->replay = NULL;
434     } else if (delayValue != 0) {
435         recPtr->timerRunning = 1;
436         recPtr->timer = Tk_CreateTimerHandler(delayValue, RecorderReplay,
437             (ClientData) recPtr);
438     } else if (doidle != 0) {
439         Tk_DoWhenIdle(RecorderReplay, (ClientData) recPtr);
440     }
441     Tcl_DStringFree(&input);
442 }
443 \f
444 /*
445  *----------------------------------------------------------------------
446  *
447  * Ck_RecorderCmd --
448  *
449  *      This procedure is invoked to process the "recorder" Tcl command.
450  *      See the user documentation for details on what it does.
451  *
452  * Results:
453  *      A standard Tcl result.
454  *
455  * Side effects:
456  *      See the user documentation.
457  *
458  *----------------------------------------------------------------------
459  */
460
461 int
462 Ck_RecorderCmd(clientData, interp, argc, argv)
463     ClientData clientData;      /* Main window associated with
464                                  * interpreter. */
465     Tcl_Interp *interp;         /* Current interpreter. */
466     int argc;                   /* Number of arguments. */
467     char **argv;                /* Argument strings. */
468 {
469     Recorder *recPtr = ckRecorder;
470     CkWindow *mainPtr = (CkWindow *) clientData;
471     int length;
472     char c;
473
474     if (recPtr == NULL) {
475         recPtr = (Recorder *) ckalloc(sizeof (Recorder));
476         recPtr->mainPtr = mainPtr;
477         recPtr->interp = NULL;
478         recPtr->timerRunning = 0;
479 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
480         recPtr->lastEvent.tv_sec = recPtr->lastEvent.tv_usec = 0;
481 #else
482         recPtr->lastEvent.sec = recPtr->lastEvent.usec = 0;
483 #endif
484         recPtr->record = NULL;
485         recPtr->replay = NULL;
486         recPtr->withDelay = 0;
487         ckRecorder = recPtr;
488     }
489
490     if (argc < 2) {
491         Tcl_AppendResult(interp, "wrong # args: should be \"",
492             argv[0], " option ?arg?\"", (char *) NULL);
493         return TCL_ERROR;
494     }
495     c = argv[1][0];
496     length = strlen(argv[1]);
497     if ((c == 'r') && (strncmp(argv[1], "replay", length) == 0)) {
498         char *fileName;
499         Tcl_DString buffer;
500 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
501         FILE *newReplay;
502 #else
503         Tcl_Channel newReplay;
504 #endif
505
506         if (argc != 3) {
507             Tcl_AppendResult(interp, "wrong # args: should be \"",
508                 argv[0], " replay fileName\"", (char *) NULL);
509             return TCL_ERROR;
510         }
511
512 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
513         fileName = Tcl_TildeSubst(interp, argv[2], &buffer);
514         if (fileName == NULL) {
515 replayError:
516             Tcl_DStringFree(&buffer);
517             return TCL_ERROR;
518         }
519         newReplay = fopen(fileName, "r");
520         if (newReplay == NULL) {
521             Tcl_AppendResult(interp, "error opening \"", fileName,
522                 "\": ", Tcl_PosixError(interp), (char *) NULL);
523             goto replayError;
524         }
525         Tcl_DStringFree(&buffer);
526         DStringGets(newReplay, &buffer);
527         if (strncmp("# CK-RECORDER", Tcl_DStringValue(&buffer), 13) != 0) {
528             fclose(newReplay);
529             Tcl_AppendResult(interp, "invalid file for replay", (char *) NULL);
530             goto replayError;
531         }
532 #else
533         fileName = Tcl_TranslateFileName(interp, argv[2], &buffer);
534         if (fileName == NULL) {
535 replayError:
536             Tcl_DStringFree(&buffer);
537             return TCL_ERROR;
538         }
539         newReplay = Tcl_OpenFileChannel(interp, fileName, "r", 0);
540         if (newReplay == NULL)
541             goto replayError;
542         Tcl_DStringFree(&buffer);
543         Tcl_Gets(newReplay, &buffer);
544         if (strncmp("# CK-RECORDER", Tcl_DStringValue(&buffer), 13) != 0) {
545             Tcl_Close(NULL, newReplay);
546             Tcl_AppendResult(interp, "invalid file for replay", (char *) NULL);
547             goto replayError;
548         }
549 #endif
550         if (recPtr->replay != NULL) {
551             if (recPtr->timerRunning)
552                 Tk_DeleteTimerHandler(recPtr->timer);
553 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
554             fclose(recPtr->replay);
555 #else
556             Tcl_Close(NULL, recPtr->replay);
557 #endif
558             recPtr->timerRunning = 0;
559         }
560         recPtr->replay = newReplay;
561         recPtr->interp = interp;
562         Tk_DoWhenIdle(RecorderReplay, (ClientData) recPtr);
563     } else if ((c == 's') && (strncmp(argv[1], "start", length) == 0) &&
564         (length > 1)) {
565         char *fileName;
566         int withDelay = 0, fileArg = 2;
567         Tcl_DString buffer;
568 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
569         FILE *newRecord;
570         time_t now;
571 #else
572         Tcl_Channel newRecord;
573         char *string;
574 #endif
575
576         if (argc < 3 || argc > 4) {
577 badStartArgs:
578             Tcl_AppendResult(interp, "wrong # or bad args: should be \"",
579                 argv[0], " start ?-withdelay? fileName\"", (char *) NULL);
580             return TCL_ERROR;
581         }
582         if (argc == 4) {
583             if (strcmp(argv[2], "-withdelay") != 0)
584                 goto badStartArgs;
585             withDelay++;
586             fileArg++;
587         }
588 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
589         fileName = Tcl_TildeSubst(interp, argv[fileArg], &buffer);
590         if (fileName == NULL) {
591 startError:
592             Tcl_DStringFree(&buffer);
593             return TCL_ERROR;
594         }
595         newRecord = fopen(fileName, "w");
596         if (newRecord == NULL) {
597             Tcl_AppendResult(interp, "error opening \"", fileName,
598                 "\": ", Tcl_PosixError(interp), (char *) NULL);
599             goto startError;
600         }
601         if (recPtr->record != NULL)
602             fclose(recPtr->record);
603         else {
604             recPtr->lastEvent.tv_sec = recPtr->lastEvent.tv_usec = 0;
605             Ck_CreateGenericHandler(RecorderInput, recPtr);
606         }
607         recPtr->record = newRecord;
608         recPtr->withDelay = withDelay;
609         time(&now);
610         fprintf(recPtr->record, "# CK-RECORDER\n# %s", ctime(&now));
611         fprintf(recPtr->record, "# %s %s\n",
612             Tcl_GetVar(interp, "argv0", TCL_GLOBAL_ONLY),
613             Tcl_GetVar(interp, "argv", TCL_GLOBAL_ONLY));
614         Tcl_DStringFree(&buffer);
615 #else
616         fileName = Tcl_TranslateFileName(interp, argv[fileArg], &buffer);
617         if (fileName == NULL) {
618 startError:
619             Tcl_DStringFree(&buffer);
620             return TCL_ERROR;
621         }
622         newRecord = Tcl_OpenFileChannel(interp, fileName, "w", 0666);
623         if (newRecord == NULL)
624             goto startError;
625         if (recPtr->record != NULL)
626             Tcl_Close(NULL, recPtr->record);
627         else {
628             recPtr->lastEvent.sec = recPtr->lastEvent.usec = 0;
629             Ck_CreateGenericHandler(RecorderInput, (ClientData) recPtr);
630         }
631         recPtr->record = newRecord;
632         recPtr->withDelay = withDelay;
633         string = "# CK-RECORDER\n# ";
634         Tcl_Write(recPtr->record, string, strlen(string));
635         Tcl_Eval(interp, "clock format [clock seconds]");
636         Tcl_Write(recPtr->record, interp->result, strlen(interp->result));
637         Tcl_ResetResult(interp);
638         Tcl_Write(recPtr->record, "\n# ", 3);
639         string = Tcl_GetVar(interp, "argv0", TCL_GLOBAL_ONLY);
640         Tcl_Write(recPtr->record, string, strlen(string));
641         Tcl_Write(recPtr->record, " ", 1);
642         string = Tcl_GetVar(interp, "argv", TCL_GLOBAL_ONLY);
643         Tcl_Write(recPtr->record, string, strlen(string));
644         Tcl_Write(recPtr->record, "\n", 1);
645         Tcl_DStringFree(&buffer);
646 #endif
647     } else if ((c == 's') && (strncmp(argv[1], "stop", length) == 0) &&
648         (length > 1)) {
649         if (argc > 3) {
650 badStopArgs:
651             Tcl_AppendResult(interp, "wrong # or bad args: should be \"",
652                 argv[0], " stop ?replay?\"", (char *) NULL);
653             return TCL_ERROR;
654         }
655         if (argc == 3) {
656             if (strcmp(argv[2], "replay") != 0)
657                 goto badStopArgs;
658             if (recPtr->replay != NULL) {
659                 if (recPtr->timerRunning)
660                     Tk_DeleteTimerHandler(recPtr->timer);
661 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
662                 fclose(recPtr->replay);
663 #else
664                 Tcl_Close(NULL, recPtr->replay);
665 #endif
666                 recPtr->replay = NULL;
667                 recPtr->timerRunning = 0;
668             }
669         } else if (recPtr->record != NULL) {
670 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
671             fclose(recPtr->record);
672 #else
673             Tcl_Close(NULL, recPtr->record);
674 #endif
675             Ck_DeleteGenericHandler(RecorderInput, (ClientData) recPtr);
676             recPtr->record = NULL;
677         }
678     } else {
679         Tcl_AppendResult(interp, "wrong # args: should be \"",
680                 argv[0], " replay, start, or stop\"", (char *) NULL);
681         return TCL_ERROR;
682     }
683     return TCL_OK;
684 }
685
686