minor code-style, initialize var at the top of function
[st.git] / st.c
diff --git a/st.c b/st.c
index bcb6473..f342e5c 100644 (file)
--- a/st.c
+++ b/st.c
@@ -3,30 +3,20 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
-#include <locale.h>
 #include <pwd.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <signal.h>
-#include <stdint.h>
 #include <sys/ioctl.h>
 #include <sys/select.h>
-#include <sys/stat.h>
-#include <sys/time.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <termios.h>
-#include <time.h>
 #include <unistd.h>
-#include <libgen.h>
-#include <fontconfig/fontconfig.h>
 #include <wchar.h>
-
-/* X11 */
-#include <X11/cursorfont.h>
-#include <X11/Xft/Xft.h>
+#include <wctype.h>
 
 #include "st.h"
 #include "win.h"
 
 /* Arbitrary sizes */
 #define UTF_INVALID   0xFFFD
+#define UTF_SIZ       4
 #define ESC_BUF_SIZ   (128*UTF_SIZ)
 #define ESC_ARG_SIZ   16
 #define STR_BUF_SIZ   ESC_BUF_SIZ
 #define STR_ARG_SIZ   ESC_ARG_SIZ
 
 /* macros */
-#define NUMMAXLEN(x)           ((int)(sizeof(x) * 2.56 + 0.5) + 1)
+#define IS_SET(flag)           ((term.mode & (flag)) != 0)
 #define ISCONTROLC0(c)         (BETWEEN(c, 0, 0x1f) || (c) == '\177')
 #define ISCONTROLC1(c)         (BETWEEN(c, 0x80, 0x9f))
 #define ISCONTROL(c)           (ISCONTROLC0(c) || ISCONTROLC1(c))
