// This file is in the public domain. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "puzzles.h" /* * Foreground black and Background white because most of the games * simply do not work if you reverse those! They interpolate between * background and black, effectively forcing black for foreground. */ static XrmDatabase db; static const char *defaults = "\ *Foreground: black\n\ *Background: white\n\ *BorderColour: blue\n\ *BorderMargin: 4\n\ *BorderWidth: 1\n\ *MenuMargin: 25\n\ "; typedef unsigned long long int TIME; static char *displayname = 0; static char *geometryspec = 0; static char *foreground = 0; static char *background = 0; static char *bordercstr = 0; static char *borderwstr = 0; static char *bordermstr = 0; static char *menumstr = 0; static char *fontstr = 0; static char *visualstr = 0; static int synch = 0; static char *rmstring = 0; static int argc; static char **argv; static const char *av0bn; static Display *disp; static Screen *scr; static Visual *visual; static Colormap defcmap; static int scrwidth; static int scrheight; static int depth; static Window rootwin; static int margin; static int menumargin; static int borderwidth; static int (*oldioerr)(Display *); static int (*olderr)(Display *, XErrorEvent *); static int dir_junk; static int asc_junk; static int dsc_junk; #define XTE_JUNK &dir_junk, &asc_junk, &dsc_junk typedef enum { LT_LEAF = 1, LT_GLUE, LT_HBOX, LT_VBOX, LT_GRID, LT_WRITE_LOC, } LAYOUT_TYPE; typedef enum { INSERT_DROP = 1, INSERT_SAME, INSERT_DEEPEN, } INSERT_STATUS; typedef struct xy XY; typedef struct layout LAYOUT; typedef struct leafops LEAFOPS; typedef struct menubar_item MENUBAR_ITEM; typedef struct mpop MPOP; typedef struct mpop_item MPOP_ITEM; typedef struct evhent EVHENT; typedef struct evhandle EVHANDLE; typedef struct cbox CBOX; typedef struct cboxx CBOXX; typedef struct cboxxchoice CBOXXCHOICE; typedef struct cboxxbool CBOXXBOOL; typedef struct cboxbutton CBOXBUTTON; typedef struct editstate EDITSTATE; struct xy { int x; int y; } ; struct evhandle { const char *name; EVHENT *root; } ; #define EVHANDLE_INIT(n) {\ .name = #n, \ .root = 0 } struct evhent { EVHENT *u; EVHENT *l; EVHENT *r; int bal; Window w; void (*handler)(void *, XEvent *); void *arg; } ; struct menubar_item { const char *text; MENUBAR_ITEM *link; int textlen; int extend_l; int extend_r; int textw; void (*click)(frontend *, Time); bool (*acttest)(midend *); int curact; LAYOUT *l; Window win; XY pmsize; Pixmap actpm; Pixmap inactpm; } ; struct leafops { const char *name; void (*init)(void *, LAYOUT *); XY (*minsize)(void *); void (*pre_create)(void *, LAYOUT *, unsigned long int *, XSetWindowAttributes *); void (*post_create)(void *, Window); void (*destroy)(void *); void (*pre_update)(void *, LAYOUT *); void (*post_update)(void *); } ; #define LEAFOPS_INIT(name) {\ #name, \ &leafop_##name##_init, \ &leafop_##name##_minsize, \ &leafop_##name##_pre_create, \ &leafop_##name##_post_create, \ &leafop_##name##_destroy, \ &leafop_##name##_pre_update, \ &leafop_##name##_post_update, \ } struct layout { LAYOUT_TYPE type; XY minsize; XY loc; XY size; union { struct { const LEAFOPS *ops; void *priv; XY winloc; XY winsize; Window win; } leaf; struct { int min; unsigned int stretchmask; #define STRETCH_GAME 0x00000001 #define STRETCH_OTHER 0x00000002 #define STRETCH_ALWAYS 0x00000004 } glue; struct { int n; LAYOUT **v; int stretch; } box; struct { int nx; int bx; int ny; int by; LAYOUT **g; #define GRIDXY(x,y,grid) ((x)+((y)*(grid)->nx)) int *cx; int *ry; Window *bwx; Window *bwy; unsigned int flags; #define GF_EQUAL_X 0x00000001 #define GF_EQUAL_Y 0x00000002 #define GF_DEBUG 0x00000004 #define GF__CANSET (GF_EQUAL_X | GF_EQUAL_Y | GF_DEBUG) int cw1; int *cw; int rh1; int *rh; } grid; int *write_loc; } u; } ; struct mpop_item { MPOP *menu; int inx; char *text; int textlen; int textw; LAYOUT *l; Window win; Pixmap pm; } ; struct mpop { frontend *fe; int nitems; MPOP_ITEM *items; int itemsa; void (*choose)(int, Time); LAYOUT *l; int up; Window win; int curitem; int wantquery; int id; } ; struct cboxbutton { CBOX *cb; const char *text; int extend_l; int extend_r; int extend_u; int extend_d; LAYOUT *l; int textlen; int textw; int isgood; Window win; } ; struct cboxxchoice { CBOXX *cbx; int cx; LAYOUT *l; int extend_l; int extend_r; int extend_u; int extend_d; const char *text; int textlen; int textw; Window win; Pixmap pmsel; Pixmap pmdesel; } ; struct cboxxbool { CBOXX *cbx; LAYOUT *l; int yn; int extend_l; int extend_r; int extend_u; int extend_d; const char *text; int textlen; int textw; Window win; Pixmap pmsel; Pixmap pmdesel; } ; struct cboxx { CBOX *cb; int i; int namelen; Window labelw; union { // discriminant: cb->cfg[i].type struct { int slen; int extend_r; Window iwin; LAYOUT *l; } string; struct { CBOXXBOOL yes; CBOXXBOOL no; } boolean; struct { int nc; CBOXXCHOICE *choices; } choices; } u; } ; struct cbox { frontend *fe; config_item *cfg; char *title; int titlelen; int titlew; Window titlewin; int cfgn; CBOXX *boxx; LAYOUT *l; Window topwin; int curitem; int up; } ; struct frontend { midend *me; Window topwin; int topw; int toph; Window menuwin; Window menubar; MENUBAR_ITEM *menubar_items; int extend_l; int extend_r; int extend_u; int extend_d; Window drawwin; Pixmap drawpix; XY drawpsize; int draww; int drawh; unsigned int flags; #define FEF_STATBAR 0x00000001 #define FEF_CONFIG 0x00000002 #define FEF_SOLVE 0x00000004 #define FEF_CAN_UNDO 0x00000008 #define FEF_CAN_REDO 0x00000010 #define FEF_CUSTOM 0x00000020 Window statbar; int statusbar_r; Window statwin; char *statbuf; int statalloc; int statlen; LAYOUT *layout_game; MENUBAR_ITEM menubar_quit; MENUBAR_ITEM menubar_undo; MENUBAR_ITEM menubar_redo; MENUBAR_ITEM menubar_solve; MENUBAR_ITEM menubar_config; MENUBAR_ITEM menubar_new; MENUBAR_ITEM menubar_game; MENUBAR_ITEM menubar_seed; CBOXBUTTON custom_cancel; CBOXBUTTON custom_done; LAYOUT *gl; int timerfd; int running; TIME lasttime; GC copygc; GC drawgc; GC bitgc; Colormap wincmap; XColor fgcolour; XColor bgcolour; XColor bdcolour; XTextProperty wn_prop; XTextProperty in_prop; XSizeHints *normal_hints; XWMHints *wm_hints; XClassHint *class_hints; XFontStruct *font; int baselineskip; int spacewidth; int vcentre; Cursor arrowcurs; struct preset_menu *cur_presets; struct preset_menu *presets; int ncolours; XColor *colours; MPOP mpop; CBOX cbox; } ; struct blitter { int w; int h; int x; int y; Pixmap pm; } ; struct editstate { int live; Window win; Pixmap pm; int winw; int xoff; int yoff; unsigned char *b; int ba; int bl; int curs; int cursx; int scroll; void (*done)(EDITSTATE *, int, void *); void *donearg; int cursup; int flashfd; int flashid; unsigned char *dispb; unsigned int dispba; unsigned int dispbl; unsigned int dispcurs; unsigned int dispcursx; unsigned int dispscroll; int dispcursup; #define EDIT_DISPCURSUP_FULLREDRAW (-1) } ; static frontend *fe; static EVHANDLE evh_expose = EVHANDLE_INIT(Expose); static EVHANDLE evh_buttonpress = EVHANDLE_INIT(ButtonPress); static EVHANDLE evh_buttonrelease = EVHANDLE_INIT(ButtonRelease); static EVHANDLE evh_motionnotify = EVHANDLE_INIT(MotionNotify); static EVHANDLE evh_enternotify = EVHANDLE_INIT(EnterNotify); static EVHANDLE evh_leavenotify = EVHANDLE_INIT(LeaveNotify); static EVHANDLE evh_configurenotify = EVHANDLE_INIT(ConfigureNotify); static const struct drawing_api x_drawing; // forward static FILE *dlog = 0; static void (*keystroke)(XKeyEvent *); static EDITSTATE es; #define DEFAULT_ABORT() default: abort(); break static char *deconst(const char *s) { char *rv; bcopy(&s,&rv,sizeof(rv)); return(rv); } static void saveargv(int ac, char **av) { int i; int nc; char *abuf; const char *s; argc = ac; argv = (char **) malloc((ac+1)*sizeof(char *)); nc = 1; for (i=0;i 0) { skip --; continue; } if (**av != '-') { fprintf(stderr,"%s: unrecognized argument `%s'\n",__progname,*av); errs ++; continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-display")) { WANTARG(); displayname = av[skip]; continue; } if (!strcmp(*av,"-geometry")) { WANTARG(); geometryspec = av[skip]; continue; } if (!strcmp(*av,"-foreground") || !strcmp(*av,"-fg")) { WANTARG(); foreground = av[skip]; continue; } if (!strcmp(*av,"-background") || !strcmp(*av,"-bg")) { WANTARG(); background = av[skip]; continue; } if (!strcmp(*av,"-bordercolor") || !strcmp(*av,"-bordercolour") || !strcmp(*av,"-bd")) { WANTARG(); bordercstr = av[skip]; continue; } if (!strcmp(*av,"-borderwidth") || !strcmp(*av,"-bw")) { WANTARG(); borderwstr = av[skip]; continue; } if (!strcmp(*av,"-bordermargin") || !strcmp(*av,"-bm")) { WANTARG(); bordermstr = av[skip]; continue; } if (!strcmp(*av,"-menumargin") || !strcmp(*av,"-mm")) { WANTARG(); menumstr = av[skip]; continue; } if (!strcmp(*av,"-visual")) { WANTARG(); visualstr = av[skip]; continue; } if (!strcmp(*av,"-font")) { WANTARG(); fontstr = av[skip]; continue; } if (!strcmp(*av,"-xrm")) { WANTARG(); strappend(&rmstring,"\n",av[skip],(char *)0); continue; } if (!strcmp(*av,"-sync")) { synch = 1; continue; } if (!strcmp(*av,"-dlog")) { WANTARG(); drawlog_open(av[skip]); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static void draw_statbar_text(frontend *fe) { if (fe->statlen > 0) { XSetForeground(disp,fe->drawgc,fe->fgcolour.pixel); XDrawString(disp,fe->statwin,fe->drawgc,0,fe->font->ascent,fe->statbuf,fe->statlen); } } static Pixmap menu_cached_pm(Pixmap *cache, MENUBAR_ITEM *i, int active) { Pixmap pm; int x; int y; if ( (i->l->size.x != i->pmsize.x) || (i->l->size.y != i->pmsize.y) ) { if (i->actpm != None) { XFreePixmap(disp,i->actpm); i->actpm = None; } if (i->inactpm != None) { XFreePixmap(disp,i->inactpm); i->inactpm = None; } i->pmsize = i->l->size; } pm = *cache; if (pm != None) return(pm); pm = XCreatePixmap(disp,rootwin,i->pmsize.x,i->pmsize.y,depth); XSetForeground(disp,fe->drawgc,fe->bgcolour.pixel); x = (i->pmsize.x - i->textw) / 2; y = (i->pmsize.y - fe->baselineskip) / 2; XFillRectangle(disp,pm,fe->drawgc,0,0,i->pmsize.x,i->pmsize.y); if (active) { XSetForeground(disp,fe->drawgc,fe->fgcolour.pixel); XDrawString(disp,pm,fe->drawgc,x,y+fe->font->ascent,i->text,i->textlen); } *cache = pm; return(pm); } static void copyarea(Display *d, Drawable f, Drawable t, GC gc, int fx, int fy, unsigned int w, unsigned int h, int tx, int ty) { if ((f == 0) || (t == 0)) abort(); XCopyArea(d,f,t,gc,fx,fy,w,h,tx,ty); } static void draw_menubar_item(frontend *fe, MENUBAR_ITEM *i) { copyarea(disp,menu_cached_pm(i->curact?&i->actpm:&i->inactpm,i,i->curact),i->win,fe->copygc,0,0,32767,32767,0,0); } static void resync_buttons(frontend *fe) { MENUBAR_ITEM *i; int act; for (i=fe->menubar_items;i;i=i->link) { act = !i->acttest || (*i->acttest)(fe->me); if (act != i->curact) { i->curact = act; draw_menubar_item(fe,i); } } } static void process_key(int x, int y, int b) { if (! midend_process_key(fe->me,x,y,b)) exit(0); resync_buttons(fe); } static void keystroke_play(XKeyEvent *e) { int b; KeySym ks; ks = XLookupKeysym(e,0); switch (ks) { case XK_Left: b = CURSOR_LEFT; break; case XK_Right: b = CURSOR_RIGHT; break; case XK_Up: b = CURSOR_UP; break; case XK_Down: b = CURSOR_DOWN; break; case XK_0: b = '0'; break; case XK_1: b = '1'; break; case XK_2: b = '2'; break; case XK_3: b = '3'; break; case XK_4: b = '4'; break; case XK_5: b = '5'; break; case XK_6: b = '6'; break; case XK_7: b = '7'; break; case XK_8: b = '8'; break; case XK_9: b = '9'; break; case XK_A: case XK_a: b = 'a'; break; case XK_B: case XK_b: b = 'b'; break; case XK_C: case XK_c: b = 'c'; break; case XK_D: case XK_d: b = 'd'; break; case XK_E: case XK_e: b = 'e'; break; case XK_F: case XK_f: b = 'f'; break; case XK_G: case XK_g: b = 'g'; break; case XK_H: case XK_h: b = 'h'; break; case XK_I: case XK_i: b = 'i'; break; case XK_J: case XK_j: b = 'j'; break; case XK_K: case XK_k: b = 'k'; break; case XK_L: case XK_l: b = 'l'; break; case XK_M: case XK_m: b = 'm'; break; case XK_N: case XK_n: b = 'n'; break; case XK_O: case XK_o: b = 'o'; break; case XK_P: case XK_p: b = 'p'; break; case XK_Q: case XK_q: b = 'q'; break; case XK_R: case XK_r: b = 'r'; break; case XK_S: case XK_s: b = 's'; break; case XK_T: case XK_t: b = 't'; break; case XK_U: case XK_u: b = 'u'; break; case XK_V: case XK_v: b = 'v'; break; case XK_W: case XK_w: b = 'w'; break; case XK_X: case XK_x: b = 'x'; break; case XK_Y: case XK_y: b = 'y'; break; case XK_Z: case XK_z: b = 'z'; break; case XK_space: b = ' '; break; case XK_Return: b = '\r'; break; case XK_Escape: b = '\e'; break; case XK_BackSpace: b = '\b'; break; case XK_Shift_L: case XK_Shift_R: case XK_Control_L: case XK_Control_R: return; // ignore break; default: printf("Unhandled keystroke: keycode=%d keysym=%lx\n",e->keycode,(long int)ks); return; break; } if (e->state & ShiftMask) b |= MOD_SHFT; if (e->state & ControlMask) b |= MOD_CTRL; process_key(0,0,b); } static void layout_dump_(LAYOUT *l, FILE *to, int indent) { fprintf(to,"%*s%p: min %dx%d, size %dx%d, loc %d,%d: ",indent,"",(void *)l,l->minsize.x,l->minsize.y,l->size.x,l->size.y,l->loc.x,l->loc.y); switch (l->type) { case LT_HBOX: fprintf(to,"HBOX"); if (0) { case LT_VBOX: fprintf(to,"VBOX"); } { int i; fprintf(to," stretch %d, containing %d\n",l->u.box.stretch,l->u.box.n); for (i=0;iu.box.n;i++) layout_dump_(l->u.box.v[i],to,indent+4); } break; case LT_GRID: { int x; int y; fprintf(to,"GRID %dx%d [border %d %d]\n",l->u.grid.nx,l->u.grid.ny,l->u.grid.bx,l->u.grid.by); fprintf(to,"%*scw:",indent+2,""); if (l->u.grid.flags & GF_EQUAL_X) fprintf(to," [%d]",l->u.grid.cw1); if (l->u.grid.cw) { for (x=0;xu.grid.nx;x++) fprintf(to," %d",l->u.grid.cw[x]); } else { fprintf(to," vector not set up yet"); } fprintf(to,"\n"); fprintf(to,"%*srh:",indent+2,""); if (l->u.grid.flags & GF_EQUAL_Y) fprintf(to," [%d]",l->u.grid.rh1); if (l->u.grid.rh) { for (y=0;yu.grid.ny;y++) fprintf(to," %d",l->u.grid.rh[y]); } else { fprintf(to," vector not set up yet"); } fprintf(to,"\n"); fprintf(to,"%*scx:",indent+2,""); if (l->u.grid.cx) { for (x=0;xu.grid.nx;x++) fprintf(to," %d",l->u.grid.cx[x]); } else { fprintf(to," vector not set up yet"); } fprintf(to,"\n"); fprintf(to,"%*sry:",indent+2,""); if (l->u.grid.ry) { for (y=0;yu.grid.ny;y++) fprintf(to," %d",l->u.grid.ry[y]); } else { fprintf(to," vector not set up yet"); } fprintf(to,"\n"); for (y=0;yu.grid.ny;y++) for (y=0;yu.grid.ny;y++) { for (x=0;xu.grid.nx;x++) { fprintf(to,"%*s[%d][%d]\n",indent+2,"",x,y); layout_dump_(l->u.grid.g[GRIDXY(x,y,&l->u.grid)],to,indent+4); } } } break; case LT_GLUE: { unsigned int v; int any; static const struct { unsigned int bit; const char *text; } bits[] = { { STRETCH_GAME, "GAME" }, { STRETCH_OTHER, "OTHER" }, { STRETCH_ALWAYS, "ALWAYS" }, { 0, 0 } }; int i; fprintf(to,"GLUE, min %d stretch ",l->u.glue.min); v = l->u.glue.stretchmask; any = 0; for (i=0;bits[i].bit;i++) { if (v & bits[i].bit) { fprintf(to,"%s%s",any?" | ":"",bits[i].text); any = 1; v &= ~bits[i].bit; } } if (! any) { fprintf(to,"%#x",v); } else if (v) { fprintf(to," | %#x",v); } fprintf(to,"\n"); } break; case LT_LEAF: fprintf(to,"LEAF, ops %s priv %p\n",l->u.leaf.ops->name,l->u.leaf.priv); break; case LT_WRITE_LOC: fprintf(to,"WRITE_LOC, to %p (value %d)\n",(void *)l->u.write_loc,*l->u.write_loc); break; DEFAULT_ABORT(); } } static void layout_dump(LAYOUT *top, FILE *to) { layout_dump_(top,to,0); } static void layout_dump_to(LAYOUT *, const char *) __attribute__((__used__)); static void layout_dump_to(LAYOUT *l, const char *fn) { FILE *f; f = fopen(fn,"w"); if (f) { layout_dump(l,f); fclose(f); } else { fprintf(stderr,"%s: can't open %s: %s\n",__progname,fn,strerror(errno)); } } static void leafop_null_init(void *arg __attribute__((__unused__)), LAYOUT *l __attribute__((__unused__))) { } static void leafop_null_post_create(void *arg __attribute__((__unused__)), Window w __attribute__((__unused__))) { } static void leafop_null_destroy(void *arg __attribute__((__unused__))) { } static void leafop_null_pre_update(void *arg __attribute__((__unused__)), LAYOUT *l __attribute__((__unused__))) { } static void leafop_null_post_update(void *arg __attribute__((__unused__))) { } static void leafop_abort_destroy(void *arg __attribute__((__unused__))) { abort(); } static void leafop_abort_pre_update(void *arg __attribute__((__unused__)), LAYOUT *l __attribute__((__unused__))) { abort(); } static void leafop_abort_post_update(void *arg __attribute__((__unused__))) { abort(); } static void leafop_justexpose_pre_create(void *arg __attribute__((__unused__)), LAYOUT *l __attribute__((__unused__)), unsigned long int *maskp, XSetWindowAttributes *attrp) { attrp->event_mask = ExposureMask; *maskp |= CWEventMask; } static LAYOUT *layout_new(void) { return(malloc(sizeof(LAYOUT))); } static LAYOUT *layout_setup_leaf(const LEAFOPS *ops, void *priv) { LAYOUT *l; l = layout_new(); l->type = LT_LEAF; l->u.leaf.ops = ops; l->u.leaf.priv = priv; (*ops->init)(priv,l); return(l); } static LAYOUT *layout_setup_glue(int min, unsigned int stretchmask) { LAYOUT *l; l = layout_new(); l->type = LT_GLUE; l->u.glue.min = min; l->u.glue.stretchmask = stretchmask; return(l); } static LAYOUT *layout_write_loc(int *to) { LAYOUT *l; l = layout_new(); l->type = LT_WRITE_LOC; l->u.write_loc = to; return(l); } static LAYOUT *layout_setup_box(LAYOUT_TYPE t, ...) #define LAYOUT_END ((LAYOUT *)0) { va_list ap; LAYOUT *l; LAYOUT *sub; int i; switch (t) { case LT_HBOX: case LT_VBOX: break; DEFAULT_ABORT(); } l = layout_new(); l->type = t; l->u.box.n = 0; va_start(ap,t); while (1) { sub = va_arg(ap,LAYOUT *); if (sub == LAYOUT_END) break; l->u.box.n ++; } va_end(ap); l->u.box.v = malloc(l->u.box.n*sizeof(*l->u.box.v)); i = 0; va_start(ap,t); while (1) { sub = va_arg(ap,LAYOUT *); if (sub == LAYOUT_END) break; l->u.box.v[i++] = sub; } va_end(ap); if (i != l->u.box.n) abort(); return(l); } static LAYOUT *layout_setup_grid(int nx, int ny, int bx, int by, unsigned int flags) { LAYOUT *l; int i; if ((nx < 1) || (ny < 1) || (bx < 0) || (by < 0) || (flags & ~GF__CANSET)) abort(); l = layout_new(); l->type = LT_GRID; l->u.grid.nx = nx; l->u.grid.bx = bx; l->u.grid.ny = ny; l->u.grid.by = by; l->u.grid.g = malloc(nx*ny*sizeof(LAYOUT *)); for (i=(nx*ny)-1;i>=0;i--) l->u.grid.g[i] = 0; l->u.grid.flags = flags; l->u.grid.cw = 0; l->u.grid.rh = 0; l->u.grid.cx = 0; l->u.grid.ry = 0; l->u.grid.bwx = 0; l->u.grid.bwy = 0; return(l); } static void layout_box_append(LAYOUT *l, ...) #define LAYOUT_END ((LAYOUT *)0) { va_list ap; LAYOUT *sub; int n; switch (l->type) { case LT_HBOX: case LT_VBOX: break; DEFAULT_ABORT(); } n = 0; va_start(ap,l); while (1) { sub = va_arg(ap,LAYOUT *); if (sub == LAYOUT_END) break; n ++; } va_end(ap); l->u.box.v = realloc(l->u.box.v,(l->u.box.n+n)*sizeof(*l->u.box.v)); va_start(ap,l); while (1) { sub = va_arg(ap,LAYOUT *); if (sub == LAYOUT_END) break; l->u.box.v[l->u.box.n++] = sub; n --; } va_end(ap); if (n != 0) abort(); } static void layout_grid_set(LAYOUT *l, int x, int y, LAYOUT *sub) { if ( (l->type != LT_GRID) || (x < 0) || (y < 0) || (x >= l->u.grid.nx) || (y >= l->u.grid.ny) ) abort(); l->u.grid.g[GRIDXY(x,y,&l->u.grid)] = sub; } static int *coord_p(LAYOUT_TYPE t, XY *xy) { switch (t) { case LT_HBOX: return(&xy->x); break; case LT_VBOX: return(&xy->y); break; DEFAULT_ABORT(); } } static int *other_coord_p(LAYOUT_TYPE t, XY *xy) { switch (t) { case LT_HBOX: return(&xy->y); break; case LT_VBOX: return(&xy->x); break; DEFAULT_ABORT(); } } static int choose_h_v(LAYOUT_TYPE t, int h, int v) { switch (t) { case LT_HBOX: return(h); break; case LT_VBOX: return(v); break; DEFAULT_ABORT(); } } static void layout_pass1(LAYOUT *l, unsigned int stretchmask) { switch (l->type) { case LT_HBOX: case LT_VBOX: { int sz; int psz; int i; LAYOUT *l2; int d; sz = 0; psz = 0; for (i=0;iu.box.n;i++) { l2 = l->u.box.v[i]; switch (l2->type) { case LT_LEAF: l2->minsize = (*l2->u.leaf.ops->minsize)(l2->u.leaf.priv); break; case LT_GLUE: l2->minsize.x = choose_h_v(l->type,l2->u.glue.min,0); l2->minsize.y = choose_h_v(l->type,0,l2->u.glue.min); break; case LT_WRITE_LOC: l2->minsize = (XY){.x=0,.y=0}; break; case LT_HBOX: case LT_VBOX: case LT_GRID: layout_pass1(l2,stretchmask); break; DEFAULT_ABORT(); } sz += *coord_p(l->type,&l2->minsize); d = *other_coord_p(l->type,&l2->minsize); if (d > psz) psz = d; } *coord_p(l->type,&l->minsize) = sz; *other_coord_p(l->type,&l->minsize) = psz; } break; case LT_GRID: { int x; int y; LAYOUT *l2; int *cw; int *rh; int cw1; int rh1; if (l->u.grid.flags & GF_DEBUG) { printf("grid before pass1\n"); layout_dump(l,stdout); } cw = malloc(l->u.grid.nx*sizeof(int)); for (x=l->u.grid.nx-1;x>=0;x--) cw[x] = 0; if (l->u.grid.flags & GF_EQUAL_X) cw1 = 0; rh = malloc(l->u.grid.ny*sizeof(int)); for (y=l->u.grid.ny-1;y>=0;y--) rh[y] = 0; if (l->u.grid.flags & GF_EQUAL_Y) rh1 = 0; for (y=l->u.grid.ny-1;y>=0;y--) { for (x=l->u.grid.nx-1;x>=0;x--) { l2 = l->u.grid.g[GRIDXY(x,y,&l->u.grid)]; switch (l2->type) { case LT_LEAF: l2->minsize = (*l2->u.leaf.ops->minsize)(l2->u.leaf.priv); break; case LT_HBOX: case LT_VBOX: case LT_GRID: layout_pass1(l2,stretchmask); break; DEFAULT_ABORT(); } if (l->u.grid.flags & GF_EQUAL_X) { if (l2->minsize.x > cw1) cw1 = l2->minsize.x; } else { if (l2->minsize.x > cw[x]) cw[x] = l2->minsize.x; } if (l->u.grid.flags & GF_EQUAL_Y) { if (l2->minsize.y > rh1) rh1 = l2->minsize.y; } else { if (l2->minsize.y > rh[y]) rh[y] = l2->minsize.y; } } } l->minsize.x = (l->u.grid.nx - 1) * l->u.grid.bx; l->minsize.y = (l->u.grid.ny - 1) * l->u.grid.by; if (l->u.grid.flags & GF_EQUAL_X) { l->minsize.x += l->u.grid.nx * cw1; l->u.grid.cw1 = cw1; } else { for (x=l->u.grid.nx-1;x>=0;x--) l->minsize.x += cw[x]; } l->u.grid.cw = cw; if (l->u.grid.flags & GF_EQUAL_Y) { l->minsize.y += l->u.grid.ny * rh1; l->u.grid.rh1 = rh1; } else { for (y=l->u.grid.ny-1;y>=0;y--) l->minsize.y += rh[y]; } l->u.grid.rh = rh; } break; DEFAULT_ABORT(); } } static void layout_pass2(LAYOUT *l, unsigned int stretchmask) { switch (l->type) { case LT_HBOX: case LT_VBOX: { LAYOUT *l2; int i; l->u.box.stretch = 0; for (i=0;iu.box.n;i++) { l2 = l->u.box.v[i]; switch (l2->type) { case LT_GLUE: if (stretchmask & l2->u.glue.stretchmask) l->u.box.stretch ++; break; case LT_WRITE_LOC: case LT_LEAF: break; case LT_HBOX: case LT_VBOX: case LT_GRID: layout_pass2(l2,stretchmask); break; DEFAULT_ABORT(); } } } break; case LT_GRID: { int i; int j; for (j=l->u.grid.ny-1;j>=0;j--) { for (i=l->u.grid.nx-1;i>=0;i--) { layout_pass2(l->u.grid.g[GRIDXY(i,j,&l->u.grid)],stretchmask); } } } break; case LT_LEAF: case LT_GLUE: case LT_WRITE_LOC: break; DEFAULT_ABORT(); } } static void layout_pass3(LAYOUT *l, int x, int y, int w, int h, unsigned int stretchmask) { l->loc.x = x; l->loc.y = y; l->size.x = w; l->size.y = h; switch (l->type) { case LT_HBOX: { int stretch; int sleft; LAYOUT *l2; int x2; int y2; int *cp; int g; int i; stretch = w - l->minsize.x; cp = &x2; if (0) { case LT_VBOX: stretch = h - l->minsize.y; cp = &y2; } sleft = l->u.box.stretch; x2 = x; y2 = y; for (i=0;iu.box.n;i++) { l2 = l->u.box.v[i]; switch (l2->type) { case LT_GLUE: if ((sleft > 1e-3) && (stretchmask & l2->u.glue.stretchmask)) { g = (stretch / sleft) + .5; stretch -= g; sleft --; g += l2->u.glue.min; } else { g = l2->u.glue.min; } layout_pass3(l2,x2,y2,choose_h_v(l->type,g,w),choose_h_v(l->type,h,g),stretchmask); *cp += g; break; case LT_WRITE_LOC: *l2->u.write_loc = choose_h_v(l->type,x2,y2); layout_pass3(l2,x2,y2,choose_h_v(l->type,0,w),choose_h_v(l->type,h,0),stretchmask); break; case LT_HBOX: case LT_VBOX: case LT_GRID: case LT_LEAF: layout_pass3(l2,x2,y2,choose_h_v(l->type,l2->minsize.x,w),choose_h_v(l->type,h,l2->minsize.y),stretchmask); *cp += choose_h_v(l->type,l2->minsize.x,l2->minsize.y); break; DEFAULT_ABORT(); } } } break; case LT_GRID: { int i; int j; int xspix; int yspix; int *cx; int *ry; int pad; int pix; int n; int c; if (l->u.grid.flags & GF_DEBUG) { printf("grid before pass3\n"); layout_dump(l,stdout); } xspix = w - l->minsize.x; yspix = h - l->minsize.y; cx = malloc(l->u.grid.nx*sizeof(int)); ry = malloc(l->u.grid.ny*sizeof(int)); c = 0; for (i=0,n=l->u.grid.nx;n>0;i++,n--) { if (i) c += l->u.grid.bx; cx[i] = x + c; pad = (xspix + (n >> 1)) / n; pix = ((l->u.grid.flags & GF_EQUAL_X) ? l->u.grid.cw1 : l->u.grid.cw[i]) + pad; c += pix; xspix -= pad; l->u.grid.cw[i] = pix; } if (c != w) abort(); c = 0; for (j=0,n=l->u.grid.ny;n>0;j++,n--) { if (j) c += l->u.grid.by; ry[j] = y + c; pad = (yspix + (n >> 1)) / n; pix = ((l->u.grid.flags & GF_EQUAL_Y) ? l->u.grid.rh1 : l->u.grid.rh[j]) + pad; c += pix; yspix -= pad; l->u.grid.rh[j] = pix; } if (c != h) abort(); for (j=l->u.grid.ny-1;j>=0;j--) { for (i=l->u.grid.nx-1;i>=0;i--) { layout_pass3(l->u.grid.g[GRIDXY(i,j,&l->u.grid)],cx[i],ry[j],l->u.grid.cw[i],l->u.grid.rh[j],stretchmask); } } l->u.grid.cx = cx; l->u.grid.ry = ry; if (l->u.grid.flags & GF_DEBUG) { printf("grid after pass3\n"); layout_dump(l,stdout); } } break; case LT_LEAF: case LT_GLUE: case LT_WRITE_LOC: break; DEFAULT_ABORT(); } } static void layout_arrange(LAYOUT *l, unsigned int stretchmask) { layout_pass1(l,stretchmask); layout_pass2(l,stretchmask); layout_pass3(l,0,0,l->minsize.x,l->minsize.y,stretchmask); } static void layout_leaf_create(LAYOUT *l, Window parent) { unsigned long int attrmask; XSetWindowAttributes attr; if (l->type != LT_LEAF) abort(); attrmask = 0; attr.background_pixel = fe->bgcolour.pixel; attrmask |= CWBackPixel; attr.border_pixel = 0; attrmask |= CWBorderPixel; attr.colormap = fe->wincmap; attrmask |= CWColormap; (*l->u.leaf.ops->pre_create)(l->u.leaf.priv,l,&attrmask,&attr); l->u.leaf.win = XCreateWindow(disp,parent,l->loc.x,l->loc.y,l->size.x,l->size.y,0,depth,InputOutput,CopyFromParent,attrmask,&attr); l->u.leaf.winloc = l->loc; l->u.leaf.winsize = l->size; (*l->u.leaf.ops->post_create)(l->u.leaf.priv,l->u.leaf.win); } static Window grid_border_window(Window parent, int x, int y, int w, int h) { unsigned long int attrmask; XSetWindowAttributes attr; attrmask = 0; attr.background_pixel = fe->bdcolour.pixel; attrmask |= CWBackPixel; attr.border_pixel = 0; attrmask |= CWBorderPixel; attr.colormap = fe->wincmap; attrmask |= CWColormap; return(XCreateWindow(disp,parent,x,y,w,h,0,depth,InputOutput,visual,attrmask,&attr)); } static void layout_create_walk(LAYOUT *l, Window parent) { switch (l->type) { case LT_HBOX: case LT_VBOX: { int i; for (i=0;iu.box.n;i++) layout_create_walk(l->u.box.v[i],parent); } break; case LT_GRID: { int x; int y; if ((l->u.grid.bx > 0) && (l->u.grid.nx > 1)) { l->u.grid.bwx = malloc((l->u.grid.nx-1)*sizeof(Window)); for (x=1;xu.grid.nx;x++) { l->u.grid.bwx[x-1] = grid_border_window(parent,l->u.grid.cx[x]-l->u.grid.bx,l->loc.y,l->u.grid.bx,l->size.y); } } if ((l->u.grid.by > 0) && (l->u.grid.ny > 1)) { l->u.grid.bwy = malloc((l->u.grid.ny-1)*sizeof(Window)); for (y=1;yu.grid.ny;y++) { l->u.grid.bwy[y-1] = grid_border_window(parent,l->loc.x,l->u.grid.ry[y]-l->u.grid.by,l->size.x,l->u.grid.by); } } for (y=0;yu.grid.ny;y++) { for (x=0;xu.grid.nx;x++) { layout_create_walk(l->u.grid.g[GRIDXY(x,y,&l->u.grid)],parent); } } } break; case LT_GLUE: case LT_WRITE_LOC: break; case LT_LEAF: layout_leaf_create(l,parent); break; DEFAULT_ABORT(); } } static void layout_create(LAYOUT *top, Window parent) { layout_create_walk(top,parent); } static void layout_leaf_destroy(LAYOUT *l) { (*l->u.leaf.ops->destroy)(l->u.leaf.priv); } static void layout_destroy_walk(LAYOUT *l) { switch (l->type) { case LT_HBOX: case LT_VBOX: { int i; for (i=0;iu.box.n;i++) layout_destroy_walk(l->u.box.v[i]); free(l->u.box.v); } break; case LT_GRID: { int x; int y; for (y=0;yu.grid.ny;y++) { for (x=0;xu.grid.nx;x++) { layout_destroy_walk(l->u.grid.g[GRIDXY(x,y,&l->u.grid)]); } } free(l->u.grid.cw); free(l->u.grid.rh); free(l->u.grid.cx); free(l->u.grid.ry); free(l->u.grid.bwx); free(l->u.grid.bwy); } break; case LT_GLUE: case LT_WRITE_LOC: break; case LT_LEAF: layout_leaf_destroy(l); break; DEFAULT_ABORT(); } free(l); } static void layout_destroy(LAYOUT *top, Window parent) { layout_destroy_walk(top); XDestroySubwindows(disp,parent); } static void layout_leaf_update(LAYOUT *l) { unsigned long int chgmask; XWindowChanges chg; if (l->type != LT_LEAF) abort(); chgmask = 0; (*l->u.leaf.ops->pre_update)(l->u.leaf.priv,l); if (l->u.leaf.winloc.x != l->loc.x) { chg.x = l->loc.x; chgmask |= CWX; } if (l->u.leaf.winloc.y != l->loc.y) { chg.y = l->loc.y; chgmask |= CWY; } if (l->u.leaf.winsize.x != l->size.x) { chg.width = l->size.x; chgmask |= CWWidth; } if (l->u.leaf.winsize.y != l->size.y) { chg.height = l->size.y; chgmask |= CWHeight; } if (chgmask) { XConfigureWindow(disp,l->u.leaf.win,chgmask,&chg); l->u.leaf.winloc = l->loc; l->u.leaf.winsize = l->size; } (*l->u.leaf.ops->post_update)(l->u.leaf.priv); } static void layout_update(LAYOUT *top) { void walk(LAYOUT *l) { switch (l->type) { case LT_HBOX: case LT_VBOX: { int i; for (i=0;iu.box.n;i++) walk(l->u.box.v[i]); } break; case LT_GRID: { int x; int y; for (y=0;yu.grid.ny;y++) { for (x=0;xu.grid.nx;x++) { walk(l->u.grid.g[GRIDXY(x,y,&l->u.grid)]); } } if ((l->u.grid.by > 0) && (l->u.grid.ny > 1)) { for (y=l->u.grid.ny-2;y>=0;y--) { XMoveResizeWindow(disp,l->u.grid.bwy[y],l->loc.x,l->u.grid.ry[y+1]-l->u.grid.by,l->size.x,l->u.grid.by); } } if ((l->u.grid.bx > 0) && (l->u.grid.nx > 1)) { for (x=l->u.grid.nx-2;x>=0;x--) { XMoveResizeWindow(disp,l->u.grid.bwx[x],l->u.grid.cx[x+1]-l->u.grid.bx,l->loc.y,l->u.grid.bx,l->size.y); } } } break; case LT_GLUE: case LT_WRITE_LOC: break; case LT_LEAF: layout_leaf_update(l); break; DEFAULT_ABORT(); } } walk(top); } static void leafop_border_pre_create( void *arg __attribute__((__unused__)), LAYOUT *l __attribute__((__unused__)), unsigned long int *maskp, XSetWindowAttributes *attrp ) { attrp->background_pixel = fe->bdcolour.pixel; *maskp |= CWBackPixel; } #define leafop_hborder_init leafop_null_init static XY leafop_hborder_minsize(void *mbiv __attribute__((__unused__))) { return((XY){.x=0,.y=borderwidth}); } #define leafop_hborder_pre_create leafop_border_pre_create #define leafop_hborder_post_create leafop_null_post_create #define leafop_hborder_destroy leafop_null_destroy #define leafop_hborder_pre_update leafop_null_pre_update #define leafop_hborder_post_update leafop_null_post_update static const LEAFOPS ops_hborder = LEAFOPS_INIT(hborder); #define leafop_vborder_init leafop_null_init static XY leafop_vborder_minsize(void *mbiv __attribute__((__unused__))) { return((XY){.x=borderwidth,.y=0}); } #define leafop_vborder_pre_create leafop_border_pre_create #define leafop_vborder_post_create leafop_null_post_create #define leafop_vborder_destroy leafop_null_destroy #define leafop_vborder_pre_update leafop_null_pre_update #define leafop_vborder_post_update leafop_null_post_update static const LEAFOPS ops_vborder = LEAFOPS_INIT(vborder); static void mpop_pop_down(MPOP *mp) { XUnmapWindow(disp,mp->win); layout_destroy(mp->l,mp->win); mp->up = 0; if (mp->id != AIO_NOID) { aio_remove_block(mp->id); mp->id = AIO_NOID; } } static void mpop_cancel(MPOP *mp) { mpop_pop_down(mp); (*mp->choose)(-1,CurrentTime); } static void ev_handle(XEvent *e, EVHANDLE *evh) { EVHENT *h; h = evh->root; while (1) { if (! h) return; if (e->xany.window < h->w) { h = h->l; } else if (e->xany.window > h->w) { h = h->r; } else { (*h->handler)(h->arg,e); return; } } } static void handle_event(XEvent *e) { switch (e->type) { default: break; case Expose: // XExposeEvent - xexpose ev_handle(e,&evh_expose); break; case MappingNotify: // XMappingEvent - xmapping XRefreshKeyboardMapping(&e->xmapping); break; case KeyPress: // XKeyPressedEvent - XKeyEvent - xkey (*keystroke)(&e->xkey); break; case ButtonPress: // XButtonPressedEvent - XButtonEvent - xbutton ev_handle(e,&evh_buttonpress); break; case ButtonRelease: // XButtonReleasedEvent - XButtonEvent - xbutton ev_handle(e,&evh_buttonrelease); break; case MotionNotify: // XPointerMovedEvent - XMotionEvent - xmotion ev_handle(e,&evh_motionnotify); break; case EnterNotify: // XEnterWindowEvent - XCrossingEvent - xcrossing ev_handle(e,&evh_enternotify); break; case LeaveNotify: // XLeaveWindowEvent - XCrossingEvent - xcrossing ev_handle(e,&evh_leavenotify); break; case ConfigureNotify: // XConfigureEvent - xconfigure ev_handle(e,&evh_configurenotify); break; } } static void rd_X(void *arg __attribute__((__unused__))) { XEvent e; while (XEventsQueued(disp,QueuedAfterFlush)) { XNextEvent(disp,&e); handle_event(&e); } } static int flush_X(void *arg __attribute__((__unused__))) { XFlush(disp); fflush(0); return(AIO_BLOCK_NIL); } static void setup_aio(void) { aio_poll_init(); aio_add_poll(XConnectionNumber(disp),&aio_rwtest_always,&aio_rwtest_never,&rd_X,0,0); aio_add_block(&flush_X,0); } static int ioerror_handler(Display *d) { return((*oldioerr)(d)); } static int error_handler(Display *d, XErrorEvent *e) { return((*olderr)(d,e)); } static void setup_error(void) { olderr = XSetErrorHandler(error_handler); oldioerr = XSetIOErrorHandler(ioerror_handler); } static void setup_db(void) { char *str; char *home; char hostname[256]; XrmDatabase db2; db = XrmGetStringDatabase(defaults); str = XResourceManagerString(disp); if (str) { db2 = XrmGetStringDatabase(str); XrmMergeDatabases(db2,&db); } home = getenv("HOME"); if (home) { str = malloc(strlen(home)+1+10+1); sprintf(str,"%s/.Xdefaults",home); db2 = XrmGetFileDatabase(str); if (db2) XrmMergeDatabases(db2,&db); free(str); gethostname(&hostname[0],(sizeof(hostname)/sizeof(hostname[0]))-1); hostname[(sizeof(hostname)/sizeof(hostname[0]))-1] = '\0'; str = malloc(strlen(home)+1+11+strlen(&hostname[0])+1); sprintf(str,"%s/.Xdefaults-%s",home,&hostname[0]); db2 = XrmGetFileDatabase(str); if (db2) XrmMergeDatabases(db2,&db); free(str); } if (rmstring) { db2 = XrmGetStringDatabase(rmstring); XrmMergeDatabases(db2,&db); } } static char *get_default_value(const char *name, const char *class) { char *type; XrmValue value; static char *n = 0; static char *c = 0; free(n); free(c); asprintf(&n,"%s.%s",av0bn,name); asprintf(&c,"Puzzle.%s",class); if (XrmGetResource(db,n,c,&type,&value) == False) return(0); return(value.addr); } static void maybeset(char **strp, char *str) { if (str && !*strp) *strp = str; } static void setup_numbers(void) { if (!bordermstr || !borderwstr || !menumstr) abort(); margin = atoi(bordermstr); borderwidth = atoi(borderwstr); menumargin = atoi(menumstr); } static void setup_visual(void) { int i; XVisualInfo *vinf; int nvinf; XVisualInfo *v; XVisualInfo template; long int mask; VisualID defid; vinf = 0; template.screen = XScreenNumberOfScreen(scr); defid = XVisualIDFromVisual(XDefaultVisualOfScreen(scr)); mask = VisualScreenMask; if (visualstr) { mask |= VisualClassMask; /* XXX accept *colour names here even though X doesn't? */ if (!strcasecmp(visualstr,"staticgray")) template.class = StaticGray; else if (!strcasecmp(visualstr,"grayscale")) template.class = GrayScale; else if (!strcasecmp(visualstr,"staticcolor")) template.class = StaticColor; else if (!strcasecmp(visualstr,"pseudocolor")) template.class = PseudoColor; else if (!strcasecmp(visualstr,"directcolor")) template.class = DirectColor; else if (!strcasecmp(visualstr,"truecolor")) template.class = TrueColor; else { unsigned long int id; char *cp; id = strtol(visualstr,&cp,0); if (*cp) { fprintf(stderr,"%s: %s: invalid visual option\n",__progname,visualstr); exit(1); } template.visualid = (VisualID) id; mask |= VisualIDMask; mask &= ~VisualClassMask; } } else { template.visualid = defid; mask |= VisualIDMask; } vinf = XGetVisualInfo(disp,mask,&template,&nvinf); if (vinf == 0) { if (visualstr) { fprintf(stderr,"%s: %s: no such visual found%s\n",__progname,visualstr,(ScreenCount(disp)==1)?"":" on this screen"); } else { fprintf(stderr,"%s: default visual not found?""?\n",__progname); } exit(1); } if (nvinf == 0) { fprintf(stderr,"%s: XGetVisualInfo succeeded but no visuals?!\n",__progname); exit(1); } if (visualstr && (mask & VisualIDMask)) { v = vinf; } else { v = 0; for (i=nvinf-1;i>=0;i--) { switch (vinf->class) { case StaticGray: case GrayScale: continue; break; case StaticColor: case TrueColor: if (vinf->bits_per_rgb < 4) continue; break; case PseudoColor: if (vinf->colormap_size < 16) continue; break; case DirectColor: if (vinf->colormap_size < 16) continue; break; } if ( (v == 0) || (vinf->visualid == defid) || (vinf->depth < v->depth) ) v = vinf; } if (v == 0) { fprintf(stderr,"%s: %s: can't find a suitable visual%s\n",__progname,visualstr,(ScreenCount(disp)==1)?"":" on this screen"); exit(1); } } visual = v->visual; if (v->visualid == defid) { defcmap = XDefaultColormapOfScreen(scr); depth = XDefaultDepthOfScreen(scr); } else { defcmap = None; { Pixmap pm; /* Grrr - shouldn't have to bother creating the pixmap! */ pm = XCreatePixmap(disp,rootwin,1,1,v->depth); XFreePixmap(disp,pm); } depth = v->depth; } XFree(vinf); } static void setup_colour_core(frontend *fe, XColor *col, void (*prwhat)(FILE *), void (*prsuff)(FILE *)) { while (XAllocColor(disp,fe->wincmap,col) == 0) { if (fe->wincmap != defcmap) { fprintf(stderr,"%s: can't allocate colormap cell for ",__progname); (*prwhat)(stderr); fprintf(stderr," colour"); (*prsuff)(stderr); fprintf(stderr,"\n"); exit(1); } fe->wincmap = XCopyColormapAndFree(disp,fe->wincmap); } } static void setup_colour_rgb(frontend *, XColor *, const char *, ...) __attribute__((__format__(__printf__,3,4))); static void setup_colour_rgb(frontend *fe, XColor *col, const char *whatfmt, ...) { va_list ap; void prwhat(FILE *f) { vfprintf(f,whatfmt,ap); } void prsuff(FILE *f __attribute__((__unused__))) { } va_start(ap,whatfmt); setup_colour_core(fe,col,&prwhat,&prsuff); va_end(ap); } static void setup_colour_text(frontend *, const char *, XColor *, const char *, ...) __attribute__((__format__(__printf__,4,5))); static void setup_colour_text(frontend *fe, const char *str, XColor *col, const char *whatfmt, ...) { va_list ap; void prwhat(FILE *f) { vfprintf(f,whatfmt,ap); } void prsuff(FILE *f __attribute__((__unused__))) { fprintf(f," `%s'",str); } va_start(ap,whatfmt); if (XParseColor(disp,fe->wincmap,str,col) == 0) { fprintf(stderr,"%s: bad ",__progname); vfprintf(stderr,whatfmt,ap); fprintf(stderr," colour `%s'\n",str); exit(1); } setup_colour_core(fe,col,&prwhat,&prsuff); va_end(ap); } static void set_nbio(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); } static void walk_choices(const char *s, void (*each)(int, const char *, int, void *), void *arg) { char sep; int x; char *n; sep = *s++; if (! sep) abort(); // XXX can this happen? x = 0; while (1) { n = index(s,sep); if (n) { (*each)(x,s,n-s,arg); } else { (*each)(x,s,strlen(s),arg); } if (! n) break; s = n + 1; x ++; } } static int evhandle_rebalance(EVHENT **up, EVHENT *uptr) { EVHENT *u; EVHENT *f; EVHENT *b; EVHENT *c; u = *up; if (uptr != u->u) abort(); switch (u->bal) { case 0: return(0); break; case -1: case 1: return(1); break; case -2: if (u->l->bal <= 0) { u->bal = -1 - u->l->bal; u->l->bal ++; *up = u->l; u->l->u = uptr; f = u->l->r; u->l->r = u; u->u = u->l; u->l = f; if (f) f->u = u; if (u->bal) return(1); } else if (u->l->bal > 0) { f = u->l->r; b = f->l; c = f->r; *up = f; f->u = uptr; f->l = u->l; f->l->u = f; f->r = u; u->u = f; f->l->r = b; if (b) b->u = f->l; u->l = c; if (c) c->u = u; f->l->bal = (f->bal > 0) ? -1 : 0; f->r->bal = (f->bal < 0) ? 1 : 0; f->bal = 0; } return(0); break; case 2: if (u->r->bal >= 0) { u->bal = 1 - u->r->bal; u->r->bal --; *up = u->r; u->r->u = uptr; f = u->r->l; u->r->l = u; u->u = u->r; u->r = f; if (f) f->u = u; if (u->bal) return(1); } else if (u->r->bal < 0) { f = u->r->l; b = f->r; c = f->l; *up = f; f->u = uptr; f->r = u->r; f->r->u = f; f->l = u; u->u = f; f->r->l = b; if (b) b->u = f->r; u->r = c; if (c) c->u = u; f->r->bal = (f->bal < 0) ? 1 : 0; f->l->bal = (f->bal > 0) ? -1 : 0; f->bal = 0; } return(0); break; } abort(); } static INSERT_STATUS evhandle_insert(EVHENT *new, EVHENT **up, EVHENT *uptr) { EVHENT *u; u = *up; if (! u) { *up = new; new->u = uptr; return(INSERT_DEEPEN); } if (new->w < u->w) { switch (evhandle_insert(new,&u->l,u)) { case INSERT_SAME: return(INSERT_SAME); break; case INSERT_DEEPEN: u->bal --; return(evhandle_rebalance(up,uptr)?INSERT_DEEPEN:INSERT_SAME); break; DEFAULT_ABORT(); } } else if (new->w > u->w) { switch (evhandle_insert(new,&u->r,u)) { case INSERT_SAME: return(INSERT_SAME); break; case INSERT_DEEPEN: u->bal ++; return(evhandle_rebalance(up,uptr)?INSERT_DEEPEN:INSERT_SAME); break; DEFAULT_ABORT(); } } else { abort(); } } static void evh_add(Window w, EVHANDLE *evh, void (*handler)(void *, XEvent *), void *arg) { EVHENT *e; e = malloc(sizeof(EVHENT)); e->w = w; e->handler = handler; e->arg = arg; e->l = 0; e->r = 0; e->bal = 0; evhandle_insert(e,&evh->root,0); } static void evh_remove(Window w, EVHANDLE *evh) { EVHENT *e; EVHENT *f; EVHENT *u; EVHENT *l; EVHENT *r; EVHENT **up; int dr; EVHENT *s; e = evh->root; while (1) { if (! e) abort(); if (w < e->w) { e = e->l; } else if (w > e->w) { e = e->r; } else { break; } } f = e; u = f->u; l = f->l; r = f->r; up = u ? (u->l == f) ? &u->l : &u->r : &evh->root; dr = u ? (u->l == f) ? 1 : -1 : 0; if (! f->r) { if (! f->l) { *up = 0; } else { f->l->u = u; *up = f->l; } } else if (! f->l) { f->r->u = u; *up = f->r; } else if (! f->r->l) { f->r->l = f->l; f->l->u = f->r; f->r->u = u; *up = f->r; u = f->r; u->bal = f->bal; dr = -1; } else { s = f->r; while (s->l) s = s->l; s->u->l = s->r; if (s->r) s->r->u = s->u; s->l = f->l; f->l->u = s; s->r = f->r; f->r->u = s; s->bal = f->bal; f = s->u; s->u = u; *up = s; u = f; dr = 1; } if (u) { u->bal += dr; while <"delrebal"> (1) { switch (u->bal) { case 0: if (u->u) { u->u->bal += (u == u->u->l) ? 1 : -1; u = u->u; continue; } break <"delrebal">; break; case -1: case 1: break <"delrebal">; break; case -2: case 2: { EVHENT *v; v = u->u; if (v) { int ob; ob = v->bal; v->bal += (u == v->l) ? 1 : -1; if (evhandle_rebalance((u==v->l)?&v->l:&v->r,v)) { v->bal = ob; break <"delrebal">; } u = v; continue; } evhandle_rebalance(&evh->root,0); break <"delrebal">; } break; DEFAULT_ABORT(); } } } free(e); } static void rd_timer(void *fev) { frontend *fe; struct timersock_event tse[64]; int n; TIME now; TIME d; fe = fev; n = read(fe->timerfd,&tse[0],sizeof(tse)); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; } fprintf(stderr,"%s: timer socket read: %s\n",__progname,strerror(errno)); exit(1); } if (! fe->running) return; now = get_now(); if (now < fe->lasttime) { // clockwarp? fe->lasttime = now; return; } d = now - fe->lasttime; fe->lasttime = now; midend_timer(fe->me,d*1e-6); } static void mpop_init(MPOP *mp) { mp->fe = 0; mp->nitems = 0; mp->items = 0; mp->itemsa = 0; mp->up = 0; mp->win = None; mp->id = AIO_NOID; } static void mpop_reset(MPOP *mp, frontend *fe, void (*choose)(int, Time)) { int i; for (i=mp->nitems-1;i>=0;i--) free(mp->items[i].text); mp->nitems = 0; mp->fe = fe; mp->choose = choose; } static void mpop_append_entry(MPOP *mp, const char *text) { MPOP_ITEM *i; if (mp->nitems >= mp->itemsa) { mp->itemsa = mp->nitems + 8; mp->items = realloc(mp->items,mp->itemsa*sizeof(*mp->items)); } i = &mp->items[mp->nitems]; i->menu = mp; i->inx = mp->nitems; i->textlen = strlen(text); i->text = malloc(i->textlen); bcopy(text,i->text,i->textlen); mp->nitems ++; } static void mpop_append_presets(MPOP *mp, struct preset_menu *pm) { int i; for (i=0;in_entries;i++) { mpop_append_entry(mp,pm->entries[i].title); } } static void leafop_mpop_item_init(void *mpiv, LAYOUT *l) { ((MPOP_ITEM *)mpiv)->l = l; } static XY leafop_mpop_item_minsize(void *mpiv) { MPOP_ITEM *mpi; XCharStruct cs; mpi = mpiv; XTextExtents(fe->font,mpi->text,mpi->textlen,XTE_JUNK,&cs); mpi->textw = cs.width; return((XY){.x=mpi->textw,.y=fe->baselineskip}); } static void leafop_mpop_item_pre_create(void *mpiv, LAYOUT *l, unsigned long int *maskp, XSetWindowAttributes *attrp) { attrp->event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask; *maskp |= CWEventMask; l->loc.x = 0; l->size.x = ((MPOP_ITEM *)mpiv)->menu->l->size.x; } static void mpop_redraw_item(MPOP_ITEM *i) { frontend *fe; fe = i->menu->fe; if (i->inx == i->menu->curitem) { XSetForeground(disp,fe->copygc,fe->bgcolour.pixel); XSetBackground(disp,fe->copygc,fe->fgcolour.pixel); } else { XSetForeground(disp,fe->copygc,fe->fgcolour.pixel); XSetBackground(disp,fe->copygc,fe->bgcolour.pixel); } XCopyPlane(disp,i->pm,i->win,fe->copygc,0,0,i->l->size.x,i->l->size.y,0,0,1); } static void expose_mpop_item(void *mpiv, XEvent *e) { MPOP_ITEM *mpi; if (e->xexpose.count != 0) return; mpi = mpiv; if (! mpi->menu->up) return; mpop_redraw_item(mpi); } static void enternotify_mpop_item(void *mpiv, XEvent *e __attribute__((__unused__))) { MPOP_ITEM *mpi; int oldcur; mpi = mpiv; if (! mpi->menu->up) return; oldcur = mpi->menu->curitem; if (oldcur != mpi->inx) { mpi->menu->curitem = mpi->inx; if (oldcur >= 0) mpop_redraw_item(&mpi->menu->items[oldcur]); mpop_redraw_item(mpi); } } static void leavenotify_mpop_item(void *mpiv, XEvent *e __attribute__((__unused__))) { MPOP_ITEM *mpi; mpi = mpiv; if (! mpi->menu->up) return; if (mpi->menu->curitem == mpi->inx) { mpi->menu->curitem = -1; mpop_redraw_item(mpi); } } static void buttonpress_mpop_item(void *mpiv, XEvent *e) { MPOP_ITEM *mpi; int i; void (*choose)(int, Time); mpi = mpiv; if (! mpi->menu->up) return; i = mpi->inx; choose = mpi->menu->choose; mpop_pop_down(mpi->menu); (*choose)(i,e->xbutton.time); } static void leafop_mpop_item_post_create(void *mpiv, Window w) { MPOP_ITEM *mpi; frontend *fe; mpi = mpiv; fe = mpi->menu->fe; evh_add(w,&evh_expose,&expose_mpop_item,mpi); evh_add(w,&evh_enternotify,&enternotify_mpop_item,mpi); evh_add(w,&evh_leavenotify,&leavenotify_mpop_item,mpi); evh_add(w,&evh_buttonpress,&buttonpress_mpop_item,mpi); mpi->win = w; mpi->pm = XCreatePixmap(disp,rootwin,mpi->l->size.x,mpi->l->size.y,1); XSetForeground(disp,fe->bitgc,0); XFillRectangle(disp,mpi->pm,fe->bitgc,0,0,32767,32767); XSetBackground(disp,fe->bitgc,0); XSetForeground(disp,fe->bitgc,1); XDrawString(disp,mpi->pm,fe->bitgc,(mpi->l->size.x-mpi->textw)/2,((mpi->l->size.y-fe->baselineskip)/2)+fe->font->ascent,mpi->text,mpi->textlen); } static int mpop_at_xy(MPOP *mp, int x, int y) { #define M (menumargin+borderwidth) if ( (x < -M) || (y < -M) || (x >= mp->l->size.x+M) || (y >= mp->l->size.y+M) ) { mpop_cancel(mp); return(1); } #undef M return(0); } static void leafop_mpop_item_destroy(void *mpiv) { MPOP_ITEM *mpi; mpi = mpiv; evh_remove(mpi->win,&evh_expose); evh_remove(mpi->win,&evh_enternotify); evh_remove(mpi->win,&evh_leavenotify); evh_remove(mpi->win,&evh_buttonpress); } #define leafop_mpop_item_pre_update leafop_abort_pre_update #define leafop_mpop_item_post_update leafop_abort_post_update static const LEAFOPS ops_mpop_item = LEAFOPS_INIT(mpop_item); static int mpop_block(void *mpv) { MPOP *mp; int rv; Window ret_root; Window ret_child; int ret_rootx; int ret_rooty; int ret_winx; int ret_winy; unsigned int ret_mask; mp = mpv; rv = AIO_BLOCK_NIL; if (mp->wantquery) { /* * When we return() here, we do so, rather than just assigning to * rv, so that the rest of the code doesn't run. (At this * writing, there is no "rest of the code"; this is * future-proofing.) */ mp->wantquery = 0; if (XQueryPointer(disp,mp->win,&ret_root,&ret_child,&ret_rootx,&ret_rooty,&ret_winx,&ret_winy,&ret_mask) != True) { mpop_cancel(mp); return(AIO_BLOCK_LOOP); } if (mpop_at_xy(mp,ret_winx,ret_winy)) return(AIO_BLOCK_LOOP); rv = AIO_BLOCK_LOOP; } return(rv); } static void motionnotify_mpop(void *mpv, XEvent *e) { MPOP *mp; mp = mpv; if (! mp->up) return; if (e->xmotion.is_hint) mp->wantquery = 1; mpop_at_xy(mp,e->xmotion.x,e->xmotion.y); } static void mpop_pop_up(MPOP *mp, Time when) { frontend *fe; int x; int y; int w; int h; Window ret_root; Window ret_child; int ret_rootx; int ret_rooty; int ret_winx; int ret_winy; unsigned int ret_mask; LAYOUT *l; fe = mp->fe; if (mp->up) abort(); if (mp->nitems < 1) abort(); if (XGrabPointer(disp,rootwin,True,ButtonPressMask|ButtonReleaseMask|PointerMotionMask|PointerMotionHintMask|EnterWindowMask|LeaveWindowMask,GrabModeSync,GrabModeAsync,None,None,when) != GrabSuccess) { return; } if (XQueryPointer(disp,rootwin,&ret_root,&ret_child,&ret_rootx,&ret_rooty,&ret_winx,&ret_winy,&ret_mask) != True) { XUngrabPointer(disp,CurrentTime); return; } l = layout_setup_box(LT_VBOX,LAYOUT_END); for (x=0;xnitems;x++) { layout_box_append(l, layout_setup_box(LT_HBOX, layout_setup_glue(0,STRETCH_ALWAYS), layout_setup_leaf(&ops_mpop_item,mp->items+x), layout_setup_glue(0,STRETCH_ALWAYS), LAYOUT_END), LAYOUT_END); } mp->l = layout_setup_box(LT_HBOX, layout_setup_glue(margin,STRETCH_ALWAYS), l, layout_setup_glue(margin,STRETCH_ALWAYS), LAYOUT_END); layout_arrange(mp->l,STRETCH_ALWAYS); w = mp->l->size.x + (2 * borderwidth); h = mp->l->size.y + (2 * borderwidth); x = ret_rootx - (w / 2); y = ret_rooty - (h / 2); if (x+w > scrwidth) x = scrwidth - w; if (y+h > scrheight) y = scrheight - h; if (x < 0) x = 0; if (y < 0) y = 0; w -= 2 * borderwidth; h -= 2 * borderwidth; if (mp->win == None) { unsigned long attrmask; XSetWindowAttributes attr; attrmask = 0; attr.background_pixel = fe->bgcolour.pixel; attrmask |= CWBackPixel; attr.border_pixel = fe->bdcolour.pixel; attrmask |= CWBorderPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = 0; attrmask |= CWEventMask; attr.override_redirect = True; attrmask |= CWOverrideRedirect; attr.colormap = fe->wincmap; attrmask |= CWColormap; attr.cursor = fe->arrowcurs; attrmask |= CWCursor; mp->win = XCreateWindow(disp,rootwin,x,y,w,h,borderwidth,depth,InputOutput,visual,attrmask,&attr); evh_add(mp->win,&evh_motionnotify,&motionnotify_mpop,mp); } else { XMoveResizeWindow(disp,mp->win,x,y,w,h); } mp->curitem = -1; layout_create(mp->l,mp->win); XMapSubwindows(disp,mp->win); XMapRaised(disp,mp->win); if (XGrabPointer(disp,mp->win,True,ButtonPressMask|ButtonReleaseMask|PointerMotionMask|PointerMotionHintMask|EnterWindowMask|LeaveWindowMask,GrabModeSync,GrabModeAsync,None,None,when) != GrabSuccess) { XUngrabPointer(disp,CurrentTime); mpop_pop_down(mp); return; } XAllowEvents(disp,AsyncPointer,when); mp->up = 1; mp->wantquery = 1; if (mp->id != AIO_NOID) abort(); mp->id = aio_add_block(&mpop_block,mp); } static void new_game(frontend *fe) { int w; int h; XWindowChanges chg; midend_new_game(fe->me); w = scrwidth; h = scrheight; midend_size(fe->me,&w,&h,false); if ((w != fe->draww) || (h != fe->drawh)) { fe->draww = w; fe->drawh = h; layout_arrange(fe->layout_game,STRETCH_ALWAYS|STRETCH_GAME|STRETCH_OTHER); w = fe->layout_game->size.x; h = fe->layout_game->size.y; chg.x = (scrwidth - w - (2 * borderwidth)) / 2; chg.y = (scrheight - h - (2 * borderwidth)) / 2; chg.width = w; chg.height = h; fe->topw = w; fe->toph = h; fe->layout_game->loc.x = chg.x; fe->layout_game->loc.y = chg.y; XReconfigureWMWindow(disp,fe->topwin,XScreenNumberOfScreen(scr),CWX|CWY|CWWidth|CWHeight,&chg); layout_update(fe->layout_game); } midend_redraw(fe->me); } static void menu_click_new(frontend *fe, Time when __attribute__((__unused__))) { new_game(fe); } static void menu_click_game(frontend *fe, Time when __attribute__((__unused__))) { char *s; s = midend_get_game_id(fe->me); printf("%s\n",s); free(s); } static void menu_click_seed(frontend *fe, Time when __attribute__((__unused__))) { char *s; s = midend_get_random_seed(fe->me); if (s) { printf("%s\n",s); } else { printf("No random seed available\n"); } free(s); } static void editor_recopy(EDITSTATE *es) { XSetForeground(disp,fe->copygc,fe->fgcolour.pixel); XSetBackground(disp,fe->copygc,fe->bgcolour.pixel); XCopyPlane(disp,es->pm,es->win,fe->copygc,0,0,es->winw,fe->baselineskip,0,0,1); } static void editor_redraw(EDITSTATE *es) { XCharStruct cs; int pcw; int x; if ( (es->dispbl == es->bl) && (es->dispcurs == es->curs) && (es->dispcursx == es->cursx) && (es->dispscroll == es->scroll) && (es->dispcursup != EDIT_DISPCURSUP_FULLREDRAW) && !bcmp(es->dispb,es->b,es->bl) ) { if (es->dispcursup == es->cursup) return; XSetForeground(disp,fe->bitgc,es->cursup?1:0); XFillRectangle(disp,es->pm,fe->bitgc,es->cursx,0,1,fe->baselineskip); XSetForeground(disp,fe->drawgc,es->cursup?fe->fgcolour.pixel:fe->bgcolour.pixel); XFillRectangle(disp,es->win,fe->drawgc,es->cursx,0,1,fe->baselineskip); es->dispcursup = es->cursup; return; } if ((es->ba < 0) || (es->bl < 0) || (es->curs < 0) || (es->curs > es->bl) || (es->scroll < 0)) abort(); if (es->dispba < es->bl) { free(es->dispb); es->dispba = es->bl; es->dispb = malloc(es->dispba); } es->dispbl = es->bl; es->dispcurs = es->curs; es->dispcursx = es->cursx; es->dispscroll = es->scroll; bcopy(es->b,es->dispb,es->bl); es->cursup = 1; es->dispcursup = 1; XSetForeground(disp,fe->bitgc,0); XFillRectangle(disp,es->pm,fe->bitgc,0,0,es->winw,fe->baselineskip); XSetForeground(disp,fe->bitgc,1); XTextExtents(fe->font,es->b,es->curs,XTE_JUNK,&cs); pcw = cs.width; if (pcw+3 > es->scroll+es->winw-fe->spacewidth) { es->scroll = pcw + fe->spacewidth - es->winw; } if (pcw < es->scroll+fe->spacewidth) { es->scroll = pcw - fe->spacewidth; if (es->scroll < 0) es->scroll = 0; } x = - es->scroll; if (es->curs > 0) { XDrawString(disp,es->pm,fe->bitgc,x,fe->font->ascent,es->b,es->curs); x += pcw; } es->cursx = x + 1; XFillRectangle(disp,es->pm,fe->bitgc,x+1,0,1,fe->baselineskip); x += 3; if (es->curs < es->bl) { XDrawString(disp,es->pm,fe->bitgc,x,fe->font->ascent,es->b+es->curs,es->bl-es->curs); } editor_recopy(es); } static void editor_rd_flash(void *esv) { EDITSTATE *es; struct timersock_event tse[64]; int n; es = esv; n = read(es->flashfd,&tse[0],sizeof(tse)); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: can't read from editor flash socket: %s\n",__progname,strerror(errno)); exit(1); } if (n < 1) abort(); es->cursup = ! es->cursup; editor_redraw(es); } // editor_start() assumes this does editor_redraw() static void editor_setcurs_click(EDITSTATE *es, int clickx) { int x; int i; int d; XCharStruct cs; int bestd; int bestx; int besti; if (clickx >= es->winw) clickx = es->winw - 1; if (clickx < 0) clickx = 0; x = - es->scroll; bestx = x; bestd = abs(x-clickx); besti = 0; i = 0; while (i < es->bl) { XTextExtents(fe->font,es->b+i,1,XTE_JUNK,&cs); x += cs.width; i ++; d = abs(x-clickx); if (d < bestd) { bestx = x; bestd = d; besti = i; } // XXX what if the font has chars with negative width? if (x > clickx) break; } es->curs = besti; es->cursx = -1; editor_redraw(es); } static void editor_done(EDITSTATE *es, int save) { XEvent e; es->live = 0; aio_remove_poll(es->flashid); close(es->flashfd); XUngrabKeyboard(disp,CurrentTime); XFreePixmap(disp,es->pm); (*es->done)(es,save,es->donearg); XClearWindow(disp,es->win); e.type = Expose; e.xexpose.window = es->win; e.xexpose.x = 0; e.xexpose.y = 0; e.xexpose.width = es->winw; e.xexpose.height = fe->baselineskip; e.xexpose.count = 0; ev_handle(&e,&evh_expose); free(es->b); keystroke = &keystroke_play; } static void editor_delete(EDITSTATE *es, int at, int n) { if ((at < 0) || (at+n > es->bl)) abort(); if (at+n < es->bl) bcopy(es->b+at+n,es->b+at,es->bl-(at+n)); es->bl -= n; } static void editor_insert(EDITSTATE *es, int at, const void *data, int n) { if ((at < 0) || (at > es->bl)) abort(); if (es->bl+n > es->ba) { es->ba = es->bl + n + 32; es->b = realloc(es->b,es->ba); } if (at < es->bl) bcopy(es->b+at,es->b+at+n,es->bl-at); bcopy(data,es->b+at,n); es->bl += n; } static void editor_type(EDITSTATE *es, unsigned char c) { switch (c) { case 10: // ^J case 13: // ^M editor_done(es,1); return; break; case 1: // ^A es->curs = 0; break; case 2: // ^B if (es->curs > 0) es->curs --; break; case 4: // ^D if (es->curs < es->bl) editor_delete(es,es->curs,1); break; case 5: // ^E es->curs = es->bl; break; case 6: // ^F if (es->curs < es->bl) es->curs ++; break; case 8: // ^H case 127: // DEL if (es->curs > 0) { es->curs --; editor_delete(es,es->curs,1); } break; case 11: // ^K es->bl = es->curs; break; case 12: // ^L es->dispcursup = EDIT_DISPCURSUP_FULLREDRAW; break; case 20: // ^T if (es->curs >= 2) { char c; c = es->b[es->curs-2]; es->b[es->curs-2] = es->b[es->curs-1]; es->b[es->curs-1] = c; } break; case 21: // ^U case 24: // ^X es->curs = 0; es->bl = 0; break; default: if ((c < 32) || ((c > 126) && (c < 160))) { XBell(disp,0); } else { editor_insert(es,es->curs,&c,1); es->curs ++; } break; } } static void keystroke_edit(XKeyEvent *e) { char kbuf[256]; KeySym ks; int nk; int i; nk = XLookupString(e,&kbuf[0],sizeof(kbuf),&ks,0); for (i=0;ilive) abort(); if (XGrabKeyboard(disp,win,False,GrabModeAsync,GrabModeAsync,time) != GrabSuccess) { XBell(disp,0); return; } es->live = 1; es->win = win; es->pm = XCreatePixmap(disp,win,winw,fe->baselineskip,1); es->winw = winw; es->xoff = xoff; es->yoff = yoff; es->ba = len + 20; es->b = malloc(es->ba); bcopy(text,es->b,len); es->bl = len; es->done = done; es->donearg = donearg; es->cursup = 0; es->flashfd = socket(AF_TIMER,SOCK_STREAM,0); set_nbio(es->flashfd); itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 250000; itv.it_value = itv.it_interval; write(es->flashfd,&itv,sizeof(itv)); es->flashid = aio_add_poll(es->flashfd,&aio_rwtest_always,&aio_rwtest_never,&editor_rd_flash,0,es); es->dispb = 0; es->dispba = 0; es->dispbl = 0; es->dispcurs = -1; es->dispcursx = -1; es->dispscroll = -1; es->dispcursup = EDIT_DISPCURSUP_FULLREDRAW; // other disp* don't matter keystroke = &keystroke_edit; editor_setcurs_click(es,startx); // does editor_redraw() } static void cbox_init(CBOX *cb) { cb->fe = 0; cb->cfg = 0; cb->title = 0; cb->titlelen = 0; cb->titlew = 0; cb->titlewin = None; cb->cfgn = 0; cb->boxx = 0; cb->l = 0; cb->topwin = None; cb->curitem = -1; cb->up = 0; } static void cbox_pop_down(CBOX *cb) { int i; XUnmapWindow(disp,cb->topwin); layout_destroy(cb->l,cb->topwin); cb->up = 0; for (i=cb->cfgn-1;i>=0;i--) { switch (cb->cfg[i].type) { case C_STRING: case C_BOOLEAN: break; case C_CHOICES: free(cb->boxx[i].u.choices.choices); break; DEFAULT_ABORT(); } } free_cfg(cb->cfg); free(cb->title); } static void cbox_cancel(CBOX *cb) { cbox_pop_down(cb); } static void count_choices(int inx __attribute__((__unused__)), const char *str __attribute__((__unused__)), int len __attribute__((__unused__)), void *iv) { ((int *)iv)[0] ++; } static void setup_cboxxchoice(int inx, const char *s, int l, void *ccv) { CBOXXCHOICE *cc; XCharStruct cs; cc = ((CBOXXCHOICE *)ccv) + inx; if (inx != cc->cx) abort(); cc->text = s; cc->textlen = l; XTextExtents(fe->font,s,l,XTE_JUNK,&cs); cc->textw = cs.width; } #define leafop_config_label_init leafop_null_init static XY leafop_config_label_minsize(void *cbxv) { return((XY){.x=((CBOXX *)cbxv)->labelw,.y=fe->baselineskip}); } #define leafop_config_label_pre_create leafop_justexpose_pre_create static void expose_config_label(void *cbxv, XEvent *e) { CBOXX *cbx; if (e->xexpose.count != 0) return; cbx = cbxv; XSetForeground(disp,fe->drawgc,fe->fgcolour.pixel); XDrawString(disp,cbx->labelw,fe->drawgc,0,fe->font->ascent,cbx->cb->cfg[cbx->i].name,cbx->namelen); } static void leafop_config_label_post_create(void *cbxv, Window w) { ((CBOXX *)cbxv)->labelw = w; evh_add(w,&evh_expose,&expose_config_label,cbxv); } static void leafop_config_label_destroy(void *cbxv) { evh_remove(((CBOXX *)cbxv)->labelw,&evh_expose); } #define leafop_config_label_pre_update leafop_null_pre_update #define leafop_config_label_post_update leafop_null_post_update static const LEAFOPS ops_config_label = LEAFOPS_INIT(config_label); static void leafop_config_string_input_init(void *cbxv, LAYOUT *l) { ((CBOXX *)cbxv)->u.string.l = l; } static XY leafop_config_string_input_minsize(void *cbxv __attribute__((__unused__))) { return((XY){.x=fe->spacewidth*20,.y=fe->baselineskip}); } static void leafop_config_string_input_pre_create(void *cbxv, LAYOUT *l, unsigned long int *maskp, XSetWindowAttributes *attrp) { attrp->event_mask = ExposureMask | ButtonPressMask; *maskp |= CWEventMask; l->size.x = ((CBOXX *)cbxv)->u.string.extend_r - l->loc.x; } static void expose_config_string_input(void *cbxv, XEvent *e) { CBOXX *cbx; if (e->xexpose.count != 0) return; cbx = cbxv; if (es.live && (es.win == cbx->u.string.iwin)) { editor_recopy(&es); return; } XSetForeground(disp,fe->drawgc,fe->fgcolour.pixel); XDrawString(disp,cbx->u.string.iwin,fe->drawgc,0,fe->font->ascent,cbx->cb->cfg[cbx->i].u.string.sval,cbx->u.string.slen); } static void editdone_config_string_input(EDITSTATE *es, int save, void *cbxv) { CBOXX *cbx; char *s; char **cpp; cbx = cbxv; if (save) { cpp = &cbx->cb->cfg[cbx->i].u.string.sval; s = malloc(es->bl+1); bcopy(es->b,s,es->bl); s[es->bl] = '\0'; cbx->u.string.slen = es->bl; free(*cpp); *cpp = s; } } static void buttonpress_config_string_input(void *cbxv, XEvent *e) { CBOXX *cbx; cbx = cbxv; if (es.live) { if (es.win == cbx->u.string.iwin) { editor_setcurs_click(&es,e->xbutton.x); return; } else { editor_done(&es,1); } } editor_start(&es,cbx->u.string.iwin,cbx->u.string.l->size.x,0,0,e->xbutton.x,cbx->cb->cfg[cbx->i].u.string.sval,cbx->u.string.slen,e->xbutton.time,&editdone_config_string_input,cbxv); } static void leafop_config_string_input_post_create(void *cbxv, Window w) { ((CBOXX *)cbxv)->u.string.iwin = w; evh_add(w,&evh_expose,&expose_config_string_input,cbxv); evh_add(w,&evh_buttonpress,&buttonpress_config_string_input,cbxv); } static void leafop_config_string_input_destroy(void *cbxv) { CBOXX *cbx; cbx = cbxv; if (es.live && (es.win == cbx->u.string.iwin)) editor_done(&es,0); evh_remove(cbx->u.string.iwin,&evh_expose); evh_remove(cbx->u.string.iwin,&evh_buttonpress); } #define leafop_config_string_input_pre_update leafop_null_pre_update #define leafop_config_string_input_post_update leafop_null_post_update static const LEAFOPS ops_config_string_input = LEAFOPS_INIT(config_string_input); static void leafop_config_boolean_button_init(void *cbxbv, LAYOUT *l) { ((CBOXXBOOL *)cbxbv)->l = l; } static XY leafop_config_boolean_button_minsize(void *cbxbv) { return((XY){.x=((CBOXXBOOL *)cbxbv)->textw,.y=fe->baselineskip}); } static void leafop_config_boolean_button_pre_create(void *cbxbv, LAYOUT *l, unsigned long int *maskp, XSetWindowAttributes *attrp) { CBOXXBOOL *cbxb; cbxb = cbxbv; l->loc.x = cbxb->extend_l; l->loc.y = cbxb->extend_u; l->size.x = cbxb->extend_r - cbxb->extend_l; l->size.y = cbxb->extend_d - cbxb->extend_u; attrp->event_mask = ExposureMask | ButtonPressMask; *maskp |= CWEventMask; } static Pixmap config_boolean_cache_pm(CBOXXBOOL *cbxb, Pixmap *pp, int act) { Pixmap pm; pm = *pp; if (pm == None) { pm = XCreatePixmap(disp,rootwin,cbxb->l->size.x,cbxb->l->size.y,1); XSetForeground(disp,fe->bitgc,0); XFillRectangle(disp,pm,fe->bitgc,0,0,cbxb->l->size.x,cbxb->l->size.y); XSetForeground(disp,fe->bitgc,1); if (act) { XFillRectangle(disp,pm,fe->bitgc,margin,margin,cbxb->l->size.x-(2*margin),cbxb->l->size.y-(2*margin)); XSetForeground(disp,fe->bitgc,0); } XDrawString(disp,pm,fe->bitgc,(cbxb->l->size.x-cbxb->textw)/2,((cbxb->l->size.y-fe->baselineskip)/2)+fe->font->ascent,cbxb->text,cbxb->textlen); *pp = pm; } return(pm); } static void redraw_config_boolean_button(CBOXXBOOL *cbxb) { Pixmap *pp; int act; act = cbxb->cbx->cb->cfg[cbxb->cbx->i].u.boolean.bval ? cbxb->yn : !cbxb->yn; pp = act ? &cbxb->pmsel : &cbxb->pmdesel; XSetForeground(disp,fe->copygc,fe->fgcolour.pixel); XSetBackground(disp,fe->copygc,fe->bgcolour.pixel); XCopyPlane(disp,config_boolean_cache_pm(cbxb,pp,act),cbxb->win,fe->copygc,0,0,cbxb->l->size.x,cbxb->l->size.y,0,0,1); } static void expose_config_boolean_button(void *cbxbv, XEvent *e) { if (e->xexpose.count != 0) return; redraw_config_boolean_button(cbxbv); } static void buttonpress_config_boolean_button(void *cbxbv, XEvent *e __attribute__((__unused__))) { CBOXXBOOL *cbxb; cbxb = cbxbv; if (cbxb->yn) { if (cbxb->cbx->cb->cfg[cbxb->cbx->i].u.boolean.bval) { return; } else { cbxb->cbx->cb->cfg[cbxb->cbx->i].u.boolean.bval = true; } } else { if (cbxb->cbx->cb->cfg[cbxb->cbx->i].u.boolean.bval) { cbxb->cbx->cb->cfg[cbxb->cbx->i].u.boolean.bval = false; } else { return; } } redraw_config_boolean_button(&cbxb->cbx->u.boolean.yes); redraw_config_boolean_button(&cbxb->cbx->u.boolean.no); } static void leafop_config_boolean_button_post_create(void *cbxbv, Window w) { CBOXXBOOL *cbxb; cbxb = cbxbv; cbxb->win = w; cbxb->pmsel = None; cbxb->pmdesel = None; evh_add(w,&evh_expose,&expose_config_boolean_button,cbxbv); evh_add(w,&evh_buttonpress,&buttonpress_config_boolean_button,cbxbv); } static void leafop_config_boolean_button_destroy(void *cbxbv) { CBOXXBOOL *cbxb; cbxb = cbxbv; evh_remove(cbxb->win,&evh_expose); evh_remove(cbxb->win,&evh_buttonpress); if (cbxb->pmsel != None) XFreePixmap(disp,cbxb->pmsel); if (cbxb->pmdesel != None) XFreePixmap(disp,cbxb->pmdesel); } #define leafop_config_boolean_button_pre_update leafop_null_pre_update #define leafop_config_boolean_button_post_update leafop_null_post_update static const LEAFOPS ops_config_boolean_button = LEAFOPS_INIT(config_boolean_button); static void leafop_config_choice_init(void *cbxcv, LAYOUT *l) { ((CBOXXCHOICE *)cbxcv)->l = l; } static XY leafop_config_choice_minsize(void *cbxcv) { return((XY){.x=((CBOXXCHOICE *)cbxcv)->textw,.y=fe->baselineskip}); } static void leafop_config_choice_pre_create(void *cbxcv, LAYOUT *l, unsigned long int *maskp, XSetWindowAttributes *attrp) { CBOXXCHOICE *cbxc; cbxc = cbxcv; l->loc.x = cbxc->extend_l; l->loc.y = cbxc->extend_u; l->size.x = cbxc->extend_r - cbxc->extend_l; l->size.y = cbxc->extend_d - cbxc->extend_u; attrp->event_mask = ExposureMask | ButtonPressMask; *maskp |= CWEventMask; } static Pixmap config_choice_cache_pm(CBOXXCHOICE *cbxc, Pixmap *pp, int act) { Pixmap pm; pm = *pp; if (pm == None) { pm = XCreatePixmap(disp,rootwin,cbxc->l->size.x,cbxc->l->size.y,1); XSetForeground(disp,fe->bitgc,0); XFillRectangle(disp,pm,fe->bitgc,0,0,cbxc->l->size.x,cbxc->l->size.y); XSetForeground(disp,fe->bitgc,1); if (act) { XFillRectangle(disp,pm,fe->bitgc,0,0,cbxc->l->size.x,cbxc->l->size.y); XSetForeground(disp,fe->bitgc,0); } XDrawString(disp,pm,fe->bitgc,(cbxc->l->size.x-cbxc->textw)/2,((cbxc->l->size.y-fe->baselineskip)/2)+fe->font->ascent,cbxc->text,cbxc->textlen); *pp = pm; } return(pm); } static void redraw_config_choice(CBOXXCHOICE *cbxc) { Pixmap *pp; int act; act = (cbxc->cbx->cb->cfg[cbxc->cbx->i].u.choices.selected == cbxc->cx); pp = act ? &cbxc->pmsel : &cbxc->pmdesel; XSetForeground(disp,fe->copygc,fe->fgcolour.pixel); XSetBackground(disp,fe->copygc,fe->bgcolour.pixel); XCopyPlane(disp,config_choice_cache_pm(cbxc,pp,act),cbxc->win,fe->copygc,0,0,cbxc->l->size.x,cbxc->l->size.y,0,0,1); } static void expose_config_choice(void *cbxcv, XEvent *e) { if (e->xexpose.count != 0) return; redraw_config_choice(cbxcv); } static void buttonpress_config_choice(void *cbxcv, XEvent *e __attribute__((__unused__))) { CBOXXCHOICE *cbxc; int old; cbxc = cbxcv; old = cbxc->cbx->cb->cfg[cbxc->cbx->i].u.choices.selected; if (old == cbxc->cx) return; cbxc->cbx->cb->cfg[cbxc->cbx->i].u.choices.selected = cbxc->cx; redraw_config_choice(&cbxc->cbx->u.choices.choices[old]); redraw_config_choice(cbxc); } static void leafop_config_choice_post_create(void *cbxcv, Window w) { CBOXXCHOICE *cbxc; cbxc = cbxcv; cbxc->win = w; evh_add(w,&evh_expose,&expose_config_choice,cbxcv); evh_add(w,&evh_buttonpress,&buttonpress_config_choice,cbxcv); } static void leafop_config_choice_destroy(void *cbxcv) { CBOXXCHOICE *cbxc; cbxc = cbxcv; evh_remove(cbxc->win,&evh_expose); evh_remove(cbxc->win,&evh_buttonpress); } #define leafop_config_choice_pre_update leafop_null_pre_update #define leafop_config_choice_post_update leafop_null_post_update static const LEAFOPS ops_config_choice = LEAFOPS_INIT(config_choice); #define leafop_config_title_init leafop_null_init static XY leafop_config_title_minsize(void *cbv) { return((XY){.x=((CBOX *)cbv)->titlew,.y=fe->baselineskip}); } #define leafop_config_title_pre_create leafop_justexpose_pre_create static void redraw_config_title(CBOX *cb) { XSetForeground(disp,fe->drawgc,fe->fgcolour.pixel); XDrawString(disp,cb->titlewin,fe->drawgc,0,fe->font->ascent,cb->title,cb->titlelen); } static void expose_config_title(void *cbv, XEvent *e) { if (e->xexpose.count != 0) return; redraw_config_title(cbv); } static void leafop_config_title_post_create(void *cbv, Window w) { ((CBOX *)cbv)->titlewin = w; evh_add(w,&evh_expose,&expose_config_title,cbv); } static void leafop_config_title_destroy(void *cbv) { evh_remove(((CBOX *)cbv)->titlewin,&evh_expose); } #define leafop_config_title_pre_update leafop_null_pre_update #define leafop_config_title_post_update leafop_null_post_update static const LEAFOPS ops_config_title = LEAFOPS_INIT(config_title); static void leafop_custom_button_init(void *cbbv, LAYOUT *l) { ((CBOXBUTTON *)cbbv)->l = l; } static XY leafop_custom_button_minsize(void *cbbv) { return((XY){.x=((CBOXBUTTON *)cbbv)->textw,.y=fe->baselineskip}); } static void leafop_custom_button_pre_create(void *cbbv, LAYOUT *l, unsigned long int *maskp, XSetWindowAttributes *attrp) { CBOXBUTTON *cbb; cbb = cbbv; l->loc.x = cbb->extend_l; l->loc.y = cbb->extend_u; l->size.x = cbb->extend_r - cbb->extend_l; l->size.y = cbb->extend_d - cbb->extend_u; attrp->event_mask = ExposureMask | ButtonPressMask; *maskp |= CWEventMask; } static void redraw_custom_button(CBOXBUTTON *cbb) { XSetForeground(disp,fe->drawgc,fe->fgcolour.pixel); XDrawString(disp,cbb->win,fe->drawgc,(cbb->l->size.x-cbb->textw)/2,((cbb->l->size.y-fe->baselineskip)/2)+fe->font->ascent,cbb->text,cbb->textlen); } static void expose_custom_button(void *cbbv, XEvent *e) { if (e->xexpose.count != 0) return; redraw_custom_button(cbbv); } static void buttonpress_custom_button(void *cbbv, XEvent *e __attribute__((__unused__))) { frontend *fe; CBOXBUTTON *cbb; const char *err; cbb = cbbv; fe = cbb->cb->fe; if (cbb->isgood) { if (es.live) editor_done(&es,1); err = midend_set_config(fe->me,CFG_SETTINGS,cbb->cb->cfg); if (err) { printf("Reconfig error: %s\n",err); return; } new_game(fe); } cbox_cancel(cbb->cb); } static void leafop_custom_button_post_create(void *cbbv, Window w) { ((CBOXBUTTON *)cbbv)->win = w; evh_add(w,&evh_expose,&expose_custom_button,cbbv); evh_add(w,&evh_buttonpress,&buttonpress_custom_button,cbbv); } static void leafop_custom_button_destroy(void *cbbv) { CBOXBUTTON *cbb; cbb = cbbv; evh_remove(cbb->win,&evh_expose); evh_remove(cbb->win,&evh_buttonpress); } #define leafop_custom_button_pre_update leafop_null_pre_update #define leafop_custom_button_post_update leafop_null_post_update static const LEAFOPS ops_custom_button = LEAFOPS_INIT(custom_button); static void cboxxbool_init(CBOXXBOOL *cbxb, CBOXX *cbx, int yn) { static int w_y = -1; static int w_n = -1; if (w_y < 0) { XCharStruct cs; XTextExtents(fe->font,"Yes",3,XTE_JUNK,&cs); w_y = cs.width; XTextExtents(fe->font,"Noes",2,XTE_JUNK,&cs); w_n = cs.width; } cbxb->cbx = cbx; cbxb->yn = yn; cbxb->text = yn ? "Yes" : "No"; cbxb->textlen = yn ? 3 : 2; cbxb->textw = yn ? w_y : w_n; } static void popup_custom_menu(frontend *fe, Time when) { CBOX *cb; Window ret_root; Window ret_child; int ret_rootx; int ret_rooty; int ret_winx; int ret_winy; unsigned int ret_mask; LAYOUT *g; int i; int j; int w; int h; int x; int y; LAYOUT *lhs; LAYOUT *rhs; CBOXX *cbx; XCharStruct cs; cb = &fe->cbox; if (cb->up) abort(); if (XGrabPointer(disp,rootwin,True,ButtonPressMask|ButtonReleaseMask|PointerMotionMask|PointerMotionHintMask|EnterWindowMask|LeaveWindowMask,GrabModeSync,GrabModeAsync,None,None,when) != GrabSuccess) { return; } if (XQueryPointer(disp,rootwin,&ret_root,&ret_child,&ret_rootx,&ret_rooty,&ret_winx,&ret_winy,&ret_mask) != True) { XUngrabPointer(disp,CurrentTime); return; } cb->fe = fe; cb->cfg = midend_get_config(fe->me,CFG_SETTINGS,&cb->title); cb->titlelen = strlen(cb->title); XTextExtents(fe->font,cb->title,cb->titlelen,XTE_JUNK,&cs); cb->titlew = cs.width; for (i=0;cb->cfg[i].type!=C_END;i++) ; cb->cfgn = i; cb->boxx = malloc(i*sizeof(CBOXX)); for (i=cb->cfgn-1;i>=0;i--) { cbx = &cb->boxx[i]; cbx->cb = cb; cbx->i = i; cbx->namelen = strlen(cb->cfg[i].name); XTextExtents(fe->font,cb->cfg[i].name,cbx->namelen,XTE_JUNK,&cs); cbx->labelw = cs.width; switch (cb->cfg[i].type) { case C_STRING: break; case C_BOOLEAN: break; case C_CHOICES: cbx->u.choices.nc = 0; walk_choices(cb->cfg[i].u.choices.choicenames,&count_choices,&cbx->u.choices.nc); cbx->u.choices.choices = malloc(cbx->u.choices.nc*sizeof(CBOXXCHOICE)); for (j=cbx->u.choices.nc-1;j>=0;j--) { CBOXXCHOICE *cbxc; cbxc = &cbx->u.choices.choices[j]; cbxc->cbx = cbx; cbxc->cx = j; // text, textlen, and textw are set in setup_cboxxchoice // win is set up in leafop_config_choice_post_create cbxc->pmsel = None; cbxc->pmdesel = None; } walk_choices(cb->cfg[i].u.choices.choicenames,&setup_cboxxchoice,cbx->u.choices.choices); break; DEFAULT_ABORT(); } } g = layout_setup_grid(2,cb->cfgn,borderwidth,borderwidth,0); for (i=0;icfgn;i++) { cbx = &cb->boxx[i]; lhs = layout_setup_box(LT_VBOX, layout_setup_glue(margin,STRETCH_ALWAYS), layout_setup_box(LT_HBOX, layout_setup_glue(margin,STRETCH_ALWAYS), layout_setup_leaf(&ops_config_label,cbx), layout_setup_glue(margin,0), LAYOUT_END), layout_setup_glue(margin,STRETCH_ALWAYS), LAYOUT_END); switch (cb->cfg[i].type) { case C_STRING: cbx->u.string.slen = strlen(cbx->cb->cfg[cbx->i].u.string.sval); rhs = layout_setup_box(LT_VBOX, layout_setup_glue(margin,STRETCH_ALWAYS), layout_setup_box(LT_HBOX, layout_setup_glue(margin,0), layout_setup_leaf(&ops_vborder,0), layout_setup_box(LT_VBOX, layout_setup_leaf(&ops_hborder,0), layout_setup_glue(margin,STRETCH_ALWAYS), layout_setup_box(LT_HBOX, layout_setup_glue(margin,0), layout_setup_leaf(&ops_config_string_input,cbx), layout_setup_glue(0,STRETCH_ALWAYS), layout_write_loc(&cbx->u.string.extend_r), layout_setup_glue(margin,0), LAYOUT_END), layout_setup_glue(margin,STRETCH_ALWAYS), layout_setup_leaf(&ops_hborder,0), LAYOUT_END), layout_setup_leaf(&ops_vborder,0), layout_setup_glue(margin,STRETCH_ALWAYS), LAYOUT_END), layout_setup_glue(margin,STRETCH_ALWAYS), LAYOUT_END); break; case C_BOOLEAN: cboxxbool_init(&cbx->u.boolean.yes,cbx,1); cboxxbool_init(&cbx->u.boolean.no,cbx,0); rhs = layout_setup_box(LT_HBOX, layout_write_loc(&cbx->u.boolean.yes.extend_l), layout_setup_glue(margin+1,STRETCH_ALWAYS), layout_setup_box(LT_VBOX, layout_write_loc(&cbx->u.boolean.yes.extend_u), layout_setup_glue(margin+1,STRETCH_ALWAYS), layout_setup_leaf(&ops_config_boolean_button,&cbx->u.boolean.yes), layout_setup_glue(margin+1,STRETCH_ALWAYS), layout_write_loc(&cbx->u.boolean.yes.extend_d), LAYOUT_END), layout_setup_glue(margin+1,STRETCH_ALWAYS), layout_write_loc(&cbx->u.boolean.yes.extend_r), layout_setup_leaf(&ops_vborder,0), layout_write_loc(&cbx->u.boolean.no.extend_l), layout_setup_glue(margin+1,STRETCH_ALWAYS), layout_setup_box(LT_VBOX, layout_write_loc(&cbx->u.boolean.no.extend_u), layout_setup_glue(margin+1,STRETCH_ALWAYS), layout_setup_leaf(&ops_config_boolean_button,&cbx->u.boolean.no), layout_setup_glue(margin+1,STRETCH_ALWAYS), layout_write_loc(&cbx->u.boolean.no.extend_d), LAYOUT_END), layout_setup_glue(margin+1,STRETCH_ALWAYS), layout_write_loc(&cbx->u.boolean.no.extend_r), LAYOUT_END); break; case C_CHOICES: { int j; CBOXXCHOICE *cbxc; rhs = layout_setup_box(LT_VBOX,layout_setup_glue(margin,STRETCH_ALWAYS),LAYOUT_END); for (j=0;ju.choices.nc;j++) { cbxc = &cbx->u.choices.choices[j]; layout_box_append(rhs, layout_write_loc(&cbxc->extend_u), layout_setup_glue(1,0), layout_setup_box(LT_HBOX, layout_setup_glue(margin,0), layout_write_loc(&cbxc->extend_l), layout_setup_glue(1,0), layout_setup_leaf(&ops_config_choice,cbxc), layout_setup_glue(1,0), layout_write_loc(&cbxc->extend_r), layout_setup_glue(margin,STRETCH_ALWAYS), LAYOUT_END), layout_setup_glue(1,0), layout_write_loc(&cbxc->extend_d), layout_setup_glue(margin,0), LAYOUT_END); } layout_box_append(rhs,layout_setup_glue(0,STRETCH_ALWAYS),LAYOUT_END); } break; DEFAULT_ABORT(); } layout_grid_set(g,0,i,lhs); layout_grid_set(g,1,i,rhs); } cb->cfgn = i; cb->l = layout_setup_box(LT_VBOX, layout_setup_glue(margin,0), layout_setup_box(LT_HBOX, layout_setup_glue(margin,STRETCH_ALWAYS), layout_setup_leaf(&ops_config_title,cb), layout_setup_glue(margin,STRETCH_ALWAYS), LAYOUT_END), layout_setup_glue(fe->baselineskip,0), layout_setup_leaf(&ops_hborder,0), g, layout_setup_leaf(&ops_hborder,0), layout_setup_glue(fe->baselineskip,0), layout_setup_leaf(&ops_hborder,0), layout_setup_box(LT_HBOX, layout_write_loc(&fe->custom_cancel.extend_l), layout_setup_glue(margin,STRETCH_ALWAYS), layout_setup_box(LT_VBOX, layout_write_loc(&fe->custom_cancel.extend_u), layout_setup_glue(margin,STRETCH_ALWAYS), layout_setup_leaf(&ops_custom_button,&fe->custom_cancel), layout_setup_glue(margin,STRETCH_ALWAYS), layout_write_loc(&fe->custom_cancel.extend_d), LAYOUT_END), layout_setup_glue(margin,STRETCH_ALWAYS), layout_write_loc(&fe->custom_cancel.extend_r), layout_setup_leaf(&ops_vborder,0), layout_write_loc(&fe->custom_done.extend_l), layout_setup_glue(margin,STRETCH_ALWAYS), layout_setup_box(LT_VBOX, layout_write_loc(&fe->custom_done.extend_u), layout_setup_glue(margin,STRETCH_ALWAYS), layout_setup_leaf(&ops_custom_button,&fe->custom_done), layout_setup_glue(margin,STRETCH_ALWAYS), layout_write_loc(&fe->custom_done.extend_d), LAYOUT_END), layout_setup_glue(margin,STRETCH_ALWAYS), layout_write_loc(&fe->custom_done.extend_r), LAYOUT_END), LAYOUT_END); layout_arrange(cb->l,STRETCH_ALWAYS); w = borderwidth + cb->l->size.x + borderwidth; h = borderwidth + cb->l->size.y + borderwidth; x = ret_rootx - (w / 2); y = ret_rooty - (w / 2); if (x+w > scrwidth) x = scrwidth - w; if (y+h > scrheight) y = scrheight - h; if (x < 0) x = 0; if (y < 0) y = 0; w -= 2 * borderwidth; h -= 2 * borderwidth; if (cb->topwin == None) { unsigned long attrmask; XSetWindowAttributes attr; attrmask = 0; attr.background_pixel = fe->bgcolour.pixel; attrmask |= CWBackPixel; attr.border_pixel = fe->bdcolour.pixel; attrmask |= CWBorderPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = 0; attrmask |= CWEventMask; attr.override_redirect = True; attrmask |= CWOverrideRedirect; attr.colormap = fe->wincmap; attrmask |= CWColormap; attr.cursor = fe->arrowcurs; attrmask |= CWCursor; cb->topwin = XCreateWindow(disp,rootwin,x,y,w,h,borderwidth,depth,InputOutput,visual,attrmask,&attr); } else { XMoveResizeWindow(disp,cb->topwin,x,y,w,h); } cb->curitem = -1; layout_create(cb->l,cb->topwin); XMapSubwindows(disp,cb->topwin); XMapRaised(disp,cb->topwin); if (XGrabPointer(disp,cb->topwin,True,ButtonPressMask|ButtonReleaseMask|PointerMotionMask|PointerMotionHintMask|EnterWindowMask|LeaveWindowMask,GrabModeSync,GrabModeAsync,None,None,when) != GrabSuccess) { XUngrabPointer(disp,CurrentTime); cbox_pop_down(cb); return; } XAllowEvents(disp,AsyncPointer,when); cb->up = 1; } static void choose_config(int n, Time when) { struct preset_menu_entry *e; if (n < 0) return; if (n >= fe->cur_presets->n_entries) { popup_custom_menu(fe,when); return; } e = &fe->cur_presets->entries[n]; if (e->submenu) { fe->cur_presets = e->submenu; mpop_reset(&fe->mpop,fe,&choose_config); mpop_append_presets(&fe->mpop,fe->cur_presets); mpop_pop_up(&fe->mpop,when); } else if (e->params) { midend_set_params(fe->me,e->params); new_game(fe); } } static void menu_click_config(frontend *fe, Time when) { fe->cur_presets = fe->presets; mpop_reset(&fe->mpop,fe,&choose_config); mpop_append_presets(&fe->mpop,fe->presets); if (fe->flags & FEF_CUSTOM) mpop_append_entry(&fe->mpop,"Custom..."); mpop_pop_up(&fe->mpop,when); } static void menu_click_solve(frontend *fe __attribute__((__unused__)), Time when __attribute__((__unused__))) { process_key(0,0,UI_SOLVE); } static void menu_click_redo(frontend *fe, Time when __attribute__((__unused__))) { if (midend_can_redo(fe->me)) { process_key(0,0,UI_REDO); } else { XBell(disp,0); } } static void menu_click_undo(frontend *fe, Time when __attribute__((__unused__))) { if (midend_can_undo(fe->me)) { process_key(0,0,UI_UNDO); } else { XBell(disp,0); } } static void menu_click_quit(frontend *fe __attribute__((__unused__)), Time when __attribute__((__unused__))) { exit(0); } static void leafop_menubar_item_init(void *mbiv, LAYOUT *l) { ((MENUBAR_ITEM *)mbiv)->l = l; } static XY leafop_menubar_item_minsize(void *mbiv) { return((XY){.x=margin+((MENUBAR_ITEM *)mbiv)->textw+margin,.y=margin+fe->baselineskip+margin}); } static void menubar_item_extend_size(MENUBAR_ITEM *mbi, LAYOUT *l) { if (mbi->extend_l >= 0) { l->size.x += l->loc.x - mbi->extend_l; l->loc.x = mbi->extend_l; } if (mbi->extend_r >= 0) { l->size.x = mbi->extend_r - l->loc.x; } if (l->size.x < 1) abort(); } static void leafop_menubar_item_pre_create(void *mbiv, LAYOUT *l, unsigned long int *maskp, XSetWindowAttributes *attrp) { MENUBAR_ITEM *mbi; mbi = mbiv; attrp->event_mask = ExposureMask | ButtonPressMask; *maskp |= CWEventMask; menubar_item_extend_size(mbi,l); } static void expose_menubar_item(void *mbiv, XEvent *e) { if (e->xexpose.count != 0) return; draw_menubar_item(fe,mbiv); } static void buttonpress_menubar_item(void *mbiv, XEvent *e) { (*((MENUBAR_ITEM *)mbiv)->click)(fe,e->xbutton.time); } static void leafop_menubar_item_post_create(void *mbiv, Window w) { MENUBAR_ITEM *mbi; mbi = mbiv; mbi->win = w; mbi->pmsize = (XY){.x=0,.y=0}; mbi->actpm = None; mbi->inactpm = None; evh_add(w,&evh_expose,&expose_menubar_item,mbiv); evh_add(w,&evh_buttonpress,&buttonpress_menubar_item,mbiv); } #define leafop_menubar_item_destroy leafop_abort_destroy static void leafop_menubar_item_pre_update(void *mbiv, LAYOUT *l) { menubar_item_extend_size(mbiv,l); } #define leafop_menubar_item_post_update leafop_null_post_update static const LEAFOPS ops_menubar_item = LEAFOPS_INIT(menubar_item); static void leafop_gamewin_init(void *fev, LAYOUT *l) { if (fev != (void *)fe) abort(); fe->gl = l; } static XY leafop_gamewin_minsize(void *fev) { frontend *fe; fe = fev; return((XY){.x=fe->draww,.y=fe->drawh}); } static void gamewin_extend_size(frontend *fe, LAYOUT *l) { if (l != fe->gl) abort(); l->loc.x = fe->extend_l; l->loc.y = fe->extend_u; l->size.x = fe->extend_r - fe->extend_l; l->size.y = fe->extend_d - fe->extend_u; } static void leafop_gamewin_pre_create(void *fev, LAYOUT *l, unsigned long int *maskp, XSetWindowAttributes *attrp) { if (fev != (void *)fe) abort(); attrp->event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask; *maskp |= CWEventMask; gamewin_extend_size(fe,l); } static void expose_gamewin(void *fev, XEvent *e) { if (fev != fe) abort(); copyarea(disp,fe->drawpix,fe->drawwin,fe->copygc,e->xexpose.x,e->xexpose.y,e->xexpose.width,e->xexpose.height,e->xexpose.x,e->xexpose.y); } static void buttonpress_gamewin(void *fev, XEvent *e) { int b; if (fev != fe) abort(); switch (e->xbutton.button) { case 1: b = LEFT_BUTTON; break; case 2: b = MIDDLE_BUTTON; break; case 3: b = RIGHT_BUTTON; break; default: return; break; } process_key(e->xbutton.x,e->xbutton.y,b); } static void buttonrelease_gamewin(void *fev, XEvent *e) { int b; if (fev != fe) abort(); switch (e->xbutton.button) { case 1: b = LEFT_RELEASE; break; case 2: b = MIDDLE_RELEASE; break; case 3: b = RIGHT_RELEASE; break; default: return; break; } process_key(e->xbutton.x,e->xbutton.y,b); } static void motionnotify_gamewin(void *fev, XEvent *e) { int b; if (fev != (void *)fe) abort(); if (e->xmotion.state & Button1Mask) { b = LEFT_DRAG; } else if (e->xmotion.state & Button2Mask) { b = MIDDLE_DRAG; } else if (e->xmotion.state & Button3Mask) { b = RIGHT_DRAG; } else { return; } process_key(e->xmotion.x,e->xmotion.y,b); } static void leafop_gamewin_post_create(void *fev, Window w) { if (fev != (void *)fe) abort(); fe->drawwin = w; fe->drawpix = XCreatePixmap(disp,fe->drawwin,fe->draww,fe->drawh,depth); fe->drawpsize = (XY){.x=fe->draww,.y=fe->drawh}; evh_add(w,&evh_expose,&expose_gamewin,fe); evh_add(w,&evh_buttonpress,&buttonpress_gamewin,fe); evh_add(w,&evh_buttonrelease,&buttonrelease_gamewin,fe); evh_add(w,&evh_motionnotify,&motionnotify_gamewin,fe); } #define leafop_gamewin_destroy leafop_abort_destroy static void leafop_gamewin_pre_update(void *fev, LAYOUT *l) { gamewin_extend_size(fev,l); } static void leafop_gamewin_post_update(void *fev) { if (fev != (void *)fe) abort(); if ((fe->gl->size.x != fe->drawpsize.x) || (fe->gl->size.y != fe->drawpsize.y)) { XFreePixmap(disp,fe->drawpix); fe->drawpsize = fe->gl->size; fe->drawpix = XCreatePixmap(disp,fe->drawwin,fe->gl->size.x,fe->gl->size.y,depth); } } static const LEAFOPS ops_gamewin = LEAFOPS_INIT(gamewin); #define leafop_statusbar_init leafop_null_init static XY leafop_statusbar_minsize(void *fev) { return((XY){.x=0,.y=((frontend *)fev)->baselineskip}); } static void leafop_statusbar_pre_create(void *fev, LAYOUT *l, unsigned long int *maskp, XSetWindowAttributes *attrp) { if (fev != (void *)fe) abort(); if (! (fe->flags & FEF_STATBAR)) abort(); attrp->event_mask = ExposureMask; *maskp |= CWEventMask; l->size.x = fe->statusbar_r - l->loc.x; if (l->size.x < 1) abort(); } static void expose_statwin(void *fev, XEvent *e) { if (e->xexpose.count != 0) return; if (fev != (void *)fe) abort(); draw_statbar_text(fev); } static void leafop_statusbar_post_create(void *fev, Window w) { if (fev != (void *)fe) abort(); if (! (fe->flags & FEF_STATBAR)) abort(); fe->statwin = w; evh_add(w,&evh_expose,&expose_statwin,fe); } #define leafop_statusbar_destroy leafop_abort_destroy static void leafop_statusbar_pre_update(void *fev, LAYOUT *l) { if (fev != (void *)fe) abort(); if (! (fe->flags & FEF_STATBAR)) abort(); l->size.x = fe->statusbar_r - l->loc.x; if (l->size.x < 1) abort(); } #define leafop_statusbar_post_update leafop_null_post_update static const LEAFOPS ops_statusbar = LEAFOPS_INIT(statusbar); static void setup_menubar_button(frontend *fe, MENUBAR_ITEM *mbi, bool (*acttest)(midend *), const char *text, void (*click)(frontend *, Time)) { XCharStruct cs; int tl; tl = strlen(text); mbi->text = text; mbi->textlen = tl; mbi->extend_l = -1; mbi->extend_r = -1; XTextExtents(fe->font,text,tl,XTE_JUNK,&cs); mbi->textw = cs.width; mbi->click = click; mbi->acttest = acttest; mbi->curact = -1; // l already set in leafop_menubar_item_init // See leafop_menubar_item_post_create for the rest mbi->link = fe->menubar_items; fe->menubar_items = mbi; } static void setup_custom_button(CBOXBUTTON *cbb, CBOX *cb, const char *text, int good) { XCharStruct cs; int tl; cbb->cb = cb; cbb->text = text; tl = strlen(text); cbb->textlen = tl; XTextExtents(fe->font,text,tl,XTE_JUNK,&cs); cbb->textw = cs.width; cbb->isgood = good; cbb->win = None; // set in leafop_custom_button_post_create() } static void set_menugrid_button(LAYOUT *grid, int x, int y, MENUBAR_ITEM *button) { layout_grid_set(grid,x,y, layout_setup_box(LT_VBOX, layout_setup_glue(0,STRETCH_OTHER), layout_setup_box(LT_HBOX, layout_write_loc(&button->extend_l), layout_setup_glue(0,STRETCH_ALWAYS), layout_setup_leaf(&ops_menubar_item,button), layout_setup_glue(0,STRETCH_ALWAYS), layout_write_loc(&button->extend_r), LAYOUT_END), layout_setup_glue(0,STRETCH_OTHER), LAYOUT_END)); } static void set_menugrid_empty(LAYOUT *grid, int x, int y) { layout_grid_set(grid,x,y, layout_setup_box(LT_HBOX, layout_setup_glue(0,STRETCH_ALWAYS), layout_setup_box(LT_VBOX, layout_setup_glue(0,STRETCH_OTHER), LAYOUT_END), LAYOUT_END)); } static void configurenotify_topwin(void *fev, XEvent *e) { frontend *fe; int gw; int gh; fe = fev; if ((e->xconfigure.width != fe->topw) || (e->xconfigure.height != fe->toph)) { layout_pass2(fe->layout_game,STRETCH_ALWAYS|STRETCH_GAME); layout_pass3(fe->layout_game,0,0,e->xconfigure.width,e->xconfigure.height,STRETCH_ALWAYS|STRETCH_GAME); gamewin_extend_size(fe,fe->gl); gw = fe->gl->size.x; gh = fe->gl->size.y; midend_size(fe->me,&gw,&gh,true); fe->draww = gw; fe->drawh = gh; layout_pass1(fe->layout_game,STRETCH_ALWAYS|STRETCH_OTHER); layout_pass2(fe->layout_game,STRETCH_ALWAYS|STRETCH_OTHER); layout_pass3(fe->layout_game,0,0,e->xconfigure.width,e->xconfigure.height,STRETCH_ALWAYS|STRETCH_OTHER); layout_update(fe->layout_game); midend_force_redraw(fe->me); fe->topw = fe->layout_game->size.x; fe->toph = fe->layout_game->size.y; } } static void setup_game(void) { int xsize; int ysize; int x; int y; int w; int h; unsigned long int attrmask; XSetWindowAttributes attr; XCharStruct cs; LAYOUT *mbl; Pixmap pm; fe = malloc(sizeof(frontend)); fe->flags = 0; pm = XCreatePixmap(disp,rootwin,1,1,depth); fe->copygc = XCreateGC(disp,pm,0,0); fe->drawgc = XCreateGC(disp,pm,0,0); XFreePixmap(disp,pm); pm = XCreatePixmap(disp,rootwin,1,1,1); fe->bitgc = XCreateGC(disp,pm,0,0); XFreePixmap(disp,pm); if (fontstr) { fe->font = XLoadQueryFont(disp,fontstr); if (! fe->font) { fprintf(stderr,"%s: can't load font: %s\n",__progname,fontstr); exit(1); } } else { fe->font = XQueryFont(disp,XGContextFromGC(fe->drawgc)); } fe->baselineskip = fe->font->ascent + fe->font->descent; XTextExtents(fe->font,"0",1,XTE_JUNK,&cs); fe->spacewidth = cs.width; XTextExtents(fe->font,"ABCDEFGHIJKLMNOPQRSTUVWXYZ",26,XTE_JUNK,&cs); fe->vcentre = cs.ascent / 2; fe->wincmap = (defcmap == None) ? XCreateColormap(disp,rootwin,visual,AllocNone) : defcmap; setup_colour_text(fe,foreground,&fe->fgcolour,"foreground"); setup_colour_text(fe,background,&fe->bgcolour,"background"); setup_colour_text(fe,bordercstr,&fe->bdcolour,"border"); fe->timerfd = socket(AF_TIMER,SOCK_STREAM,0); if (fe->timerfd < 0) { fprintf(stderr,"%s: timer socket: %s\n",__progname,strerror(errno)); exit(1); } set_nbio(fe->timerfd); fe->running = 0; aio_add_poll(fe->timerfd,&aio_rwtest_always,&aio_rwtest_never,&rd_timer,0,fe); fe->me = midend_new(fe,&thegame,&x_drawing,fe); fe->presets = midend_get_presets(fe->me,0); if (fe->presets->n_entries > 0) fe->flags |= FEF_CONFIG; if (thegame.can_configure) fe->flags |= FEF_CONFIG | FEF_CUSTOM; // if argument provided // do things // else just midend_new_game(fe->me); // if thegame.can_format_as_text_ever, "copy" command // if thegame.flags & REQUIRE_NUMPAD, create soft numpad { int i; int r; int g; int b; float *cols; float *cp; cols = midend_colours(fe->me,&fe->ncolours); drawlog("Colour count %d:",fe->ncolours); fe->colours = malloc(fe->ncolours*sizeof(*fe->colours)); cp = cols; for (i=0;incolours;i++) { r = *cp++ * 256; if (r < 0) r = 0; else if (r > 255) r = 255; g = *cp++ * 256; if (g < 0) g = 0; else if (g > 255) g = 255; b = *cp++ * 256; if (b < 0) b = 0; else if (b > 255) b = 255; fe->colours[i].red = r * 0x0101; fe->colours[i].green = g * 0x0101; fe->colours[i].blue = b * 0x0101; fe->colours[i].flags = DoRed | DoGreen | DoBlue; setup_colour_rgb(fe,&fe->colours[i],"game #%d (%d,%d,%d)",i,r,g,b); drawlog(" [%d] (%g,%g,%g) -> (%d,%d,%d) -> %lx",i,cp[-3],cp[-2],cp[-1],r,g,b,fe->colours[i].pixel); } } xsize = scrwidth; ysize = scrheight; // must call midend_new_game() before midend_size() midend_size(fe->me,&xsize,&ysize,false); if (midend_wants_statusbar(fe->me)) { fe->flags |= FEF_STATBAR; fe->statbuf = 0; fe->statalloc = 0; fe->statlen = 0; } fe->draww = xsize; fe->drawh = ysize; mbl = layout_setup_grid(4,2,1,1,GF_EQUAL_X|GF_EQUAL_Y); set_menugrid_button(mbl,0,0,&fe->menubar_quit); set_menugrid_button(mbl,1,0,&fe->menubar_game); set_menugrid_button(mbl,2,0,&fe->menubar_seed); set_menugrid_button(mbl,3,0,&fe->menubar_new); set_menugrid_button(mbl,0,1,&fe->menubar_undo); set_menugrid_button(mbl,1,1,&fe->menubar_redo); if (thegame.can_solve) { set_menugrid_button(mbl,2,1,&fe->menubar_solve); } else { set_menugrid_empty(mbl,2,1); } if (fe->flags & FEF_CONFIG) { set_menugrid_button(mbl,3,1,&fe->menubar_config); } else { set_menugrid_empty(mbl,3,1); } fe->layout_game = layout_setup_box(LT_VBOX, mbl, layout_setup_leaf(&ops_hborder,0), layout_setup_glue(0,STRETCH_OTHER), layout_write_loc(&fe->extend_u), layout_setup_glue(0,STRETCH_GAME), layout_setup_box(LT_HBOX, layout_setup_glue(0,STRETCH_OTHER), layout_write_loc(&fe->extend_l), layout_setup_glue(0,STRETCH_GAME), layout_setup_leaf(&ops_gamewin,fe), layout_setup_glue(0,STRETCH_GAME), layout_write_loc(&fe->extend_r), layout_setup_glue(0,STRETCH_OTHER), LAYOUT_END), layout_setup_glue(0,STRETCH_GAME), layout_write_loc(&fe->extend_d), layout_setup_glue(0,STRETCH_OTHER), LAYOUT_END); if (fe->flags & FEF_STATBAR) { layout_box_append(fe->layout_game, layout_setup_leaf(&ops_hborder,0), layout_setup_glue(margin,0), layout_setup_box(LT_HBOX, layout_setup_glue(margin,0), layout_setup_leaf(&ops_statusbar,fe), layout_setup_glue(0,STRETCH_ALWAYS), layout_write_loc(&fe->statusbar_r), layout_setup_glue(margin,0), LAYOUT_END), layout_setup_glue(margin,0), LAYOUT_END); } fe->menubar_items = 0; setup_menubar_button(fe,&fe->menubar_quit,0,"Quit",&menu_click_quit); setup_menubar_button(fe,&fe->menubar_undo,&midend_can_undo,"Undo",&menu_click_undo); setup_menubar_button(fe,&fe->menubar_redo,&midend_can_redo,"Redo",&menu_click_redo); if (thegame.can_solve) { fe->flags |= FEF_SOLVE; setup_menubar_button(fe,&fe->menubar_solve,0,"Solve",&menu_click_solve); } if (fe->flags & FEF_CONFIG) setup_menubar_button(fe,&fe->menubar_config,0,"Config",&menu_click_config); setup_menubar_button(fe,&fe->menubar_new,0,"New",&menu_click_new); setup_menubar_button(fe,&fe->menubar_game,0,"Game",&menu_click_game); setup_menubar_button(fe,&fe->menubar_seed,0,"Seed",&menu_click_seed); setup_custom_button(&fe->custom_cancel,&fe->cbox,"Cancel",0); setup_custom_button(&fe->custom_done,&fe->cbox,"Done",1); fe->arrowcurs = XCreateFontCursor(disp,XC_left_ptr); XRecolorCursor(disp,fe->arrowcurs,&fe->fgcolour,&fe->bgcolour); layout_arrange(fe->layout_game,STRETCH_ALWAYS|STRETCH_GAME|STRETCH_OTHER); w = borderwidth + fe->layout_game->size.x + borderwidth; h = borderwidth + fe->layout_game->size.y + borderwidth; x = (scrwidth - w) / 2; y = (scrheight - h) / 2; attrmask = 0; attr.background_pixel = fe->bgcolour.pixel; attrmask |= CWBackPixel; attr.border_pixel = fe->bdcolour.pixel; attrmask |= CWBorderPixel; attr.event_mask = StructureNotifyMask | KeyPressMask; attrmask |= CWEventMask; attr.do_not_propagate_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask; attrmask |= CWDontPropagate; attr.colormap = fe->wincmap; attrmask |= CWColormap; attr.cursor = fe->arrowcurs; attrmask |= CWCursor; w -= 2 * borderwidth; h -= 2 * borderwidth; fe->topw = w; fe->toph = h; fe->topwin = XCreateWindow(disp,rootwin,x,y,w,h,borderwidth,depth,InputOutput,visual,attrmask,&attr); fe->wn_prop.value = (unsigned char *) deconst(av0bn); fe->wn_prop.encoding = XA_STRING; fe->wn_prop.format = 8; fe->wn_prop.nitems = strlen(fe->wn_prop.value); fe->in_prop.value = (unsigned char *) deconst(av0bn); fe->in_prop.encoding = XA_STRING; fe->in_prop.format = 8; fe->in_prop.nitems = strlen(fe->in_prop.value); fe->normal_hints = XAllocSizeHints(); fe->normal_hints->flags = PMinSize | PResizeInc; fe->normal_hints->x = x; fe->normal_hints->y = y; fe->normal_hints->flags |= PPosition; fe->normal_hints->width = w; fe->normal_hints->height = h; fe->normal_hints->flags |= PSize; fe->normal_hints->min_width = w; fe->normal_hints->min_height = h; fe->normal_hints->width_inc = 1; fe->normal_hints->height_inc = 1; fe->wm_hints = XAllocWMHints(); fe->wm_hints->flags = InputHint; fe->wm_hints->input = True; fe->class_hints = XAllocClassHint(); fe->class_hints->res_name = deconst(av0bn); fe->class_hints->res_class = deconst("Puzzle"); XSetWMProperties(disp,fe->topwin,&fe->wn_prop,&fe->in_prop,argv,argc,fe->normal_hints,fe->wm_hints,fe->class_hints); evh_add(fe->topwin,&evh_configurenotify,&configurenotify_topwin,fe); layout_create(fe->layout_game,fe->topwin); XMapSubwindows(disp,fe->topwin); XMapRaised(disp,fe->topwin); mpop_init(&fe->mpop); cbox_init(&fe->cbox); resync_buttons(fe); midend_redraw(fe->me); keystroke = &keystroke_play; es.live = 0; } /* * We currently support only one font. If we want to support the ftype * and fsize arguments properly, we'll need to improve all * font-handling code. */ static void x_draw_text( void *fev, int x, int y, int ftype __attribute__((__unused__)), int fsize __attribute__((__unused__)), int align, int colour, const char *text ) { int len; XCharStruct cs; if (fev != fe) abort(); len = strlen(text); drawlog("%s, x=%d y=%d ftype=%d fsize=%d align=%d colour=%d text=%s",__func__,x,y,ftype,fsize,align,colour,text); XTextExtents(fe->font,text,len,XTE_JUNK,&cs); switch (align & (ALIGN_VNORMAL|ALIGN_VCENTRE)) { case ALIGN_VNORMAL: break; case ALIGN_VCENTRE: // XXX should we query cap-height somehow? y += fe->vcentre; break; DEFAULT_ABORT(); } switch (align & (ALIGN_HLEFT|ALIGN_HCENTRE|ALIGN_HRIGHT)) { case ALIGN_HLEFT: break; case ALIGN_HCENTRE: x -= cs.width / 2; break; case ALIGN_HRIGHT: x -= cs.width; break; DEFAULT_ABORT(); } XSetForeground(disp,fe->drawgc,fe->colours[colour].pixel); XDrawString(disp,fe->drawpix,fe->drawgc,x,y,text,len); } static void x_draw_rect(void *fev, int x, int y, int w, int h, int colour) { if (fev != fe) abort(); drawlog("%s, x=%d y=%d w=%d h=%d colour=%d",__func__,x,y,w,h,colour); if ((w < 1) || (h < 1)) return; XSetForeground(disp,fe->drawgc,fe->colours[colour].pixel); XFillRectangle(disp,fe->drawpix,fe->drawgc,x,y,w,h); } static void x_draw_line(void *fev, int x1, int y1, int x2, int y2, int colour) { if (fev != fe) abort(); drawlog("%s, x1=%d y1=%d x2=%d y2=%d colour=%d",__func__,x1,y1,x2,y2,colour); XSetForeground(disp,fe->drawgc,fe->colours[colour].pixel); XDrawLine(disp,fe->drawpix,fe->drawgc,x1,y1,x2,y2); } static void x_draw_polygon(void *fev, int *coords, int n, int fillc, int borderc) { static XPoint *pts = 0; static int apts = 0; int i; if (fev != fe) abort(); if (n+1 > apts) { free(pts); apts = n + 1; pts = malloc(apts*sizeof(XPoint)); } for (i=n-1;i>=0;i--) { pts[i].y = coords[i+i+1]; pts[i].x = coords[i+i]; } pts[n] = pts[0]; if (dlog) { drawlog("%s, n=%d fillc=%d borderc=%d",__func__,n,fillc,borderc); for (i=0;i= 0) { XSetForeground(disp,fe->drawgc,fe->colours[fillc].pixel); XFillPolygon(disp,fe->drawpix,fe->drawgc,pts,n,Complex,CoordModeOrigin); } XSetForeground(disp,fe->drawgc,fe->colours[borderc].pixel); XDrawLines(disp,fe->drawpix,fe->drawgc,pts,n+1,CoordModeOrigin); } static void x_draw_circle(void *fev, int cx, int cy, int r, int fillc, int borderc) { if (fev != fe) abort(); drawlog("%s, cx=%d cy=%d r=%d fillc=%d borderc=%d",__func__,cx,cy,r,fillc,borderc); if (r < 1) return; if (r == 1) { XSetForeground(disp,fe->drawgc,fe->colours[borderc].pixel); XDrawPoint(disp,fe->drawpix,fe->drawgc,cx,cy); } else { cx -= r - 1; cy -= r - 1; r = r + r - 2; if (fillc >= 0) { XSetForeground(disp,fe->drawgc,fe->colours[fillc].pixel); XFillArc(disp,fe->drawpix,fe->drawgc,cx,cy,r,r,0,360*64); } XSetForeground(disp,fe->drawgc,fe->colours[borderc].pixel); XDrawArc(disp,fe->drawpix,fe->drawgc,cx,cy,r,r,0,360*64); } } static void x_draw_update(void *fev, int x, int y, int w, int h) { if (fev != fe) abort(); drawlog("%s, x=%d y=%d w=%d h=%d",__func__,x,y,w,h); copyarea(disp,fe->drawpix,fe->drawwin,fe->copygc,x,y,w,h,x,y); } static void x_clip(void *fev, int x, int y, int w, int h) { XRectangle r; if (fev != fe) abort(); drawlog("%s, x=%d y=%d w=%d h=%d",__func__,x,y,w,h); r.x = x; r.y = y; r.width = w; r.height = h; XSetClipRectangles(disp,fe->drawgc,0,0,&r,1,Unsorted); } static void x_unclip(void *fev) { if (fev != fe) abort(); drawlog("%s",__func__); XSetClipMask(disp,fe->drawgc,None); } static void x_start_draw(void *fev) { if (fev != fe) abort(); } static void x_end_draw(void *fev) { if (fev != fe) abort(); } static void x_status_bar(void *fev, const char *text) { int l; if (fev != fe) abort(); drawlog("%s, text=%s",__func__,text); if (! (fe->flags & FEF_STATBAR)) abort(); l = strlen(text); if (l > fe->statlen) { free(fe->statbuf); fe->statlen = l; fe->statbuf = malloc(l); } bcopy(text,fe->statbuf,l); fe->statlen = l; XClearWindow(disp,fe->statwin); draw_statbar_text(fe); } static blitter *x_blitter_new(void *fev, int w, int h) { blitter *b; if (fev != fe) abort(); b = malloc(sizeof(blitter)); drawlog("%s, w=%d h=%d returning %p",__func__,w,h,(void *)b); b->w = w; b->h = h; b->x = 0; b->y = 0; b->pm = XCreatePixmap(disp,rootwin,w,h,depth); return(b); } static void x_blitter_free(void *fev, blitter *b) { if (fev != fe) abort(); drawlog("%s, b=%p",__func__,(void *)b); XFreePixmap(disp,b->pm); free(b); } static void x_blitter_save(void *fev, blitter *b, int x, int y) { if (fev != fe) abort(); drawlog("%s, b=%p x=%d y=%d",__func__,(void *)b,x,y); b->x = x; b->y = y; copyarea(disp,fe->drawpix,b->pm,fe->copygc,x,y,b->w,b->h,0,0); } static void x_blitter_load(void *fev, blitter *b, int x, int y) { if (fev != fe) abort(); drawlog("%s, b=%p x=%d y=%d",__func__,(void *)b,x,y); if ((x == BLITTER_FROMSAVED) && (y == BLITTER_FROMSAVED)) { x = b->x; y = b->y; } copyarea(disp,b->pm,fe->drawpix,fe->copygc,0,0,b->w,b->h,x,y); } static void x_draw_thick_line(void *fev, float w, float x1, float y1, float x2, float y2, int colour) { XPoint lr[4]; double ll; double s; double c; if (fev != fe) abort(); drawlog("%s, w=%g x1=%g y1=%g x2=%g y2=%g colour=%d",__func__,w,x1,y1,x2,y2,colour); if ((fabs(x2-x1) < 1e-3) && (fabs(y2-y1) < 1e-3)) return; if (w < 1) w = 1; ll = hypot(x2-x1,y2-y1); s = (y2 - y1) * w * .5 / ll; c = (x2 - x1) * w * .5 / ll; lr[0].x = x1 + s; lr[0].y = y1 - c; lr[1].x = x1 - s; lr[1].y = y1 + c; lr[2].x = x2 - s; lr[2].y = y2 + c; lr[3].x = x2 + s; lr[3].y = y2 - c; XSetForeground(disp,fe->drawgc,fe->colours[colour].pixel); XFillPolygon(disp,fe->drawpix,fe->drawgc,&lr[0],4,Convex,CoordModeOrigin); } static const struct drawing_api x_drawing = { &x_draw_text, &x_draw_rect, &x_draw_line, &x_draw_polygon, &x_draw_circle, &x_draw_update, &x_clip, &x_unclip, &x_start_draw, &x_end_draw, &x_status_bar, &x_blitter_new, &x_blitter_free, &x_blitter_save, &x_blitter_load, 0, // begin_doc 0, // begin_page 0, // begin_puzzle 0, // end_puzzle 0, // end_page 0, // end_doc 0, // line_width 0, // line_dotted 0, // text_fallback &x_draw_thick_line }; void get_random_seed(void **seed, int *size) { struct timeval *tv; tv = malloc(sizeof(struct timeval)); gettimeofday(tv,0); *seed = tv; *size = sizeof(*tv); } void fatal(const char *, ...) __attribute__((__format__(__printf__,1,2))); void fatal(const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); fprintf(stderr,"Fatal error: %s\n",s); exit(1); } void activate_timer(frontend *fe) { struct itimerval itv; if (fe->running) return; itv.it_value.tv_sec = 0; itv.it_value.tv_usec = 40000; itv.it_interval = itv.it_value; write(fe->timerfd,&itv,sizeof(itv)); fe->lasttime = get_now(); fe->running = 1; } void deactivate_timer(frontend *fe) { struct itimerval itv; if (! fe->running) return; itv.it_value.tv_sec = 0; itv.it_value.tv_usec = 0; itv.it_interval = itv.it_value; write(fe->timerfd,&itv,sizeof(itv)); fe->running = 0; } void frontend_default_colour(frontend *fe __attribute__((__unused__)), float *cp) { cp[0] = fe->bgcolour.red / 65535.0; cp[1] = fe->bgcolour.green / 65535.0; cp[2] = fe->bgcolour.blue / 65535.0; } // This is called only when printing; it appears to be undocumented. void document_add_puzzle( document *d __attribute__((__unused__)), const game *g __attribute__((__unused__)), game_params *p __attribute__((__unused__)), game_state *s __attribute__((__unused__)), game_state *s2 __attribute__((__unused__)) ) { } int main(int, char **); int main(int ac, char **av) { saveargv(ac,av); handleargs(ac,av); disp = XOpenDisplay(displayname); if (disp == 0) { fprintf(stderr,"%s: can't open display\n",__progname); exit(1); } setup_aio(); setup_error(); if (synch) XSynchronize(disp,True); scr = XDefaultScreenOfDisplay(disp); scrwidth = XWidthOfScreen(scr); scrheight = XHeightOfScreen(scr); rootwin = XRootWindowOfScreen(scr); setup_db(); maybeset(&geometryspec,get_default_value("geometry","Geometry")); maybeset(&foreground,get_default_value("foreground","Foreground")); maybeset(&background,get_default_value("background","Background")); maybeset(&bordercstr,get_default_value("borderColour","BorderColour")); maybeset(&borderwstr,get_default_value("borderWidth","BorderWidth")); maybeset(&bordermstr,get_default_value("borderMargin","BorderMargin")); maybeset(&visualstr,get_default_value("visual","Visual")); maybeset(&menumstr,get_default_value("menuMargin","MenuMargin")); maybeset(&fontstr,get_default_value("font","Font")); setup_numbers(); setup_visual(); setup_game(); aio_event_loop(); return(0); }