chiark / gitweb /
better reporting of bad input
[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
15 #include <X11/Xlib.h>
16 #include <X11/xpm.h>
17
18 #include "hostside.h"
19 #include "../layout/plan-data-format.h"
20
21 #include <oop.h>
22 #include <oop-read.h>
23
24 typedef struct MaskState MaskState;
25 struct MaskState {
26   int x, y, width, height;
27   GC gc;
28 };
29
30 typedef struct PosnState PosnState;
31 struct PosnState {
32   int x, y, width, height;
33   Pixmap pm;
34   MaskState edge;
35 };
36
37 typedef struct SegmovfeatState SegmovfeatState;
38 struct SegmovfeatState {
39   SegmovfeatState *next;
40   int invert, det, trainown, posn, redraw_needed;
41   MaskState whole;
42   PosnState (*posns)[2/*i*/][2/*det*/];
43     /* posns[n_posns]=unknown if n_posns>1 */
44 };
45
46 oop_source *events;
47
48 static SegmovfeatState **state, *states_head;
49 static Display *d;
50 static oop_source_sys *sys_events;
51 static Window w;
52 static int redraw_needed_count, expose_count;
53 static Pixmap bg_pixmap;
54 static unsigned long train_pixel, owned_pixel;
55
56 static const char *badcmdreport_data;
57 static size_t badcmdreport_recsz;
58
59 static void diex(const char *fn, const char *w) __attribute__((noreturn));
60 static void diex(const char *fn, const char *w) {
61   die("Xlib call failed: %s%s%s%s", fn,
62       (w)?" (":"", (w), (w)?")":"");
63 }
64
65 #define XCALL(f,w,al) do{                       \
66     if (!( (f) al ))                            \
67       diex(#f, (w));                            \
68   }while(0)
69
70 static void diexpm(const char *fn, const char *w, int xpmst)
71      __attribute__((noreturn));
72 static void diexpm(const char *fn, const char *w, int xpmst) {
73   die("Xpm call failed: %s%s%s%s: %s", fn,
74       (w)?" (":"", (w), (w)?")":"", XpmGetErrorString(xpmst));
75 }
76
77 #define XPMCALL(f,w,al) do{                     \
78     int xpmcall_xpmst;                          \
79     xpmcall_xpmst= ((f) al);                    \
80     if (xpmcall_xpmst != XpmSuccess)            \
81       diexpm(#f, (w), xpmcall_xpmst);           \
82   }while(0)
83
84 void vbadcmd(ParseState *ps, const char *fmt, va_list al) {
85   fprintf(stderr,"gui-plan: incorrect input: `%.*s': ",
86           (int)badcmdreport_recsz, badcmdreport_data);
87   vfprintf(stderr,fmt,al);
88   putc('\n',stderr);
89   exit(8);
90 }
91
92 static void *stdin_iferr(oop_source *evts, oop_read *stdin_read,
93                           oop_rd_event evt, const char *errmsg, int errnoval,
94                           const char *data, size_t recsz, void *cl_v) {
95   die("read stdin: %s", oop_rd_errmsg(stdin_read, evt,
96                                       errnoval, OOP_RD_STYLE_GETLINE));
97   return OOP_CONTINUE;
98 }
99
100 static int lstrpdbsearch(const char *str, int l,
101                          const char *what,
102                          const void *items, int n_items,
103                          int itemsz) {
104   int min, maxe, try, cmp;
105   const void *try_item;
106   const char *try_name;
107   
108   min= 0;
109   maxe= n_items;
110   for (;;) {
111     if (min >= maxe) badcmd(0,"unknown %s `%.*s'",what,l,str);
112     try= min + (maxe - min) / 2;
113     try_item= (const char*)items + itemsz * try;
114     try_name= *(const char *const *)try_item;
115     cmp= lstrstrcmp(str, l, try_name ? try_name : "");
116     if (!cmp) return try;
117     if (cmp < 0) maxe= try;
118     else min= try + 1;
119   }
120 }
121
122 static void *some_exception(oop_source *evts, int fd,
123                             oop_event evt, void *cl_v) {
124   die("poll exception on fd %d",fd);
125 }
126
127 static int range_overlap(int x1, int width1, int x2, int width2) {
128   /* works for y's and heights too, obviously. */
129   int rhs1, rhs2;
130   rhs1= x1 + width1;
131   rhs2= x2 + width2;
132   if (rhs1 <= x2 || rhs2 <= x1) return 0;
133   return 1;
134 }
135
136 static void xlib_expose(XExposeEvent *ev) {
137   SegmovfeatState *fs;
138   
139   expose_count= ev->count;
140   if (!ev->width || !ev->height) return;
141
142   for (fs= states_head;
143        fs;
144        fs= fs->next) {
145     if (fs->redraw_needed)
146       continue;
147     if (!range_overlap(fs->whole.x, fs->whole.width,
148                        ev->x, ev->width)) continue;
149     if (!range_overlap(fs->whole.y, fs->whole.height,
150                        ev->y, ev->height)) continue;
151     fs->redraw_needed= 1;
152     redraw_needed_count++;
153   }
154 }
155
156 static void redraw(SegmovfeatState *fs) {
157   PosnState *src;
158   XGCValues gcv;
159   
160   if (fs->redraw_needed) {
161     fs->redraw_needed= 0;
162     redraw_needed_count--;
163   }
164   src= 0;
165   XCALL( XCopyArea, "redraw",
166          (d, bg_pixmap, w, fs->whole.gc,
167           fs->whole.x, fs->whole.y,
168           fs->whole.width, fs->whole.height,
169           fs->whole.x, fs->whole.y) );
170   if (fs->invert >= 0) {
171     src= &fs->posns[fs->posn][fs->invert][fs->det];
172     XCALL( XCopyArea, "redraw",
173            (d, src->pm, w, fs->whole.gc,
174             0,0, src->width, src->height,
175             src->x, src->y) );
176   }
177   if (fs->trainown && src && src->edge.x >= 0) {
178     gcv.foreground= fs->trainown>1 ? train_pixel : owned_pixel;
179     XCALL( XChangeGC, "train/own",
180            (d, src->edge.gc, GCForeground, &gcv) );
181     XCALL( XFillRectangle, "train/own",
182            (d,w, src->edge.gc,
183             src->edge.x, src->edge.y,
184             src->edge.width, src->edge.height) );
185   }
186 }
187
188 static void redraw_as_needed(void) {
189   SegmovfeatState *fs;
190
191   for (fs= states_head;
192        fs;
193        fs= fs->next)
194     if (fs->redraw_needed)
195       redraw(fs);
196   assert(!redraw_needed_count);
197 }
198   
199 static Bool evpredicate_always(Display *d, XEvent *ev, XPointer a) {
200   return True;
201 }
202
203 static void xlib_process(void) {
204   XEvent ev;
205   Status xst;
206   
207   for (;;) {
208     xst= XCheckIfEvent(d,&ev,evpredicate_always,0);
209     if (!xst) {
210       if (!redraw_needed_count || expose_count)
211         return;
212       redraw_as_needed();
213       continue;
214     }
215
216     switch (ev.type) {
217     case Expose: xlib_expose(&ev.xexpose); break;
218     case NoExpose: break;
219     default: die("unrequested event type %d\n",ev.type);
220     }
221   }
222 }
223
224 static void *xlib_readable(oop_source *evts, int fd,
225                            oop_event evt, void *cl_v) {
226   xlib_process();
227   return OOP_CONTINUE;
228 }
229
230 static int thiswordeatonechar(ParseState *ps, int c) {
231   if (ps->thisword[0] == c) {
232     ps->thisword++; ps->lthisword--;
233     return 1;
234   }
235   return 0;
236 }
237
238 static void *stdin_ifok(oop_source *evts, oop_read *cl_read,
239                         oop_rd_event evt, const char *errmsg, int errnoval,
240                         const char *data, size_t recsz, void *cl_v) {
241   const char *slash, *movfeatname;
242   ParseState ps;
243   int invert, det, trainown, segment_ix, movfeat_ix, lmovfeatname;
244   long posn;
245   const PlanSegmentData *segment_d;
246   const PlanSegmovfeatData *movfeat_d;
247   SegmovfeatState *fs;
248
249   if (evt == OOP_RD_EOF)
250     exit(0);
251   
252   if (evt != OOP_RD_OK)
253     return stdin_iferr(evts,cl_read,evt,errmsg,errnoval,data,recsz,cl_v);
254
255   badcmdreport_data= data;
256   badcmdreport_recsz= recsz;
257
258   ps.cl= 0;
259   ps.remain= data;
260   ps_needword(&ps);
261
262   if (!thiswordstrcmp(&ps,"off")) {
263     invert= -1;
264     trainown= 0;
265     det= 0;
266   } else {
267     trainown=
268       thiswordeatonechar(&ps,'t') ? 2 :
269       thiswordeatonechar(&ps,'f') ? 1 : 0;
270     invert= thiswordeatonechar(&ps,'i');
271     det= (!thiswordstrcmp(&ps,"on") ? 0 :
272           !thiswordstrcmp(&ps,"det") ? 1 :
273           (badcmd(&ps,"unknown command"),-1));
274   }
275   ps_needword(&ps);
276   slash= memchr(ps.thisword, '/', ps.lthisword);
277   if (slash) {
278     movfeatname= slash + 1;
279     lmovfeatname= (ps.thisword + ps.lthisword) - movfeatname;
280     ps.lthisword= slash - ps.thisword;
281   } else {
282     movfeatname= 0;
283     lmovfeatname= 0;
284   }
285   segment_ix= lstrpdbsearch(ps.thisword, ps.lthisword,
286                             "segment", ui_plan_data.segments,
287                             ui_plan_data.n_segments, sizeof(*segment_d));
288   segment_d= &ui_plan_data.segments[segment_ix];
289   
290   movfeat_ix= lstrpdbsearch(movfeatname, lmovfeatname,
291                             "movfeat", segment_d->movfeats,
292                             segment_d->n_movfeats, sizeof(*movfeat_d));
293   movfeat_d= &segment_d->movfeats[movfeat_ix];
294
295   if (ps.remain) {
296     if (invert<0) badcmd(0,"off may not take movfeatpos");
297     ps_neednumber(&ps, &posn, 0, movfeat_d->n_posns-1, "movfeatpos");
298   } else {
299     if (invert>=0 && movfeat_d->n_posns > 1) {
300       posn= movfeat_d->n_posns;
301     } else {
302       posn= 0;
303     }
304   }
305
306   ps_neednoargs(&ps);
307
308   fs= &state[segment_ix][movfeat_ix];
309   fs->invert= invert;
310   fs->det= det;
311   fs->trainown= trainown;
312   fs->posn= posn;
313
314   redraw(fs);
315   xlib_process();
316
317   return OOP_CONTINUE;
318 }
319
320 static void loadmask(MaskState *out, const PlanPixmapDataRef *ppd,
321                      XGCValues *gcv, long gcv_mask) {
322   static XpmColorSymbol coloursymbols[2]= {
323     { (char*)"space", 0, 0 },
324     { (char*)"mark",  0, 1 }
325   };
326   
327   XpmAttributes mattribs;
328   Pixmap pm;
329
330   out->x= ppd->x;
331   out->y= ppd->y;
332   mattribs.valuemask= XpmDepth | XpmColorSymbols;
333   mattribs.depth= 1;
334   mattribs.colorsymbols= coloursymbols;
335   mattribs.numsymbols= sizeof(coloursymbols) / sizeof(*coloursymbols);
336   XPMCALL( XpmCreatePixmapFromData, "mask",
337            (d,w, (char**)ppd->d, &pm,0, &mattribs) );
338   out->width= mattribs.width;
339   out->height= mattribs.height;
340
341   gcv->clip_x_origin= out->x;
342   gcv->clip_y_origin= out->y;
343   gcv->clip_mask= pm;
344   out->gc= XCreateGC(d,w,
345                      gcv_mask | GCClipXOrigin | GCClipYOrigin | GCClipMask,
346                      gcv);
347   XCALL( XFreePixmap, "mask", (d,pm) );
348 }
349
350 int main(int argc, const char *const *argv) {
351   oop_read *rd;
352   const char *arg;
353   XpmAttributes mattribs;
354   XWindowAttributes wattribs;
355   int segment_ix, movfeat_ix, posn, invert, det, oor;
356   XGCValues gcv;
357   XColor colour;
358   SegmovfeatState *fs;
359   const PlanSegmentData *segment_d;
360   const PlanSegmovfeatData *movfeat_d;
361
362   d= XOpenDisplay(0);  if (!d) die("XOpenDisplay failed");
363
364   if ((arg= *++argv)) {
365     char *ep;
366
367     if (!strcmp(arg,"--sizes")) {
368       printf("%d\n%d\n", ui_plan_data.xsz, ui_plan_data.ysz);
369       if (ferror(stdout) || fflush(stdout)) diee("print stdout");
370       exit(0);
371     }
372
373     if (arg[0]=='-') die("invalid option(s)");
374
375     errno=0; w= strtoul(arg,&ep,0);
376     if (errno || ep==arg || *ep) die("bad windowid");
377   } else {
378     w= XCreateSimpleWindow(d, DefaultRootWindow(d),
379                            0,0, ui_plan_data.xsz, ui_plan_data.ysz,
380                            0,0, 0);
381     if (w == None) diex("XCreateSimpleWindow", "initial");
382   }
383
384   XCALL( XGetWindowAttributes, 0, (d,w,&wattribs) );
385
386   XPMCALL( XpmCreatePixmapFromData, "background",
387            (d,w, (char**)ui_plan_data.background, &bg_pixmap,0,0) );
388
389   XCALL( XSetWindowBackgroundPixmap, 0, (d,w,bg_pixmap) );
390
391   XCALL( XAllocNamedColor, "white",
392          (d, wattribs.colormap, "#ffffff",
393           &colour, &colour) );
394   train_pixel= colour.pixel;
395
396   XCALL( XAllocNamedColor, "owned",
397          (d, wattribs.colormap, "#a0a0a0",
398           &colour, &colour) );
399   owned_pixel= colour.pixel;
400
401   state= mmalloc(sizeof(*state) * ui_plan_data.n_segments);
402   for (segment_ix= 0, segment_d= ui_plan_data.segments;
403        segment_ix < ui_plan_data.n_segments;
404        segment_ix++, segment_d++) {
405     state[segment_ix]= fs=
406       mmalloc(sizeof(**state) * segment_d->n_movfeats);
407     for (movfeat_ix= 0, movfeat_d= segment_d->movfeats;
408          movfeat_ix < segment_d->n_movfeats;
409          movfeat_ix++, movfeat_d++, fs++) {
410       fs->next= states_head;  states_head= fs;
411       fs->invert= -1;
412       fs->det= 0;
413       fs->trainown= 0;
414       fs->posn= movfeat_d->n_posns;
415       if (fs->posn==1) fs->posn= 0;
416       fs->redraw_needed= 0;
417
418       loadmask(&fs->whole, &movfeat_d->mask, &gcv, 0);
419
420       fs->posns= mmalloc(sizeof(*fs->posns)*(fs->posn+1));
421       for (posn= 0; posn <= fs->posn; posn++)
422         for (invert=0; invert<2; invert++)
423           for (det=0; det<2; det++) {
424             PosnState *ps= &fs->posns[posn][invert][det];
425             const PlanPixmapOnData *ppod=
426               posn < movfeat_d->n_posns
427               ? &movfeat_d->posns[posn] : 0;
428             const PlanPixmapDataRef *ppdr= ppod
429               ? &ppod->on[invert][det]
430               : &movfeat_d->unknown[invert][det];
431             ps->x= ppdr->x;
432             ps->y= ppdr->y;
433             mattribs.valuemask= 0;
434             XPMCALL( XpmCreatePixmapFromData, "main",
435                      (d,w,
436                       (char**)ppdr->d,
437                       &ps->pm,
438                       0, &mattribs) );
439             ps->width= mattribs.width;
440             ps->height= mattribs.height;
441             if (ppod) {
442               loadmask(&ps->edge, &ppod->pedge, &gcv, 0);
443             } else {
444               ps->edge.x= -1;
445               ps->edge.gc= None;
446             }
447           }
448     }
449   }
450   
451   sys_events= oop_sys_new();  if (!sys_events) diee("oop_sys_new");
452   events= oop_sys_source(sys_events);  assert(events);
453
454   rd= oop_rd_new_fd(events, 0, 0,0);
455   if (!rd) diee("oop_rd_new_fd");
456   oor= oop_rd_read(rd, OOP_RD_STYLE_GETLINE, 1024,
457                    stdin_ifok, 0,
458                    stdin_iferr, 0);
459   if (oor) diee("oop_rd_read");
460
461   events->on_fd(events, 0,                   OOP_EXCEPTION, some_exception, 0);
462   events->on_fd(events, ConnectionNumber(d), OOP_READ,      xlib_readable,  0);
463   events->on_fd(events, ConnectionNumber(d), OOP_EXCEPTION, some_exception, 0);
464
465   XCALL( XSelectInput, 0, (d,w, ExposureMask) );
466
467   if (arg) {
468     XCALL( XClearArea, "initial", (d,w, 0,0,0,0, True) );
469   } else {
470     XCALL( XMapWindow, 0, (d,w) );
471   }
472
473   xlib_process();
474
475   oop_sys_run(sys_events);
476   abort();
477 }