+ if(e->xbutton.button == Button1) {
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ /* 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;
+ }
+ selnormalize();
+
+ /*
+ * 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 y, bufsize, size, lastx, linelen;
+ 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; y++) {
+ linelen = tlinelen(y);
+
+ if(sel.type == SEL_RECTANGULAR) {
+ gp = &term.line[y][sel.nb.x];
+ lastx = sel.ne.x;
+ } else {
+ gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
+ lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
+ }
+ last = &term.line[y][MIN(lastx, linelen-1)];
+ while(last >= gp && last->c[0] == ' ')
+ --last;
+
+ for( ; gp <= last; ++gp) {
+ if(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 || lastx >= linelen) && !(last->mode & ATTR_WRAP))
+ *ptr++ = '\n';
+ }
+ *ptr = 0;
+ return str;
+}
+
+void
+selcopy(Time t) {
+ xsetsel(getsel(), t);
+}
+
+void
+selnotify(XEvent *e) {
+ ulong nitems, ofs, rem;
+ int format;
+ uchar *data, *last, *repl;
+ Atom type;
+ XSelectionEvent *xsev;
+
+ ofs = 0;
+ xsev = &e->xselection;
+ if (xsev->property == None)
+ return;
+ do {
+ if(XGetWindowProperty(xw.dpy, xw.win, xsev->property, 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
+clipcopy(const Arg *dummy) {
+ Atom clipboard;
+
+ if(sel.clipboard != NULL)
+ free(sel.clipboard);
+
+ if(sel.primary != NULL) {
+ sel.clipboard = xstrdup(sel.primary);
+ clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
+ XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime);
+ }
+}
+
+void
+clippaste(const Arg *dummy) {
+ Atom clipboard;
+
+ clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
+ XConvertSelection(xw.dpy, clipboard, sel.xtarget, clipboard,
+ 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, clipboard;
+ char *seltext;
+
+ xsre = (XSelectionRequestEvent *) e;
+ xev.type = SelectionNotify;
+ xev.requestor = xsre->requestor;
+ xev.selection = xsre->selection;
+ xev.target = xsre->target;
+ xev.time = xsre->time;
+ if (xsre->property == None)
+ xsre->property = xsre->target;
+
+ /* 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 || xsre->target == XA_STRING) {
+ /*
+ * xith XA_STRING non ascii characters may be incorrect in the
+ * requestor. It is not our problem, use utf8.
+ */
+ clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
+ if(xsre->selection == XA_PRIMARY) {
+ seltext = sel.primary;
+ } else if(xsre->selection == clipboard) {
+ seltext = sel.clipboard;
+ } else {
+ fprintf(stderr,
+ "Unhandled clipboard selection 0x%lx\n",
+ xsre->selection);
+ return;
+ }
+ if(seltext != NULL) {
+ XChangeProperty(xsre->display, xsre->requestor,
+ xsre->property, xsre->target,
+ 8, PropModeReplace,
+ (uchar *)seltext, strlen(seltext));
+ 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, Time t) {
+ free(sel.primary);
+ sel.primary = str;
+
+ XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t);
+ if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win)
+ selclear(0);
+}
+
+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(e->xbutton.time);
+ }
+ 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));
+}
+
+void
+die(const char *errstr, ...) {
+ va_list ap;
+
+ va_start(ap, errstr);
+ vfprintf(stderr, errstr, ap);
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+void
+execsh(void) {
+ char **args, *sh, *prog;
+ const struct passwd *pw;
+ char buf[sizeof(long) * 8 + 1];
+
+ errno = 0;
+ if((pw = getpwuid(getuid())) == NULL) {
+ if(errno)
+ die("getpwuid:%s\n", strerror(errno));
+ else
+ die("who are you?\n");
+ }
+
+ if (!(sh = getenv("SHELL"))) {
+ sh = (pw->pw_shell[0]) ? pw->pw_shell : shell;
+ }
+
+ if(opt_cmd)
+ prog = opt_cmd[0];
+ else if(utmp)
+ prog = utmp;
+ else
+ prog = sh;
+ args = (opt_cmd) ? opt_cmd : (char *[]) {prog, NULL};
+
+ snprintf(buf, sizeof(buf), "%lu", xw.win);
+
+ unsetenv("COLUMNS");
+ unsetenv("LINES");
+ unsetenv("TERMCAP");
+ setenv("LOGNAME", pw->pw_name, 1);
+ setenv("USER", pw->pw_name, 1);
+ setenv("SHELL", sh, 1);
+ setenv("HOME", pw->pw_dir, 1);
+ setenv("TERM", termname, 1);
+ setenv("WINDOWID", buf, 1);
+
+ signal(SIGCHLD, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGALRM, SIG_DFL);
+
+ execvp(prog, args);
+ _exit(EXIT_FAILURE);
+}
+
+void
+sigchld(int a) {
+ int stat, ret;
+
+ if(waitpid(pid, &stat, 0) < 0)
+ die("Waiting for pid %hd failed: %s\n", pid, strerror(errno));
+
+ ret = WIFEXITED(stat) ? WEXITSTATUS(stat) : EXIT_FAILURE;
+ if (ret != EXIT_SUCCESS)
+ die("child finished with error '%d'\n", stat);
+ exit(EXIT_SUCCESS);
+}
+
+
+void
+stty(void)
+{
+ char cmd[_POSIX_ARG_MAX], **p, *q, *s;
+ size_t n, siz;
+
+ if((n = strlen(stty_args)) > sizeof(cmd)-1)
+ die("incorrect stty parameters\n");
+ memcpy(cmd, stty_args, n);
+ q = cmd + n;
+ siz = sizeof(cmd) - n;
+ for(p = opt_cmd; p && (s = *p); ++p) {
+ if((n = strlen(s)) > siz-1)
+ die("stty parameter length too long\n");
+ *q++ = ' ';
+ q = memcpy(q, s, n);
+ q += n;
+ siz-= n + 1;
+ }
+ *q = '\0';
+ system(cmd);
+}
+
+void
+ttynew(void) {
+ int m, s;
+ struct winsize w = {term.row, term.col, 0, 0};
+
+ if(opt_io) {
+ term.mode |= MODE_PRINT;
+ iofd = (!strcmp(opt_io, "-")) ?
+ STDOUT_FILENO :
+ open(opt_io, O_WRONLY | O_CREAT, 0666);
+ if(iofd < 0) {
+ fprintf(stderr, "Error opening %s:%s\n",
+ opt_io, strerror(errno));
+ }
+ }
+
+ if (opt_line) {
+ if((cmdfd = open(opt_line, O_RDWR)) < 0)
+ die("open line failed: %s\n", strerror(errno));
+ close(STDIN_FILENO);
+ dup(cmdfd);
+ stty();
+ return;
+ }
+
+ /* seems to work fine on linux, openbsd and freebsd */
+ if(openpty(&m, &s, NULL, NULL, &w) < 0)
+ die("openpty failed: %s\n", strerror(errno));
+
+ switch(pid = fork()) {
+ case -1:
+ die("fork failed\n");
+ break;
+ case 0:
+ close(iofd);
+ setsid(); /* create a new process group */
+ dup2(s, STDIN_FILENO);
+ dup2(s, STDOUT_FILENO);
+ dup2(s, STDERR_FILENO);
+ if(ioctl(s, TIOCSCTTY, NULL) < 0)
+ die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
+ close(s);
+ close(m);
+ execsh();
+ break;
+ default:
+ close(s);
+ cmdfd = m;
+ signal(SIGCHLD, sigchld);
+ break;
+ }
+}
+
+void
+ttyread(void) {
+ static char buf[BUFSIZ];
+ static int buflen = 0;
+ char *ptr;
+ char s[UTF_SIZ];
+ int charsize; /* size of utf8 char in bytes */
+ long unicodep;
+ int ret;
+
+ /* append read bytes to unprocessed bytes */
+ if((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
+ die("Couldn't read from shell: %s\n", strerror(errno));
+
+ /* process every complete utf8 char */
+ buflen += ret;
+ ptr = buf;
+ while((charsize = utf8decode(ptr, &unicodep, buflen))) {
+ utf8encode(unicodep, s, UTF_SIZ);
+ tputc(s, charsize);
+ ptr += charsize;
+ buflen -= charsize;
+ }
+
+ /* keep any uncomplete utf8 char for the next call */
+ memmove(buf, ptr, buflen);
+}
+
+void
+ttywrite(const char *s, size_t n) {
+ if(xwrite(cmdfd, s, n) == -1)
+ die("write error on tty: %s\n", strerror(errno));
+}
+
+void
+ttysend(char *s, size_t n) {
+ ttywrite(s, n);
+ if(IS_SET(MODE_ECHO))
+ techo(s, n);
+}
+
+void
+ttyresize(void) {
+ struct winsize w;
+
+ w.ws_row = term.row;
+ w.ws_col = term.col;
+ w.ws_xpixel = xw.tw;
+ w.ws_ypixel = xw.th;
+ if(ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
+ fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
+}
+
+int
+tattrset(int attr) {
+ int i, j;
+
+ for(i = 0; i < term.row-1; i++) {
+ for(j = 0; j < term.col-1; j++) {
+ if(term.line[i][j].mode & attr)
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void
+tsetdirt(int top, int bot) {