chiark / gitweb /
clearer debug
[trains.git] / hostside / gui-plan.c
index 42140edc2f177b5c362bd05621e1e14b46183ca7..3a4b9a893656a7c57042f6cdd440967a555078bf 100644 (file)
 /*
- * usage: gui-plan <windowid>
+ * usage: .../gui-plan [<windowid>]
  * protocol on stdin:
- *  series of uint32_t's in network byte order
- *   top byte is opcode
- *          0x00  off
- *          0x01  on
- *          0x01  detect
- *   next byte is movposcomb
- *   remaining bytes are segment number
+ *  series of lines
+ *   off <segname>[/[<movfeat>]
+ *   [t|f][i]on <segname>[[/<movfeat>] <movpos>]
+ *   [t|f][i]det <segname>[[/<movfeat>] <movpos>]
  */
 
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+
+#include <X11/Xlib.h>
+#include <X11/xpm.h>
+
+#include "hostside.h"
+#include "../layout/plan-data-format.h"
+
+#include <oop.h>
+#include <oop-read.h>
+
+typedef struct MaskState MaskState;
+struct MaskState {
+  int x, y, width, height;
+  GC gc;
+};
+
+typedef struct PosnState PosnState;
+struct PosnState {
+  int x, y, width, height;
+  Pixmap pm;
+  MaskState edge;
+};
+
+typedef struct SegmovfeatState SegmovfeatState;
+struct SegmovfeatState {
+  SegmovfeatState *next;
+  int invert, det, trainown, posn, redraw_needed;
+  MaskState whole;
+  PosnState (*posns)[2/*i*/][2/*det*/];
+    /* posns[n_posns]=unknown if n_posns>1 */
+};
+
+oop_source *events;
+
+static SegmovfeatState **state, *states_head;
+static Display *d;
+static oop_source_sys *sys_events;
+static Window w;
+static int redraw_needed_count, expose_count;
+static Pixmap bg_pixmap;
+static unsigned long train_pixel, owned_pixel;
+
+static void diex(const char *fn, const char *w) __attribute__((noreturn));
+static void diex(const char *fn, const char *w) {
+  die("Xlib call failed: %s%s%s%s", fn,
+      (w)?" (":"", (w), (w)?")":"");
+}
+
+#define XCALL(f,w,al) do{                      \
+    if (!( (f) al ))                           \
+      diex(#f, (w));                           \
+  }while(0)
+
+static void diexpm(const char *fn, const char *w, int xpmst)
+     __attribute__((noreturn));
+static void diexpm(const char *fn, const char *w, int xpmst) {
+  die("Xpm call failed: %s%s%s%s: %s", fn,
+      (w)?" (":"", (w), (w)?")":"", XpmGetErrorString(xpmst));
+}
+
+#define XPMCALL(f,w,al) do{                    \
+    int xpmcall_xpmst;                         \
+    xpmcall_xpmst= ((f) al);                   \
+    if (xpmcall_xpmst != XpmSuccess)           \
+      diexpm(#f, (w), xpmcall_xpmst);          \
+  }while(0)
+
+void vbadcmd(ParseState *ps, const char *fmt, va_list al) {
+  fprintf(stderr,"gui-plan: incorrect input: ");
+  vfprintf(stderr,fmt,al);
+  putc('\n',stderr);
+  exit(8);
+}
+
+static void *stdin_iferr(oop_source *evts, oop_read *stdin_read,
+                         oop_rd_event evt, const char *errmsg, int errnoval,
+                         const char *data, size_t recsz, void *cl_v) {
+  die("read stdin: %s", oop_rd_errmsg(stdin_read, evt,
+                                     errnoval, OOP_RD_STYLE_GETLINE));
+  return OOP_CONTINUE;
+}
+
+static int lstrpdbsearch(const char *str, int l,
+                        const char *what,
+                        const void *items, int n_items,
+                        int itemsz) {
+  int min, maxe, try, cmp;
+  const void *try_item;
+  const char *try_name;
+  
+  min= 0;
+  maxe= n_items;
+  for (;;) {
+    if (min >= maxe) badcmd(0,"unknown %s `%.*s'",what,l,str);
+    try= min + (maxe - min) / 2;
+    try_item= (const char*)items + itemsz * try;
+    try_name= *(const char *const *)try_item;
+    cmp= lstrstrcmp(str, l, try_name ? try_name : "");
+    if (!cmp) return try;
+    if (cmp < 0) maxe= try;
+    else min= try + 1;
+  }
+}
+
+static void *some_exception(oop_source *evts, int fd,
+                           oop_event evt, void *cl_v) {
+  die("poll exception on fd %d",fd);
+}
+
+static int range_overlap(int x1, int width1, int x2, int width2) {
+  /* works for y's and heights too, obviously. */
+  int rhs1, rhs2;
+  rhs1= x1 + width1;
+  rhs2= x2 + width2;
+  if (rhs1 <= x2 || rhs2 <= x1) return 0;
+  return 1;
+}
+
+static void xlib_expose(XExposeEvent *ev) {
+  SegmovfeatState *fs;
+  
+  expose_count= ev->count;
+  if (!ev->width || !ev->height) return;
+
+  for (fs= states_head;
+       fs;
+       fs= fs->next) {
+    if (fs->redraw_needed)
+      continue;
+    if (!range_overlap(fs->whole.x, fs->whole.width,
+                      ev->x, ev->width)) continue;
+    if (!range_overlap(fs->whole.y, fs->whole.height,
+                      ev->y, ev->height)) continue;
+    fs->redraw_needed= 1;
+    redraw_needed_count++;
+  }
+}
+
+static void redraw(SegmovfeatState *fs) {
+  PosnState *src;
+  XGCValues gcv;
+  
+  if (fs->redraw_needed) {
+    fs->redraw_needed= 0;
+    redraw_needed_count--;
+  }
+  src= 0;
+  XCALL( XCopyArea, "redraw",
+        (d, bg_pixmap, w, fs->whole.gc,
+         fs->whole.x, fs->whole.y,
+         fs->whole.width, fs->whole.height,
+         fs->whole.x, fs->whole.y) );
+  if (fs->invert >= 0) {
+    src= &fs->posns[fs->posn][fs->invert][fs->det];
+    XCALL( XCopyArea, "redraw",
+          (d, src->pm, w, fs->whole.gc,
+           0,0, src->width, src->height,
+           src->x, src->y) );
+  }
+  if (fs->trainown && src && src->edge.x >= 0) {
+    gcv.foreground= fs->trainown>1 ? train_pixel : owned_pixel;
+    XCALL( XChangeGC, "train/own",
+          (d, src->edge.gc, GCForeground, &gcv) );
+    XCALL( XFillRectangle, "train/own",
+          (d,w, src->edge.gc,
+           src->edge.x, src->edge.y,
+           src->edge.width, src->edge.height) );
+  }
+}
+
+static void redraw_as_needed(void) {
+  SegmovfeatState *fs;
+
+  for (fs= states_head;
+       fs;
+       fs= fs->next)
+    if (fs->redraw_needed)
+      redraw(fs);
+  assert(!redraw_needed_count);
+}
+  
+static Bool evpredicate_always(Display *d, XEvent *ev, XPointer a) {
+  return True;
+}
+
+static void xlib_process(void) {
+  XEvent ev;
+  Status xst;
+  
+  for (;;) {
+    xst= XCheckIfEvent(d,&ev,evpredicate_always,0);
+    if (!xst) {
+      if (!redraw_needed_count || expose_count)
+       return;
+      redraw_as_needed();
+      continue;
+    }
+
+    switch (ev.type) {
+    case Expose: xlib_expose(&ev.xexpose); break;
+    case NoExpose: break;
+    default: die("unrequested event type %d\n",ev.type);
+    }
+  }
+}
+
+static void *xlib_readable(oop_source *evts, int fd,
+                          oop_event evt, void *cl_v) {
+  xlib_process();
+  return OOP_CONTINUE;
+}
+
+static int thiswordeatonechar(ParseState *ps, int c) {
+  if (ps->thisword[0] == c) {
+    ps->thisword++; ps->lthisword--;
+    return 1;
+  }
+  return 0;
+}
+
+static void *stdin_ifok(oop_source *evts, oop_read *cl_read,
+                       oop_rd_event evt, const char *errmsg, int errnoval,
+                       const char *data, size_t recsz, void *cl_v) {
+  const char *slash, *movfeatname;
+  ParseState ps;
+  int invert, det, trainown, segment_ix, movfeat_ix, lmovfeatname;
+  long posn;
+  const PlanSegmentData *segment_d;
+  const PlanSegmovfeatData *movfeat_d;
+  SegmovfeatState *fs;
+
+  if (evt == OOP_RD_EOF)
+    exit(0);
+  
+  if (evt != OOP_RD_OK)
+    return stdin_iferr(evts,cl_read,evt,errmsg,errnoval,data,recsz,cl_v);
+
+fprintf(stderr,"gui-plan>%.*s<\n",(int)recsz,data);
+
+  ps.cl= 0;
+  ps.remain= data;
+  ps_needword(&ps);
+
+  if (!thiswordstrcmp(&ps,"off")) {
+    invert= -1;
+    trainown= 0;
+    det= 0;
+  } else {
+    trainown=
+      thiswordeatonechar(&ps,'t') ? 2 :
+      thiswordeatonechar(&ps,'f') ? 1 : 0;
+    invert= thiswordeatonechar(&ps,'i');
+    det= (!thiswordstrcmp(&ps,"on") ? 0 :
+         !thiswordstrcmp(&ps,"det") ? 1 :
+         (badcmd(&ps,"unknown command"),-1));
+  }
+  ps_needword(&ps);
+  slash= memchr(ps.thisword, '/', ps.lthisword);
+  if (slash) {
+    movfeatname= slash + 1;
+    lmovfeatname= (ps.thisword + ps.lthisword) - movfeatname;
+    ps.lthisword= slash - ps.thisword;
+  } else {
+    movfeatname= 0;
+    lmovfeatname= 0;
+  }
+  segment_ix= lstrpdbsearch(ps.thisword, ps.lthisword,
+                           "segment", ui_plan_data.segments,
+                           ui_plan_data.n_segments, sizeof(*segment_d));
+  segment_d= &ui_plan_data.segments[segment_ix];
+  
+  movfeat_ix= lstrpdbsearch(movfeatname, lmovfeatname,
+                           "movfeat", segment_d->movfeats,
+                           segment_d->n_movfeats, sizeof(*movfeat_d));
+  movfeat_d= &segment_d->movfeats[movfeat_ix];
+
+  if (ps.remain) {
+    if (invert<0) badcmd(0,"off may not take movfeatpos");
+    ps_neednumber(&ps, &posn, 0, movfeat_d->n_posns-1, "movfeatpos");
+  } else {
+    if (invert>=0 && movfeat_d->n_posns > 1) {
+      posn= movfeat_d->n_posns;
+    } else {
+      posn= 0;
+    }
+  }
+
+  ps_neednoargs(&ps);
+
+  fs= &state[segment_ix][movfeat_ix];
+  fs->invert= invert;
+  fs->det= det;
+  fs->trainown= trainown;
+  fs->posn= posn;
+
+  redraw(fs);
+  xlib_process();
+
+  return OOP_CONTINUE;
+}
+
+static void loadmask(MaskState *out, const PlanPixmapDataRef *ppd,
+                    XGCValues *gcv, long gcv_mask) {
+  static XpmColorSymbol coloursymbols[2]= {
+    { (char*)"space", 0, 0 },
+    { (char*)"mark",  0, 1 }
+  };
+  
+  XpmAttributes mattribs;
+  Pixmap pm;
+
+  out->x= ppd->x;
+  out->y= ppd->y;
+  mattribs.valuemask= XpmDepth | XpmColorSymbols;
+  mattribs.depth= 1;
+  mattribs.colorsymbols= coloursymbols;
+  mattribs.numsymbols= sizeof(coloursymbols) / sizeof(*coloursymbols);
+  XPMCALL( XpmCreatePixmapFromData, "mask",
+          (d,w, (char**)ppd->d, &pm,0, &mattribs) );
+  out->width= mattribs.width;
+  out->height= mattribs.height;
+
+  gcv->clip_x_origin= out->x;
+  gcv->clip_y_origin= out->y;
+  gcv->clip_mask= pm;
+  out->gc= XCreateGC(d,w,
+                    gcv_mask | GCClipXOrigin | GCClipYOrigin | GCClipMask,
+                    gcv);
+  XCALL( XFreePixmap, "mask", (d,pm) );
+}
+
+int main(int argc, const char *const *argv) {
+  oop_read *rd;
+  const char *arg;
+  XpmAttributes mattribs;
+  XWindowAttributes wattribs;
+  int segment_ix, movfeat_ix, posn, invert, det, oor;
+  XGCValues gcv;
+  XColor colour;
+  SegmovfeatState *fs;
+  const PlanSegmentData *segment_d;
+  const PlanSegmovfeatData *movfeat_d;
+
+  d= XOpenDisplay(0);  if (!d) die("XOpenDisplay failed");
+
+  if ((arg= *++argv)) {
+    char *ep;
+
+    if (!strcmp(arg,"--sizes")) {
+      printf("%d\n%d\n", ui_plan_data.xsz, ui_plan_data.ysz);
+      if (ferror(stdout) || fflush(stdout)) diee("print stdout");
+      exit(0);
+    }
+
+    if (arg[0]=='-') die("invalid option(s)");
+
+    errno=0; w= strtoul(arg,&ep,0);
+    if (errno || ep==arg || *ep) die("bad windowid");
+  } else {
+    w= XCreateSimpleWindow(d, DefaultRootWindow(d),
+                          0,0, ui_plan_data.xsz, ui_plan_data.ysz,
+                          0,0, 0);
+    if (w == None) diex("XCreateSimpleWindow", "initial");
+  }
+
+  XCALL( XGetWindowAttributes, 0, (d,w,&wattribs) );
+
+  XPMCALL( XpmCreatePixmapFromData, "background",
+          (d,w, (char**)ui_plan_data.background, &bg_pixmap,0,0) );
+
+  XCALL( XSetWindowBackgroundPixmap, 0, (d,w,bg_pixmap) );
+
+  XCALL( XAllocNamedColor, "white",
+         (d, wattribs.colormap, "#ffffff",
+          &colour, &colour) );
+  train_pixel= colour.pixel;
+
+  XCALL( XAllocNamedColor, "owned",
+         (d, wattribs.colormap, "#a0a0a0",
+          &colour, &colour) );
+  owned_pixel= colour.pixel;
+
+  state= mmalloc(sizeof(*state) * ui_plan_data.n_segments);
+  for (segment_ix= 0, segment_d= ui_plan_data.segments;
+       segment_ix < ui_plan_data.n_segments;
+       segment_ix++, segment_d++) {
+    state[segment_ix]= fs=
+      mmalloc(sizeof(**state) * segment_d->n_movfeats);
+    for (movfeat_ix= 0, movfeat_d= segment_d->movfeats;
+        movfeat_ix < segment_d->n_movfeats;
+        movfeat_ix++, movfeat_d++, fs++) {
+      fs->next= states_head;  states_head= fs;
+      fs->invert= -1;
+      fs->det= 0;
+      fs->trainown= 0;
+      fs->posn= movfeat_d->n_posns;
+      if (fs->posn==1) fs->posn= 0;
+      fs->redraw_needed= 0;
+
+      loadmask(&fs->whole, &movfeat_d->mask, &gcv, 0);
+
+      fs->posns= mmalloc(sizeof(*fs->posns)*(fs->posn+1));
+      for (posn= 0; posn <= fs->posn; posn++)
+       for (invert=0; invert<2; invert++)
+         for (det=0; det<2; det++) {
+           PosnState *ps= &fs->posns[posn][invert][det];
+           const PlanPixmapOnData *ppod=
+             posn < movfeat_d->n_posns
+             ? &movfeat_d->posns[posn] : 0;
+           const PlanPixmapDataRef *ppdr= ppod
+             ? &ppod->on[invert][det]
+             : &movfeat_d->unknown[invert][det];
+           ps->x= ppdr->x;
+           ps->y= ppdr->y;
+           mattribs.valuemask= 0;
+           XPMCALL( XpmCreatePixmapFromData, "main",
+                    (d,w,
+                     (char**)ppdr->d,
+                     &ps->pm,
+                     0, &mattribs) );
+           ps->width= mattribs.width;
+           ps->height= mattribs.height;
+           if (ppod) {
+             loadmask(&ps->edge, &ppod->pedge, &gcv, 0);
+           } else {
+             ps->edge.x= -1;
+             ps->edge.gc= None;
+           }
+         }
+    }
+  }
+  
+  sys_events= oop_sys_new();  if (!sys_events) diee("oop_sys_new");
+  events= oop_sys_source(sys_events);  assert(events);
+
+  rd= oop_rd_new_fd(events, 0, 0,0);
+  if (!rd) diee("oop_rd_new_fd");
+  oor= oop_rd_read(rd, OOP_RD_STYLE_GETLINE, 1024,
+                  stdin_ifok, 0,
+                  stdin_iferr, 0);
+  if (oor) diee("oop_rd_read");
+
+  events->on_fd(events, 0,                   OOP_EXCEPTION, some_exception, 0);
+  events->on_fd(events, ConnectionNumber(d), OOP_READ,      xlib_readable,  0);
+  events->on_fd(events, ConnectionNumber(d), OOP_EXCEPTION, some_exception, 0);
+
+  XCALL( XSelectInput, 0, (d,w, ExposureMask) );
+
+  if (arg) {
+    XCALL( XClearArea, "initial", (d,w, 0,0,0,0, True) );
+  } else {
+    XCALL( XMapWindow, 0, (d,w) );
+  }
+
+  xlib_process();
+
+  oop_sys_run(sys_events);
+  abort();
+}