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);
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);
998 DEF_CMD(nc_draw_image)
1000 /* 'str' identifies the image. Options are:
1001 * file:filename - load file from fs
1002 * comm:command - run command collecting bytes
1003 * 'num' is '16' if image should be stretched to fill pane
1004 * Otherwise it is the 'or' of
1005 * 0,1,2 for left/middle/right in x direction
1006 * 0,4,8 for top/middle/bottom in y direction
1007 * only one of these can be used as image will fill pane
1008 * in other direction.
1009 * If 'x' and 'y' are both positive, draw cursor box at
1010 * p->cx, p->cy of a size so that 'x' will fit across and
1011 * 'y' will fit down.
1013 struct pane *p = ci->home;
1014 struct display_data *dd = p->data;
1016 bool stretch = ci->num & 16;
1018 int w = ci->focus->w, h = ci->focus->h * 2;
1019 int cx = -1, cy = -1;
1020 MagickBooleanType status;
1027 if (strstarts(ci->str, "file:")) {
1028 wd = NewMagickWand();
1029 status = MagickReadImage(wd, ci->str + 5);
1030 if (status == MagickFalse) {
1031 DestroyMagickWand(wd);
1034 } else if (strstarts(ci->str, "comm:")) {
1035 struct call_return cr;
1036 wd = NewMagickWand();
1037 cr = call_ret(bytes, ci->str+5, ci->focus, 0, NULL, ci->str2);
1039 DestroyMagickWand(wd);
1042 status = MagickReadImageBlob(wd, cr.s, cr.i);
1044 if (status == MagickFalse) {
1045 DestroyMagickWand(wd);
1051 MagickAutoOrientImage(wd);
1053 int ih = MagickGetImageHeight(wd);
1054 int iw = MagickGetImageWidth(wd);
1056 if (iw <= 0 || iw <= 0) {
1057 DestroyMagickWand(wd);
1060 if (iw * h > ih * w) {
1061 /* Image is wider than space, use less height */
1063 switch(pos & (8+4)) {
1064 case 4: /* center */
1065 y = (h - ih) / 2; break;
1066 case 8: /* bottom */
1069 /* Keep 'h' even! */
1072 /* image is too tall, use less width */
1074 switch (pos & (1+2)) {
1075 case 1: /* center */
1076 x = (w - iw) / 2; break;
1083 MagickAdaptiveResizeImage(wd, w, h);
1084 buf = malloc(h * w * 4);
1085 MagickExportImagePixels(wd, 0, 0, w, h, "RGBA", CharPixel, buf);
1087 if (ci->x > 0 && ci->y > 0 && ci->focus->cx >= 0) {
1088 /* We want a cursor */
1089 cx = x + ci->focus->cx;
1090 cy = y + ci->focus->cy;
1092 for (i = 0; i < h; i+= 2) {
1093 static const wint_t hilo = 0x2580; /* L'▀' */
1094 for (j = 0; j < w ; j+= 1) {
1095 unsigned char *p1 = buf + i*w*4 + j*4;
1096 unsigned char *p2 = buf + (i+1)*w*4 + j*4;
1097 int rgb1[3] = { p1[0]*1000/255, p1[1]*1000/255, p1[2]*1000/255 };
1098 int rgb2[3] = { p2[0]*1000/255, p2[1]*1000/255, p2[2]*1000/255 };
1099 int fg = find_col(dd, rgb1);
1100 int bg = find_col(dd, rgb2);
1102 if (p1[3] < 128 || p2[3] < 128) {
1106 struct pane *pn2 = ci->focus;
1107 PANEL *pan = pane_panel(pn2, NULL);
1109 while (!pan && pn2->parent != pn2) {
1111 pan = pane_panel(pn2, NULL);
1114 wgetbkgrnd(panel_window(pan), &cc);
1115 if (cc.ext_color == 0)
1116 /* default. This is light
1117 * gray rather then white,
1118 * but I think it is a good
1123 pair_content(cc.ext_color, &f, &b);
1130 /* FIXME this doesn't work because
1131 * render-line knows too much and gets it wrong.
1133 if (cx == x+j && cy == y + (i/2))
1134 ncurses_text(ci->focus, p, 'X', 0,
1138 ncurses_text(ci->focus, p, hilo, 0,
1139 to_pair(dd, fg, bg),
1146 DestroyMagickWand(wd);
1148 pane_damaged(ci->home, DAMAGED_POSTORDER);
1153 DEF_CMD(nc_image_size)
1155 MagickBooleanType status;
1161 if (strstarts(ci->str, "file:")) {
1162 wd = NewMagickWand();
1163 status = MagickReadImage(wd, ci->str + 5);
1164 if (status == MagickFalse) {
1165 DestroyMagickWand(wd);
1168 } else if (strstarts(ci->str, "comm:")) {
1169 struct call_return cr;
1170 wd = NewMagickWand();
1171 cr = call_ret(bytes, ci->str+5, ci->focus, 0, NULL, ci->str2);
1173 DestroyMagickWand(wd);
1176 status = MagickReadImageBlob(wd, cr.s, cr.i);
1178 if (status == MagickFalse) {
1179 DestroyMagickWand(wd);
1185 MagickAutoOrientImage(wd);
1186 ih = MagickGetImageHeight(wd);
1187 iw = MagickGetImageWidth(wd);
1189 DestroyMagickWand(wd);
1190 comm_call(ci->comm2, "callback:size", ci->focus,
1191 0, NULL, NULL, 0, NULL, NULL,
1196 DEF_CMD(nc_refresh_size)
1198 struct pane *p = ci->home;
1201 getmaxyx(stdscr, p->h, p->w);
1206 DEF_CMD(nc_refresh_post)
1208 struct pane *p = ci->home;
1209 struct display_data *dd = p->data;
1218 /* Need to ensure stacking order and panel y,x position
1219 * is correct. FIXME it would be good if we could skip this
1222 pan = panel_above(NULL);
1225 p1 = (struct pane*) panel_userptr(pan);
1226 for (;(pan2 = panel_above(pan)) != NULL; pan = pan2) {
1227 struct pane *p2 = (struct pane*)panel_userptr(pan2);
1228 p1 = (struct pane*)panel_userptr(pan);
1232 if (p1->abs_z < p2->abs_z)
1234 if (p1->abs_z == p2->abs_z &&
1237 /* pan needs to be above pan2. All we can do is move it to
1238 * the top. Anything that needs to be above it will eventually
1242 /* Now the panel below pan might need to be over pan2 too... */
1243 pan = panel_below(pan2);
1248 /* As we need to crop pane against their parents, we cannot simply
1249 * use update_panels(). Instead we copy each to stdscr and refresh
1252 for (pan = NULL; (pan = panel_above(pan)) != NULL; ) {
1254 struct xy src, dest, destend;
1257 p1 = (void*)panel_userptr(pan);
1260 dest = pane_mapxy(p1, p, 0, 0, True);
1261 destend = pane_mapxy(p1, p, p1->w, p1->h, True);
1262 src = pane_mapxy(p1, p, 0, 0, False);
1263 src.x = dest.x - src.x;
1264 src.y = dest.y - src.y;
1265 win = panel_window(pan);
1266 getmaxyx(win, h, w);
1267 /* guard again accessing beyond boundary of win */
1268 if (destend.x > dest.x + (w - src.x))
1269 destend.x = dest.x + (w - src.x);
1270 if (destend.y > dest.y + (h - src.y))
1271 destend.y = dest.y - (h - src.y);
1272 copywin(win, stdscr, src.y, src.x,
1273 dest.y, dest.x, destend.y-1, destend.x-1, 0);
1275 /* place the cursor */
1278 while (p1 != p && (pan = pane_panel(p1, NULL)) == NULL)
1280 if (pan && p1->cx >= 0) {
1281 struct xy curs = pane_mapxy(p1, p, p1->cx, p1->cy, False);
1282 wmove(stdscr, curs.y, curs.x);
1283 } else if (p->cx >= 0)
1284 wmove(stdscr, p->cy, p->cx);
1286 record_screen(ci->home);
1290 static void ncurses_start(struct pane *p safe)
1292 struct display_data *dd = p->data;
1296 use_default_colors();
1302 intrflush(stdscr, FALSE);
1303 keypad(stdscr, TRUE);
1304 mousemask(BUTTON1_PRESSED | BUTTON1_RELEASED |
1305 BUTTON2_PRESSED | BUTTON2_RELEASED |
1306 BUTTON3_PRESSED | BUTTON3_RELEASED |
1307 BUTTON4_PRESSED | BUTTON4_RELEASED |
1308 BUTTON5_PRESSED | BUTTON5_RELEASED |
1309 BUTTON_CTRL | BUTTON_SHIFT | BUTTON_ALT |
1310 REPORT_MOUSE_POSITION, NULL);
1313 /* Enable bracketed-paste */
1314 fprintf(dd->scr_file, "\033[?2004h");
1315 fflush(dd->scr_file);
1318 getmaxyx(stdscr, rows, cols);
1319 pane_resize(p, 0, 0, cols, rows);
1322 static struct pane *ncurses_init(struct pane *ed safe,
1323 const char *tty, const char *term)
1327 struct display_data *dd;
1332 if (tty && strcmp(tty, "-") != 0)
1333 f = fopen(tty, "r+");
1335 f = fdopen(1, "r+");
1338 scr = newterm(term, f, f);
1342 p = pane_register(ed, 1, &ncurses_handle.c);
1348 dd->is_xterm = (term && strstarts(term, "xterm"));
1354 area = dd->attr_buf;
1355 dd->rs1 = tgetstr("rs1", &area);
1357 dd->rs1 = tgetstr("is1", &area);
1358 dd->rs2 = tgetstr("rs2", &area);
1360 dd->rs2 = tgetstr("is2", &area);
1361 dd->rs3 = tgetstr("rs3", &area);
1363 dd->rs3 = tgetstr("is3", &area);
1364 dd->clear = tgetstr("clear", &area);
1366 call("editor:request:all-displays", p);
1367 if (!prepare_recrep(p)) {
1368 call_comm("event:read", p, &input_handle, fileno(f));
1369 if (!tty || strcmp(tty, "-") == 0)
1370 call_comm("event:signal", p, &handle_winch, SIGWINCH);
1375 REDEF_CMD(handle_winch)
1377 struct pane *p = ci->home;
1378 struct display_data *dd = p->data;
1379 struct winsize size;
1380 ioctl(fileno(dd->scr_file), TIOCGWINSZ, &size);
1382 resize_term(size.ws_row, size.ws_col);
1384 pane_resize(p, 0, 0, size.ws_row, size.ws_col);
1388 DEF_CMD(force_redraw)
1390 struct pane *p = ci->home;
1394 /* full reset, as mosh sometimes gets confused */
1402 static void ncurses_text(struct pane *p safe, struct pane *display safe,
1403 wchar_t ch, int attr, int pair,
1404 short x, short y, short cursor)
1413 set_screen(display);
1415 if (pane_has_focus(p->z < 0 ? p->parent : p)) {
1416 /* Cursor is in-focus */
1417 struct xy curs = pane_mapxy(p, display, x, y, False);
1418 display->cx = curs.x;
1419 display->cy = curs.y;
1421 /* Cursor here, but not focus */
1422 attr = make_cursor(attr);
1425 cc.ext_color = pair;
1429 pan = pane_panel(p2, NULL);
1430 while (!pan && p2->parent != p2) {
1432 pan = pane_panel(p2, NULL);
1435 struct xy xy = pane_mapxy(p, p2, x, y, False);
1436 mvwadd_wch(panel_window(pan), xy.y, xy.x, &cc);
1440 static struct namelist {
1444 {KEY_DOWN, ":Down"},
1446 {KEY_LEFT, ":Left"},
1447 {KEY_RIGHT, ":Right"},
1448 {KEY_HOME, ":Home"},
1449 {KEY_BACKSPACE, ":Backspace"},
1450 {KEY_DL, ":DelLine"},
1451 {KEY_IL, ":InsLine"},
1454 {KEY_ENTER, ":Enter"},
1457 {KEY_NPAGE, ":Next"},
1458 {KEY_PPAGE, ":Prior"},
1460 {KEY_SDC, ":S:Del"},
1461 {KEY_SDL, ":S:DelLine"},
1462 {KEY_SEND, ":S:End"},
1463 {KEY_SHOME, ":S:Home"},
1464 {KEY_SLEFT, ":S:Left"},
1465 {KEY_SRIGHT, ":S:Right"},
1466 {KEY_BTAB, ":S:Tab"},
1470 { 0616, ":S:Prior"},
1472 { 01041, ":S:Home"},
1474 { 01066, ":S:Prior"},
1475 { 01015, ":S:Next"},
1477 { 01027, ":A:S:Home"},
1478 { 01022, ":A:S:End"},
1479 { 01046, ":A:S:Prior"},
1480 { 01047, ":A:S:Next"}, // ??
1482 { 01052, ":A:Prior"},
1483 { 01045, ":A:Next"},
1484 { 01026, ":A:Home"},
1487 { 01014, ":A:Down"},
1488 { 01040, ":A:Left"},
1489 { 01057, ":A:Right"},
1514 { 01114, ":Focus-in"},
1515 { 01115, ":Focus-out"},
1522 {'\177', ":Delete"},
1527 static char *find_name (struct namelist *l safe, wint_t c)
1530 for (i = 0; l[i].name; i++)
1536 static void send_key(int keytype, wint_t c, int alt, struct pane *p safe)
1538 struct display_data *dd = p->data;
1540 char buf[100];/* FIXME */
1542 char *a = alt ? ":A" : "";
1544 if (keytype == KEY_CODE_YES) {
1545 n = find_name(key_names, c);
1547 LOG("Unknown ncurses key 0o%o", c);
1548 sprintf(buf, "%sNcurs-%o", a, c);
1549 } else if (strstarts(n, ":Focus-"))
1550 /* Ignore focus changes for now */
1553 strcat(strcpy(buf, a), n);
1555 n = find_name(char_names, c);
1557 sprintf(buf, "%s%s", a, n);
1558 else if (c < ' ' || c == 0x7f)
1559 sprintf(buf, "%s:C-%c",
1562 sprintf(buf, "%s-%s", a, put_utf8(t, c));
1565 dd->last_event = time(NULL);
1568 call("Keystroke", p, 0, NULL, buf);
1572 static void do_send_mouse(struct pane *p safe, int x, int y, char *cmd safe,
1573 int button, char *mod, int type)
1576 struct display_data *dd = p->data;
1578 record_mouse(p, cmd, x, y);
1579 ret = call("Mouse-event", p, button, NULL, cmd, type, NULL, mod, x, y);
1580 if (type == 1 && !dd->report_position) {
1582 fprintf(dd->scr_file, "\033[?1002h");
1583 fflush(dd->scr_file);
1585 dd->report_position = 1;
1586 } else if (type == 3 && ret <= 0) {
1588 fprintf(dd->scr_file, "\033[?1002l");
1589 fflush(dd->scr_file);
1591 dd->report_position = 0;
1595 static void send_mouse(MEVENT *mev safe, struct pane *p safe)
1597 struct display_data *dd = p->data;
1603 /* MEVENT has lots of bits. We want a few numbers */
1604 for (b = 1 ; b <= (NCURSES_MOUSE_VERSION <= 1 ? 3 : 5); b++) {
1605 mmask_t s = mev->bstate;
1610 if (s & BUTTON_SHIFT) modf |= 1;
1611 if (s & BUTTON_CTRL) modf |= 2;
1612 if (s & BUTTON_ALT) modf |= 4;
1614 case 0: mod = ""; break;
1615 case 1: mod = ":S"; break;
1616 case 2: mod = ":C"; break;
1617 case 3: mod = ":C:S"; break;
1618 case 4: mod = ":A"; break;
1619 case 5: mod = ":A:S"; break;
1620 case 6: mod = ":A:C"; break;
1621 case 7: mod = ":A:C:S"; break;
1623 if (BUTTON_PRESS(s, b))
1624 action = "%s:Press-%d";
1625 else if (BUTTON_RELEASE(s, b)) {
1626 action = "%s:Release-%d";
1627 /* Modifiers only reported on button Press */
1631 snprintf(buf, sizeof(buf), action, mod, b);
1632 dd->last_event = time(NULL);
1633 do_send_mouse(p, x, y, buf, b, mod, BUTTON_PRESS(s,b) ? 1 : 2);
1635 if ((mev->bstate & REPORT_MOUSE_POSITION) &&
1636 dd->report_position)
1637 /* Motion doesn't update last_event */
1638 do_send_mouse(p, x, y, ":Motion", 0, "", 3);
1641 static void paste_start(struct pane *home safe)
1643 struct display_data *dd = home->data;
1645 dd->paste_start = time(NULL);
1646 buf_init(&dd->paste_buf);
1649 static void paste_flush(struct pane *home safe)
1651 struct display_data *dd = home->data;
1653 if (!dd->paste_start)
1655 free(dd->paste_latest);
1656 dd->paste_latest = buf_final(&dd->paste_buf);
1657 if (dd->paste_buf.len > 0)
1658 dd->paste_pending = 1;
1659 dd->paste_start = 0;
1662 static bool paste_recv(struct pane *home safe, int is_keycode, wint_t ch)
1664 struct display_data *dd = home->data;
1666 if (dd->paste_start == 0)
1669 if (dd->paste_start < now || dd->paste_start > now + 2 ||
1670 is_keycode != OK || ch == KEY_MOUSE) {
1676 /* I really don't want carriage-returns... */
1678 buf_append(&dd->paste_buf, ch);
1679 if (ch == '~' && dd->paste_buf.len >= 6 &&
1681 buf_final(&dd->paste_buf) + dd->paste_buf.len - 6) == 0) {
1682 dd->paste_buf.len -= 6;
1688 DEF_CMD(nc_get_paste)
1690 struct display_data *dd = ci->home->data;
1692 comm_call(ci->comm2, "cb", ci->focus,
1693 dd->paste_start, NULL, dd->paste_latest);
1697 REDEF_CMD(input_handle)
1699 struct pane *p = ci->home;
1700 struct display_data *dd = p->data;
1701 static const char paste_seq[] = "\e[200~";
1704 int have_escape = 0;
1709 while ((is_keycode = get_wch(&c)) != ERR) {
1710 if (dd->suspended && c != KEY_MOUSE) {
1711 dd->suspended = False;
1713 call_comm("event:free", p, &ns_resume);
1714 /* swallow the key */
1717 if (paste_recv(p, is_keycode, c))
1719 if (c == KEY_MOUSE) {
1722 while (getmouse(&mev) != ERR) {
1723 if (dd->paste_pending &&
1724 mev.bstate == REPORT_MOUSE_POSITION) {
1725 /* xcfe-terminal is a bit weird.
1726 * It captures middle-press to
1727 * sanitise the paste, but lets
1728 * middle-release though. It comes
1729 * here as REPORT_MOUSE_POSTION
1730 * and we can use that to find the
1731 * position of the paste.
1732 * '6' is an unused button, and
1733 * ensures lib-input doesn't expect
1734 * matching press/release
1736 call("Mouse-event", ci->home,
1738 6, NULL, NULL, mev.x, mev.y);
1739 dd->paste_pending = 0;
1741 send_mouse(&mev, p);
1743 } else if (c == (wint_t)paste_seq[have_escape]) {
1745 if (!paste_seq[have_escape]) {
1749 } else if (have_escape == 1) {
1750 send_key(is_keycode, c, 1, p);
1752 } else if (have_escape) {
1753 send_key(OK, paste_seq[1], 1, p);
1754 for (i = 2; i < have_escape; i++)
1755 send_key(OK, paste_seq[i], 0, p);
1756 send_key(is_keycode, c, 0, p);
1759 send_key(is_keycode, c, 0, p);
1761 /* Don't know what other code might have done,
1762 * so re-set the screen
1766 if (have_escape == 1)
1767 send_key(is_keycode, '\e', 0, p);
1768 else if (have_escape > 1) {
1769 send_key(OK, paste_seq[1], 1, p);
1770 for (i = 2; i < have_escape; i++)
1771 send_key(OK, paste_seq[i], 0, p);
1773 if (dd->paste_pending == 2) {
1774 /* no mouse event to give postion, so treat as keyboard */
1775 call("Keystroke", ci->home, 0, NULL, ":Paste");
1776 dd->paste_pending = 0;
1777 } else if (dd->paste_pending == 1) {
1778 /* Wait for possible mouse-position update. */
1779 dd->paste_pending = 2;
1780 call_comm("event:timer", p, &input_handle, 200);
1785 DEF_CMD(display_ncurses)
1788 struct pane *ed = pane_root(ci->focus);
1789 const char *tty = ci->str;
1790 const char *term = ci->str2;
1793 term = "xterm-256color";
1795 p = ncurses_init(ed, tty, term);
1797 p = call_ret(pane, "editor:activate-display", p);
1798 if (p && ci->focus != ed)
1799 /* Assume ci->focus is a document */
1800 p = home_call_ret(pane, ci->focus, "doc:attach-view", p, 1);
1802 return comm_call(ci->comm2, "callback:display", p);
1807 void edlib_init(struct pane *ed safe)
1809 call_comm("global-set-command", ed, &display_ncurses, 0, NULL,
1810 "attach-display-ncurses");
1812 nc_map = key_alloc();
1813 key_add(nc_map, "window:refresh", &force_redraw);
1814 key_add(nc_map, "window:close", &nc_close_display);
1815 key_add(nc_map, "window:external-viewer", &nc_external_viewer);
1816 key_add(nc_map, "Close", &nc_close);
1817 key_add(nc_map, "Draw:clear", &nc_clear);
1818 key_add(nc_map, "Draw:text-size", &nc_text_size);
1819 key_add(nc_map, "Draw:text", &nc_draw_text);
1821 key_add(nc_map, "Draw:image", &nc_draw_image);
1822 key_add(nc_map, "Draw:image-size", &nc_image_size);
1824 key_add(nc_map, "Refresh:size", &nc_refresh_size);
1825 key_add(nc_map, "Refresh:postorder", &nc_refresh_post);
1826 key_add(nc_map, "Paste:get", &nc_get_paste);
1827 key_add(nc_map, "all-displays", &nc_notify_display);
1828 key_add(nc_map, "Sig:Winch", &handle_winch);
1829 key_add(nc_map, "Notify:Close", &nc_pane_close);