// 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 #include #include #include extern const char *__progname; #include "puzzles.h" /* * The usual Foreground and Background simply don't work. Most of the * games impose their colours on the player. As received from * sgtatham, they impose a black foreground. I've fixed one, at this * writing, and intend to fix more, but they still don't actually let * the player specify foreground and background in any useful sense. * * We still have Foreground and Background, but all they control is the * framing stuff, like the menu at the top. */ static XrmDatabase db; static const char *defaults = "\ *Foreground: white\n\ *Background: black\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 debug = 0; static char *gamearg = 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 enum { DONE_KEYSTROKE = 1, DONE_SWITCH, DONE_DESTROY, } DONEREASON; typedef struct xy XY; typedef struct layout LAYOUT; typedef struct leafops LEAFOPS; typedef struct menubar_item MENUBAR_ITEM; typedef struct simple_button SIMPLE_BUTTON; 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; typedef struct pending PENDING; typedef struct popmsg POPMSG; typedef struct oline OLINE; typedef struct osline OSLINE; typedef struct cmd CMD; struct cmd { const char *cmd; void (*impl)(const char *, int); const char *help; int len; } ; struct osline { int len; int off; } ; struct oline { OLINE *nlink; // next newer OLINE *olink; // next older char *content; int len; int wrapcols; int nscr; OSLINE *scr; } ; struct popmsg { frontend *fe; char *text; int len; int textw; int winw; int winh; Pixmap pm; Window topwin; void (*prev_ks)(XKeyEvent *); int id; int up; } ; struct pending { frontend *fe; char *fn; int id; char *buf; int len; int fd; int err; int alloc; } ; 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 simple_button { frontend *fe; const char *text; int textlen; int textw; void (*click)(frontend *, Time); LAYOUT *l; int extend_l; int extend_r; int extend_u; int extend_d; Window win; XY pmsize; Pixmap pm; } ; 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_id; MENUBAR_ITEM menubar_seed; MENUBAR_ITEM menubar_save; MENUBAR_ITEM menubar_load; MENUBAR_ITEM menubar_config; MENUBAR_ITEM menubar_undo; MENUBAR_ITEM menubar_redo; MENUBAR_ITEM menubar_new; MENUBAR_ITEM menubar_reset; MENUBAR_ITEM menubar_solve; MENUBAR_ITEM menubar_snap; MENUBAR_ITEM menubar_cmp; CBOXBUTTON custom_cancel; CBOXBUTTON custom_done; LAYOUT *gl; int file_edit_up; Window file_topwin; Window file_label_win; Window file_edit_win; LAYOUT *file_edit_layout; int file_input_r; LAYOUT *file_layout; SIMPLE_BUTTON file_cancel; POPMSG *popmsg; const char *file_label; int file_label_len; int flw; 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; unsigned int (*done)(EDITSTATE *, DONEREASON, void *); #define ES_DONE_NOCLEAR 0x00000001 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; static unsigned int want_redraw; #define REDRAW_WANT 0x00000001 #define REDRAW_FULL 0x00000002 static FILE *output; static OLINE *alllines; static OLINE *ocur; static OLINE *otext; static int ocura; static OLINE *iline; static int ilinea; static int ilinec; static int sigpipe[2]; static volatile sig_atomic_t got_sigint; static volatile sig_atomic_t got_sigwinch; #define DEFAULT_ABORT() default: abort(); break #define Cisspace(x) isspace((unsigned char)(x)) 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 != '-') { if (! gamearg) { gamearg = *av; } else { 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,"-arg")) { WANTARG(); gamearg = av[skip]; continue; } 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,"-debug")) { debug = 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 end_curses(void) { endwin(); } static void start_curses(void) { initscr(); cbreak(); noecho(); } static void clean_exit(void) { if (! debug) { move(LINES-1,COLS-2); refresh(); end_curses(); printf("\n"); } exit(0); } 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)) clean_exit(); 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); } // designed to be called from debugger 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) { fprintf(output,"grid before pass1\n"); layout_dump(l,output); } 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) { fprintf(output,"grid before pass3\n"); layout_dump(l,output); } 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) { fprintf(output,"grid after pass3\n"); layout_dump(l,output); } } 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); 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); } str = XResourceManagerString(disp); if (str) { db2 = XrmGetStringDatabase(str); XrmMergeDatabases(db2,&db); } 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 much_changed(frontend *fe) { int w; int h; XWindowChanges chg; 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 new_game(frontend *fe) { midend_new_game(fe->me); much_changed(fe); } static void menu_click_new(frontend *fe, Time when __attribute__((__unused__))) { new_game(fe); } static void menu_click_cmp(frontend *fe, Time when __attribute__((__unused__))) { if (midend_can_cmp(fe->me)) { process_key(0,0,UI_CMP); } else { XBell(disp,0); } } static void menu_click_id(frontend *fe, Time when __attribute__((__unused__))) { char *s; s = midend_get_game_id(fe->me); fprintf(output,"%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) s = strdup("No random seed available"); fprintf(output,"%s\n",s); 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, DONEREASON why) { XEvent e; unsigned int f; es->live = 0; aio_remove_poll(es->flashid); close(es->flashfd); XUngrabKeyboard(disp,CurrentTime); XFreePixmap(disp,es->pm); f = (*es->done)(es,why,es->donearg); if (! (f & ES_DONE_NOCLEAR)) { 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,DONE_KEYSTROKE); 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 = initlen + 20; es->b = malloc(es->ba); bcopy(inittext,es->b,initlen); es->bl = initlen; 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 = 500000; 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 unsigned int editdone_config_string_input(EDITSTATE *es, DONEREASON why, void *cbxv) { CBOXX *cbx; char *s; char **cpp; cbx = cbxv; switch (why) { case DONE_KEYSTROKE: case DONE_SWITCH: 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; break; case DONE_DESTROY: break; DEFAULT_ABORT(); } return(0); } 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,DONE_SWITCH); } } 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,DONE_DESTROY); 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,DONE_SWITCH); err = midend_set_config(fe->me,CFG_SETTINGS,cbb->cb->cfg); if (err) { fprintf(output,"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 leafop_simple_button_init(void *sbv, LAYOUT *l) { ((SIMPLE_BUTTON *)sbv)->l = l; } static XY leafop_simple_button_minsize(void *sbv) { return((XY){.x=((SIMPLE_BUTTON *)sbv)->textw,.y=fe->baselineskip}); } static void leafop_simple_button_pre_create(void *sbv, LAYOUT *l, unsigned long int *maskp, XSetWindowAttributes *attrp) { SIMPLE_BUTTON *b; b = sbv; l->loc.x = b->extend_l; l->loc.y = b->extend_u; l->size.x = b->extend_r - b->extend_l; l->size.y = b->extend_d - b->extend_u; attrp->event_mask = ExposureMask | ButtonPressMask; *maskp |= CWEventMask; } static void redraw_simple_button(SIMPLE_BUTTON *b) { XSetForeground(disp,b->fe->drawgc,b->fe->fgcolour.pixel); XDrawString(disp,b->win,b->fe->drawgc,(b->l->size.x-b->textw)/2,((b->l->size.y-fe->baselineskip)/2)+fe->font->ascent,b->text,b->textlen); } static void expose_simple_button(void *sbv, XEvent *e) { if (e->xexpose.count != 0) return; redraw_simple_button(sbv); } static void buttonpress_simple_button(void *sbv, XEvent *e __attribute__((__unused__))) { SIMPLE_BUTTON *b; b = sbv; (*b->click)(b->fe,e->xbutton.time); } static void leafop_simple_button_post_create(void *sbv, Window w) { ((SIMPLE_BUTTON *)sbv)->win = w; evh_add(w,&evh_expose,&expose_simple_button,sbv); evh_add(w,&evh_buttonpress,&buttonpress_simple_button,sbv); } static void leafop_simple_button_destroy(void *sbv) { SIMPLE_BUTTON *b; b = sbv; evh_remove(b->win,&evh_expose); evh_remove(b->win,&evh_buttonpress); } #define leafop_simple_button_pre_update leafop_null_pre_update #define leafop_simple_button_post_update leafop_null_post_update static const LEAFOPS ops_simple_button = LEAFOPS_INIT(simple_button); #define leafop_file_label_init leafop_null_init static XY leafop_file_label_minsize(void *fev) { return((XY){.x=((frontend *)fev)->flw,.y=fe->baselineskip}); } #define leafop_file_label_pre_create leafop_justexpose_pre_create static void redraw_file_label(frontend *fe) { XSetForeground(disp,fe->drawgc,fe->fgcolour.pixel); XDrawString(disp,fe->file_label_win,fe->drawgc,0,fe->font->ascent,fe->file_label,fe->file_label_len); } static void expose_file_label(void *fev, XEvent *e) { if (e->xexpose.count != 0) return; redraw_file_label(fev); } static void leafop_file_label_post_create(void *fev, Window w) { ((frontend *)fev)->file_label_win = w; evh_add(w,&evh_expose,&expose_file_label,fev); } static void leafop_file_label_destroy(void *fev) { evh_remove(((frontend *)fev)->file_label_win,&evh_expose); } #define leafop_file_label_pre_update leafop_null_pre_update #define leafop_file_label_post_update leafop_null_post_update static const LEAFOPS ops_file_label = LEAFOPS_INIT(file_label); static void leafop_file_input_init(void *fev, LAYOUT *l) { ((frontend *)fev)->file_edit_layout = l; } static XY leafop_file_input_minsize(void *fev) { frontend *fe; fe = fev; return((XY){.x=fe->spacewidth*64,.y=fe->baselineskip}); } static void leafop_file_input_pre_create(void *fev, LAYOUT *l, unsigned long int *maskp, XSetWindowAttributes *attrp) { attrp->event_mask = ExposureMask; *maskp |= CWEventMask; l->size.x = ((frontend *)fev)->file_input_r - l->loc.x; } static void expose_file_input(void *fev, XEvent *e) { frontend *fe; if (e->xexpose.count != 0) return; fe = fev; if (es.live && (es.win == fe->file_edit_win)) { editor_recopy(&es); return; } abort(); } static void leafop_file_input_post_create(void *fev, Window w) { ((frontend *)fev)->file_edit_win = w; evh_add(w,&evh_expose,&expose_file_input,fev); } static void leafop_file_input_destroy(void *fev) { frontend *fe; fe = fev; if (es.live && (es.win == fe->file_edit_win)) editor_done(&es,DONE_DESTROY); evh_remove(fe->file_edit_win,&evh_expose); } #define leafop_file_input_pre_update leafop_null_pre_update #define leafop_file_input_post_update leafop_null_post_update static const LEAFOPS ops_file_input = LEAFOPS_INIT(file_input); 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 - (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 (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 popup_message_free(frontend *, char *, int); // forward 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, Time when __attribute__((__unused__))) { const char *err; err = midend_solve(fe->me); if (err) { XBell(disp,0); popup_message_free(fe,strdup(err),strlen(err)); } } 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_reset(frontend *fe, Time when __attribute__((__unused__))) { midend_restart_game(fe->me); } static void file_edit_popdown(frontend *fe) { if (! fe->file_edit_up) abort(); XUnmapWindow(disp,fe->file_topwin); layout_destroy(fe->file_layout,fe->file_topwin); fe->file_layout = 0; fe->file_edit_layout = 0; fe->file_edit_up = 0; } static void save_write(void *pv, const void *buf, int len) { PENDING *p; p = pv; p->buf = realloc(p->buf,p->len+len); bcopy(buf,p->buf+p->len,len); p->len += len; } static int destroy_popmsg(void *pmv) { POPMSG *pm; pm = pmv; aio_remove_block(pm->id); pm->id = AIO_NOID; XDestroyWindow(disp,pm->topwin); free(pm->text); return(AIO_BLOCK_LOOP); } static void popmsg_popdown(POPMSG *); // forward static void popmsg_keystroke(XKeyEvent *e __attribute__((__unused__))) { popmsg_popdown(fe->popmsg); } static void popmsg_popdown(POPMSG *pm) { if (! pm->up) return; pm->up = 0; if (pm->fe->popmsg != pm) abort(); pm->fe->popmsg = 0; XUnmapWindow(disp,pm->topwin); evh_remove(pm->topwin,&evh_expose); evh_remove(pm->topwin,&evh_buttonpress); if (keystroke == &popmsg_keystroke) keystroke = pm->prev_ks; pm->id = aio_add_block(&destroy_popmsg,pm); } static void popmsg_redraw(POPMSG *pm) { XSetForeground(disp,pm->fe->copygc,pm->fe->fgcolour.pixel); XSetBackground(disp,pm->fe->copygc,pm->fe->bgcolour.pixel); XCopyPlane(disp,pm->pm,pm->topwin,pm->fe->copygc,0,0,pm->textw,pm->fe->baselineskip,margin,margin,1); } static void expose_popmsg(void *pmv, XEvent *e) { POPMSG *pm; if (e->xexpose.count != 0) return; pm = pmv; if (! pm->up) return; popmsg_redraw(pm); } static void buttonpress_popmsg(void *pmv, XEvent *e __attribute__((__unused__))) { popmsg_popdown(pmv); } static void popup_message_free(frontend *fe, char *s, int l) { POPMSG *pm; XCharStruct cs; unsigned long attrmask; XSetWindowAttributes attr; Window ret_root; Window ret_child; int ret_rootx; int ret_rooty; int ret_winx; int ret_winy; unsigned int ret_mask; int x; int y; if (fe->popmsg) abort(); pm = malloc(sizeof(POPMSG)); pm->fe = fe; pm->text = s; pm->len = l; XTextExtents(fe->font,s,l,XTE_JUNK,&cs); pm->textw = cs.width; pm->winw = margin + pm->textw + margin; pm->winh = margin + fe->baselineskip + margin; pm->pm = XCreatePixmap(disp,rootwin,pm->textw,fe->baselineskip,1); XSetForeground(disp,fe->bitgc,0); XFillRectangle(disp,pm->pm,fe->bitgc,0,0,32767,32767); XSetForeground(disp,fe->bitgc,1); XDrawString(disp,pm->pm,fe->bitgc,0,fe->font->ascent,pm->text,pm->len); if (XQueryPointer(disp,rootwin,&ret_root,&ret_child,&ret_rootx,&ret_rooty,&ret_winx,&ret_winy,&ret_mask) != True) { x = scrwidth / 2; y = scrheight / 2; } else { x = ret_rootx; y = ret_rooty; } x -= borderwidth + (pm->winw / 2); y -= borderwidth + (pm->winh / 2); 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 = ExposureMask | ButtonPressMask; attrmask |= CWEventMask; attr.override_redirect = True; attrmask |= CWOverrideRedirect; attr.colormap = fe->wincmap; attrmask |= CWColormap; attr.cursor = fe->arrowcurs; attrmask |= CWCursor; pm->topwin = XCreateWindow(disp,rootwin,x,y,pm->winw,pm->winh,borderwidth,depth,InputOutput,visual,attrmask,&attr); evh_add(pm->topwin,&evh_expose,&expose_popmsg,pm); evh_add(pm->topwin,&evh_buttonpress,&buttonpress_popmsg,pm); XMapRaised(disp,pm->topwin); fe->popmsg = pm; pm->prev_ks = keystroke; keystroke = &popmsg_keystroke; pm->up = 1; } static int do_save(void *pv) { PENDING *p; int fd; char *text; int textlen; int w; int o; p = pv; textlen = 0; fd = open(p->fn,O_WRONLY|O_CREAT|O_TRUNC,0666); if (fd < 0) { textlen = asprintf(&text,"Can't open %s: %s",p->fn,strerror(errno)); } else { p->buf = 0; p->len = 0; save_write(p,thegame.name,strlen(thegame.name)+1); midend_serialise(p->fe->me,&save_write,p); o = 0; while (o < p->len) { w = write(fd,p->buf+o,p->len-o); if (w < 0) { textlen = asprintf(&text,"Write error on %s: %s",p->fn,strerror(errno)); break; } else if (w == 0) { textlen = asprintf(&text,"Zero write on %s",p->fn); break; } else { o += w; } } if ((close(fd) < 0) && !textlen) { textlen = asprintf(&text,"Close error on %s: %s",p->fn,strerror(errno)); } if (! textlen) { textlen = asprintf(&text,"Saved!"); } } popup_message_free(p->fe,text,textlen); aio_remove_block(p->id); free(p->fn); free(p); return(AIO_BLOCK_LOOP); } static void pend_print(PENDING *, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void pend_print(PENDING *p, const char *fmt, ...) { va_list ap; char *s; int l; va_start(ap,fmt); l = vasprintf(&s,fmt,ap); va_end(ap); if (p->len+l > p->alloc) p->buf = realloc(p->buf,p->alloc=p->len+l+64); bcopy(s,p->buf+p->len,l); p->len += l; } static void pend_putc(PENDING *p, unsigned char c) { if (p->len+1 > p->alloc) p->buf = realloc(p->buf,p->alloc=p->len+64); p->buf[p->len] = c; p->len ++; } static int take_snapshot(PENDING *p, char **textp, int *textlenp) { XImage *xi; XY sz; XY pt; unsigned long int pix; int i; int ncol; const XColor *cols; int warned; sz = p->fe->drawpsize; xi = XGetImage(disp,p->fe->drawpix,0,0,sz.x,sz.y,~0UL,ZPixmap); if (! xi) { *textlenp = asprintf(textp,"XGetImage failed"); return(0); } pend_print(p,"P6\n%d %d\n255\n",sz.x,sz.y); ncol = p->fe->ncolours; cols = p->fe->colours; warned = 0; for (pt.y=0;pt.y (pt.x=0;pt.x=0;i--) { if (pix == cols[i].pixel) { pend_putc(p,cols[i].red>>8); pend_putc(p,cols[i].green>>8); pend_putc(p,cols[i].blue>>8); continue <"pixel">; } } // Not found - what else to do? if (! warned) { fprintf(output,"Screensnap contains unrecognized pixel, generating 128/128/128\n"); warned = 1; } pend_putc(p,128); pend_putc(p,128); pend_putc(p,128); } } return(1); } static int do_snap(void *pv) { PENDING *p; int fd; int o; int w; char *text; int textlen; p = pv; p->buf = 0; p->len = 0; p->alloc = 0; textlen = 0; if (take_snapshot(p,&text,&textlen)) { fd = open(p->fn,O_WRONLY|O_CREAT|O_TRUNC,0666); if (fd < 0) { textlen = asprintf(&text,"Can't open %s: %s",p->fn,strerror(errno)); } else { o = 0; while (o < p->len) { w = write(fd,p->buf+o,p->len-o); if (w < 0) { textlen = asprintf(&text,"Write error on %s: %s",p->fn,strerror(errno)); break; } else if (w == 0) { textlen = asprintf(&text,"Zero write on %s",p->fn); break; } else { o += w; } } if ((close(fd) < 0) && !textlen) { textlen = asprintf(&text,"Close error on %s: %s",p->fn,strerror(errno)); } if (! textlen) { textlen = asprintf(&text,"Saved!"); } } } else { if (! textlen) abort(); } popup_message_free(p->fe,text,textlen); aio_remove_block(p->id); free(p->fn); free(p); return(AIO_BLOCK_LOOP); } static int multiread(int fd, void *data, int len) { char *dp; int r; int rtot; dp = data; rtot = 0; while (len > 0) { r = read(fd,dp,len); if (r < 0) return(-1); if (r == 0) break; dp += r; len -= r; rtot += r; } return(rtot); } static bool load_read(void *pv, void *buf, int len) { int r; PENDING *p; p = pv; r = multiread(p->fd,buf,len); if (r < 0) { p->err = errno; return(0); } return(r==len); } static int do_load(void *pv) { PENDING *p; char *gnt; int gnl; int n; const char *err; char *text; int textlen; p = pv; textlen = 0; p->fd = open(p->fn,O_RDONLY,0); p->err = 0; if (p->fd < 0) { textlen = asprintf(&text,"Can't open %s: %s",p->fn,strerror(errno)); } else { do { gnl = strlen(thegame.name) + 1; gnt = malloc(gnl); n = multiread(p->fd,gnt,gnl); if (n < 0) { textlen = asprintf(&text,"Read error on %s: %s",p->fn,strerror(errno)); break; } if (n != gnl) { textlen = asprintf(&text,"%s: not a save from this game (too small)",p->fn); break; } if ((gnt[gnl-1] != '\0') || strcmp(thegame.name,gnt)) { textlen = asprintf(&text,"%s: not a save from this game (ID wrong)",p->fn); break; } err = midend_deserialise(p->fe->me,load_read,p); if (err) { if (p->err) { textlen = asprintf(&text,"Read error on %s: %s",p->fn,strerror(p->err)); } else { textlen = asprintf(&text,"Can't load %s: %s",p->fn,err); } break; } } while (0); close(p->fd); } if (textlen) popup_message_free(p->fe,text,textlen); else much_changed(fe); aio_remove_block(p->id); free(p->fn); free(p); return(AIO_BLOCK_LOOP); } static char *pl_to_nt(const char *s, int l) { char *r; r = malloc(l+1); bcopy(s,r,l); r[l] = '\0'; return(r); } static unsigned int editdone_file_save(EDITSTATE *es, DONEREASON why, void *fev) { frontend *fe; PENDING *p; fe = fev; if (! fe->file_edit_up) abort(); p = 0; switch (why) { case DONE_KEYSTROKE: p = malloc(sizeof(PENDING)); p->fe = fe; p->fn = pl_to_nt(es->b,es->bl); p->id = aio_add_block(&do_save,p); file_edit_popdown(fe); break; case DONE_SWITCH: file_edit_popdown(fe); break; case DONE_DESTROY: break; DEFAULT_ABORT(); } return(ES_DONE_NOCLEAR); } static unsigned int editdone_file_snap(EDITSTATE *es, DONEREASON why, void *fev) { frontend *fe; PENDING *p; fe = fev; if (! fe->file_edit_up) abort(); p = 0; switch (why) { case DONE_KEYSTROKE: p = malloc(sizeof(PENDING)); p->fe = fe; p->fn = pl_to_nt(es->b,es->bl); p->id = aio_add_block(&do_snap,p); file_edit_popdown(fe); break; case DONE_SWITCH: file_edit_popdown(fe); break; case DONE_DESTROY: break; DEFAULT_ABORT(); } return(ES_DONE_NOCLEAR); } static unsigned int editdone_file_load(EDITSTATE *es, DONEREASON why, void *fev) { frontend *fe; PENDING *p; fe = fev; if (! fe->file_edit_up) abort(); p = 0; switch (why) { case DONE_KEYSTROKE: p = malloc(sizeof(PENDING)); p->fe = fe; p->fn = pl_to_nt(es->b,es->bl); p->id = aio_add_block(&do_load,p); file_edit_popdown(fe); break; case DONE_SWITCH: file_edit_popdown(fe); break; case DONE_DESTROY: break; DEFAULT_ABORT(); } return(ES_DONE_NOCLEAR); } static void click_file_cancel(frontend *fe, Time when __attribute__((__unused__))) { if (! fe->file_edit_up) abort(); file_edit_popdown(fe); } static void start_file_dialog(frontend *fe, Time when, const LEAFOPS *labelops, unsigned int (*done)(EDITSTATE *, DONEREASON, void *)) { int w; int h; int x; int y; Window ret_root; Window ret_child; int ret_rootx; int ret_rooty; int ret_winx; int ret_winy; unsigned int ret_mask; if (es.live) { if (es.win == fe->file_edit_win) return; editor_done(&es,DONE_SWITCH); } 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; } fe->file_layout = layout_setup_box(LT_VBOX, layout_setup_glue(margin,STRETCH_ALWAYS), layout_setup_box(LT_HBOX, layout_setup_glue(margin,0), layout_setup_box(LT_VBOX, layout_setup_glue(0,STRETCH_ALWAYS), layout_setup_leaf(labelops,fe), layout_setup_glue(0,STRETCH_ALWAYS), LAYOUT_END), layout_setup_glue(margin,STRETCH_ALWAYS), layout_setup_box(LT_VBOX, layout_setup_leaf(&ops_hborder,0), layout_write_loc(&fe->file_cancel.extend_u), layout_setup_box(LT_HBOX, layout_setup_leaf(&ops_vborder,0), layout_write_loc(&fe->file_cancel.extend_l), layout_setup_glue(margin,0), layout_setup_box(LT_VBOX, layout_setup_glue(margin,0), layout_setup_leaf(&ops_simple_button,&fe->file_cancel), layout_setup_glue(margin,0), LAYOUT_END), layout_setup_glue(margin,0), layout_write_loc(&fe->file_cancel.extend_r), layout_setup_leaf(&ops_vborder,0), LAYOUT_END), layout_write_loc(&fe->file_cancel.extend_d), layout_setup_leaf(&ops_hborder,0), LAYOUT_END), layout_setup_glue(margin,0), LAYOUT_END), 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,0), layout_setup_box(LT_HBOX, layout_setup_glue(margin,0), layout_setup_leaf(&ops_file_input,fe), layout_setup_glue(0,STRETCH_ALWAYS), layout_write_loc(&fe->file_input_r), layout_setup_glue(margin,0), LAYOUT_END), layout_setup_glue(margin,0), layout_setup_leaf(&ops_hborder,0), LAYOUT_END), layout_setup_leaf(&ops_vborder,0), layout_setup_glue(margin,0), LAYOUT_END), layout_setup_glue(margin,STRETCH_ALWAYS), LAYOUT_END); layout_arrange(fe->file_layout,STRETCH_ALWAYS); w = borderwidth + fe->file_layout->size.x + borderwidth; h = borderwidth + fe->file_layout->size.y + 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 (fe->file_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; fe->file_topwin = XCreateWindow(disp,rootwin,x,y,w,h,borderwidth,depth,InputOutput,visual,attrmask,&attr); } else { XMoveResizeWindow(disp,fe->file_topwin,x,y,w,h); } layout_create(fe->file_layout,fe->file_topwin); XMapSubwindows(disp,fe->file_topwin); XMapRaised(disp,fe->file_topwin); if (XGrabPointer(disp,fe->file_topwin,True,ButtonPressMask|ButtonReleaseMask|PointerMotionMask|PointerMotionHintMask|EnterWindowMask|LeaveWindowMask,GrabModeSync,GrabModeAsync,None,None,when) != GrabSuccess) { XUngrabPointer(disp,CurrentTime); file_edit_popdown(fe); return; } XAllowEvents(disp,AsyncPointer,when); fe->file_edit_up = 1; editor_start(&es,fe->file_edit_win,fe->file_input_r,0,0,0,"",0,when,done,fe); } static void set_file_label(frontend *fe, const char *s) { XCharStruct cs; fe->file_label = s; fe->file_label_len = strlen(s); XTextExtents(fe->font,s,fe->file_label_len,XTE_JUNK,&cs); fe->flw = cs.width; } static void menu_click_save(frontend *fe, Time when) { set_file_label(fe,"Save to file"); start_file_dialog(fe,when,&ops_file_label,editdone_file_save); } static void menu_click_load(frontend *fe, Time when) { set_file_label(fe,"Load from file"); start_file_dialog(fe,when,&ops_file_label,editdone_file_load); } static void menu_click_snap(frontend *fe __attribute__((__unused__)), Time when __attribute__((__unused__))) { set_file_label(fe,"Screensnap to file"); start_file_dialog(fe,when,&ops_file_label,editdone_file_snap); } static void menu_click_quit(frontend *fe __attribute__((__unused__)), Time when __attribute__((__unused__))) { clean_exit(); } 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 setup_simple_button(frontend *fe, SIMPLE_BUTTON *b, const char *text, void (*click)(frontend *, Time)) { XCharStruct cs; b->fe = fe; b->text = text; b->textlen = strlen(text); XTextExtents(fe->font,text,b->textlen,XTE_JUNK,&cs); b->textw = cs.width; b->click = click; b->l = 0; b->win = None; // pmsize and pm are set up when it gets used } 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 (gamearg) { const char *msg; msg = midend_game_id(fe->me,gamearg); if (msg) { fprintf(stderr,"%s: %s\n",__progname,msg); exit(1); } } 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,4,1,1,GF_EQUAL_X|GF_EQUAL_Y); fe->file_edit_up = 0; fe->file_topwin = None; fe->file_label_win = None; fe->file_edit_win = None; fe->file_edit_layout = 0; fe->file_layout = 0; /* * quit load config snap * save reset * gameid redo solve * seed new undo cmp */ set_menugrid_button(mbl,0,0,&fe->menubar_quit); set_menugrid_button(mbl,1,0,&fe->menubar_load); if (fe->flags & FEF_CONFIG) { set_menugrid_button(mbl,2,0,&fe->menubar_config); } else { set_menugrid_empty(mbl,2,0); } set_menugrid_button(mbl,3,0,&fe->menubar_snap); set_menugrid_empty(mbl,0,1); set_menugrid_button(mbl,1,1,&fe->menubar_save); set_menugrid_empty(mbl,2,1); set_menugrid_button(mbl,3,1,&fe->menubar_reset); set_menugrid_button(mbl,0,2,&fe->menubar_id); set_menugrid_empty(mbl,1,2); set_menugrid_button(mbl,2,2,&fe->menubar_redo); if (thegame.can_solve) { set_menugrid_button(mbl,3,2,&fe->menubar_solve); } else { set_menugrid_empty(mbl,3,2); } set_menugrid_button(mbl,0,3,&fe->menubar_seed); set_menugrid_button(mbl,1,3,&fe->menubar_new); set_menugrid_button(mbl,2,3,&fe->menubar_undo); set_menugrid_button(mbl,3,3,&fe->menubar_cmp); 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->popmsg = 0; fe->menubar_items = 0; setup_menubar_button(fe,&fe->menubar_quit,0,"Quit",&menu_click_quit); setup_menubar_button(fe,&fe->menubar_id,0,"Game ID",&menu_click_id); setup_menubar_button(fe,&fe->menubar_seed,0,"Seed",&menu_click_seed); setup_menubar_button(fe,&fe->menubar_save,0,"Save",&menu_click_save); setup_menubar_button(fe,&fe->menubar_load,0,"Load",&menu_click_load); if (fe->flags & FEF_CONFIG) setup_menubar_button(fe,&fe->menubar_config,0,"Config",&menu_click_config); 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); setup_menubar_button(fe,&fe->menubar_new,0,"New",&menu_click_new); setup_menubar_button(fe,&fe->menubar_reset,0,"Reset",&menu_click_reset); setup_menubar_button(fe,&fe->menubar_cmp,&midend_can_cmp,"Compare",&menu_click_cmp); if (thegame.can_solve) { fe->flags |= FEF_SOLVE; setup_menubar_button(fe,&fe->menubar_solve,0,"Solve",&menu_click_solve); } setup_menubar_button(fe,&fe->menubar_snap,0,"Snap",&menu_click_snap); setup_custom_button(&fe->custom_cancel,&fe->cbox,"Cancel",0); setup_custom_button(&fe->custom_done,&fe->cbox,"Done",1); setup_simple_button(fe,&fe->file_cancel,"Cancel",&click_file_cancel); 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("x_draw_text, x=%d y=%d ftype=%d fsize=%d align=%d colour=%d text=%s",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("x_draw_rect, x=%d y=%d w=%d h=%d colour=%d",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("x_draw_line, x1=%d y1=%d x2=%d y2=%d colour=%d",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, const 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("x_draw_polygon, n=%d fillc=%d borderc=%d",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("x_draw_circle, cx=%d cy=%d r=%d fillc=%d borderc=%d",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("x_draw_update, x=%d y=%d w=%d h=%d",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("x_clip, x=%d y=%d w=%d h=%d",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("x_unclip"); 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("x_status_bar, text=%s",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("x_blitter_new, w=%d h=%d returning %p",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("x_blitter_free, b=%p",(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("x_blitter_save, b=%p x=%d y=%d",(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("x_blitter_load, b=%p x=%d y=%d",(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("x_draw_thick_line, w=%g x1=%g y1=%g x2=%g y2=%g colour=%d",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; cp[0] = 1; cp[1] = 1; cp[2] = 1; } // 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__)) ) { } static void force_rewrap(OLINE *o) { o->wrapcols = -1; } static void cmds_prep(CMD *cv) { int i; if (cv[0].len) return; for (i=0;cv[i].cmd;i++) { cv[i].len = strlen(cv[i].cmd); } } static void cmd_top_impl_raise(const char *body __attribute__((__unused__)), int len) { if (len) { fprintf(output,"`raise' takes no arguments\n"); return; } XRaiseWindow(disp,fe->topwin); } static void cmd_top_impl_lower(const char *body __attribute__((__unused__)), int len) { if (len) { fprintf(output,"`lower' takes no arguments\n"); return; } XLowerWindow(disp,fe->topwin); } static void cmd_top_impl_quit(const char *body __attribute__((__unused__)), int len) { if (len) { fprintf(output,"`quit' takes no arguments\n"); return; } clean_exit(); } static CMD cmds_top[] = { { "raise", &cmd_top_impl_raise, "Try to raise the game window" }, { "lower", &cmd_top_impl_lower, "Try to lower the game window" }, { "quit", &cmd_top_impl_quit, "Quit the game immediately" }, { 0 } }; static void command_question(const char *text, int len) { int i; int n; if (len < 1) { fprintf(output,"? this help\n"); fprintf(output,"? XYZ help on commands beginning XYZ\n"); fprintf(output,"Commands:"); for (i=0;cmds_top[i].cmd;i++) fprintf(output," %s",cmds_top[i].cmd); fprintf(output,"\n"); return; } fprintf(output,"Commands beginning `%.*s':",len,text); n = 0; for (i=0;cmds_top[i].cmd;i++) { if ((cmds_top[i].len >= len) && !bcmp(text,cmds_top[i].cmd,len)) { fprintf(output,"\n%s: %s",cmds_top[i].cmd,cmds_top[i].help); n ++; } } if (! n) fprintf(output," (none)"); fprintf(output,"\n"); } static void iline_command(const char *text, int len) { int x; int x0; int l; int i; int f; cmds_prep(&cmds_top[0]); for (x=0;(x= len) return; if (text[x] == '?') { for (x++;(x= l) && !bcmp(cmds_top[i].cmd,text+x0,l)) { switch (f) { case -2: fprintf(output," %s",cmds_top[i].cmd); break; case -1: f = i; break; default: fprintf(output,"Ambiguous, matches are: %s %s",cmds_top[f].cmd,cmds_top[i].cmd); f = -2; break; } } } switch (f) { case -2: fprintf(output,"\n"); break; case -1: fprintf(output,"No command `%.*s' found\n",l,text+x0); break; default: (*cmds_top[f].impl)(text+x,len-x); break; } } static void iline_process(void) { iline_command(iline->content,iline->len); iline->len = 0; ilinec = 0; } static void iline_room(int n) { if (n > ilinea) { ilinea = iline->len + n; iline->content = realloc(iline->content,ilinea); } } static void iline_del_at(int at, int n) { if ((at < 0) || (n < 0) || (at > iline->len) || (at+n > iline->len)) abort(); if (n < 1) return; if (at+n < iline->len) bcopy(iline->content+at+n,iline->content+at,iline->len-(at+n)); iline->len -= n; } static void iline_ins_at(int at, const void *data, int n) { if ((at < 0) || (n < 0) || (at > iline->len)) abort(); if (n < 1) return; iline_room(iline->len+n); if (at < iline->len) bcopy(iline->content+at,iline->content+at+n,iline->len-at); bcopy(data,iline->content+at,n); iline->len += n; } static void iline_transpose(void) { char t; if (ilinec < 2) return; t = iline->content[ilinec-1]; iline->content[ilinec-1] = iline->content[ilinec-2]; iline->content[ilinec-2] = t; } static void stdin_typein(unsigned char c) { switch (c) { case 0x01: // ^A ilinec = 0; break; case 0x02: // ^B if (ilinec > 0) ilinec --; break; case 0x04: // ^D if (ilinec < iline->len) iline_del_at(ilinec,1); break; case 0x05: // ^E ilinec = iline->len; break; case 0x06: // ^F if (ilinec < iline->len) ilinec ++; break; case 0x08: // ^H case 0x7f: // DEL if (ilinec > 0) { ilinec --; iline_del_at(ilinec,1); } break; case 0x0a: // ^J case 0x0d: // ^M iline_process(); break; case 0x0b: // ^K iline->len = ilinec; break; case 0x0c: // ^L want_redraw |= REDRAW_FULL; break; case 0x14: // ^T iline_transpose(); break; case 0x15: // ^U case 0x18: // ^X iline->len = 0; ilinec = 0; break; default: if ( ((c >= 0x20) && (c < 0x7f)) || (c >= 0xa0) ) { iline_ins_at(ilinec,&c,1); ilinec ++; } break; } force_rewrap(iline); want_redraw |= REDRAW_WANT; } static void rd_stdin(void *arg __attribute__((__unused__))) { unsigned char rbuf[64]; int nr; int i; nr = read(0,&rbuf[0],sizeof(rbuf)); if (nr < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fatal("read from stdin: %s\n",strerror(errno)); } for (i=0;ilen+len > ocura) { ocura = ocur->len + len; ocur->content = realloc(ocur->content,ocura); } bcopy(data,ocur->content+ocur->len,len); ocur->len += len; if ( (ocur->wrapcols > 0) && (ocur->nscr > 0) && (ocur->scr[ocur->nscr-1].len+len < ocur->wrapcols) ) { ocur->scr[ocur->nscr-1].len += len; } else { force_rewrap(ocur); } want_redraw |= REDRAW_WANT; } static void init_oline(OLINE *o) { o->nlink = 0; o->olink = 0; o->content = 0; o->len = 0; o->wrapcols = -1; o->nscr = 0; o->scr = 0; } static void outline_next(void) { OLINE *o; o = malloc(sizeof(OLINE)); init_oline(o); o->content = malloc(ocur->len); bcopy(ocur->content,o->content,ocur->len); o->len = ocur->len; force_rewrap(o); o->nscr = 0; o->scr = 0; o->nlink = ocur; o->olink = ocur->olink; ocur->olink = o; if (o->olink) o->olink->nlink = o; ocur->len = 0; force_rewrap(ocur); want_redraw |= REDRAW_WANT; } static int out_w(void *cookie __attribute__((__unused__)), const char *data, int len0) { int len; const char *nl; len = len0; while (len > 0) { nl = memchr(data,'\n',len); if (! nl) { outline_append(data,len); break; } outline_append(data,nl-data); outline_next(); len = (data + len) - (nl + 1); data = nl + 1; } return(len0); } static void maybe_rewrap(OLINE *o) { int at; OSLINE *s; int n; if (o->wrapcols == COLS) return; free(o->scr); o->wrapcols = COLS; o->nscr = 0; o->scr = 0; at = 0; while (1) { o->nscr ++; o->scr = realloc(o->scr,o->nscr*sizeof(OSLINE)); s = &o->scr[o->nscr-1]; s->off = at; n = o->len - at; if (n >= COLS) { n = COLS; s->len = n; at += n; } else { s->len = n; break; } } } static int maybe_redraw_text(void) { int rv; want_redraw = 0; rv = AIO_BLOCK_NIL; while (otext != ocur) { printf("%.*s\n",otext->len,otext->content); otext = otext->nlink; rv = AIO_BLOCK_LOOP; } return(rv); } static int maybe_redraw_curses(void) { OLINE *o; int l; int sl; int i; OSLINE *s; int nol; int cx; int cy; fflush(0); if (want_redraw & REDRAW_FULL) { clearok(stdscr,TRUE); want_redraw &= REDRAW_FULL; want_redraw |= REDRAW_WANT; } if (! (want_redraw & REDRAW_WANT)) return(AIO_BLOCK_NIL); want_redraw = 0; nol = (LINES - 1) / 2; o = alllines; l = 0; cx = -1; cy = -1; while (o && (l < nol)) { maybe_rewrap(o); for (sl=o->nscr-1;(sl>=0)&&(lscr[sl]; move(LINES-1-l,0); addnstr(o->content+s->off,s->len); if ( (o == iline) && (ilinec >= s->off) && ( (ilinec < s->off+s->len) || ( (ilinec == s->off+s->len) && (sl == o->nscr-1) ) ) ) { cx = ilinec - s->off; cy = LINES - 1 - l; } if (sl == o->nscr-1) { if (o == ocur) { for (i=s->len;iolink; } move(LINES-1-l,0); for (i=COLS;i>0;i--) addch('='); if (cx < 0) { move(LINES-1,COLS-2); } else { move(cy,cx); } refresh(); return(AIO_BLOCK_LOOP); } static int maybe_redraw(void *arg __attribute__((__unused__))) { return(debug?maybe_redraw_text():maybe_redraw_curses()); } static void setup_curses(void) { output = funopen(0,0,&out_w,0,0); ocur = malloc(sizeof(OLINE)); otext = ocur; init_oline(ocur); ocura = 0; iline = malloc(sizeof(OLINE)); init_oline(iline); ilinea = 0; ilinec = 0; alllines = iline; iline->olink = ocur; iline->nlink = 0; ocur->olink = 0; ocur->nlink = iline; want_redraw = REDRAW_FULL; if (! debug) { start_curses(); set_nbio(0); } aio_add_poll(0,&aio_rwtest_always,&aio_rwtest_never,&rd_stdin,0,0); aio_add_block(&maybe_redraw,0); } static void handle_signal(int sig) { volatile sig_atomic_t *var; switch (sig) { case SIGINT: var = &got_sigint; break; case SIGWINCH: var = &got_sigwinch; break; default: return; break; } *var = 1; write(sigpipe[1],"",1); // ignore errors } static void catch_signal(int sig) { struct sigaction sa; sa.sa_handler = &handle_signal; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(sig,&sa,0); } static void process_sigint(void) { static int count = 0; fprintf(output,"caught SIGINT\n"); if (count++ > 3) clean_exit(); } /* * There is surely a better way to do this than the endwin/initscr * dance, but I'm not sure what it is. 5.2's libcurses has * resizeterm(), but 1.4T's doesn't. */ static void process_sigwinch(void) { if (debug) return; end_curses(); want_redraw |= REDRAW_FULL; start_curses(); } static void rd_sigpipe(void *arg __attribute__((__unused__))) { unsigned char buf[64]; read(sigpipe[0],&buf[0],sizeof(buf)); if (got_sigint) { got_sigint = 0; process_sigint(); } if (got_sigwinch) { got_sigwinch = 0; process_sigwinch(); } } static void setup_signals(void) { if (socketpair(AF_LOCAL,SOCK_STREAM,0,&sigpipe[0]) < 0) { fprintf(stderr,"%s: signal socketpair: %s\n",__progname,strerror(errno)); exit(1); } set_nbio(sigpipe[0]); set_nbio(sigpipe[1]); got_sigint = 0; got_sigwinch = 0; if (! debug) { catch_signal(SIGINT); catch_signal(SIGWINCH); } aio_add_poll(sigpipe[0],&aio_rwtest_always,&aio_rwtest_never,&rd_sigpipe,0,0); } int main(int, char **); int main(int ac, char **av) { (void)&layout_dump_to; 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_curses(); setup_game(); setup_signals(); aio_event_loop(); return(0); }