-#define ISDELIM(u)             (utf8strchr(worddelimiters, u) != NULL)
-
-/* constants */
-#define ISO14755CMD            "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
+#define ISDELIM(u)             ((iswspace(u) || iswpunct(u)) && wcschr(extrawordchars, u) == NULL)
+
+enum term_mode {
+       MODE_WRAP        = 1 << 0,
+       MODE_INSERT      = 1 << 1,
+       MODE_ALTSCREEN   = 1 << 2,
+       MODE_CRLF        = 1 << 3,
+       MODE_ECHO        = 1 << 4,
+       MODE_PRINT       = 1 << 5,
+       MODE_UTF8        = 1 << 6,
+       MODE_SIXEL       = 1 << 7,
+};
 
 enum cursor_movement {
        CURSOR_SAVE,
@@ -88,6 +87,51 @@ enum escape_state {
        ESC_DCS        =128,
 };
 
+typedef struct {
+       Glyph attr; /* current char attributes */
+       int x;
+       int y;
+       char state;
+} TCursor;
+
+typedef struct {
+       int mode;
+       int type;
+       int snap;
+       /*
+        * Selection variables:
+        * nb – normalized coordinates of the beginning of the selection
+        * ne – normalized coordinates of the end of the selection
+        * ob – original coordinates of the beginning of the selection
+        * oe – original coordinates of the end of the selection
+        */
+       struct {
+               int x, y;
+       } nb, ne, ob, oe;
+
+       int alt;
+} Selection;
+
+/* Internal representation of the screen */
+typedef struct {
+       int row;      /* nb row */
+       int col;      /* nb col */
+       Line *line;   /* screen */
+       Line *alt;    /* alternate screen */
+       int *dirty;   /* dirtyness of lines */
+       TCursor c;    /* cursor */
+       int ocx;      /* old cursor col */
+       int ocy;      /* old cursor row */
+       int top;      /* top    scroll limit */
+       int bot;      /* bottom scroll limit */
+       int mode;     /* terminal mode flags */
+       int esc;      /* escape state flags */
+       char trantbl[4]; /* charset table translation */
+       int charset;  /* current charset */
+       int icharset; /* selected charset for sequence */
+       int *tabs;
+} Term;
+
 /* CSI Escape sequence structs */
 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
 typedef struct {
@@ -109,10 +153,10 @@ typedef struct {
        int narg;              /* nb of args */
 } STREscape;
 
-
-static void execsh(char **);
+static void execsh(char *, char **);
 static void stty(char **);
 static void sigchld(int);
+static void ttywriteraw(const char *, size_t);
 
 static void csidump(void);
 static void csihandle(void);
@@ -145,6 +189,7 @@ static void tscrollup(int, int);
 static void tscrolldown(int, int);
 static void tsetattr(int *, int);
 static void tsetchar(Rune, Glyph *, int, int);
+static void tsetdirt(int, int);
 static void tsetscroll(int, int);
 static void tswapscreen(void);
 static void tsetmode(int, int, int *, int);
@@ -157,28 +202,30 @@ static int32_t tdefcolor(int *, int *, int);
 static void tdeftran(char);
 static void tstrsequence(uchar);
 
+static void drawregion(int, int, int, int);
+
+static void selnormalize(void);
 static void selscroll(int, int);
 static void selsnap(int *, int *, int);
 
+static size_t utf8decode(const char *, Rune *, size_t);
 static Rune utf8decodebyte(char, size_t *);
 static char utf8encodebyte(Rune, size_t);
-static char *utf8strchr(char *s, Rune u);
 static size_t utf8validate(Rune *, size_t);
 
 static char *base64dec(const char *);
+static char base64dec_getc(const char **);
 
 static ssize_t xwrite(int, const char *, size_t);
 
 /* Globals */
-Term term;
-Selection sel;
-int cmdfd;
-pid_t pid;
-int oldbutton   = 3; /* button event on startup: 3 = release */
-
+static Term term;
+static Selection sel;
 static CSIEscape csiescseq;
 static STREscape strescseq;
 static int iofd = 1;
+static int cmdfd;
+static pid_t pid;
 
 static uchar utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
@@ -205,10 +252,10 @@ xwrite(int fd, const char *s, size_t len)
 void *
 xmalloc(size_t len)
 {
-       void *p = malloc(len);
+       void *p;
 
-       if (!p)
-               die("Out of memory\n");
+       if (!(p = malloc(len)))
+               die("malloc: %s\n", strerror(errno));
 
        return p;
 }
@@ -217,7 +264,7 @@ void *
 xrealloc(void *p, size_t len)
 {
        if ((p = realloc(p, len)) == NULL)
-               die("Out of memory\n");
+               die("realloc: %s\n", strerror(errno));
 
        return p;
 }
@@ -226,7 +273,7 @@ char *
 xstrdup(char *s)
 {
        if ((s = strdup(s)) == NULL)
-               die("Out of memory\n");
+               die("strdup: %s\n", strerror(errno));
 
        return s;
 }
@@ -290,23 +337,6 @@ utf8encodebyte(Rune u, size_t i)
        return utfbyte[i] | (u & ~utfmask[i]);
 }
 
-char *
-utf8strchr(char *s, Rune u)
-{
-       Rune r;
-       size_t i, j, len;
-
-       len = strlen(s);
-       for (i = 0, j = 0; i < len; i += j) {
-               if (!(j = utf8decode(&s[i], &r, len - i)))
-                       break;
-               if (r == u)
-                       return &(s[i]);
-       }
-
-       return NULL;
-}
-
 size_t
 utf8validate(Rune *u, size_t i)
 {
@@ -370,13 +400,9 @@ base64dec(const char *src)
 void
 selinit(void)
 {
-       clock_gettime(CLOCK_MONOTONIC, &sel.tclick1);
-       clock_gettime(CLOCK_MONOTONIC, &sel.tclick2);
        sel.mode = SEL_IDLE;
        sel.snap = 0;
        sel.ob.x = -1;
-       sel.primary = NULL;
-       sel.clipboard = NULL;
 }
 
 int
@@ -393,6 +419,52 @@ tlinelen(int y)
        return i;
 }
 
+void
+selstart(int col, int row, int snap)
+{
+       selclear();
+       sel.mode = SEL_EMPTY;
+       sel.type = SEL_REGULAR;
+       sel.alt = IS_SET(MODE_ALTSCREEN);
+       sel.snap = snap;
+       sel.oe.x = sel.ob.x = col;
+       sel.oe.y = sel.ob.y = row;
+       selnormalize();
+
+       if (sel.snap != 0)
+               sel.mode = SEL_READY;
+       tsetdirt(sel.nb.y, sel.ne.y);
+}
+
+void
+selextend(int col, int row, int type, int done)
+{
+       int oldey, oldex, oldsby, oldsey, oldtype;
+
+       if (sel.mode == SEL_IDLE)
+               return;
+       if (done && sel.mode == SEL_EMPTY) {
+               selclear();
+               return;
+       }
+
+       oldey = sel.oe.y;
+       oldex = sel.oe.x;
+       oldsby = sel.nb.y;
+       oldsey = sel.ne.y;
+       oldtype = sel.type;
+
+       sel.oe.x = col;
+       sel.oe.y = row;
+       selnormalize();
+       sel.type = type;
+
+       if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type)
+               tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
+
+       sel.mode = done ? SEL_IDLE : SEL_READY;
+}
+
 void
 selnormalize(void)
 {
@@ -424,7 +496,8 @@ selnormalize(void)
 int
 selected(int x, int y)
 {
-       if (sel.mode == SEL_EMPTY)
+       if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
+                       sel.alt != IS_SET(MODE_ALTSCREEN))
                return 0;
 
        if (sel.type == SEL_RECTANGULAR)
@@ -585,7 +658,7 @@ die(const char *errstr, ...)
 }
 
 void
