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