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