-execsh(char **args)
+execsh(char *cmd, char **args)
 {
        char *sh, *prog;
        const struct passwd *pw;
@@ -593,13 +666,13 @@ execsh(char **args)
        errno = 0;
        if ((pw = getpwuid(getuid())) == NULL) {
                if (errno)
-                       die("getpwuid:%s\n", strerror(errno));
+                       die("getpwuid: %s\n", strerror(errno));
                else
                        die("who are you?\n");
        }
 
        if ((sh = getenv("SHELL")) == NULL)
-               sh = (pw->pw_shell[0]) ? pw->pw_shell : shell;
+               sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
 
        if (args)
                prog = args[0];
@@ -636,17 +709,18 @@ sigchld(int a)
        pid_t p;
 
        if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
-               die("Waiting for pid %hd failed: %s\n", pid, strerror(errno));
+               die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
 
        if (pid != p)
                return;
 
-       if (!WIFEXITED(stat) || WEXITSTATUS(stat))
-               die("child finished with error '%d'\n", stat);
+       if (WIFEXITED(stat) && WEXITSTATUS(stat))
+               die("child exited with status %d\n", WEXITSTATUS(stat));
+       else if (WIFSIGNALED(stat))
+               die("child terminated due to signal %d\n", WTERMSIG(stat));
        exit(0);
 }
 
-
 void
 stty(char **args)
 {
@@ -668,14 +742,13 @@ stty(char **args)
        }
        *q = '\0';
        if (system(cmd) != 0)
-           perror("Couldn't call stty");
+               perror("Couldn't call stty");
 }
 
