]> git.neil.brown.name Git - edlib.git/blobdiff - display-ncurses.c
TODO: clean out done items.
[edlib.git] / display-ncurses.c
index 28ca56f7cfe2afe02caf95675b33a7bd12854e2d..86995cd44e86ca57bb051f9bc26729dd389e127e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright Neil Brown ©2015-2020 <neil@brown.name>
+ * Copyright Neil Brown ©2015-2023 <neil@brown.name>
  * May be distributed under terms of GPLv2 - see file:COPYING
  *
  * ncurses front end for edlib.
 #endif
 
 #include <stdlib.h>
+#include <fcntl.h>
 #include <time.h>
 #include <curses.h>
+#include <panel.h>
 #include <string.h>
 #include <locale.h>
 #include <ctype.h>
 #include <signal.h>
 #include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <netdb.h>
 
+#include <wand/MagickWand.h>
+#ifdef __CHECKER__
+// enums confuse sparse...
+#define MagickBooleanType int
+#endif
+
+#include <term.h>
+
+#define PANE_DATA_TYPE struct display_data
 #include "core.h"
 
 #ifdef RECORD_REPLAY
@@ -51,33 +64,56 @@ struct display_data {
        SCREEN                  *scr;
        FILE                    *scr_file;
        int                     is_xterm;
-       struct xy               cursor;
-       char                    *noclose;
        struct col_hash         *col_hash;
        int                     report_position;
        long                    last_event;
+
+       bool                    did_close;
+       bool                    suspended;
+
+       struct buf              paste_buf;
+       time_t                  paste_start;
+       char                    *paste_latest;
+       int                     paste_pending;
+
+       struct pids {
+               pid_t           pid;
+               struct pids     *next;
+       }                       *pids;
+
+       char                    *rs1, *rs2, *rs3, *clear;
+       char                    attr_buf[1024];
        #ifdef RECORD_REPLAY
        FILE                    *log;
        FILE                    *input;
+       int                     input_sleeping;
+       /* Sometimes I get duplicate Display lines, but not consistently.
+        * To avoid these, record last, filter repeats.
+        */
+       int                     last_cx, last_cy;
        char                    last_screen[MD5_DIGEST_SIZE*2+1];
        char                    next_screen[MD5_DIGEST_SIZE*2+1];
        /* The next event to generate when idle */
        enum { DoNil, DoMouse, DoKey, DoCheck, DoClose} next_event;
        char                    event_info[30];
        struct xy               event_pos;
+
+       int                     clears; /* counts of Draw:clear events */
        #endif
 };
+#include "core-pane.h"
 
 static SCREEN *current_screen;
-static void ncurses_clear(struct pane *p safe, struct pane *display safe,
-                         int attr, short x, short y, short w, short h);
 static void ncurses_text(struct pane *p safe, struct pane *display safe,
-                        wchar_t ch, int attr, short x, short y, short cursor);
+                        wchar_t ch, int attr, int pair,
+                        short x, short y, short cursor);
+static PANEL * safe pane_panel(struct pane *p safe, struct pane *home);
 DEF_CMD(input_handle);
 DEF_CMD(handle_winch);
 static struct map *nc_map;
 DEF_LOOKUP_CMD(ncurses_handle, nc_map);
 
+static struct display_data *current_dd;
 static void set_screen(struct pane *p)
 {
        struct display_data *dd;
@@ -92,6 +128,7 @@ static void set_screen(struct pane *p)
                return;
        }
        dd = p->data;
+       current_dd = dd;
        if (!dd)
                return;
        if (dd->scr == current_screen)
@@ -102,6 +139,7 @@ static void set_screen(struct pane *p)
                for (i=0; i<100; i++)
                        if (_nc_globals[i] < (void*)stdscr &&
                            _nc_globals[i]+4*(sizeof(void*)) >= (void*)stdscr) {
+                               /* This is _nc_windowlist */
                                index = i;
                                offset = ((void*)stdscr) - _nc_globals[i];
                        }
@@ -116,10 +154,9 @@ static void set_screen(struct pane *p)
 
 #ifdef RECORD_REPLAY
 DEF_CMD(next_evt);
-DEF_CMD(abort_replay);
 
-static int parse_event(struct pane *p safe);
-static int prepare_recrep(struct pane *p safe)
+static bool parse_event(struct pane *p safe);
+static bool prepare_recrep(struct pane *p safe)
 {
        struct display_data *dd = p->data;
        char *name;
@@ -134,9 +171,9 @@ static int prepare_recrep(struct pane *p safe)
                sleep(atoi(getenv("EDLIB_PAUSE")));
        if (dd->input) {
                parse_event(p);
-               return 1;
+               return True;
        }
-       return 0;
+       return False;
 }
 
 static void close_recrep(struct pane *p safe)
@@ -144,7 +181,7 @@ static void close_recrep(struct pane *p safe)
        struct display_data *dd = p->data;
 
        if (dd->log) {
-               fprintf(dd->log, "Close\n");
+               fprintf(dd->log, "Close %d\n", dd->clears);
                fclose(dd->log);
        }
 }
@@ -165,6 +202,8 @@ static void record_key(struct pane *p safe, char *key safe)
        else
                return;
        fprintf(dd->log, "Key %c%s%c\n", q,key,q);
+       dd->last_cx = -2; /* Force next Display to be shown */
+       fflush(dd->log);
 }
 
 static void record_mouse(struct pane *p safe, char *key safe, int x, int y)
@@ -182,13 +221,15 @@ static void record_mouse(struct pane *p safe, char *key safe, int x, int y)
        else
                return;
        fprintf(dd->log, "Mouse %c%s%c %d,%d\n", q,key,q, x, y);
+       dd->last_cx = -2; /* Force next Display to be shown */
+       fflush(dd->log);
 }
 
 static void record_screen(struct pane *p safe)
 {
        struct display_data *dd = p->data;
        struct md5_state ctx;
-       uint16_t buf[CCHARW_MAX+4];
+       uint16_t buf[CCHARW_MAX+5];
        char out[MD5_DIGEST_SIZE*2+1];
        int r,c;
 
@@ -201,38 +242,45 @@ static void record_screen(struct pane *p safe)
                        cchar_t cc;
                        wchar_t wc[CCHARW_MAX+2];
                        attr_t a;
-                       short color;
+                       short color, fg, bg;
                        int l;
 
-                       mvin_wch(r,c,&cc);
+                       mvwin_wch(stdscr, r, c, &cc);
                        getcchar(&cc, wc, &a, &color, NULL);
-                       buf[0] = htole16(color);
+                       pair_content(color, &fg, &bg);
+                       buf[0] = htole16(fg);
+                       buf[1] = htole16(bg);
                        for (l = 0; l < CCHARW_MAX && wc[l]; l++)
-                               buf[l+2] = htole16(wc[l]);
-                       buf[1] = htole16(l);
-                       md5_update(&ctx, (uint8_t*)buf, (l+2) * 2);
+                               buf[l+3] = htole16(wc[l]);
+                       buf[2] = htole16(l);
+                       LOG("%d,%d %d:%d:%d:%d %lc", c,r,fg,bg,l,wc[0], wc[0] > ' ' ? wc[0] : '?');
+                       md5_update(&ctx, (uint8_t*)buf,
+                                  (l+3) * sizeof(uint16_t));
                }
        md5_final_txt(&ctx, out);
