"SfR Fresh" - the SfR Freeware/Shareware Archive

Member "xlab-0.8.3/record.c" of archive xlab-0.8.3.tar.gz:


As a special service "SfR Fresh" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. That can be also achieved for any archive member file by clicking within an archive contents listing on the first character of the file(path) respectively on the according byte size field.
    1 /*
    2  * $Id: record.c,v 1.8 1998/07/24 11:59:16 marc Exp $
    3  *
    4  *  Code to record / play back X events in script
    5  *
    6  * (c) Copyright 1996, 1997, 1998 Marc Vertes
    7  */
    8 
    9 #include <stdio.h>
   10 #include <stdlib.h>
   11 #include <X11/Xlib.h>
   12 #include <X11/Xutil.h>
   13 #include <X11/keysym.h>
   14 #include "scope.h"
   15 #include "server.h"
   16 #include "record.h"
   17 #include "x11.h"
   18 
   19 extern Display *Dpy;
   20 
   21 static char script_line[256];	 /* stored script line */
   22 
   23 struct WinTab wintab[MAXWTAB];
   24 struct WinAlias winalias[MAXWTAB];
   25 
   26 long maxalias = 0;
   27 long maxwtab = 0;
   28 int replay_counter = 0;
   29 int first_replay = 1;
   30 int first_event_in_replay = 0;
   31 
   32 int xRootPointer, yRootPointer;	/* coordinates of pointer in the root window */
   33 void (*Resource_Handler)() = 0;	/* handler to call */
   34 void *Resource_userdata = 0;	/* user data to pass */
   35 XID Resource_resource = 0;
   36 TimerID Resource_timeout = 0;	/* timeout when resource doesn't appear */
   37 int Resource_index = 0;		/* resource index within fd */
   38 FD Resource_fd = 0;		/* resource file descriptor */
   39 
   40 /* Synthetic X event rebuild from script */
   41 struct Synth_ev {
   42     Window win;
   43     Window subw;
   44     Window rootw;
   45     Time time;
   46     FD fd;
   47     unsigned short type;	/* Event type in binary form */
   48     short state;
   49     short detail;
   50     short same;
   51     short x, y;
   52     short rootx, rooty;
   53     unsigned short seq;
   54     unsigned char evbuf[32];
   55 } synth_ev;
   56 
   57 #define TheRootWindow wintab[0].newpar
   58 
   59 /*--------------------------------------------------------------*/
   60 int start_record(filename)
   61     char *filename;
   62 {
   63     if (Playback != PLAYBACK_INACTIVE && Playback != PLAYBACK_COMPLETE) {
   64 	fprintf(stderr, "Cannot record and play back in same session.\n");
   65 	return (1);
   66     }
   67     strcpy(RecordFileName, filename);
   68     RecordFile = fopen(RecordFileName, "w");
   69     if (RecordFile == 0) {
   70 	perror(RecordFileName);
   71 	return (1);
   72     }
   73     fclose(RecordFile);
   74     RecordFile = 0;
   75     Record = 1;
   76     return (0);
   77 }
   78 
   79 /*--------------------------------------------------------------*/
   80 void stop_record()
   81 {
   82     CloseFile(RecordFile);
   83     fclose(RecordFile);
   84     Record = 0;
   85     first_replay = 0;
   86 }
   87 
   88 /*--------------------------------------------------------------*/
   89 static FILE *OpenFile(name, priv)
   90     char *name;
   91     char *priv;
   92 {
   93     register FILE *file;
   94     register int fid;
   95 
   96     /* open file */
   97     file = fopen(name, priv);
   98 
   99     /* deal with failure.  snarky messages are OK, we've pretested files */
  100     if (file == 0) {
  101 	panic("OpenFile failure.\n");
  102     }
  103     /*
  104      * set up file descriptor in this program's table.
  105      * Seems to be necessary at least for SUN Solaris.
  106      */
  107     fid = fileno(file);
  108     UsingFD(fid, 0);
  109     return file;
  110 
  111 }
  112 
  113 /*--------------------------------------------------------------*/
  114 void CloseFile(file)
  115     FILE *file;
  116 {
  117     int fid = fileno(file);
  118     NotUsingFD(fid);
  119 }
  120 
  121 /*--------------------------------------------------------------*/
  122 /*
  123  *  TimeDilation expresses the time dilation in percent.
  124  *  if TimeDilation = 200, then we want the thing to play back
  125  *  twice as fast, so we want to cut all times in half.
  126  */
  127 unsigned long int Dilate(deltat)
  128     unsigned long int deltat;
  129 {
  130     unsigned long int result;
  131     result = deltat;
  132     result = ((deltat * 100) + ((TimeDilation / 2) - 1)) / TimeDilation;
  133     return result;
  134 }
  135 
  136 /*--------------------------------------------------------------*/
  137 static long int NextScriptEvent(buf, n)
  138     char *buf;
  139     long int n;
  140 {
  141     /*
  142      * This function reads the next script event
  143      * and returns it in a buffer.
  144      */
  145     char *result;
  146     int goodline = 0;
  147     int event;
  148 
  149     /* open up the recording file if necessary, first time through */
  150     if (PlaybackFile == 0) {
  151 	PlaybackFile = OpenFile(PlaybackFileName, "r");
  152 	Playback = PLAYBACK_ACTIVE;
  153 	first_event_in_replay = 1;
  154     }
  155     /* skip blanks and comments */
  156     while (goodline == 0) {
  157 	buf[0] = '\0';
  158 	result = fgets(buf, n, PlaybackFile);
  159 	if (result == 0)
  160 	    return -1;
  161 
  162 	/* skip comment-type lines */
  163 	if (buf[0] != '\0') {
  164 	    event = ascii_to_event_type(buf);
  165 	    if (event != 0) {
  166 		goodline = 1;
  167 	    }
  168 	}
  169     }
  170     return strlen(buf);
  171 }
  172 
  173 /*--------------------------------------------------------------*/
  174 /*
  175  *  this timer function is called when
  176  *  the wait for a window to become mapped
  177  *  runs out...
  178  *  It issues a warning message, then proceeds as
  179  *  if the window had, in fact, appeared.
  180  *
  181  *  Under some failure conditions, this causes an
  182  *  error cascade.
  183  */
  184 static void ResourceNeverAppeared(timer, window)
  185     TimerID timer;
  186     XID window;
  187 {
  188     if (window != TheRootWindow) {
  189 	fprintf(stderr, "Playback failure\n");
  190 	fprintf(stderr, "  Timed out waiting for window %8.8x ( %8.8x )\n", (unsigned int) window,
  191 		OriginalWid(window));
  192 	fprintf(stderr, "  %s\n", script_line);
  193 	Errorcount++;
  194     }
  195     /* call the wait handler, attempting to carry on with the run */
  196 
  197     if (Resource_Handler)
  198 	(*Resource_Handler) (Resource_resource, Resource_userdata);
  199 
  200     /* clear off the waiting flag in the resource status map */
  201     if (Resource_index < MAXRESOURCESTATUS) {
  202 	CS[Resource_fd].ResourceStatus[Resource_index] &= ~RES_Waiting;
  203     }
  204     /* Restore the resource-wait information to initial state */
  205     Resource_Handler = 0;
  206     Resource_userdata = 0;
  207     Resource_resource = 0;
  208     Resource_timeout = 0;
  209     Resource_index = 0;
  210     Resource_fd = 0;
  211 }
  212 
  213 /*--------------------------------------------------------------*/
  214 /* get the FD to which this window 'belongs' */
  215 int GetResourceBaseFd(resource)
  216     XID resource;
  217 {
  218     long ifd;
  219     unsigned long int c_res_base;
  220 
  221     for (ifd = 1; ifd <= HighestFD; ifd++) {
  222 	c_res_base = resource & ~CS[ifd].ResourceIdMask;
  223 	if (FDD[ifd].Busy) {
  224 	    if (c_res_base == CS[ifd].ResourceIdBase)
  225 		return ifd;
  226 	} else {
  227 	    if (c_res_base == CS[ifd].ResourceIdBase) {
  228 		fprintf(stderr,
  229 			"Playback Error. fd %ld for resource %x (%x) is not valid anymore\n",
  230 		        ifd, (unsigned int) resource, OriginalWid(resource));
  231 		return 0;
  232 	    }
  233 	}
  234     }
  235     /* maybe an external window */
  236     if ((resource != TheRootWindow) && (OriginalWid(resource) != 0))
  237 	fprintf(stderr, "Playback Error. Couldn't find an fd for resource %x (%x)\n",
  238 		(unsigned int) resource, OriginalWid(resource));
  239     return(0);
  240 }
  241 
  242 /*-------------------------------------------------------------*/
  243 /*
  244  * this function can be called when somebody
  245  * wants to wait for a window to appear.
  246  */
  247 void WaitForWindowMapped(fd, resource, handler, user_data)
  248     int fd;
  249     XID resource;
  250     void (*handler) ();
  251     void *user_data;
  252 
  253 {
  254     int ifd;
  255     int res_index = resource & CS[fd].ResourceIdMask;
  256 
  257 
  258     if (Resource_Handler)
  259 	panic("already waiting for a resource....only one allowed!");
  260 
  261     if (res_index >= MAXRESOURCESTATUS) {
  262 	warn("res_index beyond MAXRESOURCESTATUS in WaitForWindowMapped.");
  263 	/* beyond the resource count; just assume it's available */
  264 	if (handler)
  265 	    (*handler) (resource, user_data);
  266 	return;
  267     }
  268     /* No check for first EnterNotify event */
  269     if (first_event_in_replay && synth_ev.type == EnterNotify) {
  270 	first_event_in_replay = 0;
  271 	if (handler)
  272 	    (*handler) (resource, user_data);
  273 	return;
  274     }
  275     ifd = GetResourceBaseFd(resource);
  276 
  277     /*
  278      * a completeness test
  279      */
  280     if (resource != TheRootWindow) { /** 'res_unmapped' **/
  281 	if (ifd == 0 && OriginalWid(resource) != 0) {
  282 	    fprintf(stderr, "Playback Error. Contradiction ifd=0 resource %x (%x)\n",
  283 		    (unsigned int) resource, OriginalWid(resource));
  284 	    ReadPlaybackEvent(20L, fd);
  285 	    return;
  286 	}
  287 	if ((ifd != 0) && OriginalWid(resource) == 0) {
  288 	    fprintf(stderr,
  289 		    "Playback Error.Probably window destroyed. ifd = %d resource %x (0)\n",
  290 		    ifd, (unsigned int) resource);
  291 	    CS[ifd].ResourceStatus[res_index] = 0;
  292 	    ReadPlaybackEvent(20L, fd);
  293 	    return;
  294 	}
  295     }
  296     /*
  297      * if the window is mapped or its is an external window
  298      */
  299     if ((ifd == 0) || CS[fd].ResourceStatus[res_index] == RES_Mapped ||
  300 	CS[ifd].ResourceStatus[res_index] == RES_Mapped) {
  301 	(*handler) (resource, user_data);
  302 	return;
  303     }
  304     CS[ifd].ResourceStatus[res_index] |= RES_Waiting;
  305     Resource_Handler = handler;
  306     Resource_userdata = user_data;
  307     Resource_resource = resource;
  308     Resource_timeout = CreateTimer(2000, (void (*)()) ResourceNeverAppeared, (void *) resource);
  309     Resource_index = res_index;
  310     Resource_fd = ifd;
  311     debug(4, (stderr, "WAIT FOR %7x FD = %d IFD = %d\n", (unsigned int) resource, fd, ifd));
  312 }
  313 
  314 /*-------------------------------------------------------------*/
  315 /* debugging aid */
  316 char *getstat(status)
  317     int status;
  318 {
  319 
  320     switch (status) {
  321     case 0:
  322 	return "Unseen";
  323     case 1:
  324 	return "Seen";
  325     case 2:
  326 	return "Mapped";
  327     case 3:
  328 	return "Unmapped";
  329     case 128:
  330 	return "Wait";
  331     case 129:
  332 	return "Wait&Seen";
  333     case 130:
  334 	return "Wait&Mapped";
  335     case 131:
  336 	return "Wait&Unmapp";
  337     default:
  338 	return "illegal status";
  339     }
  340 }
  341 
  342 /*-------------------------------------------------------------*/
  343 /*
  344  * this routine gets called from the event formatters
  345  * in decode11.c
  346  * it is used to maintain event status.
  347  */
  348 void WindowStatusAnnounce(fd, buf, n, status)
  349     FD fd;
  350     unsigned char *buf;
  351     int n;
  352     ResStatus status;
  353 {
  354     XID resource = ILong(&buf[n]);
  355     int res_index = resource & CS[fd].ResourceIdMask;
  356     int waiting = 0;
  357     ResStatus oldstatus;
  358     int ifd, i;
  359 
  360     enterprocedure("WindowStatusAnnounce");
  361 
  362     ifd = GetResourceBaseFd(resource);
  363 
  364     if ((resource == 0) || (ifd == 0))
  365 	return;
  366 
  367     debug(4, (stderr,
  368 	      "curwin=%7x resindx = %3d waitwin=%7x FD = %d IFD = %d \n",
  369 	      (unsigned int) resource, res_index, (unsigned int) Resource_resource, fd, ifd));
  370 
  371     debug(4, (stderr,
  372               "                             enstat = %10s oldstat = %10s nwstat = ",
  373 	      getstat(status),
  374 	      getstat(CS[ifd].ResourceStatus[res_index])));
  375 
  376     if (res_index < MAXRESOURCESTATUS) {
  377 	oldstatus = CS[ifd].ResourceStatus[res_index];
  378 	if (oldstatus & RES_Waiting)
  379 	    waiting = 1;
  380 	CS[ifd].ResourceStatus[res_index] = status | (oldstatus & RES_Waiting);
  381     }
  382     /* if we were waiting for the resource and it is now mapped ... */
  383     if (resource == Resource_resource) {
  384 	if (waiting & (status == RES_Mapped)) {
  385 	    /* blast the wait timeout */
  386 	    if (Resource_timeout)
  387 		DeleteTimer(Resource_timeout);
  388 
  389 	    /* call the resource handler, if any */
  390 	    if (Resource_Handler)
  391 		(*Resource_Handler) (Resource_resource, Resource_userdata);
  392 
  393 	    /* zap the waiting bit in the resource map */
  394 	    if (Resource_index < MAXRESOURCESTATUS) {
  395 		CS[Resource_fd].ResourceStatus[Resource_index] &= ~RES_Waiting;
  396 	    }
  397 	    /* Restore the resource-wait information to initial state */
  398 	    Resource_Handler = 0;
  399 	    Resource_userdata = 0;
  400 	    Resource_resource = 0;
  401 	    Resource_timeout = 0;
  402 	    Resource_index = 0;
  403 	    Resource_fd = 0;
  404 	}
  405     }
  406     if (status == RES_Mapped) {
  407 	CS[ifd].ResourceStatus[res_index] |= RES_Mapped;
  408 
  409 	/* indicate that the children of this window are also mapped */
  410 	for (i = 0; i < maxwtab; i++) {
  411 	    if (wintab[i].newpar == resource) {
  412 		int ifdchild;
  413 		int res_indexchild;
  414 
  415 		ifdchild = GetResourceBaseFd(wintab[i].newid);
  416 		res_indexchild = wintab[i].newid & CS[ifdchild].ResourceIdMask;
  417 
  418 		if (ifdchild) {
  419 		    CS[ifdchild].ResourceStatus[res_indexchild] &= ~RES_Waiting;
  420 		    CS[ifdchild].ResourceStatus[res_indexchild] |= RES_Mapped;
  421 		}
  422 	    }
  423         }
  424     }
  425     debug(4, (stderr, "%10s Wait = %1d\n", getstat(CS[ifd].ResourceStatus[res_index]), waiting));
  426     return;
  427 }
  428 
  429 /*--------------------------------------------------------------*/
  430 void AnnouncePlaybackError(dpy, erev)
  431     Display *dpy;
  432     XErrorEvent *erev;
  433 {
  434     if (synth_ev.type != EnterNotify) {
  435 	fprintf(stderr, "Playback failure\n");
  436 	fprintf(stderr, "  Unable to move pointer into window %8.8x (%x)\n",
  437 	 (unsigned int) erev->resourceid, OriginalWid(erev->resourceid));
  438 	fprintf(stderr, "  %s\n", script_line);
  439 	Errorcount++;
  440     }
  441 }
  442 
  443 /*--------------------------------------------------------------*/
  444 void SendPlaybackEvent(timer, fd)
  445     TimerID timer;
  446     int fd;
  447 {
  448     struct timeval tv;
  449     FD pairfd = FDPair(fd);
  450     unsigned short int seq = 0;
  451     Time timestamp, t;
  452 
  453     /* obtain the sequence number from the client-side CS structure */
  454     if (pairfd >= StaticMaxFD)
  455 	pairfd = -1;
  456     if (pairfd >= 0)
  457 	seq = CS[pairfd].SequenceNumber;
  458 
  459     /* compute timestamp based on previously noted time offset */
  460     gettimeofday(&tv, 0);
  461     t = tv.tv_sec * 1000;
  462     t += tv.tv_usec / 1000;
  463     timestamp = t + CS[fd].TimeOffset;
  464 
  465     /* it's time to send the event to the program under test..  */
  466     /* convert it to a wire-format event buffer */
  467     OShort(&synth_ev.evbuf[2], seq);
  468     OLong(&synth_ev.evbuf[4], timestamp);
  469     OShort(&synth_ev.evbuf[20], xRootPointer);
  470     OShort(&synth_ev.evbuf[22], yRootPointer);
  471     ReplaceWid(synth_ev.evbuf, 12);
  472     ReplaceWid(synth_ev.evbuf, 16);
  473 
  474     WriteToClient(fd, synth_ev.evbuf, 32);
  475 
  476     /* if Shift + Ctrl + Button 1, activate screen_capture */
  477     if ((synth_ev.type == ButtonRelease) &&
  478 	(synth_ev.state & ShiftMask) &&
  479 	(synth_ev.state & ControlMask))
  480 	screen_capture();
  481 
  482     /* read the next event from the script file */
  483     ReadPlaybackEvent(timer, fd);
  484 }
  485 
  486 /*--------------------------------------------------------------*/
  487 extern int whichGrab;
  488 extern int status;
  489 
  490 void WarpPlaybackEvent(session_window, fd)
  491     Window session_window;
  492     int fd;
  493 {
  494     /*
  495      * This function decodes part of a stored script_line,
  496      * and warps the server pointer to the selected
  497      * position.  It is called when the window is known to be mapped.
  498      * This function ends by registering a callback to
  499      * "SendPlaybackEvent" for 20 ms later.
  500      */
  501     Window saved_window;
  502     static int first = 0;
  503     /* more variables to query pointer */
  504     Window root_return, child_return;
  505     int win_x_return, win_y_return;
  506     unsigned int mask_Return;
  507 
  508     if (first == 0) {
  509 	(void) XSetErrorHandler((int (*)()) AnnouncePlaybackError);
  510 	first = 1;
  511     }
  512     saved_window = NewWid(synth_ev.win);
  513 
  514     /* EnterNotify events always causes problems */
  515     if (!(synth_ev.type == 7 &&
  516 	  saved_window == TheRootWindow && synth_ev.subw == 0)) {
  517 
  518 	if (saved_window) {
  519 	    XWarpPointer(Dpy, None, saved_window, 0, 0, 0, 0, synth_ev.x, synth_ev.y);
  520 
  521             /*
  522 	     * This code does not work in all cases where you 'drag a window'
  523              * because the server or pointer is grabbed by another client
  524 	     */
  525 	    if (!((whichGrab == 4 /*server */  || whichGrab == 1 /*pntr */ ) && status == 1)) {
  526 		XSync(Dpy, False);
  527 		/*
  528 		 * Get root_x and root_y for the pointer, to set event coord accordingly
  529 		 */
  530 		XQueryPointer(Dpy, saved_window, &root_return, &child_return,
  531 			      &xRootPointer, &yRootPointer, &win_x_return, &win_y_return, &mask_Return);
  532 	    } else {
  533 		XFlush(Dpy);
  534 		if (saved_window == TheRootWindow) {
  535 		    xRootPointer = synth_ev.x;
  536 		    yRootPointer = synth_ev.y;
  537 		}
  538 	    }
  539 	    CreateTimer(20L, (void (*)()) SendPlaybackEvent, (void *) fd);
  540 	    return;
  541 	}
  542     }
  543     ReadPlaybackEvent(20L, fd);
  544 }
  545 
  546 
  547 /*--------------------------------------------------------------*/
  548 void TranslatePlaybackEvent(timer, fd)
  549     TimerID timer;
  550     int fd;
  551 {
  552     /*
  553      * This function decodes part of a stored script_line,
  554      * and waits for the named window to appear.
  555      */
  556     Window session_window = NewWid(synth_ev.win);
  557 
  558     if (session_window == 0)
  559 	fprintf(stderr, "Error. session_window = 0 \n");
  560     WaitForWindowMapped(fd, session_window, (void (*)()) WarpPlaybackEvent, (void *) fd);
  561 }
  562 
  563 /*--------------------------------------------------------------*/
  564 void end_replay()
  565 {
  566     CloseFile(PlaybackFile);
  567     warn("Playback completed");
  568     Playback = PLAYBACK_COMPLETE;
  569     replay_counter--;
  570     if (replay_counter > 0) {
  571 	warn("Activate next playback event");
  572 	start_replay(PlaybackFileName);
  573     }
  574 }
  575 
  576 /*--------------------------------------------------------------*/
  577 void ReadPlaybackEvent(timer, fd)
  578     TimerID timer;
  579     int fd;
  580 {
  581     /*
  582      * This function reads an event from the script file.
  583      * It then establishes a timer call back to
  584      * activate WarpPlaybackEvent at the appropriate time
  585      * (based on the elapsed time specified in the script file.)
  586      *
  587      * If NextScriptEvent comes back = -1, it's the end of
  588      * the script file;  forget about further timer callbacks.
  589      * However, don't stop the xscript pseudo-server, because
  590      * it should stay running until client-under-test disconnect.
  591      */
  592     long int linelen = NextScriptEvent(script_line, 256);
  593     unsigned long int delay, delay2;
  594 
  595     if (linelen < 0) {
  596         /*
  597 	 * End of file ... wait 5 seconds to let application finish
  598          * windows closing.
  599          * Mark playback as complete
  600          */
  601 	warn("Playback completion in 5s");
  602 	CreateTimer(5000, end_replay, (void *) 0);
  603     } else {
  604 	/*
  605 	 * Process next event from file.
  606 	 */
  607 	if (ReadSynthEvent(script_line) == 1) {
  608 	    ReadPlaybackEvent(20L, fd);
  609 	    return;
  610 	}
  611 	delay = synth_ev.time;
  612 
  613 	switch (synth_ev.type) {
  614 	case ButtonPress:
  615 	    /*
  616 	     *  to avoid fouling up double clicks,
  617 	     *  don't mess around with delay times before ButtonPress < 250 ms
  618 	     */
  619 	    if (delay < DoubleClickTime) {
  620 		/* delay is shorter than doubleclick time, don't touch it */
  621 		delay2 = delay;
  622 	    } else {
  623 		/*
  624 		 * Original delay is longer than doubleclick time,
  625 		 * don't let recomputed delay end up shorter than doubleclick time
  626 		 */
  627 		delay2 = Dilate(delay);
  628 		if (delay2 < DoubleClickTime)
  629 		    delay2 = delay;
  630 	    }
  631 	    break;
  632 
  633 	default:
  634 	    /*
  635 	     * It's OK to compress other event delays more-or-less arbitrarily
  636 	     */
  637 	    delay2 = Dilate(delay);
  638 	    break;
  639 	}
  640 
  641 	/* subtract delay time we'll use for waiting for motion */
  642 	if (delay2 > 20)
  643 	    delay2 -= 20;
  644 	else
  645 	    delay2 = 0;
  646 
  647 	if (delay2 < 5)
  648 	    delay2 = 5;
  649 	debug(128, (stderr, "%ld=delay %s\n", delay2, script_line));
  650 	CreateTimer(delay2, (void (*)()) TranslatePlaybackEvent, (void *) synth_ev.fd);
  651     }
  652 }
  653 
  654 /*--------------------------------------------------------------*/
  655 int start_replay(filename)
  656     char *filename;
  657 {
  658     if (Record) {
  659 	fprintf(stderr, "Cannot record and play back in same session.\n");
  660 	return (1);
  661     }
  662     if (Playback != PLAYBACK_INACTIVE && Playback != PLAYBACK_COMPLETE) {
  663 	fprintf(stderr, "Previous playback not yet completed.\n");
  664 	return (1);
  665     }
  666     strcpy(PlaybackFileName, filename);
  667 
  668     /* ensure we can open playback file correctly */
  669     PlaybackFile = fopen(PlaybackFileName, "r");
  670     if (PlaybackFile == 0) {
  671 	perror(PlaybackFileName);
  672 	return (1);
  673     }
  674     fclose(PlaybackFile);
  675     PlaybackFile = 0;
  676     Playback = PLAYBACK_PENDING;
  677 
  678     /* read playbackfile to to extract Windows info */
  679     if (first_replay) {
  680 	LoadWtab("PlaybackFileName");
  681 	first_replay = 0;
  682     } else {
  683 	CreateTimer(5000, (void (*)()) ReadPlaybackEvent, (void *) 0);
  684     }
  685     return (0);
  686 }
  687 
  688 /*--------------------------------------------------------------*/
  689 void RecordEvent(fd, buf, n)
  690     FD fd;
  691     unsigned char *buf;
  692     long n;
  693 {
  694     register short type = IByte(&buf[0]);	/* event type */
  695 
  696     if (RecordFile == 0) {
  697 	/* open up the recording file if necessary, first time through */
  698 	RecordFile = OpenFile(RecordFileName, "w");
  699     }
  700     /* high-order bit means SendEvent generated */
  701     if (type & 0x80) {
  702 	type = type & 0x7F;
  703     }
  704     /*
  705      * note that we can't cast stuff to
  706      * the XEvent structure here, because
  707      * the information is in a message buffer,
  708      * not correctly unpacked and byteswapped.
  709      */
  710     switch (type) {
  711     case ButtonPress:
  712     case ButtonRelease:
  713     case KeyPress:
  714     case KeyRelease:
  715     case MotionNotify:
  716     case EnterNotify:
  717 	/* process the event */
  718 	event_to_ascii(fd, buf, RecordFile);
  719 	break;
  720     default:
  721 	/*EMPTY *//* ignore the event */
  722 	break;
  723     }
  724 }
  725 
  726 /*-------------------------------------------------------------*/
  727 void RecordRequest(fd, buf, n)
  728     FD fd;
  729     unsigned char *buf;
  730     long n;
  731 {
  732     register short request = IByte(&buf[0]);	/* request type */
  733     unsigned long wid = ILong(&buf[4]);
  734 
  735     if (RecordFile == 0) {
  736 	/* open up the recording file if necessary, first time through */
  737 	RecordFile = OpenFile(RecordFileName, "w");
  738     }
  739     switch (request) {
  740     case 1:			/* CreateWindow */
  741 	if (!MatchCreateWinRec(buf))
  742 	    req_to_ascii(fd, buf, RecordFile);
  743 	break;
  744 
  745     case 4:			/* DestroyWindow */
  746 	SetInferiorsDelflag(wid, 1);
  747 	SetDelflag(wid, 1);
  748 	break;
  749 
  750     case 5:			/* DestroySubWindows */
  751 	SetInferiorsDelflag(wid, 1);
  752 	break;
  753     }
  754 }
  755 
  756 /*-------------------------------------------------------------*/
  757 /*
  758  *returns 1 if a window is found, 0 otherwise
  759  */
  760 int MatchCreateWinRec(buf)
  761     unsigned char *buf;
  762 {
  763     unsigned long wid, parent, mask;
  764     int depth, x, y, width, height;
  765     long i;
  766 
  767     depth = IByte(&buf[1]);
  768     wid = ILong(&buf[4]);
  769     parent = ILong(&buf[8]);
  770     x = IShort(&buf[12]);
  771     y = IShort(&buf[14]);
  772     width = IShort(&buf[16]);
  773     height = IShort(&buf[18]);
  774     mask = ILong(&buf[28]);
  775 
  776     for (i = 0; i < maxwtab; i++) {
  777 	if (parent == wintab[i].parent &&
  778 	    depth == wintab[i].depth &&
  779 	    mask == wintab[i].mask &&
  780 	    x == wintab[i].x &&
  781 	    y == wintab[i].y &&
  782 	    width == wintab[i].width &&
  783 	    height == wintab[i].height &&
  784 	    wintab[i].delflag != 0) {
  785 	    /*
  786 	     * could match this window: means that a previously
  787 	     * destroyed window has been created again.
  788 	     * alias it to the original and reset the deleted flag
  789 	     */
  790 	    wintab[i].delflag = 0;
  791 	    if (maxalias < MAXALIAS) {
  792 		winalias[maxalias].wid = wid;
  793 		winalias[maxalias].alias = wintab[i].wid;
  794 		maxalias++;
  795 	    } else {
  796 		fprintf(stderr, "Max number of window aliases reached, increase MAXALIAS.\n");
  797 	    }
  798 	    return (1);
  799 	}
  800     }
  801     /*
  802      * Couldn't match window, add it int the table.
  803      * Set newid and newpart to current wid and parent,
  804      * to match directly if we replay in the same session.
  805      */
  806     wintab[maxwtab].wid = wid;
  807     wintab[maxwtab].newid = wid;
  808     wintab[maxwtab].parent = parent;
  809     wintab[maxwtab].newpar = parent;
  810     wintab[maxwtab].depth = depth;
  811     wintab[maxwtab].x = x;
  812     wintab[maxwtab].y = y;
  813     wintab[maxwtab].width = width;
  814     wintab[maxwtab].height = height;
  815     wintab[maxwtab].mask = mask;
  816     maxwtab++;
  817     return (0);
  818 }
  819 
  820 /*-------------------------------------------------------------*/
  821 void LoadWtab(filename)
  822     char *filename;
  823 {
  824     char line[256];
  825     int lsize = 256;
  826     unsigned int wid, parent, m;
  827     int d, x, y, w, h;
  828     char header[20];
  829 
  830     PlaybackFile = fopen(PlaybackFileName, "r");
  831 
  832     while (fgets(line, lsize, PlaybackFile) && maxwtab <= MAXWTAB) {
  833 	if (line[0] == 'X') {
  834 	    if (9 != sscanf(line, "%s %x %x %x %d %d %d %d %x",
  835 			  header, &wid, &parent, &d, &x, &y, &w, &h, &m))
  836 		warn("Error decoding in LoadWtab");
  837 
  838 	    wintab[maxwtab].wid = wid;
  839 	    wintab[maxwtab].parent = parent;
  840 	    wintab[maxwtab].depth = d;
  841 	    wintab[maxwtab].x = x;
  842 	    wintab[maxwtab].y = y;
  843 	    wintab[maxwtab].width = w;
  844 	    wintab[maxwtab].height = h;
  845 	    wintab[maxwtab].mask = m;
  846 	    maxwtab++;
  847 	}
  848     }
  849     if (maxwtab == MAXWTAB) {
  850 	warn("LoadWtab: Number max of XCreateWindow reached, increase MAXWTAB");
  851     }
  852     fclose(PlaybackFile);
  853     PlaybackFile = 0;
  854 }
  855 
  856 /*-------------------------------------------------------------*/
  857 /* mainly a debugging aid. get the original window */
  858 int OriginalWid(wid)
  859     unsigned long wid;
  860 {
  861     long i;
  862 
  863     for (i = 0; i < maxwtab; i++)
  864 	if (wintab[i].newid == wid)
  865 	    return wintab[i].wid;
  866     return 0;
  867 }
  868 /*-------------------------------------------------------------*/
  869 int NewWid(wid)
  870     unsigned long wid;
  871 {
  872     long i;
  873 
  874     for (i = 0; i < maxwtab; i++)
  875 	if (wintab[i].wid == wid)
  876 	    return(wintab[i].newid ? wintab[i].newid : wid);
  877 
  878     if (wid != TheRootWindow) {
  879 	fprintf(stderr, "NewWid: Couldn't match Window id %lx\n", wid);
  880     }
  881     return wid;
  882 
  883 }
  884 /*-------------------------------------------------------------*/
  885 void SetNewParent(old, new)
  886     unsigned long old, new;
  887 {
  888     long i;
  889 
  890     for (i = 0; i < maxwtab; i++)
  891 	if (wintab[i].parent == old)
  892 	    wintab[i].newpar = new;
  893 }
  894 
  895 /*-------------------------------------------------------------*/
  896 void SetNewid(old, new)
  897     unsigned long old, new;
  898 {
  899     long i;
  900 
  901     for (i = 0; i < maxwtab; i++)
  902 	if (wintab[i].newid == old)
  903 	    wintab[i].newid = new;
  904 }
  905 
  906 /*-------------------------------------------------------------*/
  907 unsigned long GetWinAlias(wid)
  908     unsigned long wid;
  909 {
  910     long i;
  911 
  912     for (i = 0; i < maxalias; i++)
  913 	if (winalias[i].wid == wid)
  914 	    return (winalias[i].alias);
  915 
  916     return (wid);
  917 }
  918 
  919 /*-------------------------------------------------------------*/
  920 void SetInferiorsNewid(wid, new)
  921     unsigned long wid, new;
  922 {
  923     long i;
  924 
  925     for (i = 0; i < maxwtab; i++) {
  926 	if (wintab[i].newpar == wid) {
  927 	    SetInferiorsNewid