-void
-ttynew(char *line, char *out, char **args)
+int
+ttynew(char *line, char *cmd, char *out, char **args)
 {
        int m, s;
-       struct winsize w = {term.row, term.col, 0, 0};
 
        if (out) {
                term.mode |= MODE_PRINT;
@@ -689,19 +762,20 @@ ttynew(char *line, char *out, char **args)
 
        if (line) {
                if ((cmdfd = open(line, O_RDWR)) < 0)
-                       die("open line failed: %s\n", strerror(errno));
+                       die("open line '%s' failed: %s\n",
+                           line, strerror(errno));
                dup2(cmdfd, 0);
                stty(args);
-               return;
+               return cmdfd;
        }
 
        /* seems to work fine on linux, openbsd and freebsd */
-       if (openpty(&m, &s, NULL, NULL, &w) < 0)
+       if (openpty(&m, &s, NULL, NULL, NULL) < 0)
                die("openpty failed: %s\n", strerror(errno));
 
        switch (pid = fork()) {
        case -1:
-               die("fork failed\n");
+               die("fork failed: %s\n", strerror(errno));
                break;
        case 0:
                close(iofd);
@@ -713,14 +787,23 @@ ttynew(char *line, char *out, char **args)
                        die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
                close(s);
                close(m);
-               execsh(args);
+#ifdef __OpenBSD__
+               if (pledge("stdio getpw proc exec", NULL) == -1)
+                       die("pledge\n");
+#endif
+               execsh(cmd, args);
                break;
        default:
+#ifdef __OpenBSD__
+               if (pledge("stdio rpath tty proc", NULL) == -1)
+                       die("pledge\n");
+#endif
                close(s);
                cmdfd = m;
                signal(SIGCHLD, sigchld);
                break;
        }
+       return cmdfd;
 }
 
 size_t
@@ -733,7 +816,7 @@ ttyread(void)
 
        /* 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));
+               die("couldn't read from shell: %s\n", strerror(errno));
        buflen += ret;
 
        written = twrite(buf, buflen, 0);
@@ -746,7 +829,35 @@ ttyread(void)
 }
 
 void
-ttywrite(const char *s, size_t n)
+ttywrite(const char *s, size_t n, int may_echo)
+{
+       const char *next;
+
+       if (may_echo && IS_SET(MODE_ECHO))
+               twrite(s, n, 1);
+
+       if (!IS_SET(MODE_CRLF)) {
+               ttywriteraw(s, n);
+               return;
+       }
+
+       /* This is similar to how the kernel handles ONLCR for ttys */
+       while (n > 0) {
+               if (*s == '\r') {
+                       next = s + 1;
+                       ttywriteraw("\r\n", 2);
+               } else {
+                       next = memchr(s, '\r', n);
+                       DEFAULT(next, s + n);
+                       ttywriteraw(s, next - s);
+               }
+               n -= next - s;
+               s = next;
+       }
+}
+
+void
+ttywriteraw(const char *s, size_t n)
 {
        fd_set wfd, rfd;
        ssize_t r;
@@ -802,14 +913,6 @@ write_error:
        die("write error on tty: %s\n", strerror(errno));
 }
 
-void
-ttysend(char *s, size_t n)
-{
-       ttywrite(s, n);
-       if (IS_SET(MODE_ECHO))
-               twrite(s, n, 1);
-}
-
 void
 ttyresize(int tw, int th)
 {
@@ -823,6 +926,13 @@ ttyresize(int tw, int th)
                fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
 }
 
+void
+ttyhangup()
+{
+       /* Send SIGHUP to shell */
+       kill(pid, SIGHUP);
+}
+
 int
 tattrset(int attr)
 {
@@ -918,8 +1028,6 @@ tnew(int col, int row)
 {
        term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
        tresize(col, row);
-       term.numlock = 1;
-
        treset();
 }
 
@@ -1329,7 +1437,8 @@ tsetattr(int *attr, int l)
                        } else {
                                fprintf(stderr,
                                        "erresc(default): gfx attr %d unknown\n",
-                                       attr[i]), csidump();
+                                       attr[i]);
+                               csidump();
                        }
                        break;
                }
