+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 = STDOUT_FILENO;
+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;
+static int oldbutton = 3; /* button event on startup: 3 = release */
+
+static char *usedfont = NULL;
+static double usedfontsize = 0;
+
+static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
+static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
+static long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
+static long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
+
+/* Font Ring Cache */
+enum {
+ FRC_NORMAL,
+ FRC_ITALIC,
+ FRC_BOLD,
+ FRC_ITALICBOLD
+};
+
+typedef struct {
+ XftFont *font;
+ int flags;
+} Fontcache;
+
+/* Fontcache is an array now. A new font will be appended to the array. */
+static Fontcache frc[16];
+static int frclen = 0;
+
+ssize_t
+xwrite(int fd, const char *s, size_t len) {
+ size_t aux = len;
+
+ while(len > 0) {
+ ssize_t r = write(fd, s, len);
+ if(r < 0)
+ return r;
+ len -= r;
+ s += r;
+ }
+ return aux;
+}
+
+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;
+}
+
+char *
+xstrdup(char *s) {
+ if((s = strdup(s)) == NULL)
+ die("Out of memory\n");
+
+ return s;
+}
+
+size_t
+utf8decode(char *c, long *u, size_t clen) {
+ size_t i, j, len, type;
+ long udecoded;
+
+ *u = UTF_INVALID;
+ if(!clen)
+ return 0;
+ udecoded = utf8decodebyte(c[0], &len);
+ if(!BETWEEN(len, 1, UTF_SIZ))
+ return 1;
+ for(i = 1, j = 1; i < clen && j < len; ++i, ++j) {
+ udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
+ if(type != 0)
+ return j;
+ }
+ if(j < len)
+ return 0;
+ *u = udecoded;
+ utf8validate(u, len);
+ return len;
+}
+
+long
+utf8decodebyte(char c, size_t *i) {
+ for(*i = 0; *i < LEN(utfmask); ++(*i))
+ if(((uchar)c & utfmask[*i]) == utfbyte[*i])
+ return (uchar)c & ~utfmask[*i];
+ return 0;
+}
+
+size_t
+utf8encode(long u, char *c, size_t clen) {
+ size_t len, i;
+
+ len = utf8validate(&u, 0);
+ if(clen < len)
+ return 0;
+ for(i = len - 1; i != 0; --i) {
+ c[i] = utf8encodebyte(u, 0);
+ u >>= 6;
+ }
+ c[0] = utf8encodebyte(u, len);
+ return len;
+}
+
+char
+utf8encodebyte(long u, size_t i) {
+ return utfbyte[i] | (u & ~utfmask[i]);
+}
+
+size_t
+utf8len(char *c) {
+ return utf8decode(c, &(long){0}, UTF_SIZ);
+}
+
+size_t
+utf8validate(long *u, size_t i) {
+ if(!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
+ *u = UTF_INVALID;
+ for(i = 1; *u > utfmax[i]; ++i)
+ ;
+ return i;
+}
+
+static void
+selinit(void) {
+ memset(&sel.tclick1, 0, sizeof(sel.tclick1));
+ memset(&sel.tclick2, 0, sizeof(sel.tclick2));
+ sel.mode = 0;
+ sel.ob.x = -1;
+ sel.clip = NULL;
+ sel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
+ if(sel.xtarget == None)
+ sel.xtarget = XA_STRING;
+}
+
+static int
+x2col(int x) {
+ x -= borderpx;
+ x /= xw.cw;
+
+ return LIMIT(x, 0, term.col-1);
+}
+
+static int
+y2row(int y) {
+ y -= borderpx;
+ y /= xw.ch;
+
+ return LIMIT(y, 0, term.row-1);
+}
+
+static void
+selsort(void) {
+ if(sel.ob.y == sel.oe.y) {
+ sel.nb.x = MIN(sel.ob.x, sel.oe.x);
+ sel.ne.x = MAX(sel.ob.x, sel.oe.x);
+ } else {
+ sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
+ sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
+ }
+ sel.nb.y = MIN(sel.ob.y, sel.oe.y);
+ sel.ne.y = MAX(sel.ob.y, sel.oe.y);
+}
+
+static inline bool
+selected(int x, int y) {
+ if(sel.type == SEL_RECTANGULAR)
+ return BETWEEN(y, sel.nb.y, sel.ne.y)
+ && BETWEEN(x, sel.nb.x, sel.ne.x);
+
+ return BETWEEN(y, sel.nb.y, sel.ne.y)
+ && (y != sel.nb.y || x >= sel.nb.x)
+ && (y != sel.ne.y || x <= sel.ne.x);
+}
+
+void
+selsnap(int mode, int *x, int *y, int direction) {
+ int i;
+
+ switch(mode) {
+ case SNAP_WORD:
+ /*
+ * Snap around if the word wraps around at the end or
+ * beginning of a line.
+ */
+ for(;;) {
+ if(direction < 0 && *x <= 0) {
+ if(*y > 0 && term.line[*y - 1][term.col-1].mode
+ & ATTR_WRAP) {
+ *y -= 1;
+ *x = term.col-1;
+ } else {
+ break;
+ }
+ }
+ if(direction > 0 && *x >= term.col-1) {
+ if(*y < term.row-1 && term.line[*y][*x].mode
+ & ATTR_WRAP) {
+ *y += 1;
+ *x = 0;
+ } else {
+ break;
+ }
+ }
+
+ if(term.line[*y][*x+direction].mode & ATTR_WDUMMY) {
+ *x += direction;
+ continue;
+ }
+
+ if(strchr(worddelimiters,
+ term.line[*y][*x+direction].c[0])) {
+ break;
+ }
+
+ *x += direction;
+ }
+ break;
+ case SNAP_LINE:
+ /*
+ * Snap around if the the previous line or the current one
+ * has set ATTR_WRAP at its end. Then the whole next or
+ * previous line will be selected.
+ */
+ *x = (direction < 0) ? 0 : term.col - 1;
+ if(direction < 0 && *y > 0) {
+ for(; *y > 0; *y += direction) {
+ if(!(term.line[*y-1][term.col-1].mode
+ & ATTR_WRAP)) {
+ break;
+ }
+ }
+ } else if(direction > 0 && *y < term.row-1) {
+ for(; *y < term.row; *y += direction) {
+ if(!(term.line[*y][term.col-1].mode
+ & ATTR_WRAP)) {
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ /*
+ * Select the whole line when the end of line is reached.
+ */
+ if(direction > 0) {
+ i = term.col;
+ while(--i > 0 && term.line[*y][i].c[0] == ' ')
+ /* nothing */;
+ if(i > 0 && i < *x)
+ *x = term.col - 1;
+ }
+ break;
+ }
+}
+
+void
+getbuttoninfo(XEvent *e) {
+ int type;
+ uint state = e->xbutton.state & ~(Button1Mask | forceselmod);
+
+ sel.alt = IS_SET(MODE_ALTSCREEN);
+
+ sel.oe.x = x2col(e->xbutton.x);
+ sel.oe.y = y2row(e->xbutton.y);
+
+ if(sel.ob.y < sel.oe.y
+ || (sel.ob.y == sel.oe.y && sel.ob.x < sel.oe.x)) {
+ selsnap(sel.snap, &sel.ob.x, &sel.ob.y, -1);
+ selsnap(sel.snap, &sel.oe.x, &sel.oe.y, +1);
+ } else {
+ selsnap(sel.snap, &sel.oe.x, &sel.oe.y, -1);
+ selsnap(sel.snap, &sel.ob.x, &sel.ob.y, +1);
+ }
+ selsort();
+
+ sel.type = SEL_REGULAR;
+ for(type = 1; type < LEN(selmasks); ++type) {
+ if(match(selmasks[type], state)) {
+ sel.type = type;
+ break;
+ }
+ }
+}
+
+void
+mousereport(XEvent *e) {
+ int x = x2col(e->xbutton.x), y = y2row(e->xbutton.y),
+ button = e->xbutton.button, state = e->xbutton.state,
+ len;
+ char buf[40];
+ static int ox, oy;
+
+ /* from urxvt */
+ if(e->xbutton.type == MotionNotify) {
+ if(x == ox && y == oy)
+ return;
+ if(!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY))
+ return;
+ /* MOUSE_MOTION: no reporting if no button is pressed */
+ if(IS_SET(MODE_MOUSEMOTION) && oldbutton == 3)
+ return;
+
+ button = oldbutton + 32;
+ ox = x;
+ oy = y;
+ } else {
+ if(!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) {
+ button = 3;
+ } else {
+ button -= Button1;
+ if(button >= 3)
+ button += 64 - 3;
+ }
+ if(e->xbutton.type == ButtonPress) {
+ oldbutton = button;
+ ox = x;
+ oy = y;
+ } else if(e->xbutton.type == ButtonRelease) {
+ oldbutton = 3;
+ /* MODE_MOUSEX10: no button release reporting */
+ if(IS_SET(MODE_MOUSEX10))
+ return;
+ }
+ }
+
+ if(!IS_SET(MODE_MOUSEX10)) {
+ button += (state & ShiftMask ? 4 : 0)
+ + (state & Mod4Mask ? 8 : 0)
+ + (state & ControlMask ? 16 : 0);
+ }
+
+ len = 0;
+ if(IS_SET(MODE_MOUSESGR)) {
+ len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c",
+ button, x+1, y+1,
+ e->xbutton.type == ButtonRelease ? 'm' : 'M');
+ } else if(x < 223 && y < 223) {
+ len = snprintf(buf, sizeof(buf), "\033[M%c%c%c",
+ 32+button, 32+x+1, 32+y+1);
+ } else {
+ return;
+ }
+
+ ttywrite(buf, len);
+}
+
+void
+bpress(XEvent *e) {
+ struct timeval now;
+ Mousekey *mk;
+
+ if(IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) {
+ mousereport(e);
+ return;
+ }
+
+ for(mk = mshortcuts; mk < mshortcuts + LEN(mshortcuts); mk++) {
+ if(e->xbutton.button == mk->b
+ && match(mk->mask, e->xbutton.state)) {
+ ttysend(mk->s, strlen(mk->s));
+ return;
+ }
+ }
+
+ if(e->xbutton.button == Button1) {
+ gettimeofday(&now, NULL);
+
+ /* Clear previous selection, logically and visually. */
+ selclear(NULL);
+ sel.mode = 1;
+ sel.type = SEL_REGULAR;
+ sel.oe.x = sel.ob.x = x2col(e->xbutton.x);
+ sel.oe.y = sel.ob.y = y2row(e->xbutton.y);
+
+ /*
+ * If the user clicks below predefined timeouts specific
+ * snapping behaviour is exposed.
+ */
+ if(TIMEDIFF(now, sel.tclick2) <= tripleclicktimeout) {
+ sel.snap = SNAP_LINE;
+ } else if(TIMEDIFF(now, sel.tclick1) <= doubleclicktimeout) {
+ sel.snap = SNAP_WORD;
+ } else {
+ sel.snap = 0;
+ }
+ selsnap(sel.snap, &sel.ob.x, &sel.ob.y, -1);
+ selsnap(sel.snap, &sel.oe.x, &sel.oe.y, +1);
+ selsort();
+
+ /*
+ * Draw selection, unless it's regular and we don't want to
+ * make clicks visible
+ */
+ if(sel.snap != 0) {
+ sel.mode++;
+ tsetdirt(sel.nb.y, sel.ne.y);
+ }
+ sel.tclick2 = sel.tclick1;
+ sel.tclick1 = now;
+ }
+}
+
+char *
+getsel(void) {
+ char *str, *ptr;
+ int x, y, bufsize, size, i, ex;
+ Glyph *gp, *last;
+
+ if(sel.ob.x == -1)
+ return NULL;
+
+ bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
+ ptr = str = xmalloc(bufsize);
+
+ /* append every set & selected glyph to the selection */
+ for(y = sel.nb.y; y < sel.ne.y + 1; y++) {
+ gp = &term.line[y][0];
+ last = &gp[term.col-1];
+
+ while(last >= gp && !(selected(last - gp, y) &&
+ strcmp(last->c, " ") != 0)) {
+ --last;
+ }
+
+ for(x = 0; gp <= last; x++, ++gp) {
+ if(!selected(x, y) || (gp->mode & ATTR_WDUMMY))
+ continue;
+
+ size = utf8len(gp->c);
+ memcpy(ptr, gp->c, size);
+ ptr += size;
+ }
+
+ /*
+ * Copy and pasting of line endings is inconsistent
+ * in the inconsistent terminal and GUI world.
+ * The best solution seems like to produce '\n' when
+ * something is copied from st and convert '\n' to
+ * '\r', when something to be pasted is received by
+ * st.
+ * FIXME: Fix the computer world.
+ */
+ if(y < sel.ne.y && x > 0 && !((gp-1)->mode & ATTR_WRAP))
+ *ptr++ = '\n';
+
+ /*
+ * If the last selected line expands in the selection
+ * after the visible text '\n' is appended.
+ */
+ if(y == sel.ne.y) {
+ i = term.col;
+ while(--i > 0 && term.line[y][i].c[0] == ' ')
+ /* nothing */;
+ ex = sel.ne.x;
+ if(sel.nb.y == sel.ne.y && sel.ne.x < sel.nb.x)
+ ex = sel.nb.x;
+ if(i < ex)
+ *ptr++ = '\n';
+ }
+ }
+ *ptr = 0;
+ return str;
+}
+
+void
+selcopy(void) {
+ xsetsel(getsel());
+}
+
+void
+selnotify(XEvent *e) {
+ ulong nitems, ofs, rem;
+ int format;
+ uchar *data, *last, *repl;
+ 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;
+ }
+
+ /*
+ * As seen in getsel:
+ * Line endings are inconsistent in the terminal and GUI world
+ * copy and pasting. When receiving some selection data,
+ * replace all '\n' with '\r'.
+ * FIXME: Fix the computer world.
+ */
+ repl = data;
+ last = data + nitems * format / 8;
+ while((repl = memchr(repl, '\n', last - repl))) {
+ *repl++ = '\r';
+ }
+
+ if(IS_SET(MODE_BRCKTPASTE))
+ ttywrite("\033[200~", 6);
+ ttysend((char *)data, nitems * format / 8);
+ if(IS_SET(MODE_BRCKTPASTE))
+ ttywrite("\033[201~", 6);
+ XFree(data);
+ /* number of 32-bit chunks returned */
+ ofs += nitems * format / 32;
+ } while(rem > 0);
+}
+
+void
+selpaste(const Arg *dummy) {
+ XConvertSelection(xw.dpy, XA_PRIMARY, sel.xtarget, XA_PRIMARY,
+ xw.win, CurrentTime);
+}
+
+void
+clippaste(const Arg *dummy) {
+ Atom clipboard;
+
+ clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
+ XConvertSelection(xw.dpy, clipboard, sel.xtarget, XA_PRIMARY,
+ xw.win, CurrentTime);
+}
+
+void
+selclear(XEvent *e) {
+ if(sel.ob.x == -1)
+ return;
+ sel.ob.x = -1;
+ tsetdirt(sel.nb.y, sel.ne.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) {
+ if(IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) {
+ mousereport(e);
+ return;
+ }
+
+ if(e->xbutton.button == Button2) {
+ selpaste(NULL);
+ } else if(e->xbutton.button == Button1) {
+ if(sel.mode < 2) {
+ selclear(NULL);
+ } else {
+ getbuttoninfo(e);
+ selcopy();
+ }
+ sel.mode = 0;
+ tsetdirt(sel.nb.y, sel.ne.y);
+ }
+}
+
+void
+bmotion(XEvent *e) {
+ int oldey, oldex, oldsby, oldsey;
+
+ if(IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) {
+ mousereport(e);
+ return;
+ }
+
+ if(!sel.mode)
+ return;
+
+ sel.mode++;
+ oldey = sel.oe.y;
+ oldex = sel.oe.x;
+ oldsby = sel.nb.y;
+ oldsey = sel.ne.y;
+ getbuttoninfo(e);
+
+ if(oldey != sel.oe.y || oldex != sel.oe.x)
+ tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
+}