+static DC dc;
+static XWindow xw;
+static Term term;
+static CSIEscape csiescseq;
+static STREscape strescseq;
+static int cmdfd;
+static pid_t pid;
+static Selection sel;
+static int iofd = -1;
+static char **opt_cmd = NULL;
+static char *opt_io = NULL;
+static char *opt_title = NULL;
+static char *opt_embed = NULL;
+static char *opt_class = NULL;
+static char *opt_font = NULL;
+
+void *
+xmalloc(size_t len) {
+       void *p = malloc(len);
+
+       if(!p)
+               die("Out of memory\n");
+
+       return p;
+}
+
+void *
+xrealloc(void *p, size_t len) {
+       if((p = realloc(p, len)) == NULL)
+               die("Out of memory\n");
+
+       return p;
+}
+
+void *
+xcalloc(size_t nmemb, size_t size) {
+       void *p = calloc(nmemb, size);
+
+       if(!p)
+               die("Out of memory\n");
+
+       return p;
+}
+
+char *
+smstrcat(char *src, ...)
+{
+       va_list fmtargs;
+       char *ret, *p, *v;
+       int len, slen, flen;
+
+       len = slen = strlen(src);
+
+       va_start(fmtargs, src);
+       for(;;) {
+               v = va_arg(fmtargs, char *);
+               if(v == NULL)
+                       break;
+               len += strlen(v);
+       }
+       va_end(fmtargs);
+
+       p = ret = xmalloc(len+1);
+       memmove(p, src, slen);
+       p += slen;
+
+       va_start(fmtargs, src);
+       for(;;) {
+               v = va_arg(fmtargs, char *);
+               if(v == NULL)
+                       break;
+               flen = strlen(v);
+               memmove(p, v, flen);
+               p += flen;
+       }
+       va_end(fmtargs);
+
+       ret[len] = '\0';
+
+       return ret;
+}
+
+int
+utf8decode(char *s, long *u) {
+       uchar c;
+       int i, n, rtn;
+
+       rtn = 1;
+       c = *s;
+       if(~c & B7) { /* 0xxxxxxx */
+               *u = c;
+               return rtn;
+       } else if((c & (B7|B6|B5)) == (B7|B6)) { /* 110xxxxx */
+               *u = c&(B4|B3|B2|B1|B0);
+               n = 1;
+       } else if((c & (B7|B6|B5|B4)) == (B7|B6|B5)) { /* 1110xxxx */
+               *u = c&(B3|B2|B1|B0);
+               n = 2;
+       } else if((c & (B7|B6|B5|B4|B3)) == (B7|B6|B5|B4)) { /* 11110xxx */
+               *u = c & (B2|B1|B0);
+               n = 3;
+       } else {
+               goto invalid;
+       }
+
+       for(i = n, ++s; i > 0; --i, ++rtn, ++s) {
+               c = *s;
+               if((c & (B7|B6)) != B7) /* 10xxxxxx */
+                       goto invalid;
+               *u <<= 6;
+               *u |= c & (B5|B4|B3|B2|B1|B0);
+       }
+
+       if((n == 1 && *u < 0x80) ||
+          (n == 2 && *u < 0x800) ||
+          (n == 3 && *u < 0x10000) ||
+          (*u >= 0xD800 && *u <= 0xDFFF)) {
+               goto invalid;
+       }
+
+       return rtn;
+invalid:
+       *u = 0xFFFD;
+
+       return rtn;
+}
+
+int
+utf8encode(long *u, char *s) {
+       uchar *sp;
+       ulong uc;
+       int i, n;
+
+       sp = (uchar *)s;
+       uc = *u;
+       if(uc < 0x80) {
+               *sp = uc; /* 0xxxxxxx */
+               return 1;
+       } else if(*u < 0x800) {
+               *sp = (uc >> 6) | (B7|B6); /* 110xxxxx */
+               n = 1;
+       } else if(uc < 0x10000) {
+               *sp = (uc >> 12) | (B7|B6|B5); /* 1110xxxx */
+               n = 2;
+       } else if(uc <= 0x10FFFF) {
+               *sp = (uc >> 18) | (B7|B6|B5|B4); /* 11110xxx */
+               n = 3;
+       } else {
+               goto invalid;
+       }
+
+       for(i=n,++sp; i>0; --i,++sp)
+               *sp = ((uc >> 6*(i-1)) & (B5|B4|B3|B2|B1|B0)) | B7; /* 10xxxxxx */
+
+       return n+1;
+invalid:
+       /* U+FFFD */
+       *s++ = '\xEF';
+       *s++ = '\xBF';
+       *s = '\xBD';
+
+       return 3;
+}
+
+/* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode
+   UTF-8 otherwise return 0 */
+int
+isfullutf8(char *s, int b) {
+       uchar *c1, *c2, *c3;
+
+       c1 = (uchar *)s;
+       c2 = (uchar *)++s;
+       c3 = (uchar *)++s;
+       if(b < 1) {
+               return 0;
+       } else if((*c1&(B7|B6|B5)) == (B7|B6) && b == 1) {
+               return 0;
+       } else if((*c1&(B7|B6|B5|B4)) == (B7|B6|B5) &&
+           ((b == 1) ||
+           ((b == 2) && (*c2&(B7|B6)) == B7))) {
+               return 0;
+       } else if((*c1&(B7|B6|B5|B4|B3)) == (B7|B6|B5|B4) &&
+           ((b == 1) ||
+           ((b == 2) && (*c2&(B7|B6)) == B7) ||
+           ((b == 3) && (*c2&(B7|B6)) == B7 && (*c3&(B7|B6)) == B7))) {
+               return 0;
+       } else {
+               return 1;
+       }
+}
+
+int
+utf8size(char *s) {
+       uchar c = *s;
+
+       if(~c&B7) {
+               return 1;
+       } else if((c&(B7|B6|B5)) == (B7|B6)) {
+               return 2;
+       } else if((c&(B7|B6|B5|B4)) == (B7|B6|B5)) {
+               return 3;
+       } else {
+               return 4;
+       }
+}
+
+void
+selinit(void) {
+       memset(&sel.tclick1, 0, sizeof(sel.tclick1));
+       memset(&sel.tclick2, 0, sizeof(sel.tclick2));
+       sel.mode = 0;
+       sel.bx = -1;
+       sel.clip = NULL;
+       sel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
+       if(sel.xtarget == None)
+               sel.xtarget = XA_STRING;
+}
+
+static inline bool
+selected(int x, int y) {
+       int bx, ex;
+
+       if(sel.ey == y && sel.by == y) {
+               bx = MIN(sel.bx, sel.ex);
+               ex = MAX(sel.bx, sel.ex);
+               return BETWEEN(x, bx, ex);
+       }
+
+       return ((sel.b.y < y && y < sel.e.y)
+                       || (y == sel.e.y && x <= sel.e.x))
+                       || (y == sel.b.y && x >= sel.b.x
+                               && (x <= sel.e.x || sel.b.y != sel.e.y));
+}
+
+void
+getbuttoninfo(XEvent *e, int *b, int *x, int *y) {
+       if(b)
+               *b = e->xbutton.button;
+
+       *x = X2COL(e->xbutton.x);
+       *y = Y2ROW(e->xbutton.y);
+       sel.b.x = sel.by < sel.ey ? sel.bx : sel.ex;
+       sel.b.y = MIN(sel.by, sel.ey);
+       sel.e.x = sel.by < sel.ey ? sel.ex : sel.bx;
+       sel.e.y = MAX(sel.by, sel.ey);
+}
+
+void
+mousereport(XEvent *e) {
+       int x = X2COL(e->xbutton.x);
+       int y = Y2ROW(e->xbutton.y);
+       int button = e->xbutton.button;
+       int state = e->xbutton.state;
+       char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 };
+       static int ob, ox, oy;
+
+       /* from urxvt */
+       if(e->xbutton.type == MotionNotify) {
+               if(!IS_SET(MODE_MOUSEMOTION) || (x == ox && y == oy))
+                       return;
+               button = ob + 32;
+               ox = x, oy = y;
+       } else if(e->xbutton.type == ButtonRelease || button == AnyButton) {
+               button = 3;
+       } else {
+               button -= Button1;
+               if(button >= 3)
+                       button += 64 - 3;
+               if(e->xbutton.type == ButtonPress) {
+                       ob = button;
+                       ox = x, oy = y;
+               }
+       }
+
+       buf[3] = 32 + button + (state & ShiftMask ? 4 : 0)
+               + (state & Mod4Mask    ? 8  : 0)
+               + (state & ControlMask ? 16 : 0);
+
+       ttywrite(buf, sizeof(buf));
+}
+
+void
+bpress(XEvent *e) {
+       if(IS_SET(MODE_MOUSE)) {
+               mousereport(e);
+       } else if(e->xbutton.button == Button1) {
+               if(sel.bx != -1) {
+                       sel.bx = -1;
+                       tsetdirt(sel.b.y, sel.e.y);
+                       draw();
+               }
+               sel.mode = 1;
+               sel.ex = sel.bx = X2COL(e->xbutton.x);
+               sel.ey = sel.by = Y2ROW(e->xbutton.y);
+       }
+}
+
+void
+selcopy(void) {
+       char *str, *ptr, *p;
+       int x, y, bufsize, is_selected = 0, size;
+       Glyph *gp;
+
+       if(sel.bx == -1) {
+               str = NULL;
+       } else {
+               bufsize = (term.col+1) * (sel.e.y-sel.b.y+1) * UTF_SIZ;
+               ptr = str = xmalloc(bufsize);
+
+               /* append every set & selected glyph to the selection */
+               for(y = 0; y < term.row; y++) {
+                       for(x = 0; x < term.col; x++) {
+                               gp = &term.line[y][x];
+
+                               if(!(is_selected = selected(x, y)))
+                                       continue;
+                               p = (gp->state & GLYPH_SET) ? gp->c : " ";
+                               size = utf8size(p);
+                               memcpy(ptr, p, size);
+                               ptr += size;
+                       }
+                       /* \n at the end of every selected line except for the last one */
+                       if(is_selected && y < sel.e.y)
+                               *ptr++ = '\n';
+               }
+               *ptr = 0;
+       }
+       sel.alt = IS_SET(MODE_ALTSCREEN);
+       xsetsel(str);
+}
+
+void
+selnotify(XEvent *e) {
+       ulong nitems, ofs, rem;
+       int format;
+       uchar *data;
+       Atom type;
+
+       ofs = 0;
+       do {
+               if(XGetWindowProperty(xw.dpy, xw.win, XA_PRIMARY, ofs, BUFSIZ/4,
+                                       False, AnyPropertyType, &type, &format,
+                                       &nitems, &rem, &data)) {
+                       fprintf(stderr, "Clipboard allocation failed\n");
+                       return;
+               }
+               ttywrite((const char *) data, nitems * format / 8);
+               XFree(data);
+               /* number of 32-bit chunks returned */
+               ofs += nitems * format / 32;
+       } while(rem > 0);
+}
+
+void
+selpaste(void) {
+       XConvertSelection(xw.dpy, XA_PRIMARY, sel.xtarget, XA_PRIMARY,
+                       xw.win, CurrentTime);
+}
+
+void selclear(XEvent *e) {
+       if(sel.bx == -1)
+               return;
+       sel.bx = -1;
+       tsetdirt(sel.b.y, sel.e.y);
+}
+
+void
+selrequest(XEvent *e) {
+       XSelectionRequestEvent *xsre;
+       XSelectionEvent xev;
+       Atom xa_targets, string;
+
+       xsre = (XSelectionRequestEvent *) e;
+       xev.type = SelectionNotify;
+       xev.requestor = xsre->requestor;
+       xev.selection = xsre->selection;
+       xev.target = xsre->target;
+       xev.time = xsre->time;
+       /* reject */
+       xev.property = None;
+
+       xa_targets = XInternAtom(xw.dpy, "TARGETS", 0);
+       if(xsre->target == xa_targets) {
+               /* respond with the supported type */
+               string = sel.xtarget;
+               XChangeProperty(xsre->display, xsre->requestor, xsre->property,
+                               XA_ATOM, 32, PropModeReplace,
+                               (uchar *) &string, 1);
+               xev.property = xsre->property;
+       } else if(xsre->target == sel.xtarget && sel.clip != NULL) {
+               XChangeProperty(xsre->display, xsre->requestor, xsre->property,
+                               xsre->target, 8, PropModeReplace,
+                               (uchar *) sel.clip, strlen(sel.clip));
+               xev.property = xsre->property;
+       }
+
+       /* all done, send a notification to the listener */
+       if(!XSendEvent(xsre->display, xsre->requestor, True, 0, (XEvent *) &xev))
+               fprintf(stderr, "Error sending SelectionNotify event\n");
+}
+
+void
+xsetsel(char *str) {
+       /* register the selection for both the clipboard and the primary */
+       Atom clipboard;
+
+       free(sel.clip);
+       sel.clip = str;
+
+       XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, CurrentTime);
+
+       clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
+       XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime);
+}
+
+void
+brelease(XEvent *e) {
+       struct timeval now;
+
+       if(IS_SET(MODE_MOUSE)) {
+               mousereport(e);
+               return;
+       }
+
+       if(e->xbutton.button == Button2) {
+               selpaste();
+       } else if(e->xbutton.button == Button1) {
+               sel.mode = 0;
+               getbuttoninfo(e, NULL, &sel.ex, &sel.ey);
+               term.dirty[sel.ey] = 1;
+               if(sel.bx == sel.ex && sel.by == sel.ey) {
+                       sel.bx = -1;
+                       gettimeofday(&now, NULL);
+
+                       if(TIMEDIFF(now, sel.tclick2) <= TRIPLECLICK_TIMEOUT) {
+                               /* triple click on the line */
+                               sel.b.x = sel.bx = 0;
+                               sel.e.x = sel.ex = term.col;
+                               sel.b.y = sel.e.y = sel.ey;
+                               selcopy();
+                       } else if(TIMEDIFF(now, sel.tclick1) <= DOUBLECLICK_TIMEOUT) {
+                               /* double click to select word */
+                               sel.bx = sel.ex;
+                               while(sel.bx > 0 && term.line[sel.ey][sel.bx-1].state & GLYPH_SET &&
+                                               term.line[sel.ey][sel.bx-1].c[0] != ' ') {
+                                       sel.bx--;
+                               }
+                               sel.b.x = sel.bx;
+                               while(sel.ex < term.col-1 && term.line[sel.ey][sel.ex+1].state & GLYPH_SET &&
+                                               term.line[sel.ey][sel.ex+1].c[0] != ' ') {
+                                       sel.ex++;
+                               }
+                               sel.e.x = sel.ex;
+                               sel.b.y = sel.e.y = sel.ey;
+                               selcopy();
+                       }
+               } else {
+                       selcopy();
+               }
+       }
+
+       memcpy(&sel.tclick2, &sel.tclick1, sizeof(struct timeval));
+       gettimeofday(&sel.tclick1, NULL);
+}
+
+void
+bmotion(XEvent *e) {
+       int starty, endy, oldey, oldex;
+
+       if(IS_SET(MODE_MOUSE)) {
+               mousereport(e);
+               return;
+       }
+
+       if(sel.mode) {
+               oldey = sel.ey;
+               oldex = sel.ex;
+               getbuttoninfo(e, NULL, &sel.ex, &sel.ey);
+
+               if(oldey != sel.ey || oldex != sel.ex) {
+                       starty = MIN(oldey, sel.ey);
+                       endy = MAX(oldey, sel.ey);
+                       tsetdirt(starty, endy);
+               }
+       }
+}