2 * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * ncurses front end for edlib.
7 * There is currently only support for a single terminal window
8 * which provides a single pane.
10 * Rendering operations are:
11 * draw text with attributes at location
12 * erase with attributes in rectangle
20 #define _XOPEN_SOURCE_EXTENDED
21 #ifndef _DEFAULT_SOURCE
22 #define _DEFAULT_SOURCE
34 #include <sys/ioctl.h>
38 #include <wand/MagickWand.h>
40 // enums confuse sparse...
41 #define MagickBooleanType int
46 #define PANE_DATA_TYPE struct display_data
53 typedef char hash_t[MD5_DIGEST_SIZE*2+1];
57 #undef NCURSES_OK_ADDR
58 #define NCURSES_OK_ADDR(p) ((void*)0 != NCURSES_CAST(const void *, (p)))
67 struct col_hash *col_hash;
84 char *rs1, *rs2, *rs3, *clear;
90 /* Sometimes I get duplicate Display lines, but not consistently.
91 * To avoid these, record last, filter repeats.
94 char last_screen[MD5_DIGEST_SIZE*2+1];
95 char next_screen[MD5_DIGEST_SIZE*2+1];
96 /* The next event to generate when idle */
97 enum { DoNil, DoMouse, DoKey, DoCheck, DoClose} next_event;
101 int clears; /* counts of Draw:clear events */
104 #include "core-pane.h"
106 static SCREEN *current_screen;
107 static void ncurses_text(struct pane *p safe, struct pane *display safe,
108 wchar_t ch, int attr, int pair,
109 short x, short y, short cursor);
110 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home);
111 DEF_CMD(input_handle);
112 DEF_CMD(handle_winch);
113 static struct map *nc_map;
114 DEF_LOOKUP_CMD(ncurses_handle, nc_map);
116 static struct display_data *current_dd;
117 static void set_screen(struct pane *p)
119 struct display_data *dd;
120 extern void *_nc_globals[100];
122 static int index = -1, offset=0;
125 if (current_screen && index >= 0)
126 _nc_globals[index] = NULL;
127 current_screen = NULL;
134 if (dd->scr == current_screen)
139 for (i=0; i<100; i++)
140 if (_nc_globals[i] < (void*)stdscr &&
141 _nc_globals[i]+4*(sizeof(void*)) >= (void*)stdscr) {
142 /* This is _nc_windowlist */
144 offset = ((void*)stdscr) - _nc_globals[i];
149 current_screen = dd->scr;
151 _nc_globals[index] = ((void*)stdscr) - offset;
158 static bool parse_event(struct pane *p safe);
159 static bool prepare_recrep(struct pane *p safe)
161 struct display_data *dd = p->data;
164 name = getenv("EDLIB_RECORD");
166 dd->log = fopen(name, "w");
167 name = getenv("EDLIB_REPLAY");
169 dd->input = fopen(name, "r");
170 if (getenv("EDLIB_PAUSE"))
171 sleep(atoi(getenv("EDLIB_PAUSE")));
179 static void close_recrep(struct pane *p safe)
181 struct display_data *dd = p->data;
184 fprintf(dd->log, "Close %d\n", dd->clears);
189 static void record_key(struct pane *p safe, char *key safe)
191 struct display_data *dd = p->data;
196 if (!strchr(key, '"'))
198 else if (!strchr(key, '\''))
200 else if (!strchr(key, '/'))
204 fprintf(dd->log, "Key %c%s%c\n", q,key,q);
205 dd->last_cx = -2; /* Force next Display to be shown */
209 static void record_mouse(struct pane *p safe, char *key safe, int x, int y)
211 struct display_data *dd = p->data;
215 if (!strchr(key, '"'))
217 else if (!strchr(key, '\''))
219 else if (!strchr(key, '/'))
223 fprintf(dd->log, "Mouse %c%s%c %d,%d\n", q,key,q, x, y);
224 dd->last_cx = -2; /* Force next Display to be shown */
228 static void record_screen(struct pane *p safe)
230 struct display_data *dd = p->data;
231 struct md5_state ctx;
232 uint16_t buf[CCHARW_MAX+5];
233 char out[MD5_DIGEST_SIZE*2+1];
236 if (!dd->log && !(dd->input && dd->next_event == DoCheck))
240 for (r = 0; r < p->h; r++)
241 for (c = 0; c < p->w; c++) {
243 wchar_t wc[CCHARW_MAX+2];
248 mvwin_wch(stdscr, r, c, &cc);
249 getcchar(&cc, wc, &a, &color, NULL);
250 pair_content(color, &fg, &bg);
251 buf[0] = htole16(fg);
252 buf[1] = htole16(bg);
253 for (l = 0; l < CCHARW_MAX && wc[l]; l++)
254 buf[l+3] = htole16(wc[l]);
256 LOG("%d,%d %d:%d:%d:%d %lc", c,r,fg,bg,l,wc[0], wc[0] > ' ' ? wc[0] : '?');
257 md5_update(&ctx, (uint8_t*)buf,
258 (l+3) * sizeof(uint16_t));
260 md5_final_txt(&ctx, out);
261 if (strcmp(out, dd->last_screen) == 0 &&
262 p->cx == dd->last_cx && p->cy == dd->last_cy) {
263 /* No change - filter it */
265 } else if (dd->log) {
266 fprintf(dd->log, "Display %d,%d %s", p->w, p->h, out);
268 fprintf(dd->log, " %d,%d", p->cx, p->cy);
269 fprintf(dd->log, "\n");
271 strcpy(dd->last_screen, out);
272 dd->last_cx = p->cx; dd->last_cy = p->cy;
274 if (dd->input && dd->input_sleeping) {
275 char *delay = getenv("EDLIB_REPLAY_DELAY");
276 call_comm("event:free", p, &next_evt);
278 call_comm("event:timer", p, &next_evt, atoi(delay));
280 call_comm("event:on-idle", p, &next_evt);
284 static char *copy_quote(char *line safe, char *buf safe)
290 if (q != '"' && q != '\'' && q != '/')
292 while (*line != q && *line)
300 static char *get_coord(char *line safe, struct xy *co safe)
307 v = strtol(line, &ep, 10);
308 if (!ep || ep == line || *ep != ',')
312 v = strtol(line, &ep, 10);
313 if (!ep || ep == line || (*ep && *ep != ' ' && *ep != '\n'))
319 static char *get_hash(char *line safe, hash_t hash safe)
324 for (i = 0; i < MD5_DIGEST_SIZE*2 && *line; i++)
331 static bool parse_event(struct pane *p safe)
333 struct display_data *dd = p->data;
337 dd->next_event = DoNil;
339 fgets(line, sizeof(line)-1, dd->input) == NULL)
341 else if (strstarts(line, "Key ")) {
342 if (!copy_quote(line+4, dd->event_info))
344 dd->next_event = DoKey;
345 } else if (strstarts(line, "Mouse ")) {
346 char *f = copy_quote(line+6, dd->event_info);
349 f = get_coord(f, &dd->event_pos);
352 dd->next_event = DoMouse;
353 } else if (strstarts(line, "Display ")) {
354 char *f = get_coord(line+8, &dd->event_pos);
357 f = get_hash(f, dd->next_screen);
358 dd->next_event = DoCheck;
359 } else if (strstarts(line, "Close")) {
360 dd->next_event = DoClose;
362 LOG("parse %s", line);
364 dd->input_sleeping = 1;
365 if (dd->next_event != DoCheck) {
366 char *delay = getenv("EDLIB_REPLAY_DELAY");
368 call_comm("event:timer", p, &next_evt, atoi(delay));
370 call_comm("event:on-idle", p, &next_evt);
372 call_comm("event:timer", p, &next_evt, 10*1000);
378 struct pane *p = ci->home;
379 struct display_data *dd = p->data;
380 int button = 0, type = 0;
382 dd->input_sleeping = 0;
383 switch(dd->next_event) {
385 record_key(p, dd->event_info);
386 call("Keystroke", p, 0, NULL, dd->event_info);
389 record_mouse(p, dd->event_info, dd->event_pos.x,
391 if (strstr(dd->event_info, ":Press"))
393 else if (strstr(dd->event_info, ":Release"))
395 else if (strstr(dd->event_info, ":Motion"))
397 if (type == 1 || type == 2) {
398 char *e = dd->event_info + strlen(dd->event_info) - 1;
401 call("Mouse-event", p, button, NULL, dd->event_info,
403 dd->event_pos.x, dd->event_pos.y);
406 /* No point checking, just do a diff against new trace log. */
407 /* not; (strcmp(dd->next_screen, dd->last_screen) != 0) */
410 call("event:deactivate", p);
414 call_comm("event:read", p, &input_handle, 0);
415 call_comm("event:signal", p, &handle_winch, SIGWINCH);
422 static inline bool prepare_recrep(struct pane *p safe) {return False;}
423 static inline void record_key(struct pane *p safe, char *key) {}
424 static inline void record_mouse(struct pane *p safe, char *key safe,
426 static inline void record_screen(struct pane *p safe) {}
427 static inline void close_recrep(struct pane *p safe) {}
432 struct call_return *cr = container_of(ci->comm, struct call_return, c);
438 static void ncurses_end(struct pane *p safe);
440 DEF_CMD(nc_close_display)
442 /* If this is only display, then refuse to close this one */
443 struct call_return cr;
444 char *nc = pane_attr_get(ci->home, "no-close");
447 call("Message", ci->focus, 0, NULL, nc);
453 call_comm("editor:notify:all-displays", ci->focus, &cr.c);
455 /* Need to call ncurses_end() before we send a Notify:Close
456 * notification, else server exits too early
458 ncurses_end(ci->home);
461 call("Message", ci->focus, 0, NULL,
462 "Cannot close only window.");
466 static int nc_putc(int ch)
469 fputc(ch, current_dd->scr_file);
473 static char *fnormalize(struct pane *p safe, const char *str) safe
475 char *ret = strsave(p, str);
478 for (cp = ret ; cp && *cp ; cp++)
480 !strchr("/_-+=.,@#", *cp))
481 /* Don't like this char */
486 static void wait_for(struct display_data *dd safe)
488 struct pids **pp = &dd->pids;
491 struct pids *p = *pp;
492 if (waitpid(p->pid, NULL, WNOHANG) > 0) {
502 struct display_data *dd = ci->home->data;
505 dd->suspended = False;
506 set_screen(ci->home);
512 DEF_CMD(nc_external_viewer)
514 struct pane *p = ci->home;
515 struct display_data *dd = p->data;
516 char *disp = pane_attr_get(p, "DISPLAY");
517 char *disp_auth = pane_attr_get(p, "XAUTHORITY");
518 char *remote = pane_attr_get(p, "REMOTE_SESSION");
520 const char *path = ci->str;
530 switch (pid = fork()) {
534 setenv("DISPLAY", disp, 1);
536 setenv("XAUTHORITY", disp_auth, 1);
537 fd = open("/dev/null", O_RDWR);
545 execlp("xdg-open", "xdg-open", path, NULL);
547 default: /* parent */
548 pds = malloc(sizeof(*pds));
550 pds->next = dd->pids;
557 /* handle no-display case */
558 if (remote && strcmp(remote, "yes") == 0 &&
560 gethostname(buf, sizeof(buf)) == 0) {
561 struct addrinfo *res;
562 const struct addrinfo hints = {
563 .ai_flags = AI_CANONNAME,
565 if (getaddrinfo(buf, NULL, &hints, &res) == 0 &&
566 res && res->ai_canonname)
567 fqdn = strdup(res->ai_canonname);
572 ioctl(fileno(dd->scr_file), FIONREAD, &n);
574 n -= read(fileno(dd->scr_file), buf,
575 n <= (int)sizeof(buf) ? n : (int)sizeof(buf));
577 /* stay in raw mode */
581 /* Endwin doesn't seem to reset properly, at least on xfce-terminal.
585 tputs(dd->rs1, 1, nc_putc);
587 tputs(dd->rs2, 1, nc_putc);
589 tputs(dd->rs3, 1, nc_putc);
591 tputs(dd->clear, 1, nc_putc);
592 fflush(dd->scr_file);
594 fprintf(dd->scr_file, "# Consider copy-pasting following\r\n");
595 if (fqdn && path[0] == '/') {
596 /* File will not be local for the user, so help them copy it. */
597 const char *tmp = fnormalize(p, ci->str2 ?: "XXXXXX");
598 const char *fname = fnormalize(p, ci->str);
600 if (strcmp(fname, ci->str) != 0)
601 /* file name had unusuable chars, need to create safe name */
602 link(ci->str, fname);
603 fprintf(dd->scr_file, "f=`mktemp --tmpdir %s`; scp %s:%s $f ; ",
608 fprintf(dd->scr_file, "xdg-open %s\r\n", path);
609 fprintf(dd->scr_file, "# Press Enter to continue\r\n");
610 dd->suspended = True;
611 call_comm("event:timer", p, &ns_resume, 30*1000);
615 static void ncurses_stop(struct pane *p safe)
617 struct display_data *dd = p->data;
620 /* disable bracketed-paste */
621 fprintf(dd->scr_file, "\033[?2004l");
622 fflush(dd->scr_file);
625 free(buf_final(&dd->paste_buf));
627 free(dd->paste_latest);
628 dd->paste_latest = NULL;
632 tputs(dd->rs1, 1, nc_putc);
634 tputs(dd->rs2, 1, nc_putc);
636 tputs(dd->rs3, 1, nc_putc);
637 fflush(dd->scr_file);
640 static void ncurses_end(struct pane *p safe)
642 struct display_data *dd = p->data;
646 dd->did_close = True;
654 * hash table for colours and pairs
655 * key is r,g,b (0-1000) in 10bit fields,
656 * or fg,bg in 16 bit fields with bit 31 set
657 * content is colour number of colour pair number.
658 * We never delete entries, unless we delete everything.
665 #define COL_KEY(r,g,b) ((r<<20) | (g<<10) | b)
666 #define PAIR_KEY(fg, bg) ((1<<31) | (fg<<16) | bg)
667 #define hash_key(k) ((((k) * 0x61C88647) >> 20) & 0xff)
670 int next_col, next_pair;
671 struct chash *tbl[256];
674 static struct col_hash *safe hash_init(struct display_data *dd safe)
677 dd->col_hash = malloc(sizeof(*dd->col_hash));
678 memset(dd->col_hash, 0, sizeof(*dd->col_hash));
679 dd->col_hash->next_col = 16;
680 dd->col_hash->next_pair = 1;
685 static void hash_free(struct display_data *dd safe)
694 for (h = 0; h < 255; h++)
695 while ((c = ch->tbl[h]) != NULL) {
696 ch->tbl[h] = c->next;
703 static int find_col(struct display_data *dd safe, int rgb[])
705 if (0 /* dynamic colours */) {
706 struct col_hash *ch = hash_init(dd);
707 int k = COL_KEY(rgb[0], rgb[1], rgb[2]);
711 for (c = ch->tbl[h]; c; c = c->next)
714 c = malloc(sizeof(*c));
716 c->content = ch->next_col++;
717 c->next = ch->tbl[h];
719 init_color(c->content, rgb[0], rgb[1], rgb[2]);
722 /* If colour is grey, map to 1 of 26 for 0, 232 to 255, 15
723 * The 24 grey shades have bit values from 8 to 238, so the
724 * gap to white is a little bigger, but that probably doesn't
726 * Otherwise map to 6x6x6 rgb cube from 16
727 * Actual colours are biased bright, at 0,95,135,175,215,255
728 * with a 95 gap at bottom and 40 elsewhere.
729 * So we divide 5 and 2 half ranges, and merge bottom 2.
734 //printf("want %d,%d,%d\n", rgb[0], rgb[1], rgb[2]);
735 if (abs(rgb[0] - rgb[1]) < 10 &&
736 abs(rgb[1] - rgb[2]) < 10) {
737 /* grey - within 1% */
738 int v = (rgb[0] + rgb[1] + rgb[2]) / 3;
740 /* We divide the space in 24 ranges surrounding
741 * the grey values, and 2 half-ranges near black
742 * and white. So add half a range - 1000/50 -
743 * then divide by 1000/25 to get a number from 0 to 25.
745 v = (v + 1000/50) / (1000/25);
747 return 0; /* black */
749 return 15; /* white */
750 //printf(" grey %d\n", v + 231);
751 /* grey shades are from 232 to 255 inclusive */
754 for (h = 0; h < 3; h++) {
757 v = (v + 1000/12) / (1000/6);
758 /* v is from 0 to 6, we want up to 5
759 * with 0 and 1 merged
766 //printf(" color %d\n", c + 16);
771 static int to_pair(struct display_data *dd safe, int fg, int bg)
773 struct col_hash *ch = hash_init(dd);
774 int k = PAIR_KEY(fg, bg);
778 for (c = ch->tbl[h]; c; c = c->next)
781 c = malloc(sizeof(*c));
783 c->content = ch->next_pair++;
784 c->next = ch->tbl[h];
786 init_pair(c->content, fg, bg);
790 static int cvt_attrs(struct pane *p safe, struct pane *home safe,
791 const char *attrs, int *pairp safe)
793 struct display_data *dd = home->data;
798 int fg = COLOR_BLACK;
799 int bg = COLOR_WHITE+8;
802 while (p->parent != p &&(pan = pane_panel(p, NULL)) == NULL)
805 /* Get 'default colours for this pane - set at clear */
806 int at = getbkgd(panel_window(pan));
807 int pair = PAIR_NUMBER(at);
809 pair_content(pair, &dfg, &dbg);
816 foreach_attr(a, v, attrs, NULL) {
817 if (amatch(a, "inverse"))
819 else if (amatch(a, "noinverse"))
821 else if (amatch(a, "bold"))
823 else if (amatch(a, "nobold"))
825 else if (amatch(a, "underline"))
827 else if (amatch(a, "nounderline"))
828 attr &= ~A_UNDERLINE;
829 else if (amatch(a, "fg") && v) {
830 struct call_return cr =
831 call_ret(all, "colour:map", home,
832 0, NULL, aupdate(&col, v));
833 int rgb[3] = {cr.i, cr.i2, cr.x};
834 fg = find_col(dd, rgb);
835 } else if (amatch(a, "bg") && v) {
836 struct call_return cr =
837 call_ret(all, "colour:map", home,
838 0, NULL, aupdate(&col, v));
839 int rgb[3] = {cr.i, cr.i2, cr.x};
840 bg = find_col(dd, rgb);
844 if (fg != COLOR_BLACK || bg != COLOR_WHITE+8)
845 *pairp = to_pair(dd, fg, bg);
849 static int make_cursor(int attr)
851 return attr ^ A_UNDERLINE;
854 DEF_CMD(nc_notify_display)
856 struct display_data *dd = ci->home->data;
857 comm_call(ci->comm2, "callback:display", ci->home, dd->last_event);
861 DEF_CMD_CLOSED(nc_close)
863 struct pane *p = ci->home;
864 struct display_data *dd = p->data;
867 fclose(dd->scr_file);
871 DEF_CMD(nc_pane_close)
875 set_screen(ci->home);
876 while ((pan = panel_above(pan)) != NULL)
877 if (panel_userptr(pan) == ci->focus)
880 WINDOW *win = panel_window(pan);
883 pane_damaged(ci->home, DAMAGED_POSTORDER);
888 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home)
892 while ((pan = panel_above(pan)) != NULL)
893 if (panel_userptr(pan) == p)
899 pan = new_panel(newwin(p->h, p->w, 0, 0));
900 set_panel_userptr(pan, p);
901 pane_add_notify(home, p, "Notify:Close");
908 struct pane *p = ci->home;
909 struct display_data *dd = p->data;
912 /* default come from parent when clearing pane */
913 int attr = cvt_attrs(ci->focus->parent, p, ci->str, &pair);
919 panel = pane_panel(ci->focus, p);
922 win = panel_window(panel);
924 if (h != ci->focus->h || w != ci->focus->w) {
925 wresize(win, ci->focus->h, ci->focus->w);
926 replace_panel(panel, win);
931 wbkgrndset(win, &cc);
935 pane_damaged(p, DAMAGED_POSTORDER);
939 DEF_CMD(nc_text_size)
941 int max_space = ci->num;
944 const char *str = ci->str;
948 while (str[0] != 0) {
949 wint_t wc = get_utf8(&str, NULL);
957 if (size <= max_space)
958 max_bytes = str - ci->str;
960 return comm_call(ci->comm2, "callback:size", ci->focus,
961 max_bytes, NULL, NULL,
962 0, NULL, NULL, size, 1);
965 DEF_CMD(nc_draw_text)
967 struct pane *p = ci->home;
969 int attr = cvt_attrs(ci->focus, p, ci->str2, &pair);
970 int cursor_offset = ci->num;
971 short x = ci->x, y = ci->y;
972 const char *str = ci->str;
977 while (str[0] != 0) {
978 int precurs = str <= ci->str + cursor_offset;
979 wint_t wc = get_utf8(&str, NULL);
981 if (wc == WEOF || wc == WERR)
986 if (precurs && str > ci->str + cursor_offset)
987 ncurses_text(ci->focus, p, wc, attr, pair, x, y, 1);
989 ncurses_text(ci->focus, p, wc, attr, pair, x, y, 0);
992 if (str == ci->str + cursor_offset)
993 ncurses_text(ci->focus, p, ' ', 0, 0, x, y, 1);
994 pane_damaged(p, DAMAGED_POSTORDER);
1000 MagickWand *wd safe;
1003 struct pane *p safe;
1006 DEF_CB(nc_draw_image_cb)
1008 struct di_info *dii = container_of(ci->comm, struct di_info, c);
1009 struct display_data *dd = dii->p->data;
1013 switch (ci->key[0]) {
1014 case 'w': /* width */
1015 return MagickGetImageWidth(dii->wd);
1016 case 'h': /* height */
1017 return MagickGetImageHeight(dii->wd);
1018 case 's': /* scale */
1019 MagickResizeImage(dii->wd, ci->num, ci->num2, BoxFilter, 1.0);
1021 case 'c': /* crop or cursor */
1022 if (ci->key[1] != 'u') {
1031 /* FIXME this doesn't work because
1032 * render-line knows too much and gets it wrong.
1034 ncurses_text(ci->focus, dii->p, 'X', 0,
1037 (ci->y + dii->xo)/2, 1);
1040 case 'd': /* draw */
1041 if (dii->w <= 0 || dii->h <= 0)
1043 buf = malloc(dii->h * dii->w * 4);
1045 MagickExportImagePixels(dii->wd, dii->x, dii->y,
1047 "RGBA", CharPixel, buf);
1049 for (i = 0; i < dii->h; i+= 2) {
1050 static const wint_t hilo = 0x2580; /* L'▀' */
1051 static unsigned char blk[4] = "\0\0\0";
1052 for (j = 0; j < dii->w ; j+= 1) {
1053 unsigned char *p1 = buf + i*dii->w*4 + j*4;
1054 unsigned char *p2 = i + 1 < dii->h ?
1055 buf + (i+1)*dii->w*4 + j*4 : blk;
1056 int rgb1[3] = { p1[0]*1000/255, p1[1]*1000/255,
1058 int rgb2[3] = { p2[0]*1000/255, p2[1]*1000/255,
1060 int fg = find_col(dd, rgb1);
1061 int bg = find_col(dd, rgb2);
1063 if (p1[3] < 128 || p2[3] < 128) {
1067 struct pane *pn2 = ci->focus;
1068 PANEL *pan = pane_panel(pn2, NULL);
1070 while (!pan && pn2->parent != pn2) {
1072 pan = pane_panel(pn2, NULL);
1075 wgetbkgrnd(panel_window(pan), &cc);
1076 if (cc.ext_color == 0)
1077 /* default. This is light
1078 * gray rather then white,
1079 * but I think it is a good
1084 pair_content(cc.ext_color, &f, &b);
1091 ncurses_text(ci->focus, dii->p, hilo, 0,
1092 to_pair(dd, fg, bg),
1093 ci->num + dii->xo + j,
1094 (ci->num2 + dii->yo + i)/2,
1105 DEF_CMD(nc_draw_image)
1107 /* 'str' identifies the image. Options are:
1108 * file:filename - load file from fs
1109 * comm:command - run command collecting bytes
1110 * 'str2' and numbers are handled by Draw:scale-image.
1112 struct pane *p = ci->home;
1113 MagickBooleanType status;
1114 MagickWand *wd = NULL;
1119 if (strstarts(ci->str, "file:")) {
1120 wd = NewMagickWand();
1121 status = MagickReadImage(wd, ci->str + 5);
1122 if (status == MagickFalse) {
1123 DestroyMagickWand(wd);
1126 } else if (strstarts(ci->str, "comm:")) {
1127 struct call_return cr;
1128 wd = NewMagickWand();
1129 cr = call_ret(bytes, ci->str+5, ci->focus);
1131 DestroyMagickWand(wd);
1134 status = MagickReadImageBlob(wd, cr.s, cr.i);
1136 if (status == MagickFalse) {
1137 DestroyMagickWand(wd);
1145 MagickAutoOrientImage(wd);
1146 dii.c = nc_draw_image_cb;
1149 dii.w = dii.h = dii.x = dii.y = dii.xo = dii.yo = 0;
1150 call("Draw:scale-image", ci->focus,
1151 ci->num, NULL, NULL, ci->num2, NULL, ci->str2,
1152 ci->x, ci->y, &dii.c);
1154 DestroyMagickWand(wd);
1156 pane_damaged(ci->home, DAMAGED_POSTORDER);
1161 DEF_CMD(nc_image_size)
1163 MagickBooleanType status;
1169 if (strstarts(ci->str, "file:")) {
1170 wd = NewMagickWand();
1171 status = MagickReadImage(wd, ci->str + 5);
1172 if (status == MagickFalse) {
1173 DestroyMagickWand(wd);
1176 } else if (strstarts(ci->str, "comm:")) {
1177 struct call_return cr;
1178 wd = NewMagickWand();
1179 cr = call_ret(bytes, ci->str+5, ci->focus);
1181 DestroyMagickWand(wd);
1184 status = MagickReadImageBlob(wd, cr.s, cr.i);
1186 if (status == MagickFalse) {
1187 DestroyMagickWand(wd);
1193 MagickAutoOrientImage(wd);
1194 ih = MagickGetImageHeight(wd);
1195 iw = MagickGetImageWidth(wd);
1197 DestroyMagickWand(wd);
1198 comm_call(ci->comm2, "callback:size", ci->focus,
1199 0, NULL, NULL, 0, NULL, NULL,
1204 DEF_CMD(nc_refresh_size)
1206 struct pane *p = ci->home;
1209 getmaxyx(stdscr, p->h, p->w);
1214 DEF_CMD(nc_refresh_post)
1216 struct pane *p = ci->home;
1217 struct display_data *dd = p->data;
1226 /* Need to ensure stacking order and panel y,x position
1227 * is correct. FIXME it would be good if we could skip this
1230 pan = panel_above(NULL);
1233 p1 = (struct pane*) panel_userptr(pan);
1234 for (;(pan2 = panel_above(pan)) != NULL; pan = pan2) {
1235 struct pane *p2 = (struct pane*)panel_userptr(pan2);
1236 p1 = (struct pane*)panel_userptr(pan);
1240 if (p1->abs_z < p2->abs_z)
1242 if (p1->abs_z == p2->abs_z &&
1245 /* pan needs to be above pan2. All we can do is move it to
1246 * the top. Anything that needs to be above it will eventually
1250 /* Now the panel below pan might need to be over pan2 too... */
1251 pan = panel_below(pan2);
1256 /* As we need to crop pane against their parents, we cannot simply
1257 * use update_panels(). Instead we copy each to stdscr and refresh
1260 for (pan = NULL; (pan = panel_above(pan)) != NULL; ) {
1262 struct xy src, dest, destend;
1265 p1 = (void*)panel_userptr(pan);
1268 dest = pane_mapxy(p1, p, 0, 0, True);
1269 destend = pane_mapxy(p1, p, p1->w, p1->h, True);
1270 src = pane_mapxy(p1, p, 0, 0, False);
1271 src.x = dest.x - src.x;
1272 src.y = dest.y - src.y;
1273 win = panel_window(pan);
1274 getmaxyx(win, h, w);
1275 /* guard again accessing beyond boundary of win */
1276 if (destend.x > dest.x + (w - src.x))
1277 destend.x = dest.x + (w - src.x);
1278 if (destend.y > dest.y + (h - src.y))
1279 destend.y = dest.y - (h - src.y);
1280 copywin(win, stdscr, src.y, src.x,
1281 dest.y, dest.x, destend.y-1, destend.x-1, 0);
1283 /* place the cursor */
1286 while (p1 != p && (pan = pane_panel(p1, NULL)) == NULL)
1288 if (pan && p1->cx >= 0) {
1289 struct xy curs = pane_mapxy(p1, p, p1->cx, p1->cy, False);
1290 wmove(stdscr, curs.y, curs.x);
1291 } else if (p->cx >= 0)
1292 wmove(stdscr, p->cy, p->cx);
1294 record_screen(ci->home);
1298 static void ncurses_start(struct pane *p safe)
1300 struct display_data *dd = p->data;
1304 use_default_colors();
1310 intrflush(stdscr, FALSE);
1311 keypad(stdscr, TRUE);
1312 mousemask(BUTTON1_PRESSED | BUTTON1_RELEASED |
1313 BUTTON2_PRESSED | BUTTON2_RELEASED |
1314 BUTTON3_PRESSED | BUTTON3_RELEASED |
1315 BUTTON4_PRESSED | BUTTON4_RELEASED |
1316 BUTTON5_PRESSED | BUTTON5_RELEASED |
1317 BUTTON_CTRL | BUTTON_SHIFT | BUTTON_ALT |
1318 REPORT_MOUSE_POSITION, NULL);
1321 /* Enable bracketed-paste */
1322 fprintf(dd->scr_file, "\033[?2004h");
1323 fflush(dd->scr_file);
1326 getmaxyx(stdscr, rows, cols);
1327 pane_resize(p, 0, 0, cols, rows);
1330 static struct pane *ncurses_init(struct pane *ed safe,
1331 const char *tty, const char *term)
1335 struct display_data *dd;
1340 if (tty && strcmp(tty, "-") != 0)
1341 f = fopen(tty, "r+");
1343 f = fdopen(1, "r+");
1346 scr = newterm(term, f, f);
1350 p = pane_register(ed, 1, &ncurses_handle.c);
1356 dd->is_xterm = (term && strstarts(term, "xterm"));
1358 attr_set_str(&p->attrs, "Display:pixels", "1x2");
1364 area = dd->attr_buf;
1365 dd->rs1 = tgetstr("rs1", &area);
1367 dd->rs1 = tgetstr("is1", &area);
1368 dd->rs2 = tgetstr("rs2", &area);
1370 dd->rs2 = tgetstr("is2", &area);
1371 dd->rs3 = tgetstr("rs3", &area);
1373 dd->rs3 = tgetstr("is3", &area);
1374 dd->clear = tgetstr("clear", &area);
1376 call("editor:request:all-displays", p);
1377 if (!prepare_recrep(p)) {
1378 call_comm("event:read", p, &input_handle, fileno(f));
1379 if (!tty || strcmp(tty, "-") == 0)
1380 call_comm("event:signal", p, &handle_winch, SIGWINCH);
1385 REDEF_CMD(handle_winch)
1387 struct pane *p = ci->home;
1388 struct display_data *dd = p->data;
1389 struct winsize size;
1390 ioctl(fileno(dd->scr_file), TIOCGWINSZ, &size);
1392 resize_term(size.ws_row, size.ws_col);
1394 pane_resize(p, 0, 0, size.ws_row, size.ws_col);
1398 DEF_CMD(force_redraw)
1400 struct pane *p = ci->home;
1404 /* full reset, as mosh sometimes gets confused */
1409 pane_damaged(p, DAMAGED_POSTORDER);
1413 static void ncurses_text(struct pane *p safe, struct pane *display safe,
1414 wchar_t ch, int attr, int pair,
1415 short x, short y, short cursor)
1424 set_screen(display);
1426 if (pane_has_focus(p->z < 0 ? p->parent : p)) {
1427 /* Cursor is in-focus */
1428 struct xy curs = pane_mapxy(p, display, x, y, False);
1429 display->cx = curs.x;
1430 display->cy = curs.y;
1432 /* Cursor here, but not focus */
1433 attr = make_cursor(attr);
1436 cc.ext_color = pair;
1440 pan = pane_panel(p2, NULL);
1441 while (!pan && p2->parent != p2) {
1443 pan = pane_panel(p2, NULL);
1446 struct xy xy = pane_mapxy(p, p2, x, y, False);
1447 mvwadd_wch(panel_window(pan), xy.y, xy.x, &cc);
1451 static struct namelist {
1455 {KEY_DOWN, ":Down"},
1457 {KEY_LEFT, ":Left"},
1458 {KEY_RIGHT, ":Right"},
1459 {KEY_HOME, ":Home"},
1460 {KEY_BACKSPACE, ":Backspace"},
1461 {KEY_DL, ":DelLine"},
1462 {KEY_IL, ":InsLine"},
1465 {KEY_ENTER, ":Enter"},
1468 {KEY_NPAGE, ":Next"},
1469 {KEY_PPAGE, ":Prior"},
1471 {KEY_SDC, ":S:Del"},
1472 {KEY_SDL, ":S:DelLine"},
1473 {KEY_SEND, ":S:End"},
1474 {KEY_SHOME, ":S:Home"},
1475 {KEY_SLEFT, ":S:Left"},
1476 {KEY_SRIGHT, ":S:Right"},
1477 {KEY_BTAB, ":S:Tab"},
1481 { 0616, ":S:Prior"},
1483 { 01041, ":S:Home"},
1485 { 01066, ":S:Prior"},
1486 { 01015, ":S:Next"},
1488 { 01027, ":A:S:Home"},
1489 { 01022, ":A:S:End"},
1490 { 01046, ":A:S:Prior"},
1491 { 01047, ":A:S:Next"}, // ??
1493 { 01052, ":A:Prior"},
1494 { 01045, ":A:Next"},
1495 { 01026, ":A:Home"},
1498 { 01014, ":A:Down"},
1499 { 01040, ":A:Left"},
1500 { 01057, ":A:Right"},
1525 { 01114, ":Focus-in"},
1526 { 01115, ":Focus-out"},
1533 {'\177', ":Delete"},
1538 static char *find_name (struct namelist *l safe, wint_t c)
1541 for (i = 0; l[i].name; i++)
1547 static void send_key(int keytype, wint_t c, int alt, struct pane *p safe)
1549 struct display_data *dd = p->data;
1551 char buf[100];/* FIXME */
1553 char *a = alt ? ":A" : "";
1555 if (keytype == KEY_CODE_YES) {
1556 n = find_name(key_names, c);
1558 LOG("Unknown ncurses key 0o%o", c);
1559 sprintf(buf, "%sNcurs-%o", a, c);
1560 } else if (strstarts(n, ":Focus-"))
1561 /* Ignore focus changes for now */
1564 strcat(strcpy(buf, a), n);
1566 n = find_name(char_names, c);
1568 sprintf(buf, "%s%s", a, n);
1569 else if (c < ' ' || c == 0x7f)
1570 sprintf(buf, "%s:C-%c",
1573 sprintf(buf, "%s-%s", a, put_utf8(t, c));
1576 dd->last_event = time(NULL);
1579 call("Keystroke", p, 0, NULL, buf);
1583 static void do_send_mouse(struct pane *p safe, int x, int y, char *cmd safe,
1584 int button, char *mod, int type)
1587 struct display_data *dd = p->data;
1589 record_mouse(p, cmd, x, y);
1590 ret = call("Mouse-event", p, button, NULL, cmd, type, NULL, mod, x, y);
1591 if (type == 1 && !dd->report_position) {
1593 fprintf(dd->scr_file, "\033[?1002h");
1594 fflush(dd->scr_file);
1596 dd->report_position = 1;
1597 } else if (type == 3 && ret <= 0) {
1599 fprintf(dd->scr_file, "\033[?1002l");
1600 fflush(dd->scr_file);
1602 dd->report_position = 0;
1606 static void send_mouse(MEVENT *mev safe, struct pane *p safe)
1608 struct display_data *dd = p->data;
1614 /* MEVENT has lots of bits. We want a few numbers */
1615 for (b = 1 ; b <= (NCURSES_MOUSE_VERSION <= 1 ? 3 : 5); b++) {
1616 mmask_t s = mev->bstate;
1621 if (s & BUTTON_SHIFT) modf |= 1;
1622 if (s & BUTTON_CTRL) modf |= 2;
1623 if (s & BUTTON_ALT) modf |= 4;
1625 case 0: mod = ""; break;
1626 case 1: mod = ":S"; break;
1627 case 2: mod = ":C"; break;
1628 case 3: mod = ":C:S"; break;
1629 case 4: mod = ":A"; break;
1630 case 5: mod = ":A:S"; break;
1631 case 6: mod = ":A:C"; break;
1632 case 7: mod = ":A:C:S"; break;
1634 if (BUTTON_PRESS(s, b))
1635 action = "%s:Press-%d";
1636 else if (BUTTON_RELEASE(s, b)) {
1637 action = "%s:Release-%d";
1638 /* Modifiers only reported on button Press */
1642 snprintf(buf, sizeof(buf), action, mod, b);
1643 dd->last_event = time(NULL);
1644 do_send_mouse(p, x, y, buf, b, mod, BUTTON_PRESS(s,b) ? 1 : 2);
1646 if ((mev->bstate & REPORT_MOUSE_POSITION) &&
1647 dd->report_position)
1648 /* Motion doesn't update last_event */
1649 do_send_mouse(p, x, y, ":Motion", 0, "", 3);
1652 static void paste_start(struct pane *home safe)
1654 struct display_data *dd = home->data;
1656 dd->paste_start = time(NULL);
1657 buf_init(&dd->paste_buf);
1660 static void paste_flush(struct pane *home safe)
1662 struct display_data *dd = home->data;
1664 if (!dd->paste_start)
1666 free(dd->paste_latest);
1667 dd->paste_latest = buf_final(&dd->paste_buf);
1668 if (dd->paste_buf.len > 0)
1669 dd->paste_pending = 1;
1670 dd->paste_start = 0;
1673 static bool paste_recv(struct pane *home safe, int is_keycode, wint_t ch)
1675 struct display_data *dd = home->data;
1677 if (dd->paste_start == 0)
1680 if (dd->paste_start < now || dd->paste_start > now + 2 ||
1681 is_keycode != OK || ch == KEY_MOUSE) {
1687 /* I really don't want carriage-returns... */
1689 buf_append(&dd->paste_buf, ch);
1690 if (ch == '~' && dd->paste_buf.len >= 6 &&
1692 buf_final(&dd->paste_buf) + dd->paste_buf.len - 6) == 0) {
1693 dd->paste_buf.len -= 6;
1699 DEF_CMD(nc_get_paste)
1701 struct display_data *dd = ci->home->data;
1703 comm_call(ci->comm2, "cb", ci->focus,
1704 dd->paste_start, NULL, dd->paste_latest);
1708 REDEF_CMD(input_handle)
1710 struct pane *p = ci->home;
1711 struct display_data *dd = p->data;
1712 static const char paste_seq[] = "\e[200~";
1715 int have_escape = 0;
1720 while ((is_keycode = get_wch(&c)) != ERR) {
1721 if (dd->suspended && c != KEY_MOUSE) {
1722 dd->suspended = False;
1724 call_comm("event:free", p, &ns_resume);
1725 /* swallow the key */
1728 if (paste_recv(p, is_keycode, c))
1730 if (c == KEY_MOUSE) {
1733 while (getmouse(&mev) != ERR) {
1734 if (dd->paste_pending &&
1735 mev.bstate == REPORT_MOUSE_POSITION) {
1736 /* xcfe-terminal is a bit weird.
1737 * It captures middle-press to
1738 * sanitise the paste, but lets
1739 * middle-release though. It comes
1740 * here as REPORT_MOUSE_POSTION
1741 * and we can use that to find the
1742 * position of the paste.
1743 * '6' is an unused button, and
1744 * ensures lib-input doesn't expect
1745 * matching press/release
1747 call("Mouse-event", ci->home,
1749 6, NULL, NULL, mev.x, mev.y);
1750 dd->paste_pending = 0;
1752 send_mouse(&mev, p);
1754 } else if (c == (wint_t)paste_seq[have_escape]) {
1756 if (!paste_seq[have_escape]) {
1760 } else if (have_escape == 1) {
1761 send_key(is_keycode, c, 1, p);
1763 } else if (have_escape) {
1764 send_key(OK, paste_seq[1], 1, p);
1765 for (i = 2; i < have_escape; i++)
1766 send_key(OK, paste_seq[i], 0, p);
1767 send_key(is_keycode, c, 0, p);
1770 send_key(is_keycode, c, 0, p);
1772 /* Don't know what other code might have done,
1773 * so re-set the screen
1777 if (have_escape == 1)
1778 send_key(is_keycode, '\e', 0, p);
1779 else if (have_escape > 1) {
1780 send_key(OK, paste_seq[1], 1, p);
1781 for (i = 2; i < have_escape; i++)
1782 send_key(OK, paste_seq[i], 0, p);
1784 if (dd->paste_pending == 2) {
1785 /* no mouse event to give postion, so treat as keyboard */
1786 call("Keystroke", ci->home, 0, NULL, ":Paste");
1787 dd->paste_pending = 0;
1788 } else if (dd->paste_pending == 1) {
1789 /* Wait for possible mouse-position update. */
1790 dd->paste_pending = 2;
1791 call_comm("event:timer", p, &input_handle, 200);
1796 DEF_CMD(display_ncurses)
1799 struct pane *ed = pane_root(ci->focus);
1800 const char *tty = ci->str;
1801 const char *term = ci->str2;
1804 term = "xterm-256color";
1806 p = call_ret(pane, "attach-window-core", ed);
1810 p = ncurses_init(p, tty, term);
1812 return comm_call(ci->comm2, "callback:display", p);
1817 void edlib_init(struct pane *ed safe)
1819 call_comm("global-set-command", ed, &display_ncurses, 0, NULL,
1820 "attach-display-ncurses");
1822 nc_map = key_alloc();
1823 key_add(nc_map, "Window:refresh", &force_redraw);
1824 key_add(nc_map, "Window:close", &nc_close_display);
1825 key_add(nc_map, "Window:external-viewer", &nc_external_viewer);
1826 key_add(nc_map, "Close", &nc_close);
1827 key_add(nc_map, "Draw:clear", &nc_clear);
1828 key_add(nc_map, "Draw:text-size", &nc_text_size);
1829 key_add(nc_map, "Draw:text", &nc_draw_text);
1831 key_add(nc_map, "Draw:image", &nc_draw_image);
1832 key_add(nc_map, "Draw:image-size", &nc_image_size);
1834 key_add(nc_map, "Refresh:size", &nc_refresh_size);
1835 key_add(nc_map, "Refresh:postorder", &nc_refresh_post);
1836 key_add(nc_map, "Paste:get", &nc_get_paste);
1837 key_add(nc_map, "all-displays", &nc_notify_display);
1838 key_add(nc_map, "Sig:Winch", &handle_winch);
1839 key_add(nc_map, "Notify:Close", &nc_pane_close);