[comp.windows.x] X Terminal Emulator design question

gtoye@supernet.haus.com (Gene Toye) (08/25/90)

I am new to X programming.  I have been reading the O'Reilly books as well
as others.  The concepts of X programming are clear enough based on my
experience with other windowing systems.  I am designing a terminal emulator
and have a design issue I cannot find a good answer for:

How can I code a X event loop that also watches for traffic on a second
socket connection?  I would like to implement the program as a single
process without polling loops.  Is there some way to do it with select()?  I
have already considered these options:
   a second process to watch the socket and signal the main.
   a polling loop to poll the X event queue and the socket.

If anybody knows of a good example of this type of program (terminal emulation)
on the X distribution (R4) other than the standard xterm client, please let
me know.

Thanks.
-- 
Gene Toye: Harris Adacom Corporation / 16001 Dallas Pkwy. / Dallas, TX 75248
Internet: gtoye@supernet.haus.com or gtoye@supernet.lonestar.org
Usenet:   uunet!{iex,ntvax}!supernet!gtoye
DISCLAIMER: My employer never knows what I am going to say next.

mouse@LARRY.MCRCIM.MCGILL.EDU (08/26/90)

> I am designing a terminal emulator and have a design issue I cannot
> find a good answer for:

> How can I code a X event loop that also watches for traffic on a
> second socket connection?

There is no portable way to do this.  The UNIX way is, as far as I can
tell, to use XConnectionNumber(dpy) to get the file descriptor
corresponding to the server connection and then toss that into the pot
when you do your select().

> If anybody knows of a good example of this type of program (terminal
> emulation) on the X distribution (R4) other than the standard xterm
> client, please let me know.

It's not on the R4 distribution, but I have one.  Here's its main loop,
for your edification and amusement.  (I've edited this down to the
pieces more-or-less relevant to the question at hand.)

#define SECUSEC 1000000 /* # of tv_usec in a tv_sec */
#define EACH_DISP(var) var=disps;var;var=var->link

run()
{
 XEvent e;
 DISP_INFO *d;
 int selrv;
 int wantflush;
 int wantupd;
 fd_set fds;
 int mustcontinue;
 int poll;
 struct timeval timeout;
 struct timeval then;

 for (EACH_DISP(d)) d->xfd = XConnectionNumber(d->disp);
 wantflush = 1;
 wantupd = 0;
 while (1)
  { mustcontinue = 0;
    for (EACH_DISP(d))
     { if (XQLength(d->disp) > 0)
	{ do
	   { XNextEvent(d->disp,&e);
	     handle_event(&e);
	   } while (XQLength(d->disp) > 0);
	  wantupd = 1;
	  mustcontinue = 1;
	}
     }
    if (mustcontinue) continue;
    FD_ZERO(&fds);
    for (EACH_DISP(d))
     { FD_SET(d->xfd,&fds);
       if (!d->have_focus || (d->flash.time == 0)) d->want_cursor_up = 1;
     }
    FD_SET(ptyfd,&fds);
    then = now;
    gettimeofday(&now,(struct timezone *)0);
    /* sleep no longer than about 1000 seconds (exactly: 1073 seconds = 17 minutes 53 seconds). */
    /* this is so that now minus then can (normally) be converted to usec and */
    /*  fit in a signed long int, with room for adding small-integer*SECUSEC without overflow. */
    timeout = now;
    timeout.tv_sec += 0x40000000 / SECUSEC;
#define TVGTR(t1,t2) ((t1.tv_sec > t2.tv_sec) || ((t1.tv_sec == t2.tv_sec) && (t1.tv_usec > t2.tv_usec)))
    poll = 0;
    for (EACH_DISP(d))
     { ...check timer-based stuff and possibly modify timeout...
     }
    if (wantflush || wantupd) poll = 1;
    if (! TVGTR(timeout,now)) poll = 1; /* eg, timer expired during other code */
    if (poll)
     { timeout.tv_sec = 0;
       timeout.tv_usec = 0;
     }
    else
     { timeout.tv_sec -= now.tv_sec;
       timeout.tv_usec -= now.tv_usec;
       if (timeout.tv_usec < 0)
	{ timeout.tv_usec += SECUSEC;
	  timeout.tv_sec --;
	}
     }
    selrv = select(FD_SETSIZE,&fds,(fd_set *)0,(fd_set *)0,&timeout);
    if (childpid == 0)
     { utexit(0);
     }
    if (selrv < 0)
     { if (errno == EINTR) continue;
       perror("select");
       utexit(1);
     }
    for (EACH_DISP(d))
     { int t;
       TIMER *tm;
       for (t=0;t<NTIMERS;t++)
	{ tm = &d->timers[t];
	  if (tm->want)
	   { ...check and handle timers for this display...
	   }
	}
       if (d->want_cursor_up != d->cursor_up)
	{ resetcursor(d);
	  wantflush = 1;
	}
     }
    if (selrv == 0)
     { if (wantupd)
	{ update_display();
	  wantflush = 1;
	  wantupd = 0;
	}
       if (wantflush)
	{ for (EACH_DISP(d))
	   { /* there's a race in scrolling.  using XSync instead of XFlush helps but doesn't cure. */
	     /* basic problem: no timestamps on chars from pty, so can't interleave properly */
	     /* with events in event stream coming from X server. */
	     /* basic race is fixable, but requires a lot of hairy code to handle GraphicsExpose */
	     /* and/or NoExpose events from all the blitting going on in do_line_motion */
	     XFlush(d->disp);
	   }
	  if (debugging) fflush(stdout);
	  wantflush = 0;
	}
       continue;
     }
    mustcontinue = 0;
    for (EACH_DISP(d))
     { if (FD_ISSET(d->xfd,&fds))
	{ int bpend;
	  ioctl(d->xfd,FIONREAD,&bpend);
	  if (bpend == 0) utexit(0); /* socket EOF */
	  XEventsQueued(d->disp,QueuedAfterReading);
	  mustcontinue = 1;
	}
     }
    if (mustcontinue) continue;
    if (FD_ISSET(ptyfd,&fds))
     { unsigned char obuf[256];
       int nr;
       nr = read(ptyfd,(char *)&obuf[0],sizeof(obuf));
       if (nr < 0) if (errno != EINTR) utexit(0);
       if (nr == 0) utexit(0);
       if (nr > 0) (*type->typeout)(&obuf[0],nr);
       wantupd = 1;
       for (EACH_DISP(d))
	{ d->flash.phase = 0;
	  d->flash.next = now;
	  d->want_cursor_up = 0;
	}
     }
  }
}

					der Mouse

			old: mcgill-vision!mouse
			new: mouse@larry.mcrcim.mcgill.edu