/*
- * 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 <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
SCREEN *scr;
FILE *scr_file;
int is_xterm;
- 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_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;
return;
}
dd = p->data;
+ current_dd = dd;
if (!dd)
return;
if (dd->scr == current_screen)
#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;
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)
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);
}
}
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)
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;
cchar_t cc;
wchar_t wc[CCHARW_MAX+2];
attr_t a;
- short color;
+ short color, fg, bg;
int l;
- int x,y,w,h;
- PANEL *pan = NULL;
-
- while ((pan = panel_below(pan)) != NULL) {
- getbegyx(panel_window(pan), y, x);
- getmaxyx(panel_window(pan), h, w);
- if (r < y || r >= y + h)
- continue;
- if (c < x || c >= x + w)
- continue;
- break;
- }
- if (!pan)
- continue;
- mvwin_wch(panel_window(pan), r-y, c-x, &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 (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;
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];
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)
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);
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) {}
static inline void close_recrep(struct pane *p safe) {}
#endif
-DEF_CMD(cnt_disp)
+DEF_CB(cnt_disp)
{
struct call_return *cr = container_of(ci->comm, struct call_return, c);
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);
}
/*
return c->content;
}
-
static int cvt_attrs(struct pane *p safe, struct pane *home safe,
- const char *attrs)
+ 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;
set_screen(home);
- do {
+ while (p->parent != p &&(pan = pane_panel(p, NULL)) == NULL)
p = p->parent;
- } while (p->parent != p &&(pan = pane_panel(p, NULL)) == NULL);
if (pan) {
/* Get 'default colours for this pane - set at clear */
int at = getbkgd(panel_window(pan));
if (dbg >= 0)
bg = dbg;
}
- 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) {
+
+ 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;
}
+ free(col);
if (fg != COLOR_BLACK || bg != COLOR_WHITE+8)
- attr |= COLOR_PAIR(to_pair(dd, fg, bg));
+ *pairp = to_pair(dd, fg, bg);
return attr;
}
{
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;
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;
- struct xy xy;
while ((pan = panel_above(pan)) != NULL)
if (panel_userptr(pan) == p)
if (!home)
return pan;
- xy = pane_mapxy(p, home, 0, 0);
-
- pan = new_panel(newwin(p->h, p->w, xy.y, xy.x));
+ pan = new_panel(newwin(p->h, p->w, 0, 0));
set_panel_userptr(pan, p);
pane_add_notify(home, p, "Notify:Close");
DEF_CMD(nc_clear)
{
struct pane *p = ci->home;
- int attr = cvt_attrs(ci->focus, 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;
wresize(win, ci->focus->h, ci->focus->w);
replace_panel(panel, win);
}
- wbkgdset(win, attr);
+ cc.attr = attr;
+ cc.ext_color = pair;
+ cc.chars[0] = ' ';
+ wbkgrndset(win, &cc);
werase(win);
+ dd->clears += 1;
pane_damaged(p, DAMAGED_POSTORDER);
return 1;
while (str[0] != 0) {
wint_t wc = get_utf8(&str, NULL);
int width;
- if (wc == WEOF || wc == WERR)
+ if (wc >= WERR)
break;
width = wcwidth(wc);
if (width < 0)
DEF_CMD(nc_draw_text)
{
struct pane *p = ci->home;
- int attr = cvt_attrs(ci->focus, p, ci->str2);
+ int pair = 0;
+ int attr = cvt_attrs(ci->focus, p, ci->str2, &pair);
int cursor_offset = ci->num;
short x = ci->x, y = ci->y;
const char *str = ci->str;
if (width < 0)
break;
if (precurs && str > ci->str + cursor_offset)
- ncurses_text(ci->focus, p, wc, attr, x, y, 1);
+ ncurses_text(ci->focus, p, wc, attr, pair, x, y, 1);
else
- ncurses_text(ci->focus, p, wc, attr, x, y, 0);
+ ncurses_text(ci->focus, p, wc, attr, pair, x, y, 0);
x += width;
}
if (str == ci->str + cursor_offset)
- ncurses_text(ci->focus, p, ' ', 0, x, y, 1);
+ 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;
DEF_CMD(nc_refresh_post)
{
struct pane *p = ci->home;
+ struct display_data *dd = p->data;
struct pane *p1;
PANEL *pan, *pan2;
- struct xy xy;
- int ox, oy;
+
+ if (dd->suspended)
+ return 1;
set_screen(p);
if (!pan)
return 1;
p1 = (struct pane*) panel_userptr(pan);
- if (p1) {
- xy = pane_mapxy(p1, p, 0 ,0);
- getbegyx(panel_window(pan), oy, ox);
- if (ox != xy.x || oy != xy.y)
- move_panel(pan, xy.y, xy.x);
- }
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;
- xy = pane_mapxy(p2, p, 0 ,0);
- getbegyx(panel_window(pan2), oy, ox);
- if (ox != xy.x || oy != xy.y)
- move_panel(pan2, xy.y, xy.x);
-
- if (p1->abs_z <= p2->abs_z)
+ 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
pan2 = pan;
}
- update_panels();
+ /* 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_leaf(p);
+ p1 = pane_focus(p);
pan = NULL;
while (p1 != p && (pan = pane_panel(p1, NULL)) == NULL)
p1 = p1->parent;
- if (pan) {
- wmove(panel_window(pan), p1->cy, p1->cx);
- wnoutrefresh(panel_window(pan));
- }
- doupdate();
+ 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(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;
- int rows, cols;
+ char *area;
FILE *f;
set_screen(NULL);
- if (tty)
+ if (tty && strcmp(tty, "-") != 0)
f = fopen(tty, "r+");
else
f = fdopen(1, "r+");
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->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, 1, &ncurses_handle.c, dd);
- if (!p) {
- unalloc(dd, pane);
- return NULL;
- }
set_screen(p);
- 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(10);
+ ncurses_start(p);
- getmaxyx(stdscr, rows, cols);
- pane_resize(p, 0, 0, cols, rows);
+ 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);
}
return p;
struct pane *p = ci->home;
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)
{
PANEL *pan;
struct pane *p2;
if (x < 0 || y < 0)
return;
- if (cursor) {
- p2 = p;
- cursor = 2;
- while (p2->parent != p2 && p2 != display) {
- if (p2->parent->focus != p2 && p2->z >= 0)
- cursor = 1;
- p2 = p2->parent;
- }
- }
set_screen(display);
- if (cursor == 2) {
- /* Cursor is in-focus */
- struct xy curs = pane_mapxy(p, display, x, y);
- display->cx = curs.x;
- display->cy = curs.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;
p2 = p;
pan = pane_panel(p2, NULL);
}
if (pan) {
- struct xy xy = pane_mapxy(p, p2, x, y);
+ struct xy xy = pane_mapxy(p, p2, x, y, False);
mvwadd_wch(panel_window(pan), xy.y, xy.x, &cc);
}
}
{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"},
{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"},
{ 00436, ":S:F10"},
{ 00437, ":S:F11"},
{ 00440, ":S:F12"},
+ { 01114, ":Focus-in"},
+ { 01115, ":Focus-out"},
{0, NULL}
}, char_names[] = {
{'\e', ":ESC"},
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 t[5];
- char *m = meta ? ":M" : "";
+ 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-%s", m, put_utf8(t, 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,
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);
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";
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;
}
"attach-display-ncurses");
nc_map = key_alloc();
- key_add(nc_map, "Display:refresh", &force_redraw);
- 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);