/* * Multi-mterm front end. * * Manages multiple mterms in a single window-manager window. * * The effect is rather like window(1), only the interface is natively * X instead of termcap-based. * * We set up a container window and manage multiple mterm instances * within it. Each mterm instance is run with -blanket and * -keystroke-fd, -blanket to make it fit a window we manage within * the container window and -keystroke-fd so we can get keystrokes to * it properly. */ #include #include #include #include #include #include #include #include #include #include extern const char *__progname; static const char *displayarg = 0; static const char *fontarg = 0; static const char *geometryarg = 0; static const char *fgarg = "white"; static const char *bgarg = "black"; static const char *bcarg = "white"; static const char *ecarg = "#800080008000"; static const char *bwarg = "1"; static const char *bmarg = "1"; typedef struct callonce CALLONCE; typedef struct geom GEOM; typedef struct col COL; typedef struct em EM; typedef struct emstart EMSTART; struct emstart { EM *e; int go; int xp; } ; struct em { EM *flink; EM *blink; unsigned int flags; #define EMF_DEAD 0x00000001 int x; int y; int w; int h; LX_XID pwin; pid_t kid; int ctlfd; AIO_OQ ctloq; int id; } ; struct col { const char *str; LX_RGB rgb; unsigned int pix; int good; } ; struct geom { unsigned int x; unsigned int y; int w; int h; unsigned int flags; #define GF_POS 0x00000001 #define GF_X_NEG 0x00000002 #define GF_Y_NEG 0x00000004 } ; struct callonce { void (*fn)(void *); void *arg; int id; } ; static LX_CONN *xc; static int scrno; static LX_XID root; static int depth; static LX_XID visual; static LX_XID cmap; static LX_XID fontid; static LX_FONTINFO *fontinfo; static LX_SGC gc; static LX_SGC gc1; static COL fg_col; static COL bg_col; static COL bc_col; static COL ec_col; static int startup_pending; static int startid; static EM *ems; static LX_XID topwin; static LX_XID contwin; static LX_XID evtwin; static GEOM geom; static int bordermargin; static int borderwidth; static int charwidth; static int baselineskip; static LX_XID cursor; static unsigned int scrw; static unsigned int scrh; static EM *focus; static int focusid; static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { fprintf(stderr,"%s: stray argument `%s'\n",__progname,*av); errs = 1; continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs = 1; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-display")) { WANTARG(); displayarg = av[skip]; continue; } if (!strcmp(*av,"-geometry")) { WANTARG(); geometryarg = av[skip]; continue; } if (!strcmp(*av,"-font")) { WANTARG(); fontarg = av[skip]; continue; } if (!strcmp(*av,"-fg") || !strcmp(*av,"-foreground")) { WANTARG(); fgarg = av[skip]; continue; } if (!strcmp(*av,"-bg") || !strcmp(*av,"-background")) { WANTARG(); bgarg = av[skip]; continue; } if (!strcmp(*av,"-bc") || !strcmp(*av,"-bordercolour") || !strcmp(*av,"-bordercolor")) { WANTARG(); bcarg = av[skip]; continue; } if (!strcmp(*av,"-ec") || !strcmp(*av,"-emptycolour") || !strcmp(*av,"-emptycolor")) { WANTARG(); ecarg = av[skip]; continue; } if (!strcmp(*av,"-bw") || !strcmp(*av,"-borderwidth")) { WANTARG(); bwarg = av[skip]; continue; } if (!strcmp(*av,"-bm") || !strcmp(*av,"-bordermargin")) { WANTARG(); bmarg = av[skip]; continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs = 1; } if (errs) exit(1); } static void setup_preopen(void) { borderwidth = atoi(bwarg); if (borderwidth < 0) borderwidth = 0; bordermargin = atoi(bmarg); if (bordermargin < 0) bordermargin = 0; } static int call_once_call(void *cov) { CALLONCE *co; void (*fn)(void *); void *arg; co = cov; fn = co->fn; arg = co->arg; aio_remove_block(co->id); free(co); (*fn)(arg); return(AIO_BLOCK_LOOP); } static void call_once(void (*fn)(void *), void *arg) { CALLONCE *co; co = malloc(sizeof(CALLONCE)); co->fn = fn; co->arg = arg; co->id = aio_add_block(&call_once_call,co); } static GEOM parse_geometry(const char *arg) { int v[4]; char c[2][2]; int n1; int n2; n1 = -1; n2 = -1; sscanf(arg,"%dx%d%1[+-]%d%1[+-]%d %n%*c%n",&v[0],&v[1],&c[0][0],&v[2],&c[1][0],&v[3],&n1,&n2); if (n2 >= 0) { fprintf(stderr,"%s: geometry spec has trailing junk: %s\n",__progname,arg); exit(1); } if (n1 >= 0) { if ((v[0] <= 0) || (v[1] <= 0)) { fprintf(stderr,"%s: geometry spec has nonpositive width or height: %s\n",__progname,arg); exit(1); } return((GEOM){.w=v[0],.h=v[1],.x=v[2],.y=v[3],.flags=GF_POS|((c[0][0]=='-')?GF_X_NEG:0)|((c[1][0]=='-')?GF_Y_NEG:0)}); } sscanf(arg,"%dx%d %n%*c%n",&v[0],&v[1],&n1,&n2); if (n2 >= 0) { fprintf(stderr,"%s: geometry spec has trailing junk: %s\n",__progname,arg); exit(1); } if (n1 >= 0) { if ((v[0] <= 0) || (v[1] <= 0)) { fprintf(stderr,"%s: geometry spec has nonpositive width or height: %s\n",__progname,arg); exit(1); } return((GEOM){.w=v[0],.h=v[1],.x=0,.y=0,.flags=0}); } fprintf(stderr,"%s: can't parse geometry spec: %s\n",__progname,arg); exit(1); } static void em_win_created(void *esv) { EMSTART *es; int e; int n; es = esv; write(es->go,"\1",1); close(es->go); n = recv(es->xp,&e,sizeof(e),MSG_WAITALL); if (n < 0) { fprintf(stderr,"%s: exec mterm: protocol error: recv: %s\n",__progname,strerror(errno)); exit(1); } close(es->xp); switch (n) { case 0: es->e->flink = ems; es->e->blink = 0; if (ems) ems->blink = es->e; ems = es->e; free(es); break; case sizeof(int): fprintf(stderr,"%s: exec mterm: %s\n",__progname,strerror(e)); close(es->e->ctlfd); lx_DestroyWindow(xc,es->e->pwin); aio_oq_flush(&es->e->ctloq); aio_remove_poll(es->e->id); free(es->e); free(es); break; default: fprintf(stderr,"%s: exec mterm: protocol error: read %d, expected 0 or %d\n",__progname,n,(int)sizeof(int)); exit(1); break; } } static int destroy_em(void *ev) { EM *e; e = ev; if (! (e->flags & EMF_DEAD)) abort(); aio_remove_block(e->id); lx_DestroyWindow(xc,e->pwin); close(e->ctlfd); aio_oq_flush(&e->ctloq); if (e->flink) e->flink->blink = e->blink; if (e->blink) e->blink->flink = e->flink; else ems = e->flink; free(e); return(AIO_BLOCK_LOOP); } static void close_em(EM *e) { if (e->flags & EMF_DEAD) return; e->flags |= EMF_DEAD; aio_remove_poll(e->id); e->id = aio_add_block(&destroy_em,e); } static int wtest_em_ctl(void *ev) { EM *e; e = ev; return((e->flags&EMF_DEAD)?0:aio_oq_nonempty(&e->ctloq)); } static int em_ctl_send(void *ev, const struct iovec *iov, int niov) { EM *e; struct msghdr mh; e = ev; mh.msg_name = 0; mh.msg_namelen = 0; // XXX const poisoning botch *(const struct iovec **)&mh.msg_iov = iov; mh.msg_iovlen = niov; mh.msg_control = 0; mh.msg_controllen = 0; mh.msg_flags = 0; return(sendmsg(e->ctlfd,&mh,MSG_NOSIGNAL)); } static void rd_em_ctl(void *ev) { // If the ctlfd shows readable, mterm must have died, so... close_em(ev); } static void wr_em_ctl(void *ev) { EM *e; int w; e = ev; if (e->flags & EMF_DEAD) return; w = aio_oq_custom_writev(&e->ctloq,-1,&em_ctl_send,e); if (w < 0) { if (w == AIO_WRITEV_ERROR) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: emulator write: %s\n",__progname,strerror(errno)); close_em(e); return; } fprintf(stderr,"%s: aio_oq_writev returned impossible %d\n",__progname,w); } aio_oq_dropdata(&e->ctloq,w); } static void start_emulator(int x, int y, int w, int h) { int px; int py; int pw; int ph; EM *e; EMSTART *es; int ctl[2]; int go[2]; int xp[2]; char cmd; px = (x * charwidth) + bordermargin; py = (y * baselineskip) + bordermargin; pw = w * charwidth; ph = h * baselineskip; if (socketpair(AF_LOCAL,SOCK_STREAM,0,&ctl[0]) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } if (socketpair(AF_LOCAL,SOCK_STREAM,0,&xp[0]) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } if (pipe(&go[0]) < 0) { fprintf(stderr,"%s: pipe: %s\n",__progname,strerror(errno)); exit(1); } e = malloc(sizeof(EM)); es = malloc(sizeof(EMSTART)); e->flags = 0; e->x = x; e->y = y; e->w = w; e->h = h; e->pwin = lx_CreateWindow_va(xc,contwin,px,py,pw,ph,0,depth,LX_WCLASS_InputOutput,visual, LX_CWV_BackPixmap(LX_PIXMAP_None), LX_CWV_Colormap(cmap), LX_CWV_END); lx_MapWindow(xc,e->pwin); fflush(0); e->kid = fork(); if (e->kid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } fcntl(xp[1],F_SETFD,1); if (e->kid == 0) { char *winarg; char *fdarg; char *fgarg; char *bgarg; int err; asprintf(&winarg,"%#lx",(unsigned long int)e->pwin); asprintf(&fdarg,"%d",ctl[1]); asprintf(&fgarg,"#%04x%04x%04x",fg_col.rgb.r,fg_col.rgb.g,fg_col.rgb.b); asprintf(&bgarg,"#%04x%04x%04x",bg_col.rgb.r,bg_col.rgb.g,bg_col.rgb.b); close(xp[0]); close(ctl[0]); close(go[1]); if ((read(go[0],&cmd,1) != 1) || (cmd != 1)) _exit(0); close(go[0]); execlp("mterm","mterm","-blanket",winarg,"-control-fd",fdarg,"-fg",fgarg,"-bg",bgarg,"-bm","0","-bw","0", displayarg?"-display":(char *)0,displayarg,(char *)0); err = errno; write(xp[1],&err,sizeof(int)); _exit(1); } close(ctl[1]); close(xp[1]); close(go[0]); e->ctlfd = ctl[0]; aio_oq_init(&e->ctloq); e->id = aio_add_poll(e->ctlfd,&aio_rwtest_always,&wtest_em_ctl,&rd_em_ctl,&wr_em_ctl,e); es->e = e; es->go = go[1]; es->xp = xp[0]; lx_op_callback(lx_GetInputFocus(xc,0,0),&em_win_created,es,0); } static void tell_em_point(EM *e, const char *s, int l) { aio_oq_queue_point(&e->ctloq,s,l); } static void tell_em_copy(EM *e, const char *s, int l) { aio_oq_queue_copy(&e->ctloq,s,l); } static void focus_on(EM *e) { if (focus == e) return; if (focus) tell_em_point(focus,"fl",2); focus = e; tell_em_point(e,"ft",2); } static int check_focus(void *arg __attribute__((__unused__))) { if (!focus && ems) { focus_on(ems); return(AIO_BLOCK_LOOP); } return(AIO_BLOCK_NIL); } static int startup_step2(void *arg __attribute__((__unused__))) { int x; int y; int iw; int ih; int ow; int oh; LX_XID pm_d; LX_RECTANGLE r; if (startup_pending > 0) return(AIO_BLOCK_NIL); aio_remove_block(startid); if (!fg_col.good || !bg_col.good || !bc_col.good || !ec_col.good) exit(1); ems = 0; baselineskip = fontinfo->maxbounds.ascent + fontinfo->maxbounds.descent; charwidth = fontinfo->maxbounds.width; pm_d = lx_CreatePixmap(xc,root,1,3,baselineskip+2); lx_ChangeSGC_va(xc,gc1,LX_GCV_Foreground(0),LX_GCV_END); r.x = 0; r.y = 0; r.w = 3; r.h = baselineskip + 2; lx_PolyFillRectangle(xc,pm_d,lx_SGC_GC(xc,gc1),1,&r); lx_ChangeSGC_va(xc,gc1,LX_GCV_Foreground(1),LX_GCV_END); r.x = 1; r.y = 1; r.w = 1; r.h = baselineskip; lx_PolyFillRectangle(xc,pm_d,lx_SGC_GC(xc,gc1),1,&r); cursor = lx_CreateCursor_rgb(xc,pm_d,LX_PIXMAP_None,fg_col.rgb,bg_col.rgb,1,(baselineskip/2)+1); lx_FreePixmap(xc,pm_d); iw = (geom.w * charwidth) + (2 * bordermargin); ih = (geom.h * baselineskip) + (2 * bordermargin); ow = iw + (2 * borderwidth); oh = ih + (2 * borderwidth); if (geom.flags & GF_POS) { x = (geom.flags & GF_X_NEG) ? scrw - (ow + geom.x) : geom.x; y = (geom.flags & GF_Y_NEG) ? scrh - (oh + geom.y) : geom.y; } else { x = (scrw - ow) / 2; y = (scrh - oh) / 2; } topwin = lx_CreateWindow_va(xc,root,x,y,iw,ih,borderwidth,depth,LX_WCLASS_InputOutput,visual, LX_CWV_BackPixmap(LX_PIXMAP_None), LX_CWV_BorderPixel(bc_col.pix), LX_CWV_EventMask(LX_EM_StructureNotify|LX_EM_KeyPress), LX_CWV_Colormap(cmap), LX_CWV_Cursor(cursor), LX_CWV_END); contwin = lx_CreateWindow_va(xc,topwin,0,0,65535,65535,0,depth,LX_WCLASS_InputOutput,visual, LX_CWV_BackPixel(ec_col.pix), LX_CWV_END); evtwin = lx_CreateWindow_va(xc,topwin,0,0,65535,65535,0,0,LX_WCLASS_InputOnly,visual,LX_CWV_END); lx_MapSubwindows(xc,topwin); lx_MapWindow(xc,topwin); focus = 0; focusid = aio_add_block(&check_focus,0); start_emulator(0,0,geom.w,geom.h); return(AIO_BLOCK_LOOP); } static void keypress(LX_CONN *xc, LX_EVENT_KeyPress *e) { unsigned char s[LX_KBMAP_MAX_STRING]; int l; int i; unsigned char hdr[2]; l = lx_kbmap_string(xc,e->keycode,e->state,&s[0]); if (l == 0) { printf("KeyPress: kc %d state %04x, ignored\n",e->keycode,e->state); } else { printf("KeyPress: kc %d state %04x, string",e->keycode,e->state); for (i=0;itype) { case LX_EV_ConfigureNotify: printf("ConfigureNotify: "); if (e->u.ConfigureNotify.window != contwin) { printf("[*** win=%lx] ",(unsigned long int)e->u.ConfigureNotify.window); } printf( "%dx%d+%d+%d, bw %d\n", e->u.ConfigureNotify.w, e->u.ConfigureNotify.h, e->u.ConfigureNotify.x, e->u.ConfigureNotify.y, e->u.ConfigureNotify.borderwidth ); break; case LX_EV_KeyPress: keypress(xc,&e->u.KeyPress); break; default: printf("Event type %d\n",(int)e->type); break; } } static void startup_got_colour(void *cv) { COL *c; c = cv; printf("Colour: %s -> %04x,%04x,%04x, pixel %lx\n",c->str,c->rgb.r,c->rgb.g,c->rgb.b,(unsigned long int)c->pix); startup_pending --; } static void startup_alloc_colour(void *cv) { COL *c; c = cv; if (c->good) { lx_op_callback(lx_AllocColor(xc,cmap,c->rgb.r,c->rgb.g,c->rgb.b,&c->pix,&c->rgb.r,&c->rgb.g,&c->rgb.b),&startup_got_colour,c,0); } else { printf("Colour: %s -> fail\n",c->str); startup_pending --; } } static void setup_colour(COL *c, const char *s) { c->str = s; lx_op_callback(lx_lookup_color_rgb(xc,cmap,s,-1,&c->rgb,&c->good),&startup_alloc_colour,c,0); startup_pending ++; } static void dec_pending(void *arg __attribute__((__unused__))) { startup_pending --; } static void startup_step1(LX_CONN *newxc, void *arg __attribute__((__unused__))) { LX_XID pm1; xc = newxc; lx_set_event_handler(xc,&handle_event); scrno = lx_default_screen(xc); root = lx_root(xc,scrno); depth = lx_root_depth(xc,scrno); visual = lx_root_visual(xc,scrno); cmap = lx_root_colormap(xc,scrno); scrw = lx_root_width(xc,scrno); scrh = lx_root_height(xc,scrno); if (! geometryarg) geometryarg = "80x24"; geom = parse_geometry(geometryarg); if (fontarg) { fontid = lx_OpenFont(xc,fontarg); gc = lx_CreateSGC_va(xc,root,LX_GCV_Font(fontid),LX_GCV_END); } else { gc = lx_CreateSGC_va(xc,root,LX_GCV_END); fontid = lx_SGC_GC(xc,gc); } pm1 = lx_CreatePixmap(xc,root,1,1,1); gc1 = lx_CreateSGC_va(xc,pm1,LX_GCV_END); lx_SGC_GC(xc,gc1); lx_FreePixmap(xc,pm1); startup_pending = 2; // for QueryFont and kbmap setup lx_op_callback(lx_QueryFont(xc,fontid,&fontinfo),&dec_pending,0,0); lx_op_callback(lx_kbmap_setup(xc),&dec_pending,0,0); setup_colour(&fg_col,fgarg); setup_colour(&bg_col,bgarg); setup_colour(&bc_col,bcarg); setup_colour(&ec_col,ecarg); startid = aio_add_block(&startup_step2,0); } static void setup_signals(void) { signal(SIGCHLD,SIG_IGN); } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); setup_signals(); setup_preopen(); aio_poll_init(); lx_open(displayarg,0,&startup_step1,0,0,0); aio_event_loop(); (void)&call_once; return(1); }