-       if (dd->log) {
+       if (strcmp(out, dd->last_screen) == 0 &&
+            p->cx == dd->last_cx && p->cy == dd->last_cy) {
+               /* No  change - filter it */
+               dd->clears -= 1;
+       } else if (dd->log) {
                fprintf(dd->log, "Display %d,%d %s", p->w, p->h, out);
-               strcpy(dd->last_screen, out);
-               if (dd->cursor.x >= 0)
-                       fprintf(dd->log, " %d,%d", dd->cursor.x, dd->cursor.y);
+               if (p->cx >= 0)
+                       fprintf(dd->log, " %d,%d", p->cx, p->cy);
                fprintf(dd->log, "\n");
+               fflush(dd->log);
+               strcpy(dd->last_screen, out);
+               dd->last_cx = p->cx; dd->last_cy = p->cy;
        }
-       if (dd->input && dd->next_event == DoCheck) {
-               call_comm("event:free", p, &abort_replay);
-//             if (strcmp(dd->last_screen, dd->next_screen) != 0)
-//                     dd->next_event = DoClose;
-               call_comm("editor-on-idle", p, &next_evt);
+       if (dd->input && dd->input_sleeping) {
+               char *delay = getenv("EDLIB_REPLAY_DELAY");
+               call_comm("event:free", p, &next_evt);
+               if (delay)
+                       call_comm("event:timer", p, &next_evt, atoi(delay));
+               else
+                       call_comm("event:on-idle", p, &next_evt);
        }
 }
 
-static inline int match(char *line safe, char *w safe)
-{
-       return strncmp(line, w, strlen(w)) == 0;
-}
-
 static char *copy_quote(char *line safe, char *buf safe)
 {
        char q;
@@ -280,15 +328,7 @@ static char *get_hash(char *line safe, hash_t hash safe)
        return line;
 }
 
-REDEF_CMD(abort_replay)
-{
-       struct display_data *dd = ci->home->data;
-
-       dd->next_event = DoClose;
-       return next_evt_func(ci);
-}
-
-static int parse_event(struct pane *p safe)
+static bool parse_event(struct pane *p safe)
 {
        struct display_data *dd = p->data;
        char line[80];
@@ -297,34 +337,40 @@ static int parse_event(struct pane *p safe)
        dd->next_event = DoNil;
        if (!dd->input ||
            fgets(line, sizeof(line)-1, dd->input) == NULL)
-               ;
-       else if (match(line, "Key ")) {
+               line[0]=0;
+       else if (strstarts(line, "Key ")) {
                if (!copy_quote(line+4, dd->event_info))
-                       return 0;
+                       return False;
                dd->next_event = DoKey;
-       } else if (match(line, "Mouse ")) {
+       } else if (strstarts(line, "Mouse ")) {
                char *f = copy_quote(line+6, dd->event_info);
                if (!f)
-                       return 0;
+                       return False;
                f = get_coord(f, &dd->event_pos);
                if (!f)
-                       return 0;
+                       return False;
                dd->next_event = DoMouse;
-       } else if (match(line, "Display ")) {
+       } else if (strstarts(line, "Display ")) {
                char *f = get_coord(line+8, &dd->event_pos);
                if (!f)
-                       return 0;
+                       return False;
                f = get_hash(f, dd->next_screen);
                dd->next_event = DoCheck;
-       } else if (match(line, "Close")) {
+       } else if (strstarts(line, "Close")) {
                dd->next_event = DoClose;
        }
+       LOG("parse %s", line);
 
-       if (dd->next_event != DoCheck)
-               call_comm("editor-on-idle", p, &next_evt);
-       else
-               call_comm("event:timer", p, &abort_replay, 10*1000);
-       return 1;
+       dd->input_sleeping = 1;
+       if (dd->next_event != DoCheck) {
+               char *delay = getenv("EDLIB_REPLAY_DELAY");
+               if (delay)
+                       call_comm("event:timer", p, &next_evt, atoi(delay));
+               else
+                       call_comm("event:on-idle", p, &next_evt);
+       } else
+               call_comm("event:timer", p, &next_evt, 10*1000);
+       return True;
 }
 
 REDEF_CMD(next_evt)
@@ -332,12 +378,8 @@ REDEF_CMD(next_evt)
        struct pane *p = ci->home;
        struct display_data *dd = p->data;
        int button = 0, type = 0;
-       char *delay;
-
-       delay = getenv("EDLIB_REPLAY_DELAY");
-       if (delay && dd->next_event != DoCheck)
-               usleep(atoi(delay)*1000);
 
+       dd->input_sleeping = 0;
        switch(dd->next_event) {
        case DoKey:
                record_key(p, dd->event_info);
@@ -377,7 +419,7 @@ REDEF_CMD(next_evt)
        return 1;
 }
 #else
-static inline int  prepare_recrep(struct pane *p safe) {return 0;}
+static inline bool  prepare_recrep(struct pane *p safe) {return False;}
 static inline void record_key(struct pane *p safe, char *key) {}
 static inline void record_mouse(struct pane *p safe, char *key safe,
                                int x, int y) {}
@@ -385,18 +427,7 @@ static inline void record_screen(struct pane *p safe) {}
 static inline void close_recrep(struct pane *p safe) {}
 #endif
 
-DEF_CMD(nc_refresh)
-{
-       struct pane *p = ci->home;
-
-       call("Sig:Winch", p);
-       set_screen(p);
-       clear();
-       pane_damaged(p,  DAMAGED_SIZE);
-       return 1;
-}
-
-DEF_CMD(cnt_disp)
+DEF_CB(cnt_disp)
 {
        struct call_return *cr = container_of(ci->comm, struct call_return, c);
 
@@ -404,45 +435,219 @@ DEF_CMD(cnt_disp)
        return 1;
 }
 
+static void ncurses_end(struct pane *p safe);
+
 DEF_CMD(nc_close_display)
 {
        /* If this is only display, then refuse to close this one */
        struct call_return cr;
-       struct display_data *dd = ci->home->data;
+       char *nc = pane_attr_get(ci->home, "no-close");
 
-       if (dd->noclose) {
-               call("Message", ci->focus, 0, NULL, dd->noclose);
+       if (nc) {
+               call("Message", ci->focus, 0, NULL, nc);
                return 1;
        }
 
        cr.c = cnt_disp;
        cr.i = 0;
        call_comm("editor:notify:all-displays", ci->focus, &cr.c);
-       if (cr.i > 1)
-               pane_close(ci->home);
-       else
+       if (cr.i > 1) {
+               /* Need to call ncurses_end() before we send a Notify:Close
+                * notification, else server exits too early
+                */
+               ncurses_end(ci->home);
+               return Efallthrough;
+       } else
                call("Message", ci->focus, 0, NULL,
                     "Cannot close only window.");
        return 1;
 }
 
-DEF_CMD(nc_set_noclose)
+static int nc_putc(int ch)
+{
+       if (current_dd)
+               fputc(ch, current_dd->scr_file);
+       return 1;
+}
+
+static char *fnormalize(struct pane *p safe, const char *str) safe
+{
+       char *ret = strsave(p, str);
+       char *cp;
+
+       for (cp = ret ; cp && *cp ; cp++)
+               if (!isalnum(*cp) &&
+                   !strchr("/_-+=.,@#", *cp))
+                       /* Don't like this char */
+                       *cp = '_';
+       return ret ?: "_";
+}
+
+static void wait_for(struct display_data *dd safe)
+{
+       struct pids **pp = &dd->pids;
+
+       while (*pp) {
+               struct pids *p = *pp;
+               if (waitpid(p->pid, NULL, WNOHANG) > 0) {
+                       *pp = p->next;
+                       free(p);
+               } else
+                       pp = &p->next;
+       }
+}
+
+DEF_CB(ns_resume)
 {
        struct display_data *dd = ci->home->data;
 
-       free(dd->noclose);
-       dd->noclose = NULL;
-       if (ci->str)
-               dd->noclose = strdup(ci->str);
+       if (dd->suspended) {
+               dd->suspended = False;
+               set_screen(ci->home);
+               doupdate();
+       }
        return 1;
 }
 
-static void ncurses_end(struct pane *p safe)
+DEF_CMD(nc_external_viewer)
 {
+       struct pane *p = ci->home;
+       struct display_data *dd = p->data;
+       char *disp = pane_attr_get(p, "DISPLAY");
+       char *disp_auth = pane_attr_get(p, "XAUTHORITY");
+       char *remote = pane_attr_get(p, "REMOTE_SESSION");
+       char *fqdn = NULL;
+       const char *path = ci->str;
+       int pid;
+       char buf[100];
+       int n;
+       int fd;
+
+       if (!path)
+               return Enoarg;
+       if (disp && *disp) {
+               struct pids *pds;
+               switch (pid = fork()) {
+               case -1:
+                       return Efail;
+               case 0: /* Child */
+                       setenv("DISPLAY", disp, 1);
+                       if (disp_auth)
+                               setenv("XAUTHORITY", disp_auth, 1);
+                       fd = open("/dev/null", O_RDWR);
+                       if (fd) {
+                               dup2(fd, 0);
+                               dup2(fd, 1);
+                               dup2(fd, 2);
+                               if (fd > 2)
+                                       close(fd);
+                       }
+                       execlp("xdg-open", "xdg-open", path, NULL);
+                       exit(1);
+               default: /* parent */
+                       pds = malloc(sizeof(*pds));
+                       pds->pid = pid;
+                       pds->next = dd->pids;
+                       dd->pids = pds;
+                       break;
+               }
+               wait_for(dd);
+               return 1;
+       }
+       /* handle no-display case */
+       if (remote && strcmp(remote, "yes") == 0 &&
+           path[0] == '/' &&
+           gethostname(buf, sizeof(buf)) == 0) {
+               struct addrinfo *res;
+               const struct addrinfo hints = {
+                       .ai_flags = AI_CANONNAME,
+               };
+               if (getaddrinfo(buf, NULL, &hints, &res) == 0 &&
+                   res && res->ai_canonname)
+                       fqdn = strdup(res->ai_canonname);
+               freeaddrinfo(res);
+       }
        set_screen(p);
-       close_recrep(p);
+       n = 0;
+       ioctl(fileno(dd->scr_file), FIONREAD, &n);
+       if (n)
+               n -= read(fileno(dd->scr_file), buf,
+                         n <= (int)sizeof(buf) ? n : (int)sizeof(buf));
+       endwin();
+       /* stay in raw mode */
+       raw();
+       noecho();
+
+       /* Endwin doesn't seem to reset properly, at least on xfce-terminal.
+        * So do it manually
+        */
+       if (dd->rs1)
+               tputs(dd->rs1, 1, nc_putc);
+       if (dd->rs2)
+               tputs(dd->rs2, 1, nc_putc);
+       if (dd->rs3)
+               tputs(dd->rs3, 1, nc_putc);
+       if (dd->clear)
+               tputs(dd->clear, 1, nc_putc);
+       fflush(dd->scr_file);
+
+       fprintf(dd->scr_file, "# Consider copy-pasting following\r\n");
+       if (fqdn && path[0] == '/') {
+               /* File will not be local for the user, so help them copy it. */
+               const char *tmp = fnormalize(p, ci->str2 ?: "XXXXXX");
+               const char *fname = fnormalize(p, ci->str);
+
+               if (strcmp(fname, ci->str) != 0)
+                       /* file name had unusuable chars, need to create safe name */
+                       link(ci->str, fname);
+               fprintf(dd->scr_file, "f=`mktemp --tmpdir %s`; scp %s:%s $f ; ",
+                       tmp, fqdn, fname);
+               path = "$f";
+       }
+       free(fqdn);
+       fprintf(dd->scr_file, "xdg-open %s\r\n", path);
+       fprintf(dd->scr_file, "# Press Enter to continue\r\n");
+       dd->suspended = True;
+       call_comm("event:timer", p, &ns_resume, 30*1000);
+       return 1;
+}
+
+static void ncurses_stop(struct pane *p safe)
+{
+       struct display_data *dd = p->data;
+
+       if (dd->is_xterm) {
+               /* disable bracketed-paste */
+               fprintf(dd->scr_file, "\033[?2004l");
+               fflush(dd->scr_file);
+       }
+       if (dd->paste_start)
+               free(buf_final(&dd->paste_buf));
+       dd->paste_start = 0;
+       free(dd->paste_latest);
+       dd->paste_latest = NULL;
        nl();
        endwin();
+       if (dd->rs1)
+               tputs(dd->rs1, 1, nc_putc);
+       if (dd->rs2)
+               tputs(dd->rs2, 1, nc_putc);
+       if (dd->rs3)
+               tputs(dd->rs3, 1, nc_putc);
+       fflush(dd->scr_file);
+}
+
+static void ncurses_end(struct pane *p safe)
+{
+       struct display_data *dd = p->data;
+
+       if (dd->did_close)
+               return;
+       dd->did_close = True;
+       set_screen(p);
+       close_recrep(p);
+
+       ncurses_stop(p);
 }
 
 /*
@@ -582,54 +787,62 @@ static int to_pair(struct display_data *dd safe, int fg, int bg)
        return c->content;
 }
 
-
-static int cvt_attrs(struct pane *home safe, const char *attrs)
+static int cvt_attrs(struct pane *p safe, struct pane *home safe,
+                    const char *attrs, int *pairp safe)
 {
        struct display_data *dd = home->data;
-
        int attr = 0;
-       char tmp[40];
-       const char *a;
+       const char *a, *v;
+       char *col = NULL;
+       PANEL *pan = NULL;
        int fg = COLOR_BLACK;
        int bg = COLOR_WHITE+8;
 
-       if (!attrs)
-               return 0;
        set_screen(home);
-       a = attrs;
-       while (a && *a) {
-               const char *c;
-               if (*a == ',') {
-                       a++;
-                       continue;
-               }
-               c = strchr(a, ',');
-               if (!c)
-                       c = a+strlen(a);
-               strncpy(tmp, a, c-a);
-               tmp[c-a] = 0;
-               if (strcmp(tmp, "inverse")==0) attr |= A_STANDOUT;
-               else if (strcmp(tmp, "bold")==0) attr |= A_BOLD;
-               else if (strcmp(tmp, "underline")==0) attr |= A_UNDERLINE;
-               else if (strncmp(tmp, "fg:", 3) == 0) {
+       while (p->parent != p &&(pan = pane_panel(p, NULL)) == NULL)
+               p = p->parent;
+       if (pan) {
+               /* Get 'default colours for this pane - set at clear */
+               int at = getbkgd(panel_window(pan));
+               int pair = PAIR_NUMBER(at);
+               short dfg, dbg;
+               pair_content(pair, &dfg, &dbg);
+               if (dfg >= 0)
+                       fg = dfg;
+               if (dbg >= 0)
+                       bg = dbg;
+       }
+
+       foreach_attr(a, v, attrs, NULL) {
+               if (amatch(a, "inverse"))
+                       attr |= A_STANDOUT;
+               else if (amatch(a, "noinverse"))
+                       attr &= ~A_STANDOUT;
+               else if (amatch(a, "bold"))
+                       attr |= A_BOLD;
+               else if (amatch(a, "nobold"))
+                       attr &= ~A_BOLD;
+               else if (amatch(a, "underline"))
+                       attr |= A_UNDERLINE;
+               else if (amatch(a, "nounderline"))
+                       attr &= ~A_UNDERLINE;
+               else if (amatch(a, "fg") && v) {
                        struct call_return cr =
                                call_ret(all, "colour:map", home,
-                                        0, NULL, tmp+3);
+                                        0, NULL, aupdate(&col, v));
                        int rgb[3] = {cr.i, cr.i2, cr.x};
                        fg = find_col(dd, rgb);
-               } else if (strncmp(tmp, "bg:", 3) == 0) {
+               } else if (amatch(a, "bg") && v) {
                        struct call_return cr =
                                call_ret(all, "colour:map", home,
-                                        0, NULL, tmp+3);
+                                        0, NULL, aupdate(&col, v));
                        int rgb[3] = {cr.i, cr.i2, cr.x};
                        bg = find_col(dd, rgb);
                }
-               a = c;
-       }
-       if (fg != COLOR_BLACK || bg != COLOR_WHITE+8) {
-               int p = to_pair(dd, fg, bg);
-               attr |= COLOR_PAIR(p);
        }
