+       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) {