/*
- * 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();
+}