+       free(col);
+       if (fg != COLOR_BLACK || bg != COLOR_WHITE+8)
+               *pairp = to_pair(dd, fg, bg);
        return attr;
 }
 
@@ -642,10 +855,10 @@ DEF_CMD(nc_notify_display)
 {
        struct display_data *dd = ci->home->data;
        comm_call(ci->comm2, "callback:display", ci->home, dd->last_event);
-       return 0;
+       return 1;
 }
 
-DEF_CMD(nc_close)
+DEF_CMD_CLOSED(nc_close)
 {
        struct pane *p = ci->home;
        struct display_data *dd = p->data;
@@ -655,12 +868,70 @@ DEF_CMD(nc_close)
        return 1;
 }
 
+DEF_CMD(nc_pane_close)
+{
+       PANEL *pan = NULL;
+
+       set_screen(ci->home);
+       while ((pan = panel_above(pan)) != NULL)
+               if (panel_userptr(pan) == ci->focus)
+                       break;
+       if (pan) {
+               WINDOW *win = panel_window(pan);
+               del_panel(pan);
+               delwin(win);
+               pane_damaged(ci->home, DAMAGED_POSTORDER);
+       }
+       return 1;
+}
+
+static PANEL * safe pane_panel(struct pane *p safe, struct pane *home)
+{
+       PANEL *pan = NULL;
+
+       while ((pan = panel_above(pan)) != NULL)
+               if (panel_userptr(pan) == p)
+                       return pan;
+
+       if (!home)
+               return pan;
+
+       pan = new_panel(newwin(p->h, p->w, 0, 0));
+       set_panel_userptr(pan, p);
+       pane_add_notify(home, p, "Notify:Close");
+
+       return pan;
+}
+
 DEF_CMD(nc_clear)
 {
        struct pane *p = ci->home;
-       int attr = cvt_attrs(p, ci->str2?:ci->str);
+       struct display_data *dd = p->data;
+       cchar_t cc = {};
+       int pair = 0;
+       /* default come from parent when clearing pane */
+       int attr = cvt_attrs(ci->focus->parent, p, ci->str, &pair);
+       PANEL *panel;
+       WINDOW *win;
+       int w, h;
+
+       set_screen(p);
+       panel = pane_panel(ci->focus, p);
+       if (!panel)
+               return Efail;
+       win = panel_window(panel);
+       getmaxyx(win, h, w);
+       if (h != ci->focus->h || w != ci->focus->w) {
+               wresize(win, ci->focus->h, ci->focus->w);
+               replace_panel(panel, win);
+       }
+       cc.attr = attr;
+       cc.ext_color = pair;
+       cc.chars[0] = ' ';
+       wbkgrndset(win, &cc);
+       werase(win);
+       dd->clears += 1;
 
-       ncurses_clear(ci->focus, p, attr, 0, 0, 0, 0);
        pane_damaged(p, DAMAGED_POSTORDER);
        return 1;
 }
