chiark / gitweb /
can fit 139 lines on an atp -B page
[trains.git] / hostside / gui-plan.c
1 /*
2  * usage: .../gui-plan [<windowid>]
3  * protocol on stdin:
4  *  series of lines
5  *   off <segname>[/[<movfeat>]
6  *   [t|f][i]on <segname>[[/<movfeat>] <movpos>]
7  *   [t|f][i]det <segname>[[/<movfeat>] <movpos>]
8  */
9
10 #include <stdlib.h>
11 #include <assert.h>
12 #include <string.h>
13 #include <errno.h>
14 #include <stdarg.h>
15
16 #include <sys/types.h>
17 #include <sys/socket.h>
18 #include <netdb.h>
19
20 #include <X11/Xlib.h>
21 #include <X11/xpm.h>
22
23 typedef int ErrorCode;
24
25 #include "daemons.h"
26 #include "dliste.h"
27 #include "../layout/plan-data-format.h"
28 #include "../layout/layout-data.h"
29 #include "stastate.h"
30
31 #include <oop.h>
32 #include <oop-read.h>
33
34 #define NSEGMENTS (ui_plan_data.n_segments)
35
36 typedef struct MaskState MaskState;
37 struct MaskState {
38   int x, y, width, height;
39   GC gc;
40 };
41
42 typedef struct PosnState PosnState;
43 struct PosnState {
44   int x, y, width, height;
45   Pixmap pm;
46   MaskState edge;
47 };
48
49 typedef struct {
50   unsigned on:1, inv:1, det:1, trainown:2, updated_tmp:1,
51     resolution_problem:1; /* for multiplexer client only */
52 } SegFlags;
53
54 typedef struct SegmovfeatState SegmovfeatState;
55 struct SegmovfeatState {
56   SegmovfeatState *next;
57   SegFlags flags;
58   unsigned redraw_needed:1;
59   int posn;
60   MaskState whole;
61   PosnState (*posns)[2/*i*/][2/*det*/];
62     /* posns[n_posns]=unknown if n_posns>1 */
63 };
64
65 oop_source *events;
66 const char *progname= "gui-plan";
67
68 typedef struct TrainState TrainState;
69
70 typedef struct {
71   SegmovfeatState *mfs;
72   SegFlags flags; /* used by multiplexer client */
73   TrainState *owner;
74 } SegState;
75
76 static FILE *debug;
77 static SegState *state;
78 static SegmovfeatState *states_head;
79 static StartupState stastate;
80 static Display *d;
81 static oop_source_sys *sys_events;
82 static Window w;
83 static int redraw_needed_count, expose_count;
84 static Pixmap bg_pixmap;
85 static unsigned long train_pixel, owned_pixel;
86
87 static const char *stastate_names[]= DEFINE_STASTATE_DATA;
88
89 static const char *badcmdreport_data;
90 static size_t badcmdreport_recsz;
91
92 void die_hook(void) { }
93 void die_vprintf_hook(const char *fmt, va_list al) { }
94
95 static void xlib_process(void);
96
97 static void diex(const char *fn, const char *w) __attribute__((noreturn));
98 static void diex(const char *fn, const char *w) {
99   die("Xlib call failed: %s%s%s%s", fn,
100       (w)?" (":"", (w), (w)?")":"");
101 }
102
103 #define XCALL(f,w,al) do{                       \
104     if (!( (f) al ))                            \
105       diex(#f, (w));                            \
106   }while(0)
107
108 static void diexpm(const char *fn, const char *w, int xpmst)
109      __attribute__((noreturn));
110 static void diexpm(const char *fn, const char *w, int xpmst) {
111   die("Xpm call failed: %s%s%s%s: %s", fn,
112       (w)?" (":"", (w), (w)?")":"", XpmGetErrorString(xpmst));
113 }
114
115 #define XPMCALL(f,w,al) do{                     \
116     int xpmcall_xpmst;                          \
117     xpmcall_xpmst= ((f) al);                    \
118     if (xpmcall_xpmst != XpmSuccess)            \
119       diexpm(#f, (w), xpmcall_xpmst);           \
120   }while(0)
121
122 /*---------- input handling ----------*/
123
124 typedef struct {
125   const char *name;
126   void (*on_eof)(void);
127   void (*on_input_line)(ParseState *ps);
128   int (*on_badcmd)(void);
129 } InputStream;
130
131 static const InputStream *instream;
132
133 static void *input_iferr(oop_source *evts, oop_read *stdin_read,
134                           oop_rd_event evt, const char *errmsg, int errnoval,
135                           const char *data, size_t recsz, void *u_v) {
136   const char *emsg;
137   emsg= oop_rd_errmsg(stdin_read, evt, errnoval, OOP_RD_STYLE_GETLINE);
138   die("%s: %s", instream->name, emsg);
139 }
140
141 static void *input_ifok(oop_source *evts, oop_read *cl_read,
142                         oop_rd_event evt, const char *errmsg, int errnoval,
143                         const char *data, size_t recsz, void *u_v) {
144   ParseState ps;
145
146   if (evt == OOP_RD_EOF) {
147     instream->on_eof();
148     abort();
149   }
150   if (evt != OOP_RD_OK) {
151     input_iferr(evts,cl_read,evt,errmsg,errnoval,data,recsz,0);
152     return OOP_CONTINUE;
153   }
154
155   badcmdreport_data= data;
156   badcmdreport_recsz= recsz;
157
158   ps.remain= data;
159   instream->on_input_line(&ps);
160   xlib_process();
161   return OOP_CONTINUE;
162 }
163
164 static void *some_exception(oop_source *evts, int fd,
165                             oop_event evt, void *name_v) {
166   const char *name= name_v;
167   die("poll exception on %s (fd %d)",name,fd);
168 }
169
170 int vbadcmd(ParseState *ps, const char *fmt, va_list al) {
171   fprintf(stderr,"gui-plan: incorrect input: `%.*s': ",
172           (int)badcmdreport_recsz, badcmdreport_data);
173   vfprintf(stderr,fmt,al);
174   putc('\n',stderr);
175   return instream->on_badcmd();
176 }
177
178 static int lstrpdbsearch(const char *str, int l,
179                          const char *what,
180                          const void *items, int n_items,
181                          int itemsz) {
182   int min, maxe, try, cmp;
183   const void *try_item;
184   const char *try_name;
185   
186   min= 0;
187   maxe= n_items;
188   for (;;) {
189     if (min >= maxe) { badcmd(0,"unknown %s `%.*s'",what,l,str); return -1; }
190     try= min + (maxe - min) / 2;
191     try_item= (const char*)items + itemsz * try;
192     try_name= *(const char *const *)try_item;
193     cmp= lstrstrcmp(str, l, try_name ? try_name : "");
194     if (!cmp) return try;
195     if (cmp < 0) maxe= try;
196     else min= try + 1;
197   }
198 }
199
200 static int lstrpdbsearch_movfeat(const char *str, int l,
201                                  const PlanSegmentData *sd) {
202   return lstrpdbsearch(str, l, "movfeat", sd->movfeats, sd->n_movfeats,
203                        sizeof(*sd->movfeats));
204 }
205 static int lstrpdbsearch_segment(const char *str, int l) {
206   return lstrpdbsearch(str, l, "segment",
207                        ui_plan_data.segments, ui_plan_data.n_segments,
208                        sizeof(*ui_plan_data.segments));
209 }
210
211 /*---------- drawing etc. ----------*/
212
213 static int range_overlap(int x1, int width1, int x2, int width2) {
214   /* works for y's and heights too, obviously. */
215   int rhs1, rhs2;
216   rhs1= x1 + width1;
217   rhs2= x2 + width2;
218   if (rhs1 <= x2 || rhs2 <= x1) return 0;
219   return 1;
220 }
221
222 static void redraw_mark(SegmovfeatState *fs) {
223   if (fs->redraw_needed) return;
224   fs->redraw_needed= 1;
225   redraw_needed_count++;
226 }
227
228 static void xlib_expose(XExposeEvent *ev) {
229   SegmovfeatState *fs;
230   
231   expose_count= ev->count;
232   if (!ev->width || !ev->height) return;
233
234   for (fs= states_head;
235        fs;
236        fs= fs->next) {
237     if (!range_overlap(fs->whole.x, fs->whole.width,
238                        ev->x, ev->width)) continue;
239     if (!range_overlap(fs->whole.y, fs->whole.height,
240                        ev->y, ev->height)) continue;
241     redraw_mark(fs);
242   }
243 }
244
245 static void redraw(SegmovfeatState *fs) {
246   PosnState *src;
247   XGCValues gcv;
248   
249   if (fs->redraw_needed) {
250     fs->redraw_needed= 0;
251     redraw_needed_count--;
252   }
253   src= 0;
254   XCALL( XCopyArea, "redraw",
255          (d, bg_pixmap, w, fs->whole.gc,
256           fs->whole.x, fs->whole.y,
257           fs->whole.width, fs->whole.height,
258           fs->whole.x, fs->whole.y) );
259   if (fs->flags.on) {
260     src= &fs->posns[fs->posn][fs->flags.inv][fs->flags.det];
261     XCALL( XCopyArea, "redraw",
262            (d, src->pm, w, fs->whole.gc,
263             0,0, src->width, src->height,
264             src->x, src->y) );
265   }
266   if (fs->flags.trainown && src && src->edge.x >= 0) {
267     gcv.foreground= fs->flags.trainown>1 ? train_pixel : owned_pixel;
268     XCALL( XChangeGC, "train/own",
269            (d, src->edge.gc, GCForeground, &gcv) );
270     XCALL( XFillRectangle, "train/own",
271            (d,w, src->edge.gc,
272             src->edge.x, src->edge.y,
273             src->edge.width, src->edge.height) );
274   }
275 }
276
277 static void redraw_as_needed(void) {
278   SegmovfeatState *fs;
279
280   for (fs= states_head;
281        fs;
282        fs= fs->next)
283     if (fs->redraw_needed)
284       redraw(fs);
285   assert(!redraw_needed_count);
286 }
287   
288 static Bool evpredicate_always(Display *d, XEvent *ev, XPointer a) {
289   return True;
290 }
291
292 static void xlib_process(void) {
293   XEvent ev;
294   Status xst;
295   
296   for (;;) {
297     xst= XCheckIfEvent(d,&ev,evpredicate_always,0);
298     if (!xst) {
299       if (!redraw_needed_count || expose_count)
300         return;
301       redraw_as_needed();
302       continue;
303     }
304
305     switch (ev.type) {
306     case Expose: xlib_expose(&ev.xexpose); break;
307     case NoExpose: break;
308     default: die("unrequested event type %d\n",ev.type);
309     }
310   }
311 }
312
313 static void *xlib_readable(oop_source *evts, int fd,
314                            oop_event evt, void *cl_v) {
315   xlib_process();
316   return OOP_CONTINUE;
317 }
318
319 static int thiswordeatonechar(ParseState *ps, int c) {
320   if (ps->thisword[0] == c) {
321     ps->thisword++; ps->lthisword--;
322     return 1;
323   }
324   return 0;
325 }
326
327 static void loadmask(MaskState *out, const PlanPixmapDataRef *ppd,
328                      XGCValues *gcv, long gcv_mask) {
329   static XpmColorSymbol coloursymbols[2]= {
330     { (char*)"space", 0, 0 },
331     { (char*)"mark",  0, 1 }
332   };
333   
334   XpmAttributes mattribs;
335   Pixmap pm;
336
337   out->x= ppd->x;
338   out->y= ppd->y;
339   mattribs.valuemask= XpmDepth | XpmColorSymbols;
340   mattribs.depth= 1;
341   mattribs.colorsymbols= coloursymbols;
342   mattribs.numsymbols= sizeof(coloursymbols) / sizeof(*coloursymbols);
343   XPMCALL( XpmCreatePixmapFromData, "mask",
344            (d,w, (char**)ppd->d, &pm,0, &mattribs) );
345   out->width= mattribs.width;
346   out->height= mattribs.height;
347
348   gcv->clip_x_origin= out->x;
349   gcv->clip_y_origin= out->y;
350   gcv->clip_mask= pm;
351   out->gc= XCreateGC(d,w,
352                      gcv_mask | GCClipXOrigin | GCClipYOrigin | GCClipMask,
353                      gcv);
354   XCALL( XFreePixmap, "mask", (d,pm) );
355 }
356
357 /*---------- stdin input handling ----------*/
358
359 static void stdin_eof(void) { exit(0); }
360 static int stdin_badcmd(void) { exit(8); }
361
362 static void stdin_input_line(ParseState *ps) {
363   const char *slash, *movfeatname;
364   int invert, det, trainown, segment_ix, movfeat_ix, lmovfeatname;
365   long posn;
366   const PlanSegmentData *segment_d;
367   const PlanSegmovfeatData *movfeat_d;
368   SegmovfeatState *fs;
369
370   ps_needword(ps);
371
372   if (!thiswordstrcmp(ps,"off")) {
373     invert= -1;
374     trainown= 0;
375     det= 0;
376   } else {
377     trainown=
378       thiswordeatonechar(ps,'t') ? 2 :
379       thiswordeatonechar(ps,'f') ? 1 : 0;
380     invert= thiswordeatonechar(ps,'i');
381     det= (!thiswordstrcmp(ps,"on") ? 0 :
382           !thiswordstrcmp(ps,"det") ? 1 :
383           (badcmd(ps,"unknown command"),-1));
384   }
385   ps_needword(ps);
386   slash= memchr(ps->thisword, '/', ps->lthisword);
387   if (slash) {
388     movfeatname= slash + 1;
389     lmovfeatname= (ps->thisword + ps->lthisword) - movfeatname;
390     ps->lthisword= slash - ps->thisword;
391   } else {
392     movfeatname= 0;
393     lmovfeatname= 0;
394   }
395   segment_ix= lstrpdbsearch_segment(ps->thisword,ps->lthisword);
396   segment_d= &ui_plan_data.segments[segment_ix];
397
398   movfeat_ix= lstrpdbsearch_movfeat(movfeatname, lmovfeatname, segment_d);
399   movfeat_d= &segment_d->movfeats[movfeat_ix];
400
401   if (ps->remain) {
402     if (invert<0) badcmd(0,"off may not take movfeatpos");
403     ps_neednumber(ps, &posn, 0, movfeat_d->n_posns-1, "movfeatpos");
404   } else {
405     if (invert>=0 && movfeat_d->n_posns > 1) {
406       posn= movfeat_d->n_posns;
407     } else {
408       posn= 0;
409     }
410   }
411
412   ps_neednoargs(ps);
413
414   fs= &state[segment_ix].mfs[movfeat_ix];
415   if (invert>=0) {
416     fs->flags.on= 1;
417     fs->flags.inv= invert;
418   } else {
419     fs->flags.on= 0;
420     fs->flags.inv= 0;
421   }
422   fs->flags.det= det;
423   fs->flags.trainown= trainown;
424   fs->posn= posn;
425
426   redraw(fs);
427 }
428
429 static const InputStream stdin_is= {
430   "stdin", stdin_eof, stdin_input_line, stdin_badcmd
431 };
432
433 /*---------- multiplexer client ----------*/
434
435 static OutBufferChain sock= { (char*)"multiplexer socket", -1 };
436
437 static void sockvprintf(const char *fmt, va_list al)
438   __attribute__((format(printf,1,0)));
439 static void sockprintf(const char *fmt, ...)
440   __attribute__((format(printf,1,2)));
441
442 static void sockvprintf(const char *fmt, va_list al) {
443   ovprintf(&sock,fmt,al);
444 }
445 static void sockprintf(const char *fmt, ...) {
446   va_list al;
447   va_start(al,fmt);
448   sockvprintf(fmt,al);
449   va_end(al);
450 }
451
452 static int sock_clientconnect(const char *node, const char *service) {
453   struct addrinfo *aires, *try, hints;
454   char niaddrbuf[256], niportbuf[64];
455   int r, sock;
456   
457   memset(&hints,0,sizeof(hints));
458   hints.ai_family= PF_UNSPEC;
459   hints.ai_socktype= SOCK_STREAM;
460   r= getaddrinfo(node,service,&hints,&aires);
461   if (r) die("getaddrinfo node `%s' service `%s' failed: %s\n",
462              node,service,gai_strerror(r));
463
464   for (try=aires; try; try=try->ai_next) {
465     assert(try->ai_socktype == SOCK_STREAM);
466
467     r= getnameinfo(try->ai_addr, try->ai_addrlen,
468                    niaddrbuf, sizeof(niaddrbuf),
469                    niportbuf, sizeof(niportbuf),
470                    NI_NUMERICHOST|NI_NUMERICSERV);
471     assert(!r);
472
473     printf("trying %s,%s... ",niaddrbuf,niportbuf);
474     mflushstdout();
475
476     sock= socket(try->ai_family, SOCK_STREAM, try->ai_protocol);
477     if (sock<0) {
478       printf("couldn't create socket: %s\n",strerror(errno));
479       continue;
480     }
481
482     r= connect(sock, try->ai_addr, try->ai_addrlen);
483     if (!r) {
484       printf("connected\n");
485       return sock;
486     }
487
488     printf("connect failed: %s\n",strerror(errno));
489     close(sock);
490   }
491   mflushstdout();
492   return -1;
493 }
494
495 static void sock_eof(void) { die("EOF on multiplexer connection"); }
496 static int sock_badcmd(void) { return -1; }
497
498 /*---------- multiplexer protocol ----------*/
499
500 struct TrainState {
501   struct { struct TrainState *next, *back; } others;
502   char *name;
503 };
504
505 static int poweron;
506 static struct { TrainState *head, *tail; } trains;
507
508 #define FOR_S for (s=0; s<NSEGMENTS; s++)
509
510 static void mx_clear_updated(void) {
511   int s;
512   FOR_S
513     state[s].flags.updated_tmp= 0;
514 }
515
516 static void mx_redraw_feat(int s, int f) {
517   SegState *ss= &state[s];
518   SegmovfeatState *fs= &ss->mfs[f];
519   fs->flags= ss->flags;
520   fs->flags.on= stastate > Sta_Off && poweron;
521   if (stastate==Sta_Resolving)
522     fs->flags.trainown= ss->flags.resolution_problem ? 2 : 0;
523   redraw_mark(fs);
524 }
525
526 static void mx_redraw_seg(int s) {
527   int f;
528   assert(!!state[s].flags.trainown == !!state[s].owner);
529   for (f=0; f < ui_plan_data.segments[s].n_movfeats; f++)
530     mx_redraw_feat(s,f);
531 }
532
533 static void mx_redraw_all(void) {
534   int s;
535   FOR_S
536     mx_redraw_seg(s);
537 }
538
539 static int ps_needsegment(ParseState *ps) {
540   int r= ps_needword(ps);                                if (r) return -1;
541   return lstrpdbsearch_segment(ps->thisword,ps->lthisword);
542 }
543
544 static void si_detect(ParseState *ps) {
545   long dl;
546   int r, s;
547
548   s= ps_needsegment(ps);                                 if (s<0) return;
549   r= ps_neednumber(ps,&dl,0,1,"detection flag");         if (r) return;
550   state[s].flags.det= dl;
551   mx_redraw_seg(s);
552 }
553
554 static void si_polarity(ParseState *ps) {
555   char *delim, *end;
556   int s;
557
558   mx_clear_updated();
559   if (*ps->remain++ != '<') { badcmd(ps,"missing <"); return; }
560
561   end= strchr(ps->remain,'>');
562   if (!end) { badcmd(ps,"missing >"); return; }
563
564   while (ps->remain < end) {
565     delim= memchr(ps->remain, ',', end - ps->remain);
566     if (!delim) delim= end;
567
568     ps->thisword= ps->remain;
569     ps->lthisword= delim - ps->remain;
570     ps->remain= delim+1;
571
572     s= lstrpdbsearch_segment(ps->thisword,ps->lthisword);   if (s<0) continue;
573     state[s].flags.updated_tmp= 1;
574   }
575   FOR_S {
576     if (state[s].flags.inv == state[s].flags.updated_tmp)
577       continue;
578     state[s].flags.inv= state[s].flags.updated_tmp;
579     mx_redraw_seg(s);
580   }
581 }
582
583 static void si_movpos(ParseState *ps) {
584   long pl;
585   int r,s,f, n_posns;
586   s= ps_needsegment(ps);                                 if (s<0) return;
587   r= ps_needword(ps);                                    if (r) return;
588   if (thiswordstrcmp(ps,"feat")) { badcmd(ps,"weird movpos"); return; }
589
590   r= ps_needword(ps);                                    if (r) return;
591   f= lstrpdbsearch_movfeat(ps->thisword, ps->lthisword,
592                            &ui_plan_data.segments[s]);   if (f<0) return;
593   
594   n_posns= ui_plan_data.segments[s].movfeats[f].n_posns;
595   if (ps->remain[0]=='?') goto unknown;
596   r= ps_neednumber(ps,&pl,0,n_posns-1,"movfeat posn");   if (r) goto unknown;
597   state[s].mfs[f].posn= pl;
598   mx_redraw_seg(s);
599   return;
600
601 unknown:
602   state[s].mfs[f].posn= n_posns;
603   mx_redraw_seg(s);
604 }
605
606 static void si_on(ParseState *ps) {
607   poweron= 1;
608   mx_redraw_all();
609 }
610
611 static void si_off(ParseState *ps) {
612   poweron= 0;
613   mx_redraw_all();
614 }
615
616 static void si_train(ParseState *ps) {
617   int r,sl,s;
618   int lastchar;
619   TrainState *train;
620   const char *seg;
621
622   mx_clear_updated();
623   r= ps_needword(ps); /* <train> */                if (r) return;
624
625   /* atomise the train name */
626   for (train=trains.head;
627        train;
628        train= train->others.next)
629     if (!thiswordstrcmp(ps,train->name))
630       goto found;
631   /* not found */
632   train= mmalloc(sizeof(*train));
633   train->name= mmalloc(ps->lthisword+1);
634   memcpy(train->name,ps->thisword,ps->lthisword);
635   train->name[ps->lthisword]= 0;
636   DLIST2_APPEND(trains,train,others);
637 found:
638
639   r= ps_needword(ps);                              if (r) return;
640   if (thiswordstrcmp(ps,"has")) { badcmd(ps,"weird train"); return; }
641   while (ps_word(ps) >= 0) {
642     sl= strcspn(ps->thisword, "/.!*~#+");
643     if (sl > ps->lthisword) sl= ps->lthisword;
644     seg= ps->thisword;
645     if (*seg=='-') { seg++; sl--; }
646     s= lstrpdbsearch_segment(seg,sl);              if (s<0) continue;
647     lastchar= ps->thisword[ps->lthisword-1];
648     state[s].owner= train;
649     state[s].flags.updated_tmp= 1;
650     state[s].flags.trainown= 1 + (lastchar=='!' || lastchar=='*');
651   }
652   FOR_S {
653     if (state[s].flags.updated_tmp) {
654       mx_redraw_seg(s);
655     } else if (state[s].owner==train) {
656       state[s].owner= 0;
657       state[s].flags.trainown= 0;
658       mx_redraw_seg(s);
659     }
660   }
661 }
662
663 typedef struct MuxEventInfo MuxEventInfo;
664 typedef void MuxEventFn(ParseState *ps);
665
666 struct MuxEventInfo {
667   const char *prefix; /* 0: sentinel */
668   const char *remainpat; /* 0: no pattern in select needed */
669   MuxEventFn *fn; /* 0: just ignore matching messages */
670 };
671 static const MuxEventInfo muxeventinfos[];
672
673 static void si_stastate(ParseState *ps) {
674   int s;
675   const char *const *new_stastate;
676
677   new_stastate= some_needword_lookup(ps, stastate_names, "stastate");
678   stastate= new_stastate ? new_stastate - stastate_names : 0;
679   
680   FOR_S {
681     state[s].flags.resolution_problem= 0;
682   }
683   if (stastate <= Sta_Resolving) {
684     FOR_S {
685       state[s].flags.det= 0;
686       state[s].flags.trainown= 0;
687       state[s].owner= 0;
688     }
689   }
690   mx_redraw_all();
691 }
692
693 static void mx_resolution_problem(int s) {
694   state[s].flags.resolution_problem= 1;
695   mx_redraw_seg(s);
696 }
697 static void si_resolution_inexplicable(ParseState *ps) {
698   int s;
699   s= ps_needsegment(ps);                                 if (s<0) return;
700   mx_resolution_problem(s);
701 }
702 static void si_resolution_mispositioned(ParseState *ps) {
703   int r, s;
704   r= ps_needword(ps);  /* head|tail */                   if (r) return;
705   r= ps_needword(ps);  /* <train> */                     if (r) return;
706   s= ps_needsegment(ps);                                 if (s<0) return;
707   mx_resolution_problem(s);
708 }
709
710 static void si_connected(ParseState *ps) {
711   const MuxEventInfo *mxi;
712   const char *p;
713   int c;
714   
715   sockprintf("select-replay");
716   for (mxi=muxeventinfos; mxi->prefix; mxi++) {
717     if (!mxi->remainpat)
718       continue;
719     sockprintf(" ");
720     for (p=mxi->prefix; (c=*p); p++) {
721       switch (c) {
722       case ' ': c= '_';
723       }
724       sockprintf("%c",c);
725     }
726     sockprintf("%s",mxi->remainpat);
727   }
728   sockprintf(" ~|*\n");
729
730   /* set the fixed moveable features */
731   const SegmentInfo *segi;
732   const MovFeatInfo *mfi;
733   const PlanSegmentData *planseg;
734   const PlanSegmovfeatData *planfeat;
735   int segn, s, mfn, f;
736   
737   for (segn=0; segn<info_nsegments; segn++) {
738     segi= &info_segments[segn];
739     if (!segi->n_fixedmovfeats) continue;
740     for (s=0; s<NSEGMENTS; s++)
741       if (!strcmp(segi->pname, (planseg=&ui_plan_data.segments[s])->segname))
742         goto found_ld_seg;
743     die("layout data segment %s not found\n",segi->pname);
744   found_ld_seg:
745     for (mfn=0, mfi= segi->movfeats + segi->n_movfeats;
746          mfn < segi->n_fixedmovfeats;
747          mfn++, mfi++) {
748       for (f=1; f<planseg->n_movfeats; f++)
749         if (!strcmp(mfi->pname, (planfeat=&planseg->movfeats[f])->movfeatname))
750           goto found_ld_feat;
751       die("layout data movfeat %s/%s not found\n",segi->pname,mfi->pname);
752     found_ld_feat:
753       state[s].mfs[f].posn= mfi->posns;
754       if (debug) fprintf(debug,"fixed %s/%s=%d\n",
755                          segi->pname,mfi->pname,mfi->posns);
756     }
757   }
758 }
759
760 static void si_fatal(ParseState *ps) {
761   die("multiplexer reports problem: %.*s\n",
762       (int)badcmdreport_recsz, badcmdreport_data);
763 }
764 static void si_ack(ParseState *ps) {
765   ps_needword(ps); /* command */
766   ps_needword(ps); /* status */
767   if (thiswordstrcmp(ps,"ok")) si_fatal(0);
768 }
769
770 static const MuxEventInfo muxeventinfos[]= {
771   { "?detect", "",                          si_detect                     },
772   { "?picio out polarity", "",              si_polarity                   },
773   { "?movpos", "_*_feat",                   si_movpos                     },
774   { "?picio out on", "",                    si_on                         },
775   { "?picio out off", "",                   si_off                        },
776   { "?train", "_*_has",                     si_train                      },
777                                                                        
778   { "?stastate", "",                        si_stastate                   },
779                                                 
780   { "?resolution inexplicable", "",         si_resolution_inexplicable    },
781   { "?resolution mispositioned", "",        si_resolution_mispositioned   },
782                                                 
783   { "=connected", "",                       si_connected                  },
784   { "=permission", "",                      0                             },
785                                                                         
786   { "+executing", 0,                        0                             },
787   { "+ack", 0,                              si_ack                        },
788   { "+nak", 0,                              si_fatal                      },
789   { "=failed", 0,                           si_fatal                      },
790   { "=denied", 0,                           si_fatal                      },
791   { 0 }
792 };
793
794 static void sock_input_line(ParseState *ps) {
795   const MuxEventInfo *mxi;
796   const char *got, *expected;
797   int l, c;
798   if (!ps->remain || !ps->remain[0])
799     return;
800   ps->thisword= ps->remain;
801   for (mxi=muxeventinfos; mxi->prefix; mxi++) {
802     got= ps->remain;
803     expected= mxi->prefix;
804     l= ps->lthisword= strlen(expected);
805     if (*expected=='?') { got++; expected++; l--; }
806     if (memcmp(got, expected, l)) continue;
807     if (!got[l] || got[l]==' ') goto found;
808   }
809   return;
810 found:
811   if (!mxi->fn) return;
812   ps->remain= got + l;
813   if ((c= *ps->remain)) { assert(c==' '); ps->remain++; }
814   if (debug) fprintf(debug,"calling <%s> with <%.*s|%s>\n",
815                      mxi->prefix, ps->lthisword,ps->thisword, ps->remain);
816   mxi->fn(ps);
817 }
818
819 static const InputStream sock_is= {
820   "multiplexer connection",
821   sock_eof,
822   sock_input_line,
823   sock_badcmd
824 };
825
826 /*---------- main program including much of the initialisation ----------*/
827
828 int main(int argc, const char *const *argv) {
829   oop_read *rd;
830   const char *arg;
831   XpmAttributes mattribs;
832   XWindowAttributes wattribs;
833   int segment_ix, movfeat_ix, posn, invert, det, oor, infd;
834   Window wspec=None;
835   XGCValues gcv;
836   XColor colour;
837   SegmovfeatState *fs;
838   const PlanSegmentData *segment_d;
839   const PlanSegmovfeatData *movfeat_d;
840   char *ep;
841   
842   sys_events= oop_sys_new();  if (!sys_events) diee("oop_sys_new");
843   events= oop_sys_source(sys_events);  assert(events);
844
845   while ((arg= *++argv)) {
846     if (!strcmp(arg,"--sizes")) {
847       printf("%d\n%d\n", ui_plan_data.xsz, ui_plan_data.ysz);
848       if (ferror(stdout) || fflush(stdout)) diee("print stdout");
849       exit(0);
850     } else if (!strcmp(arg,"--debug")) {
851       debug= stderr;
852     } else if (arg[0]=='@') {
853       char *node, *comma;
854       const char *service;
855       node= (char*)arg+1;
856       comma= strchr(node,',');
857       if (comma) {
858         *comma= 0;
859         service= comma+1;
860       } else {
861         service= STR(TRAINMX_PORT);
862       }
863       sock.fd= sock_clientconnect(node,service);
864       if (sock.fd<0) die("unable to connect to multiplexer");
865       obc_init(&sock);
866       if (comma) *comma= ',';
867     } else if (arg[0]=='-') {
868       die("invalid option(s)");
869     } else {
870       errno=0; wspec= strtoul(arg,&ep,0);
871       if (errno || ep==arg || *ep || wspec==None) die("bad windowid");
872     }
873   }
874
875   d= XOpenDisplay(0);  if (!d) die("XOpenDisplay failed");
876
877   if (wspec==None) {
878     w= XCreateSimpleWindow(d, DefaultRootWindow(d),
879                            0,0, ui_plan_data.xsz, ui_plan_data.ysz,
880                            0,0, 0);
881     if (w == None) diex("XCreateSimpleWindow", "initial");
882   } else {
883     w= wspec;
884   }
885
886   XCALL( XGetWindowAttributes, 0, (d,w,&wattribs) );
887
888   XPMCALL( XpmCreatePixmapFromData, "background",
889            (d,w, (char**)ui_plan_data.background, &bg_pixmap,0,0) );
890
891   XCALL( XSetWindowBackgroundPixmap, 0, (d,w,bg_pixmap) );
892
893   XCALL( XAllocNamedColor, "white",
894          (d, wattribs.colormap, "#ffffff",
895           &colour, &colour) );
896   train_pixel= colour.pixel;
897
898   XCALL( XAllocNamedColor, "owned",
899          (d, wattribs.colormap, "#a0a0a0",
900           &colour, &colour) );
901   owned_pixel= colour.pixel;
902
903   state= mmalloc(sizeof(*state) * NSEGMENTS);
904   for (segment_ix= 0, segment_d= ui_plan_data.segments;
905        segment_ix < ui_plan_data.n_segments;
906        segment_ix++, segment_d++) {
907     state[segment_ix].flags.on= 0;
908     state[segment_ix].flags.inv= 0;
909     state[segment_ix].flags.det= 0;
910     state[segment_ix].flags.trainown= 0;
911     state[segment_ix].flags.updated_tmp= 0;
912     state[segment_ix].mfs= fs=
913       mmalloc(sizeof(*state[segment_ix].mfs) * segment_d->n_movfeats);
914     for (movfeat_ix= 0, movfeat_d= segment_d->movfeats;
915          movfeat_ix < segment_d->n_movfeats;
916          movfeat_ix++, movfeat_d++, fs++) {
917       fs->next= states_head;  states_head= fs;
918       fs->flags= state[segment_ix].flags;
919       fs->posn= movfeat_d->n_posns;
920       if (fs->posn==1) fs->posn= 0;
921       fs->redraw_needed= 0;
922
923       loadmask(&fs->whole, &movfeat_d->mask, &gcv, 0);
924
925       fs->posns= mmalloc(sizeof(*fs->posns)*(fs->posn+1));
926       for (posn= 0; posn <= fs->posn; posn++)
927         for (invert=0; invert<2; invert++)
928           for (det=0; det<2; det++) {
929             PosnState *ps= &fs->posns[posn][invert][det];
930             const PlanPixmapOnData *ppod=
931               posn < movfeat_d->n_posns
932               ? &movfeat_d->posns[posn] : 0;
933             const PlanPixmapDataRef *ppdr= ppod
934               ? &ppod->on[invert][det]
935               : &movfeat_d->unknown[invert][det];
936             ps->x= ppdr->x;
937             ps->y= ppdr->y;
938             mattribs.valuemask= 0;
939             XPMCALL( XpmCreatePixmapFromData, "main",
940                      (d,w,
941                       (char**)ppdr->d,
942                       &ps->pm,
943                       0, &mattribs) );
944             ps->width= mattribs.width;
945             ps->height= mattribs.height;
946             if (ppod) {
947               loadmask(&ps->edge, &ppod->pedge, &gcv, 0);
948             } else {
949               ps->edge.x= -1;
950               ps->edge.gc= None;
951             }
952           }
953     }
954   }
955
956   if (sock.fd<0) {
957     infd= 0;
958     instream= &stdin_is;
959   } else {
960     infd= sock.fd;
961     instream= &sock_is;
962   }
963   
964   events->on_fd(events, infd, OOP_EXCEPTION, some_exception,
965                 (void*)instream->name);
966
967   rd= oop_rd_new_fd(events, infd, 0,0);
968   if (!rd) diee("oop_rd_new_fd");
969
970   oor= oop_rd_read(rd, OOP_RD_STYLE_GETLINE, 1024,
971                    input_ifok,0, input_iferr,0);
972   if (oor) diee("oop_rd_read");
973
974   events->on_fd(events, ConnectionNumber(d), OOP_READ,      xlib_readable,  0);
975   events->on_fd(events, ConnectionNumber(d), OOP_EXCEPTION, some_exception,
976                 (void*)"xserver connection");
977
978   XCALL( XSelectInput, 0, (d,w, ExposureMask) );
979
980   if (wspec!=None) {
981     XCALL( XClearArea, "initial", (d,w, 0,0,0,0, True) );
982   } else {
983     XCALL( XMapWindow, 0, (d,w) );
984   }
985
986   xlib_process();
987
988   oop_sys_run(sys_events);
989   abort();
990 }