@@ -1355,20 +1464,16 @@ tsetscroll(int t, int b)
 void
 tsetmode(int priv, int set, int *args, int narg)
 {
-       int *lim, mode;
-       int alt;
+       int alt, *lim;
 
        for (lim = args + narg; args < lim; ++args) {
                if (priv) {
                        switch (*args) {
                        case 1: /* DECCKM -- Cursor key */
-                               MODBIT(term.mode, set, MODE_APPCURSOR);
+                               xsetmode(set, MODE_APPCURSOR);
                                break;
                        case 5: /* DECSCNM -- Reverse video */
-                               mode = term.mode;
-                               MODBIT(term.mode, set, MODE_REVERSE);
-                               if (mode != term.mode)
-                                       redraw();
+                               xsetmode(set, MODE_REVERSE);
                                break;
                        case 6: /* DECOM -- Origin */
                                MODBIT(term.c.state, set, CURSOR_ORIGIN);
@@ -1388,36 +1493,36 @@ tsetmode(int priv, int set, int *args, int narg)
                        case 12: /* att610 -- Start blinking cursor (IGNORED) */
                                break;
                        case 25: /* DECTCEM -- Text Cursor Enable Mode */
-                               MODBIT(term.mode, !set, MODE_HIDE);
+                               xsetmode(!set, MODE_HIDE);
                                break;
                        case 9:    /* X10 mouse compatibility mode */
                                xsetpointermotion(0);
-                               MODBIT(term.mode, 0, MODE_MOUSE);
-                               MODBIT(term.mode, set, MODE_MOUSEX10);
+                               xsetmode(0, MODE_MOUSE);
+                               xsetmode(set, MODE_MOUSEX10);
                                break;
                        case 1000: /* 1000: report button press */
                                xsetpointermotion(0);
-                               MODBIT(term.mode, 0, MODE_MOUSE);
-                               MODBIT(term.mode, set, MODE_MOUSEBTN);
+                               xsetmode(0, MODE_MOUSE);
+                               xsetmode(set, MODE_MOUSEBTN);
                                break;
                        case 1002: /* 1002: report motion on button press */
                                xsetpointermotion(0);
-                               MODBIT(term.mode, 0, MODE_MOUSE);
-                               MODBIT(term.mode, set, MODE_MOUSEMOTION);
+                               xsetmode(0, MODE_MOUSE);
+                               xsetmode(set, MODE_MOUSEMOTION);
                                break;
                        case 1003: /* 1003: enable all mouse motions */
                                xsetpointermotion(set);
-                               MODBIT(term.mode, 0, MODE_MOUSE);
-                               MODBIT(term.mode, set, MODE_MOUSEMANY);
+                               xsetmode(0, MODE_MOUSE);
+                               xsetmode(set, MODE_MOUSEMANY);
                                break;
                        case 1004: /* 1004: send focus events to tty */
-                               MODBIT(term.mode, set, MODE_FOCUS);
+                               xsetmode(set, MODE_FOCUS);
                                break;
                        case 1006: /* 1006: extended reporting mode */
-                               MODBIT(term.mode, set, MODE_MOUSESGR);
+                               xsetmode(set, MODE_MOUSESGR);
                                break;
                        case 1034:
-                               MODBIT(term.mode, set, MODE_8BIT);
+                               xsetmode(set, MODE_8BIT);
                                break;
                        case 1049: /* swap screen & set/restore cursor as xterm */
                                if (!allowaltscreen)
@@ -1442,7 +1547,7 @@ tsetmode(int priv, int set, int *args, int narg)
                                tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
                                break;
                        case 2004: /* 2004: bracketed paste mode */
-                               MODBIT(term.mode, set, MODE_BRCKTPASTE);
+                               xsetmode(set, MODE_BRCKTPASTE);
                                break;
                        /* Not implemented mouse modes. See comments there. */
                        case 1001: /* mouse highlight mode; can hang the
@@ -1453,6 +1558,7 @@ tsetmode(int priv, int set, int *args, int narg)
                        case 1015: /* urxvt mangled mouse mode; incompatible
                                      and can be mistaken for other control
                                      codes. */
+                               break;
                        default:
                                fprintf(stderr,
                                        "erresc: unknown private set/reset mode %d\n",
@@ -1463,8 +1569,8 @@ tsetmode(int priv, int set, int *args, int narg)
                        switch (*args) {
                        case 0:  /* Error (IGNORED) */
                                break;
-                       case 2:  /* KAM -- keyboard action */
-                               MODBIT(term.mode, set, MODE_KBDLOCK);
+                       case 2:
+                               xsetmode(set, MODE_KBDLOCK);
                                break;
                        case 4:  /* IRM -- Insertion-replacement */
                                MODBIT(term.mode, set, MODE_INSERT);
@@ -1532,7 +1638,7 @@ csihandle(void)
                break;
        case 'c': /* DA -- Device Attributes */
                if (csiescseq.arg[0] == 0)
-                       ttywrite(vtiden, strlen(vtiden));
+                       ttywrite(vtiden, strlen(vtiden), 0);
                break;
        case 'C': /* CUF -- Cursor <n> Forward */
        case 'a': /* HPR -- Cursor <n> Forward */
@@ -1579,7 +1685,6 @@ csihandle(void)
                tputtab(csiescseq.arg[0]);
                break;
        case 'J': /* ED -- Clear screen */
-               selclear();
                switch (csiescseq.arg[0]) {
                case 0: /* below */
                        tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
@@ -1660,7 +1765,7 @@ csihandle(void)
                if (csiescseq.arg[0] == 6) {
                        len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
                                        term.c.y+1, term.c.x+1);
-                       ttywrite(buf, len);
+                       ttywrite(buf, len, 0);
                }
                break;
        case 'r': /* DECSTBM -- Set Scrolling Region */
@@ -1725,7 +1830,7 @@ csireset(void)
 void
 strhandle(void)
 {
-       char *p = NULL;
+       char *p = NULL, *dec;
        int j, narg, par;
 
        term.esc &= ~(ESC_STR_END|ESC_STR);
@@ -1743,8 +1848,6 @@ strhandle(void)
                        return;
                case 52:
                        if (narg > 2) {
-                               char *dec;
-
                                dec = base64dec(strescseq.args[2]);
                                if (dec) {
                                        xsetsel(dec);
@@ -1859,28 +1962,6 @@ tprinter(char *s, size_t len)
        }
 }
 
-void
-iso14755(const Arg *arg)
-{
-       FILE *p;
-       char *us, *e, codepoint[9], uc[UTF_SIZ];
-       unsigned long utf32;
-
-       if (!(p = popen(ISO14755CMD, "r")))
-               return;
-
-       us = fgets(codepoint, sizeof(codepoint), p);
-       pclose(p);
-
-       if (!us || *us == '\0' || *us == '-' || strlen(us) > 7)
-               return;
-       if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX ||
-           (*e != '\n' && *e != '\0'))
-               return;
-
-       ttysend(uc, utf8encode(utf32, uc));
-}
-
 void
 toggleprinter(const Arg *arg)
 {
@@ -2091,7 +2172,7 @@ tcontrolcode(uchar ascii)
        case 0x99:   /* TODO: SGCI */
                break;
        case 0x9a:   /* DECID -- Identify Terminal */
-               ttywrite(vtiden, strlen(vtiden));
+               ttywrite(vtiden, strlen(vtiden), 0);
                break;
        case 0x9b:   /* TODO: CSI */
        case 0x9c:   /* TODO: ST */
@@ -2163,18 +2244,18 @@ eschandle(uchar ascii)
                }
                break;
        case 'Z': /* DECID -- Identify Terminal */
-               ttywrite(vtiden, strlen(vtiden));
+               ttywrite(vtiden, strlen(vtiden), 0);
                break;
-       case 'c': /* RIS -- Reset to inital state */
+       case 'c': /* RIS -- Reset to initial state */
                treset();
                resettitle();
                xloadcols();
                break;
        case '=': /* DECPAM -- Application keypad */
-               term.mode |= MODE_APPKEYPAD;
+               xsetmode(1, MODE_APPKEYPAD);
                break;
        case '>': /* DECPNM -- Normal keypad */
-               term.mode &= ~MODE_APPKEYPAD;
+               xsetmode(0, MODE_APPKEYPAD);
                break;
        case '7': /* DECSC -- Save Cursor */
                tcursor(CURSOR_SAVE);
@@ -2236,7 +2317,6 @@ tputc(Rune u)
                        goto check_control_code;
                }
 
-
                if (IS_SET(MODE_SIXEL)) {
                        /* TODO: implement sixel mode */
                        return;
@@ -2462,14 +2542,45 @@ resettitle(void)
 }
 
 void
-redraw(void)
+drawregion(int x1, int y1, int x2, int y2)
 {
-       tfulldirt();
-       draw();
+       int y;
+       for (y = y1; y < y2; y++) {
+               if (!term.dirty[y])
+                       continue;
+
+               term.dirty[y] = 0;
+               xdrawline(term.line[y], x1, y, x2);
+       }
 }
 
 void
-numlock(const Arg *dummy)
+draw(void)
 {
-       term.numlock ^= 1;
+       int cx = term.c.x;
+
+       if (!xstartdraw())
+               return;
+
+       /* adjust cursor position */
+       LIMIT(term.ocx, 0, term.col-1);
+       LIMIT(term.ocy, 0, term.row-1);
+       if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
+               term.ocx--;
+       if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
+               cx--;
+
+       drawregion(0, 0, term.col, term.row);
+       xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
+                       term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
+       term.ocx = cx, term.ocy = term.c.y;
+       xfinishdraw();
+       xximspot(term.ocx, term.ocy);
+}
+
+void
+redraw(void)
+{
+       tfulldirt();
+       draw();
 }