4 * This file provides a simple event recorder.
6 * Copyright (c) 1996-1999 Christian Werner
8 * See the file "license.terms" for information on usage and redistribution
9 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
16 * There is one structure of the following type for the global data
25 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
26 struct timeval lastEvent;
38 static Recorder *ckRecorder = NULL;
41 * Internal procedures.
44 static int RecorderInput _ANSI_ARGS_((ClientData clientData,
46 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
47 static int DStringGets _ANSI_ARGS_((FILE *filePtr, Tcl_DString *dsPtr));
49 static int DStringGets _ANSI_ARGS_((Tcl_Channel chan,
52 static void DeliverEvent _ANSI_ARGS_((ClientData clientData));
53 static void RecorderReplay _ANSI_ARGS_((ClientData clientData));
56 *----------------------------------------------------------------------
60 * This procedure is installed as generic event handler.
61 * For certain events it adds lines to the recorder file.
63 *----------------------------------------------------------------------
67 RecorderInput(clientData, eventPtr)
68 ClientData clientData;
71 Recorder *recPtr = (Recorder *) clientData;
72 int hadEvent = 0, type = eventPtr->any.type;
73 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
77 extern void TclpGetTime _ANSI_ARGS_((Tcl_Time *timePtr));
80 char *keySym, *barCode, *result;
83 if (recPtr->record == NULL) {
84 Ck_DeleteGenericHandler(RecorderInput, clientData);
88 if (type != CK_EV_KEYPRESS && type != CK_EV_BARCODE &&
89 type != CK_EV_MOUSE_UP && type != CK_EV_MOUSE_DOWN)
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) {
98 diff = now.tv_sec * 1000 + now.tv_usec / 1000;
99 diff -= recPtr->lastEvent.tv_sec * 1000 +
100 recPtr->lastEvent.tv_usec / 1000;
104 fprintf(recPtr->record, "<Delay> %d\n", (int) diff);
110 if (recPtr->withDelay && recPtr->lastEvent.sec != 0 &&
111 recPtr->lastEvent.usec != 0) {
115 diff = now.sec * 1000 + now.usec / 1000;
116 diff -= recPtr->lastEvent.sec * 1000 +
117 recPtr->lastEvent.usec / 1000;
121 sprintf(string, "<Delay> %d\n", (int) diff);
122 Tcl_Write(recPtr->record, string, strlen(string));
131 keySym = CkKeysymToString(eventPtr->key.keycode, 1);
132 if (strcmp(keySym, "NoSymbol") != 0)
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);
140 if (argv[2] != NULL) {
142 argv[1] = eventPtr->key.winPtr == NULL ? "" :
143 eventPtr->key.winPtr->pathName;
144 result = Tcl_Merge(3, argv);
146 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
147 fprintf(recPtr->record, "%s\n", result);
149 Tcl_Write(recPtr->record, result, strlen(result));
150 Tcl_Write(recPtr->record, "\n", 1);
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;
164 result = Tcl_Merge(3, argv);
170 case CK_EV_MOUSE_DOWN:
172 char bbuf[16], xbuf[16], ybuf[16], rxbuf[16], rybuf[16];
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);
180 sprintf(xbuf, "%d", eventPtr->mouse.x);
182 sprintf(ybuf, "%d", eventPtr->mouse.y);
184 sprintf(rxbuf, "%d", eventPtr->mouse.rootx);
186 sprintf(rybuf, "%d", eventPtr->mouse.rooty);
188 result = Tcl_Merge(7, argv);
195 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
196 fflush(recPtr->record);
198 Tcl_Flush(recPtr->record);
200 recPtr->lastEvent = now;
207 *----------------------------------------------------------------------
211 * Similar to the fgets library routine, a dynamic string is
212 * read from a file. Can deal with backslash-newline continuation.
216 * A standard Tcl result.
218 *----------------------------------------------------------------------
221 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
223 DStringGets(filePtr, dsPtr)
227 int count, c, p = EOF;
233 return count ? TCL_OK : TCL_ERROR;
234 else if (c == '\n') {
241 Tcl_DStringAppend(dsPtr, &buf, 1);
248 DStringGets(chan, dsPtr)
256 code = Tcl_Gets(chan, dsPtr);
257 length = Tcl_DStringLength(dsPtr);
259 return length == 0 ? TCL_ERROR : TCL_OK;
261 p = Tcl_DStringValue(dsPtr) + length - 1;
274 *----------------------------------------------------------------------
278 * Call by do-when-idle mechanism, dispatched by replay handler.
279 * Deliver event, but first reschedule replay handler. This order
282 *----------------------------------------------------------------------
286 DeliverEvent(clientData)
287 ClientData clientData;
289 Recorder *recPtr = (Recorder *) clientData;
291 Tk_DoWhenIdle(RecorderReplay, (ClientData) recPtr);
292 Ck_HandleEvent(recPtr->mainPtr->mainPtr, &recPtr->event);
296 *----------------------------------------------------------------------
300 * Replay handler, called by the do-when-idle mechanism or by a
301 * timer's expiration.
306 *----------------------------------------------------------------------
310 RecorderReplay(clientData)
311 ClientData clientData;
313 Recorder *recPtr = (Recorder *) clientData;
316 int getsResult, delayValue = 0, doidle = 1;
318 recPtr->timerRunning = 0;
319 if (recPtr->replay == NULL)
322 Tcl_DStringInit(&input);
323 while ((getsResult = DStringGets(recPtr->replay, &input)) == TCL_OK) {
324 p = Tcl_DStringValue(&input);
325 while (*p == ' ' || *p == '\t')
328 Tcl_DStringTrunc(&input, 0);
333 int cmdError = TCL_OK, deliver = 0;
337 if (Tcl_SplitList(recPtr->interp, p, &argc, &argv) != TCL_OK) {
338 Tk_BackgroundError(recPtr->interp);
339 getsResult = TCL_ERROR;
342 if (strcmp(argv[0], "<Delay>") == 0) {
345 Tcl_AppendResult(recPtr->interp,
346 "wrong # args for ", argv[0], (char *) NULL);
347 cmdError = TCL_ERROR;
349 cmdError = Tcl_GetInt(recPtr->interp, argv[1],
351 } else if (strcmp(argv[0], "<Key>") == 0) {
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;
368 } else if (strncmp(argv[2], "0x", 2) == 0 &&
369 strlen(argv[2]) == 4) {
370 sscanf(&argv[2][2], "%x", &event.key.keycode);
372 } else if ((keySym = CkStringToKeysym(argv[2])) != NoSymbol) {
373 event.key.keycode = keySym;
376 } else if (strcmp(argv[0], "<BarCode>") == 0) {
380 } else if (strcmp(argv[0], "<ButtonPress>") == 0) {
383 event.any.type = CK_EV_MOUSE_DOWN;
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;
391 cmdError |= Tcl_GetInt(recPtr->interp, argv[2],
392 &event.mouse.button);
393 cmdError |= Tcl_GetInt(recPtr->interp, argv[3],
395 cmdError |= Tcl_GetInt(recPtr->interp, argv[4],
397 cmdError |= Tcl_GetInt(recPtr->interp, argv[5],
399 cmdError |= Tcl_GetInt(recPtr->interp, argv[6],
401 if (cmdError == TCL_OK)
404 } else if (strcmp(argv[0], "<ButtonRelease>") == 0) {
407 event.any.type = CK_EV_MOUSE_UP;
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);
420 } else if (Tcl_GlobalEval(recPtr->interp, p) != TCL_OK) {
421 Tk_BackgroundError(recPtr->interp);
422 getsResult = TCL_ERROR;
425 Tcl_DStringTrunc(&input, 0);
427 if (getsResult != TCL_OK) {
428 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
429 fclose(recPtr->replay);
431 Tcl_Close(NULL, recPtr->replay);
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);
441 Tcl_DStringFree(&input);
445 *----------------------------------------------------------------------
449 * This procedure is invoked to process the "recorder" Tcl command.
450 * See the user documentation for details on what it does.
453 * A standard Tcl result.
456 * See the user documentation.
458 *----------------------------------------------------------------------
462 Ck_RecorderCmd(clientData, interp, argc, argv)
463 ClientData clientData; /* Main window associated with
465 Tcl_Interp *interp; /* Current interpreter. */
466 int argc; /* Number of arguments. */
467 char **argv; /* Argument strings. */
469 Recorder *recPtr = ckRecorder;
470 CkWindow *mainPtr = (CkWindow *) clientData;
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;
482 recPtr->lastEvent.sec = recPtr->lastEvent.usec = 0;
484 recPtr->record = NULL;
485 recPtr->replay = NULL;
486 recPtr->withDelay = 0;
491 Tcl_AppendResult(interp, "wrong # args: should be \"",
492 argv[0], " option ?arg?\"", (char *) NULL);
496 length = strlen(argv[1]);
497 if ((c == 'r') && (strncmp(argv[1], "replay", length) == 0)) {
500 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
503 Tcl_Channel newReplay;
507 Tcl_AppendResult(interp, "wrong # args: should be \"",
508 argv[0], " replay fileName\"", (char *) NULL);
512 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
513 fileName = Tcl_TildeSubst(interp, argv[2], &buffer);
514 if (fileName == NULL) {
516 Tcl_DStringFree(&buffer);
519 newReplay = fopen(fileName, "r");
520 if (newReplay == NULL) {
521 Tcl_AppendResult(interp, "error opening \"", fileName,
522 "\": ", Tcl_PosixError(interp), (char *) NULL);
525 Tcl_DStringFree(&buffer);
526 DStringGets(newReplay, &buffer);
527 if (strncmp("# CK-RECORDER", Tcl_DStringValue(&buffer), 13) != 0) {
529 Tcl_AppendResult(interp, "invalid file for replay", (char *) NULL);
533 fileName = Tcl_TranslateFileName(interp, argv[2], &buffer);
534 if (fileName == NULL) {
536 Tcl_DStringFree(&buffer);
539 newReplay = Tcl_OpenFileChannel(interp, fileName, "r", 0);
540 if (newReplay == NULL)
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);
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);
556 Tcl_Close(NULL, recPtr->replay);
558 recPtr->timerRunning = 0;
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) &&
566 int withDelay = 0, fileArg = 2;
568 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
572 Tcl_Channel newRecord;
576 if (argc < 3 || argc > 4) {
578 Tcl_AppendResult(interp, "wrong # or bad args: should be \"",
579 argv[0], " start ?-withdelay? fileName\"", (char *) NULL);
583 if (strcmp(argv[2], "-withdelay") != 0)
588 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
589 fileName = Tcl_TildeSubst(interp, argv[fileArg], &buffer);
590 if (fileName == NULL) {
592 Tcl_DStringFree(&buffer);
595 newRecord = fopen(fileName, "w");
596 if (newRecord == NULL) {
597 Tcl_AppendResult(interp, "error opening \"", fileName,
598 "\": ", Tcl_PosixError(interp), (char *) NULL);
601 if (recPtr->record != NULL)
602 fclose(recPtr->record);
604 recPtr->lastEvent.tv_sec = recPtr->lastEvent.tv_usec = 0;
605 Ck_CreateGenericHandler(RecorderInput, recPtr);
607 recPtr->record = newRecord;
608 recPtr->withDelay = withDelay;
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);
616 fileName = Tcl_TranslateFileName(interp, argv[fileArg], &buffer);
617 if (fileName == NULL) {
619 Tcl_DStringFree(&buffer);
622 newRecord = Tcl_OpenFileChannel(interp, fileName, "w", 0666);
623 if (newRecord == NULL)
625 if (recPtr->record != NULL)
626 Tcl_Close(NULL, recPtr->record);
628 recPtr->lastEvent.sec = recPtr->lastEvent.usec = 0;
629 Ck_CreateGenericHandler(RecorderInput, (ClientData) recPtr);
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);
647 } else if ((c == 's') && (strncmp(argv[1], "stop", length) == 0) &&
651 Tcl_AppendResult(interp, "wrong # or bad args: should be \"",
652 argv[0], " stop ?replay?\"", (char *) NULL);
656 if (strcmp(argv[2], "replay") != 0)
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);
664 Tcl_Close(NULL, recPtr->replay);
666 recPtr->replay = NULL;
667 recPtr->timerRunning = 0;
669 } else if (recPtr->record != NULL) {
670 #if (TCL_MAJOR_VERSION == 7) && (TCL_MINOR_VERSION <= 4)
671 fclose(recPtr->record);
673 Tcl_Close(NULL, recPtr->record);
675 Ck_DeleteGenericHandler(RecorderInput, (ClientData) recPtr);
676 recPtr->record = NULL;
679 Tcl_AppendResult(interp, "wrong # args: should be \"",
680 argv[0], " replay, start, or stop\"", (char *) NULL);