@@ -670,26 +941,21 @@ DEF_CMD(nc_text_size)
        int max_space = ci->num;
        int max_bytes = 0;
        int size = 0;
-       int offset = 0;
-       mbstate_t mbs = {};
        const char *str = ci->str;
-       int len;
 
        if (!str)
                return Enoarg;
-       len = strlen(str);
-       while (str[offset] != 0) {
-               wchar_t wc;
-               int skip = mbrtowc(&wc, str+offset, len-offset, &mbs);
-               if (skip < 0)
+       while (str[0] != 0) {
+               wint_t wc = get_utf8(&str, NULL);
+               int width;
+               if (wc >= WERR)
                        break;
-               offset += skip;
-               skip = wcwidth(wc);
-               if (skip < 0)
+               width = wcwidth(wc);
+               if (width < 0)
                        break;
-               size += skip;
+               size += width;
                if (size <= max_space)
-                       max_bytes = offset;
+                       max_bytes = str - ci->str;
        }
        return comm_call(ci->comm2, "callback:size", ci->focus,
                         max_bytes, NULL, NULL,
@@ -699,47 +965,249 @@ DEF_CMD(nc_text_size)
 DEF_CMD(nc_draw_text)
 {
        struct pane *p = ci->home;
-       int attr = cvt_attrs(p, ci->str2);
+       int pair = 0;
+       int attr = cvt_attrs(ci->focus, p, ci->str2, &pair);
        int cursor_offset = ci->num;
-       short offset = 0;
        short x = ci->x, y = ci->y;
-       mbstate_t mbs = {};
        const char *str = ci->str;
-       int len;
 
        if (!str)
                return Enoarg;
        set_screen(p);
-       len = strlen(str);
-       while (str[offset] != 0) {
-               wchar_t wc;
-               int skip = mbrtowc(&wc, str+offset, len-offset, &mbs);
+       while (str[0] != 0) {
+               int precurs = str <= ci->str + cursor_offset;
+               wint_t wc = get_utf8(&str, NULL);
                int width;
-               if (skip < 0)
+               if (wc == WEOF || wc == WERR)
                        break;
                width = wcwidth(wc);
                if (width < 0)
                        break;
-               if (cursor_offset >= offset &&
-                   cursor_offset < offset + skip)
-                       ncurses_text(ci->focus, p, wc, attr, x, y, 1);
+               if (precurs && str > ci->str + cursor_offset)
+                       ncurses_text(ci->focus, p, wc, attr, pair, x, y, 1);
                else
-                       ncurses_text(ci->focus, p, wc, attr, x, y, 0);
-               offset += skip;
+                       ncurses_text(ci->focus, p, wc, attr, pair, x, y, 0);
                x += width;
        }
-       if (offset == cursor_offset)
-               ncurses_text(ci->focus, p, ' ', 0, x, y, 1);
+       if (str == ci->str + cursor_offset)
+               ncurses_text(ci->focus, p, ' ', 0, 0, x, y, 1);
        pane_damaged(p, DAMAGED_POSTORDER);
        return 1;
 }
 
+struct di_info {
+       struct command c;
+       MagickWand *wd safe;
+       int x,y,w,h;
+       int xo, yo;
+       struct pane *p safe;
+};
+
+DEF_CB(nc_draw_image_cb)
+{
+       struct di_info *dii = container_of(ci->comm, struct di_info, c);
+       struct display_data *dd = dii->p->data;
+       int i, j;
+       unsigned char *buf;
+
+       switch (ci->key[0]) {
+       case 'w': /* width */
+               return MagickGetImageWidth(dii->wd);
+       case 'h': /* height */
+               return MagickGetImageHeight(dii->wd);
+       case 's': /* scale */
+               MagickResizeImage(dii->wd, ci->num, ci->num2, BoxFilter, 1.0);
+               return 1;
+       case 'c': /* crop or cursor */
+               if (ci->key[1] != 'u') {
+                       /* crop */
+                       dii->x = ci->x;
+                       dii->y = ci->y;
+                       dii->w = ci->num;
+                       dii->h = ci->num2;
+                       return 1;
+               } else {
+                       /* cursor */
+                       /* FIXME this doesn't work because
+                        * render-line knows too much and gets it wrong.
+                        */
+                       ncurses_text(ci->focus, dii->p, 'X', 0,
+                                    to_pair(dd, 0, 0),
+                                    ci->x + dii->xo,
+                                    (ci->y + dii->xo)/2, 1);
+                       return 1;
+               }
+       case 'd': /* draw */
+               if (dii->w <= 0 || dii->h <= 0)
+                       return Efail;
+               buf = malloc(dii->h * dii->w * 4);
+
+               MagickExportImagePixels(dii->wd, dii->x, dii->y,
+                                       dii->w, dii->h,
+                                       "RGBA", CharPixel, buf);
+
+               for (i = 0; i < dii->h; i+= 2) {
+                       static const wint_t hilo = 0x2580; /* L'▀' */
+                       static unsigned char blk[4] = "\0\0\0";
+                       for (j = 0; j < dii->w ; j+= 1) {
+                               unsigned char *p1 = buf + i*dii->w*4 + j*4;
+                               unsigned char *p2 = i + 1 < dii->h ?
+                                       buf + (i+1)*dii->w*4 + j*4 : blk;
+                               int rgb1[3] = { p1[0]*1000/255, p1[1]*1000/255,
+                                              p1[2]*1000/255 };
+                               int rgb2[3] = { p2[0]*1000/255, p2[1]*1000/255,
+                                              p2[2]*1000/255 };
+                               int fg = find_col(dd, rgb1);
+                               int bg = find_col(dd, rgb2);
+
+                               if (p1[3] < 128 || p2[3] < 128) {
+                                       /* transparent */
+                                       cchar_t cc;
+                                       short f,b;
+                                       struct pane *pn2 = ci->focus;
+                                       PANEL *pan = pane_panel(pn2, NULL);
+
+                                       while (!pan && pn2->parent != pn2) {
+                                               pn2 = pn2->parent;
+                                               pan = pane_panel(pn2, NULL);
+                                       }
+                                       if (pan) {
+                                               wgetbkgrnd(panel_window(pan), &cc);
+                                               if (cc.ext_color == 0)
+                                                       /* default.  This is light
+                                                        * gray rather then white,
+                                                        * but I think it is a good
+                                                        * result.
+                                                        */
+                                                       b = COLOR_WHITE;
+                                               else
+                                                       pair_content(cc.ext_color, &f, &b);
+                                               if (p1[3] < 128)
+                                                       fg = b;
+                                               if (p2[3] < 128)
+                                                       bg = b;
+                                       }
+                               }
+                               ncurses_text(ci->focus, dii->p, hilo, 0,
+                                            to_pair(dd, fg, bg),
+                                            ci->num + dii->xo + j,
+                                            (ci->num2 + dii->yo + i)/2,
+                                            0);
+                       }
+               }
+               free(buf);
+               return 1;
+       default:
+               return Efail;
+       }
+}
+
+DEF_CMD(nc_draw_image)
+{
+       /* 'str' identifies the image. Options are:
+        *     file:filename  - load file from fs
+        *     comm:command   - run command collecting bytes
+        * 'str2' and numbers are handled by Draw:scale-image.
+        */
+       struct pane *p = ci->home;
+       MagickBooleanType status;
+       MagickWand *wd = NULL;
+       struct di_info dii;
+
+       if (!ci->str)
+               return Enoarg;
+       if (strstarts(ci->str, "file:")) {
+               wd = NewMagickWand();
+               status = MagickReadImage(wd, ci->str + 5);
+               if (status == MagickFalse) {
+                       DestroyMagickWand(wd);
+                       return Efail;
+               }
+       } else if (strstarts(ci->str, "comm:")) {
+               struct call_return cr;
+               wd = NewMagickWand();
+               cr = call_ret(bytes, ci->str+5, ci->focus);
+               if (!cr.s) {
+                       DestroyMagickWand(wd);
+                       return Efail;
+               }
+               status = MagickReadImageBlob(wd, cr.s, cr.i);
+               free(cr.s);
+               if (status == MagickFalse) {
+                       DestroyMagickWand(wd);
+                       return Efail;
+               }
+       }
+
+       if (!wd)
+               return Einval;
+
+       MagickAutoOrientImage(wd);
+       dii.c = nc_draw_image_cb;
+       dii.wd = wd;
+       dii.p = p;
+       dii.w = dii.h = dii.x = dii.y = dii.xo = dii.yo = 0;
+       call("Draw:scale-image", ci->focus,
+            ci->num, NULL, NULL, ci->num2, NULL, ci->str2,
+            ci->x, ci->y, &dii.c);
+
+       DestroyMagickWand(wd);
+
+       pane_damaged(ci->home, DAMAGED_POSTORDER);
+
+       return 1;
+}
+
+DEF_CMD(nc_image_size)
+{
+       MagickBooleanType status;
+       MagickWand *wd;
+       int ih, iw;
+
+       if (!ci->str)
+               return Enoarg;
+       if (strstarts(ci->str, "file:")) {
+               wd = NewMagickWand();
+               status = MagickReadImage(wd, ci->str + 5);
+               if (status == MagickFalse) {
+                       DestroyMagickWand(wd);
+                       return Efail;
+               }
+       } else if (strstarts(ci->str, "comm:")) {
+               struct call_return cr;
+               wd = NewMagickWand();
+               cr = call_ret(bytes, ci->str+5, ci->focus);
+               if (!cr.s) {
+                       DestroyMagickWand(wd);
+                       return Efail;
+               }
+               status = MagickReadImageBlob(wd, cr.s, cr.i);
+               free(cr.s);
+               if (status == MagickFalse) {
+                       DestroyMagickWand(wd);
+                       return Efail;
+               }
+       } else
+               return Einval;
+
+       MagickAutoOrientImage(wd);
+       ih = MagickGetImageHeight(wd);
+       iw = MagickGetImageWidth(wd);
+
+       DestroyMagickWand(wd);
+       comm_call(ci->comm2, "callback:size", ci->focus,
+                 0, NULL, NULL, 0, NULL, NULL,
+                 iw, ih);
+       return 1;
+}
+
 DEF_CMD(nc_refresh_size)
 {
        struct pane *p = ci->home;
 
        set_screen(p);
        getmaxyx(stdscr, p->h, p->w);
+       clearok(curscr, 1);
        return 0;
 }
 
@@ -747,24 +1215,129 @@ DEF_CMD(nc_refresh_post)
 {
        struct pane *p = ci->home;
        struct display_data *dd = p->data;
+       struct pane *p1;
+       PANEL *pan, *pan2;
+
+       if (dd->suspended)
+               return 1;
+
        set_screen(p);
-       if (dd->cursor.x >= 0)
-               move(dd->cursor.y, dd->cursor.x);
+
+       /* Need to ensure stacking order and panel y,x position
+        * is correct.  FIXME it would be good if we could skip this
+        * almost always.
+        */
+       pan = panel_above(NULL);
+       if (!pan)
+               return 1;
+       p1 = (struct pane*) panel_userptr(pan);
+       for (;(pan2 = panel_above(pan)) != NULL; pan = pan2) {
+               struct pane *p2 = (struct pane*)panel_userptr(pan2);
+               p1 = (struct pane*)panel_userptr(pan);
+               if (!p1 || !p2)
+                       continue;
+
+               if (p1->abs_z < p2->abs_z)
+                       continue;
+               if (p1->abs_z == p2->abs_z &&
+                   p1->z <= p2->z)
+                       continue;
+               /* pan needs to be above pan2.  All we can do is move it to
+                * the top. Anything that needs to be above it will eventually
+                * be pushed up too.
+                */
+               top_panel(pan);
+               /* Now the panel below pan might need to be over pan2 too... */
+               pan = panel_below(pan2);
+               if (pan)
+                       pan2 = pan;
+       }
+
+       /* As we need to crop pane against their parents, we cannot simply
+        * use update_panels().  Instead we copy each to stdscr and refresh
+        * that.
+        */
+       for (pan = NULL; (pan = panel_above(pan)) != NULL; ) {
+               WINDOW *win;
+               struct xy src, dest, destend;
+               int w, h;
+
+               p1 = (void*)panel_userptr(pan);
+               if (!p1)
+                       continue;
+               dest = pane_mapxy(p1, p, 0, 0, True);
+               destend = pane_mapxy(p1, p, p1->w, p1->h, True);
+               src = pane_mapxy(p1, p, 0, 0, False);
+               src.x = dest.x - src.x;
+               src.y = dest.y - src.y;
+               win = panel_window(pan);
+               getmaxyx(win, h, w);
+               /* guard again accessing beyond boundary of win */
+               if (destend.x > dest.x + (w - src.x))
+                       destend.x = dest.x + (w - src.x);
+               if (destend.y > dest.y + (h - src.y))
+                       destend.y = dest.y - (h - src.y);
+               copywin(win, stdscr, src.y, src.x,
+                       dest.y, dest.x, destend.y-1, destend.x-1, 0);
+       }
+       /* place the cursor */
+       p1 = pane_focus(p);
+       pan = NULL;
+       while (p1 != p && (pan = pane_panel(p1, NULL)) == NULL)
+               p1 = p1->parent;
+       if (pan && p1->cx >= 0) {
+               struct xy curs = pane_mapxy(p1, p, p1->cx, p1->cy, False);
+               wmove(stdscr, curs.y, curs.x);
+       } else if (p->cx >= 0)
+               wmove(stdscr, p->cy, p->cx);
        refresh();
-       record_screen(p);
+       record_screen(ci->home);
        return 1;
 }
 
-static struct pane *ncurses_init(struct pane *ed,
+static void ncurses_start(struct pane *p safe)
+{
+       struct display_data *dd = p->data;
+       int rows, cols;
+
+       start_color();
+       use_default_colors();
+       raw();
+       noecho();
+       nonl();
+       timeout(0);
+       set_escdelay(100);
+       intrflush(stdscr, FALSE);
+       keypad(stdscr, TRUE);
+       mousemask(BUTTON1_PRESSED | BUTTON1_RELEASED |
+                 BUTTON2_PRESSED | BUTTON2_RELEASED |
+                 BUTTON3_PRESSED | BUTTON3_RELEASED |
+                 BUTTON4_PRESSED | BUTTON4_RELEASED |
+                 BUTTON5_PRESSED | BUTTON5_RELEASED |
+                 BUTTON_CTRL | BUTTON_SHIFT | BUTTON_ALT |
+                 REPORT_MOUSE_POSITION, NULL);
+       mouseinterval(0);
+       if (dd->is_xterm) {
+               /* Enable bracketed-paste */
+               fprintf(dd->scr_file, "\033[?2004h");
+               fflush(dd->scr_file);
+       }
+
+       getmaxyx(stdscr, rows, cols);
+       pane_resize(p, 0, 0, cols, rows);
+}
+
+static struct pane *ncurses_init(struct pane *ed safe,
                                 const char *tty, const char *term)
 {
        SCREEN *scr;
        struct pane *p;
        struct display_data *dd;
+       char *area;
        FILE *f;
 
        set_screen(NULL);
-       if (tty)
+       if (tty && strcmp(tty, "-") != 0)
                f = fopen(tty, "r+");
        else
                f = fdopen(1, "r+");
@@ -774,36 +1347,38 @@ static struct pane *ncurses_init(struct pane *ed,
        if (!scr)
                return NULL;
 
-       alloc(dd, pane);
+       p = pane_register(ed, 1, &ncurses_handle.c);
+       if (!p)
+               return NULL;
+       dd = p->data;
        dd->scr = scr;
        dd->scr_file = f;
-       dd->cursor.x = dd->cursor.y = -1;
-       dd->is_xterm =  (term && strncmp(term, "xterm", 5) == 0);
+       dd->is_xterm = (term && strstarts(term, "xterm"));
+
+       attr_set_str(&p->attrs, "Display:pixels", "1x2");
 
-       p = pane_register(ed, 0, &ncurses_handle.c, dd);
        set_screen(p);
 
-       start_color();
-       use_default_colors();
-       raw();
-       noecho();
-       nonl();
-       timeout(0);
-       set_escdelay(100);
-       intrflush(stdscr, FALSE);
-       keypad(stdscr, TRUE);
-       mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL);
-       mouseinterval(0);
+       ncurses_start(p);
 
-       getmaxyx(stdscr, p->h, p->w);
+       area = dd->attr_buf;
+       dd->rs1 = tgetstr("rs1", &area);
+       if (!dd->rs1)
+               dd->rs1 = tgetstr("is1", &area);
+       dd->rs2 = tgetstr("rs2", &area);
+       if (!dd->rs2)
+               dd->rs2 = tgetstr("is2", &area);
+       dd->rs3 = tgetstr("rs3", &area);
+       if (!dd->rs3)
+               dd->rs3 = tgetstr("is3", &area);
+       dd->clear = tgetstr("clear", &area);
 
        call("editor:request:all-displays", p);
        if (!prepare_recrep(p)) {
                call_comm("event:read", p, &input_handle, fileno(f));
-               if (!tty)
+               if (!tty || strcmp(tty, "-") == 0)
                        call_comm("event:signal", p, &handle_winch, SIGWINCH);
        }
-       pane_damaged(p, DAMAGED_SIZE);
        return p;
 }
 
@@ -816,75 +1391,61 @@ REDEF_CMD(handle_winch)
        set_screen(p);
        resize_term(size.ws_row, size.ws_col);
 
-       clear();
-       pane_damaged(p, DAMAGED_SIZE);
+       pane_resize(p, 0, 0, size.ws_row, size.ws_col);
        return 1;
 }
 
-static void ncurses_clear(struct pane *p safe, struct pane *display safe,
-                         int attr, short x, short y, short w, short h)
+DEF_CMD(force_redraw)
 {
-       short r, c;
-       short w0, h0;
-
-       if (w == 0)
-               w = p->w - x;
-       if (h == 0)
-               h = p->h - y;
-       pane_absxy(p, &x, &y, &w, &h);
-       w0 = w; h0 = h;
-       if (pane_masked(display, x, y, p->abs_z, &w0, &h0))
-               w0 = h0 = 0;
+       struct pane *p = ci->home;
 
-       set_screen(display);
-       attrset(attr);
-       for (r = y; r < y+h; r++)
-               for (c = x; c < x+w; c++)
-                       if ((r < y+h0 && c < x+w0) ||
-                           !pane_masked(display, c, r, p->abs_z, NULL, NULL))
-                               mvaddch(r, c, ' ');
+       set_screen(p);
+
+       /* full reset, as mosh sometimes gets confused */
+       ncurses_stop(p);
+       ncurses_start(p);
+
+       clearok(curscr, 1);
+       pane_damaged(p, DAMAGED_POSTORDER);
+       return 1;
 }
 
 static void ncurses_text(struct pane *p safe, struct pane *display safe,
-                        wchar_t ch, int attr, short x, short y, short cursor)
+                        wchar_t ch, int attr, int pair,
+                        short x, short y, short cursor)
 {
-       struct display_data *dd;
+       PANEL *pan;
+       struct pane *p2;
        cchar_t cc = {};
-       short w=1, h=1;
 
        if (x < 0 || y < 0)
                return;
-       if (cursor) {
-               struct pane *p2 = p;
-               cursor = 2;
-               while (p2->parent != p2 && p2 != display) {
-                       if (p2->parent->focus != p2 && p2->z >= 0)
-                               cursor = 1;
-                       p2 = p2->parent;
-               }
-       }
-
-       pane_absxy(p, &x, &y, &w, &h);
-       if (w < 1 || h < 1)
-               return;
-
-       if (pane_masked(display, x, y, p->abs_z, NULL, NULL))
-               return;
 
-       dd = display->data;
        set_screen(display);
-       if (cursor == 2) {
-               /* Cursor is in-focus */
-               dd->cursor.x = x;
-               dd->cursor.y = y;
+       if (cursor) {
+               if (pane_has_focus(p->z < 0 ? p->parent : p)) {
+                       /* Cursor is in-focus */
+                       struct xy curs = pane_mapxy(p, display, x, y, False);
+                       display->cx = curs.x;
+                       display->cy = curs.y;
+               } else
+                       /* Cursor here, but not focus */
+                       attr = make_cursor(attr);
        }
-       if (cursor == 1)
-               /* Cursor here, but not focus */
-               attr = make_cursor(attr);
        cc.attr = attr;
+       cc.ext_color = pair;
        cc.chars[0] = ch;
 
-       mvadd_wch(y, x, &cc);
+       p2 = p;
+       pan = pane_panel(p2, NULL);
+       while (!pan && p2->parent != p2) {
+               p2 = p2->parent;
+               pan = pane_panel(p2, NULL);
+       }
+       if (pan) {
+               struct xy xy = pane_mapxy(p, p2, x, y, False);
+               mvwadd_wch(panel_window(pan), xy.y, xy.x, &cc);
+       }
 }
 
 static struct namelist {
@@ -896,12 +1457,12 @@ static struct namelist {
        {KEY_LEFT, ":Left"},
        {KEY_RIGHT, ":Right"},
        {KEY_HOME, ":Home"},
-       {KEY_BACKSPACE, ":Backspace\037:C-H\037:C-h"},
+       {KEY_BACKSPACE, ":Backspace"},
        {KEY_DL, ":DelLine"},
        {KEY_IL, ":InsLine"},
        {KEY_DC, ":Del"},
        {KEY_IC, ":Ins"},
-       {KEY_ENTER, ":Enter\037:C-M\037:C-m"},
+       {KEY_ENTER, ":Enter"},
        {KEY_END, ":End"},
 
        {KEY_NPAGE, ":Next"},
@@ -915,12 +1476,28 @@ static struct namelist {
        {KEY_SRIGHT, ":S:Right"},
        {KEY_BTAB, ":S:Tab"},
 
-       { 01057, ":M:Prior"},
-       { 01051, ":M:Next"},
-       { 01072, ":M:Up"},
-       { 01061, ":M:Down"},
-       { 01042, ":M:Left"},
-       { 01064, ":M:Right"},
+       {  0521, ":S:Up"},
+       {  0520, ":S:Down"},
+       {  0616, ":S:Prior"},
+       {  0614, ":S:Next"},
+       { 01041, ":S:Home"},
+       { 01060, ":S:End"},
+       { 01066, ":S:Prior"},
+       { 01015, ":S:Next"},
+
+       { 01027, ":A:S:Home"},
+       { 01022, ":A:S:End"},
+       { 01046, ":A:S:Prior"},
+       { 01047, ":A:S:Next"}, // ??
+
+       { 01052, ":A:Prior"},
+       { 01045, ":A:Next"},
+       { 01026, ":A:Home"},
+       { 01021, ":A:End"},
+       { 01065, ":A:Up"},
+       { 01014, ":A:Down"},
+       { 01040, ":A:Left"},
+       { 01057, ":A:Right"},
        { 00411, ":F1"},
        { 00412, ":F2"},
        { 00413, ":F3"},
@@ -945,6 +1522,8 @@ static struct namelist {
        { 00436, ":S:F10"},
        { 00437, ":S:F11"},
        { 00440, ":S:F12"},
+       { 01114, ":Focus-in"},
+       { 01115, ":Focus-out"},
        {0, NULL}
 }, char_names[] = {
        {'\e', ":ESC"},
@@ -965,36 +1544,40 @@ static char *find_name (struct namelist *l safe, wint_t c)
        return NULL;
 }
 
-static void send_key(int keytype, wint_t c, int meta, struct pane *p safe)
+static void send_key(int keytype, wint_t c, int alt, struct pane *p safe)
 {
        struct display_data *dd = p->data;
        char *n;
        char buf[100];/* FIXME */
-       char *m = meta ? ":M" : "";
+       char t[5];
+       char *a = alt ? ":A" : "";
 
        if (keytype == KEY_CODE_YES) {
                n = find_name(key_names, c);
-               if (!n)
-                       sprintf(buf, "%sNcurs-%o", m, c);
+               if (!n) {
+                       LOG("Unknown ncurses key 0o%o", c);
+                       sprintf(buf, "%sNcurs-%o", a, c);
+               } else if (strstarts(n, ":Focus-"))
+                       /* Ignore focus changes for now */
+                       buf[0] = 0;
                else
-                       strcat(strcpy(buf, m), n);
+                       strcat(strcpy(buf, a), n);
        } else {
                n = find_name(char_names, c);
                if (n)
-                       sprintf(buf, "%s%s\037%s:C-%c\037%s:C-%c",
-                               m, n,
-                               m, c+64,
-                               m, c+96);
-               else if (c < ' ')
-                       sprintf(buf, "%s:C-%c\037%s:C-%c",
-                               m, c+64, m, c+96);
+                       sprintf(buf, "%s%s", a, n);
+               else if (c < ' ' || c == 0x7f)
+                       sprintf(buf, "%s:C-%c",
+                               a, c ^ 64);
                else
-                       sprintf(buf, "%s-%lc", m, c);
+                       sprintf(buf, "%s-%s", a, put_utf8(t, c));
        }
 
        dd->last_event = time(NULL);
-       record_key(p, buf);
-       call("Keystroke", p, 0, NULL, buf);
+       if (buf[0]) {
+               record_key(p, buf);
+               call("Keystroke", p, 0, NULL, buf);
+       }
 }
 
 static void do_send_mouse(struct pane *p safe, int x, int y, char *cmd safe,
@@ -1011,7 +1594,7 @@ static void do_send_mouse(struct pane *p safe, int x, int y, char *cmd safe,
                        fflush(dd->scr_file);
                }
                dd->report_position = 1;
-       } else if (type == 3 && !ret) {
+       } else if (type == 3 && ret <= 0) {
                if (dd->is_xterm) {
                        fprintf(dd->scr_file, "\033[?1002l");
                        fflush(dd->scr_file);
@@ -1043,10 +1626,10 @@ static void send_mouse(MEVENT *mev safe, struct pane *p safe)
                case 1: mod = ":S"; break;
                case 2: mod = ":C"; break;
                case 3: mod = ":C:S"; break;
-               case 4: mod = ":M"; break;
-               case 5: mod = ":M:S"; break;
-               case 6: mod = ":M:C"; break;
-               case 7: mod = ":M:C:S"; break;
+               case 4: mod = ":A"; break;
+               case 5: mod = ":A:S"; break;
+               case 6: mod = ":A:C"; break;
+               case 7: mod = ":A:C:S"; break;
                }
                if (BUTTON_PRESS(s, b))
                        action = "%s:Press-%d";
@@ -1066,58 +1649,168 @@ static void send_mouse(MEVENT *mev safe, struct pane *p safe)
                do_send_mouse(p, x, y, ":Motion", 0, "", 3);
 }
 
+static void paste_start(struct pane *home safe)
+{
+       struct display_data *dd = home->data;
+
+       dd->paste_start = time(NULL);
+       buf_init(&dd->paste_buf);
+}
+
+static void paste_flush(struct pane *home safe)
+{
+       struct display_data *dd = home->data;
+
+       if (!dd->paste_start)
+               return;
+       free(dd->paste_latest);
+       dd->paste_latest = buf_final(&dd->paste_buf);
+       if (dd->paste_buf.len > 0)
+               dd->paste_pending = 1;
+       dd->paste_start = 0;
+}
+
+static bool paste_recv(struct pane *home safe, int is_keycode, wint_t ch)
+{
+       struct display_data *dd = home->data;
+       time_t now;
+       if (dd->paste_start == 0)
+               return False;
+       now = time(NULL);
+       if (dd->paste_start < now || dd->paste_start > now + 2 ||
+           is_keycode != OK || ch == KEY_MOUSE) {
+               /* time to close */
+               paste_flush(home);
+               return False;
+       }
+       if (ch == '\r')
+               /* I really don't want carriage-returns... */
+               ch = '\n';
+       buf_append(&dd->paste_buf, ch);
+       if (ch == '~' && dd->paste_buf.len >= 6 &&
+           strcmp("\e[201~",
+                  buf_final(&dd->paste_buf) + dd->paste_buf.len - 6) == 0) {
+               dd->paste_buf.len -= 6;
+               paste_flush(home);
+       }
+       return True;
+}
+
+DEF_CMD(nc_get_paste)
+{
+       struct display_data *dd = ci->home->data;
+
+       comm_call(ci->comm2, "cb", ci->focus,
+                 dd->paste_start, NULL, dd->paste_latest);
+       return 1;
+}
+
 REDEF_CMD(input_handle)
 {
        struct pane *p = ci->home;
-
+       struct display_data *dd = p->data;
+       static const char paste_seq[] = "\e[200~";
        wint_t c;
        int is_keycode;
        int have_escape = 0;
+       int i;
 
-       if (!(void*)p->data)
-               /* already closed */
-               return 0;
+       wait_for(dd);
        set_screen(p);
        while ((is_keycode = get_wch(&c)) != ERR) {
+               if (dd->suspended && c != KEY_MOUSE) {
+                       dd->suspended = False;
+                       doupdate();
+                       call_comm("event:free", p, &ns_resume);
+                       /* swallow the key */
+                       continue;
+               }
+               if (paste_recv(p, is_keycode, c))
+                       continue;
                if (c == KEY_MOUSE) {
                        MEVENT mev;
-                       while (getmouse(&mev) != ERR)
+                       paste_flush(p);
+                       while (getmouse(&mev) != ERR) {
+                               if (dd->paste_pending &&
+                                   mev.bstate == REPORT_MOUSE_POSITION) {
+                                       /* xcfe-terminal is a bit weird.
+                                        * It captures middle-press to
+                                        * sanitise the paste, but lets
+                                        * middle-release though. It comes
+                                        * here as REPORT_MOUSE_POSTION
+                                        * and we can use that to find the
+                                        * position of the paste.
+                                        * '6' is an unused button, and
+                                        * ensures lib-input doesn't expect
+                                        * matching press/release
+                                        */
+                                       call("Mouse-event", ci->home,
+                                            1, NULL, ":Paste",
+                                            6, NULL, NULL, mev.x, mev.y);
+                                       dd->paste_pending = 0;
+                               }
                                send_mouse(&mev, p);
-               } else if (have_escape) {
+                       }
+               } else if (c == (wint_t)paste_seq[have_escape]) {
+                       have_escape += 1;
+                       if (!paste_seq[have_escape]) {
+                               paste_start(p);
+                               have_escape = 0;
+                       }
+               } else if (have_escape == 1) {
                        send_key(is_keycode, c, 1, p);
                        have_escape = 0;
-               } else if (c == '\e')
-                       have_escape = 1;
-               else
+               } else if (have_escape) {
+                       send_key(OK, paste_seq[1], 1, p);
+                       for (i = 2; i < have_escape; i++)
+                               send_key(OK, paste_seq[i], 0, p);
+                       send_key(is_keycode, c, 0, p);
+                       have_escape = 0;
+               } else {
                        send_key(is_keycode, c, 0, p);
+               }
                /* Don't know what other code might have done,
                 * so re-set the screen
                 */
                set_screen(p);
        }
-       if (have_escape)
+       if (have_escape == 1)
                send_key(is_keycode, '\e', 0, p);
+       else if (have_escape > 1) {
+               send_key(OK, paste_seq[1], 1, p);
+               for (i = 2; i < have_escape; i++)
+                       send_key(OK, paste_seq[i], 0, p);
+       }
+       if (dd->paste_pending == 2) {
+               /* no mouse event to give postion, so treat as keyboard */
+               call("Keystroke", ci->home, 0, NULL, ":Paste");
+               dd->paste_pending = 0;
+       } else if (dd->paste_pending == 1) {
+               /* Wait for possible mouse-position update. */
+               dd->paste_pending = 2;
+               call_comm("event:timer", p, &input_handle, 200);
+       }
        return 1;
 }
 
 DEF_CMD(display_ncurses)
 {
        struct pane *p;
-       char *term;
+       struct pane *ed = pane_root(ci->focus);
+       const char *tty = ci->str;
+       const char *term = ci->str2;
 
-       term = pane_attr_get(ci->focus, "TERM");
-       if (!term)
-               term = getenv("TERM");
        if (!term)
                term = "xterm-256color";
 
-       p = ncurses_init(ci->focus, ci->str, term);
-       if (p) {
-               struct pane *p2 = call_ret(pane, "attach-x11selection", p);
-               if (p2)
-                       p = p2;
+       p = call_ret(pane, "attach-window-core", ed);
+       if (!p)
+               return Efail;
+
+       p = ncurses_init(p, tty, term);
+       if (p)
                return comm_call(ci->comm2, "callback:display", p);
-       }
+
        return Efail;
 }
 
@@ -1127,16 +1820,21 @@ void edlib_init(struct pane *ed safe)
                  "attach-display-ncurses");
 
        nc_map = key_alloc();
-       key_add(nc_map, "Display:refresh", &nc_refresh);
-       key_add(nc_map, "Display:close", &nc_close_display);
-       key_add(nc_map, "Display:set-noclose", &nc_set_noclose);
+       key_add(nc_map, "Window:refresh", &force_redraw);
+       key_add(nc_map, "Window:close", &nc_close_display);
+       key_add(nc_map, "Window:external-viewer", &nc_external_viewer);
        key_add(nc_map, "Close", &nc_close);
-       key_add(nc_map, "Free", &edlib_do_free);
-       key_add(nc_map, "pane-clear", &nc_clear);
-       key_add(nc_map, "text-size", &nc_text_size);
+       key_add(nc_map, "Draw:clear", &nc_clear);
+       key_add(nc_map, "Draw:text-size", &nc_text_size);
        key_add(nc_map, "Draw:text", &nc_draw_text);
+
+       key_add(nc_map, "Draw:image", &nc_draw_image);
+       key_add(nc_map, "Draw:image-size", &nc_image_size);
+
        key_add(nc_map, "Refresh:size", &nc_refresh_size);
        key_add(nc_map, "Refresh:postorder", &nc_refresh_post);
+       key_add(nc_map, "Paste:get", &nc_get_paste);
        key_add(nc_map, "all-displays", &nc_notify_display);
        key_add(nc_map, "Sig:Winch", &handle_winch);
+       key_add(nc_map, "Notify:Close", &nc_pane_close);
 }