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)))
68 struct col_hash *col_hash;
85 char *rs1, *rs2, *rs3, *clear;
91 /* Sometimes I get duplicate Display lines, but not consistently.
92 * To avoid these, record last, filter repeats.
95 char last_screen[MD5_DIGEST_SIZE*2+1];
96 char next_screen[MD5_DIGEST_SIZE*2+1];
97 /* The next event to generate when idle */
98 enum { DoNil, DoMouse, DoKey, DoCheck, DoClose} next_event;
102 int clears; /* counts of Draw:clear events */
105 #include "core-pane.h"
107 static SCREEN *current_screen;
108 static void ncurses_text(struct pane *p safe, struct pane *display safe,
109 wchar_t ch, int attr, int pair,
110 short x, short y, short cursor);
111 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home);
112 DEF_CMD(input_handle);
113 DEF_CMD(handle_winch);
114 static struct map *nc_map;
115 DEF_LOOKUP_CMD(ncurses_handle, nc_map);
117 static struct display_data *current_dd;
118 static void set_screen(struct pane *p)
120 struct display_data *dd;
121 extern void *_nc_globals[100];
123 static int index = -1, offset=0;
126 if (current_screen && index >= 0)
127 _nc_globals[index] = NULL;
128 current_screen = NULL;
135 if (dd->scr == current_screen)
140 for (i=0; i<100; i++)
141 if (_nc_globals[i] < (void*)stdscr &&
142 _nc_globals[i]+4*(sizeof(void*)) >= (void*)stdscr) {
143 /* This is _nc_windowlist */
145 offset = ((void*)stdscr) - _nc_globals[i];
150 current_screen = dd->scr;
152 _nc_globals[index] = ((void*)stdscr) - offset;
159 static bool parse_event(struct pane *p safe);
160 static bool prepare_recrep(struct pane *p safe)
162 struct display_data *dd = &p->data;
165 name = getenv("EDLIB_RECORD");
167 dd->log = fopen(name, "w");
168 name = getenv("EDLIB_REPLAY");
170 dd->input = fopen(name, "r");
171 if (getenv("EDLIB_PAUSE"))
172 sleep(atoi(getenv("EDLIB_PAUSE")));
180 static void close_recrep(struct pane *p safe)
182 struct display_data *dd = &p->data;
185 fprintf(dd->log, "Close %d\n", dd->clears);
190 static void record_key(struct pane *p safe, char *key safe)
192 struct display_data *dd = &p->data;
197 if (!strchr(key, '"'))
199 else if (!strchr(key, '\''))
201 else if (!strchr(key, '/'))
205 fprintf(dd->log, "Key %c%s%c\n", q,key,q);
206 dd->last_cx = -2; /* Force next Display to be shown */
210 static void record_mouse(struct pane *p safe, char *key safe, int x, int y)
212 struct display_data *dd = &p->data;
216 if (!strchr(key, '"'))
218 else if (!strchr(key, '\''))
220 else if (!strchr(key, '/'))
224 fprintf(dd->log, "Mouse %c%s%c %d,%d\n", q,key,q, x, y);
225 dd->last_cx = -2; /* Force next Display to be shown */
229 static void record_screen(struct pane *p safe)
231 struct display_data *dd = &p->data;
232 struct md5_state ctx;
233 uint16_t buf[CCHARW_MAX+5];
234 char out[MD5_DIGEST_SIZE*2+1];
237 if (!dd->log && !(dd->input && dd->next_event == DoCheck))
241 for (r = 0; r < p->h; r++)
242 for (c = 0; c < p->w; c++) {
244 wchar_t wc[CCHARW_MAX+2];
249 mvwin_wch(stdscr, r, c, &cc);
250 getcchar(&cc, wc, &a, &color, NULL);
251 pair_content(color, &fg, &bg);
252 buf[0] = htole16(fg);
253 buf[1] = htole16(bg);
254 for (l = 0; l < CCHARW_MAX && wc[l]; l++)
255 buf[l+3] = htole16(wc[l]);
257 LOG("%d,%d %d:%d:%d:%d %lc", c,r,fg,bg,l,wc[0], wc[0] > ' ' ? wc[0] : '?');
258 md5_update(&ctx, (uint8_t*)buf,
259 (l+3) * sizeof(uint16_t));
261 md5_final_txt(&ctx, out);
262 if (strcmp(out, dd->last_screen) == 0 &&
263 p->cx == dd->last_cx && p->cy == dd->last_cy) {
264 /* No change - filter it */
266 } else if (dd->log) {
267 fprintf(dd->log, "Display %d,%d %s", p->w, p->h, out);
269 fprintf(dd->log, " %d,%d", p->cx, p->cy);
270 fprintf(dd->log, "\n");
272 strcpy(dd->last_screen, out);
273 dd->last_cx = p->cx; dd->last_cy = p->cy;
275 if (dd->input && dd->input_sleeping) {
276 char *delay = getenv("EDLIB_REPLAY_DELAY");
277 call_comm("event:free", p, &next_evt);
279 call_comm("event:timer", p, &next_evt, atoi(delay));
281 call_comm("event:on-idle", p, &next_evt);
285 static char *copy_quote(char *line safe, char *buf safe)
291 if (q != '"' && q != '\'' && q != '/')
293 while (*line != q && *line)
301 static char *get_coord(char *line safe, struct xy *co safe)
308 v = strtol(line, &ep, 10);
309 if (!ep || ep == line || *ep != ',')
313 v = strtol(line, &ep, 10);
314 if (!ep || ep == line || (*ep && *ep != ' ' && *ep != '\n'))
320 static char *get_hash(char *line safe, hash_t hash safe)
325 for (i = 0; i < MD5_DIGEST_SIZE*2 && *line; i++)
332 static bool parse_event(struct pane *p safe)
334 struct display_data *dd = &p->data;
338 dd->next_event = DoNil;
340 fgets(line, sizeof(line)-1, dd->input) == NULL)
342 else if (strstarts(line, "Key ")) {
343 if (!copy_quote(line+4, dd->event_info))
345 dd->next_event = DoKey;
346 } else if (strstarts(line, "Mouse ")) {
347 char *f = copy_quote(line+6, dd->event_info);
350 f = get_coord(f, &dd->event_pos);
353 dd->next_event = DoMouse;
354 } else if (strstarts(line, "Display ")) {
355 char *f = get_coord(line+8, &dd->event_pos);
358 f = get_hash(f, dd->next_screen);
359 dd->next_event = DoCheck;
360 } else if (strstarts(line, "Close")) {
361 dd->next_event = DoClose;
363 LOG("parse %s", line);
365 dd->input_sleeping = 1;
366 if (dd->next_event != DoCheck) {
367 char *delay = getenv("EDLIB_REPLAY_DELAY");
369 call_comm("event:timer", p, &next_evt, atoi(delay));
371 call_comm("event:on-idle", p, &next_evt);
373 call_comm("event:timer", p, &next_evt, 10*1000);
379 struct pane *p = ci->home;
380 struct display_data *dd = &p->data;
381 int button = 0, type = 0;
383 dd->input_sleeping = 0;
384 switch(dd->next_event) {
386 record_key(p, dd->event_info);
387 call("Keystroke", p, 0, NULL, dd->event_info);
390 record_mouse(p, dd->event_info, dd->event_pos.x,
392 if (strstr(dd->event_info, ":Press"))
394 else if (strstr(dd->event_info, ":Release"))
396 else if (strstr(dd->event_info, ":Motion"))
398 if (type == 1 || type == 2) {
399 char *e = dd->event_info + strlen(dd->event_info) - 1;
402 call("Mouse-event", p, button, NULL, dd->event_info,
404 dd->event_pos.x, dd->event_pos.y);
407 /* No point checking, just do a diff against new trace log. */
408 /* not; (strcmp(dd->next_screen, dd->last_screen) != 0) */
411 call("event:deactivate", p);
415 call_comm("event:read", p, &input_handle, 0);
416 call_comm("event:signal", p, &handle_winch, SIGWINCH);
423 static inline bool prepare_recrep(struct pane *p safe) {return False;}
424 static inline void record_key(struct pane *p safe, char *key) {}
425 static inline void record_mouse(struct pane *p safe, char *key safe,
427 static inline void record_screen(struct pane *p safe) {}
428 static inline void close_recrep(struct pane *p safe) {}
433 struct call_return *cr = container_of(ci->comm, struct call_return, c);
439 static void ncurses_end(struct pane *p safe);
441 DEF_CMD(nc_close_display)
443 /* If this is only display, then refuse to close this one */
444 struct call_return cr;
445 struct display_data *dd = &ci->home->data;
448 call("Message", ci->focus, 0, NULL, dd->noclose);
454 call_comm("editor:notify:all-displays", ci->focus, &cr.c);
456 /* Need to call ncurses_end() before we send a Notify:Close
457 * notification, else server exits too early
459 ncurses_end(ci->home);
462 call("Message", ci->focus, 0, NULL,
463 "Cannot close only window.");
467 DEF_CMD(nc_set_noclose)
469 struct display_data *dd = &ci->home->data;
474 dd->noclose = strdup(ci->str);
478 static int nc_putc(int ch)
481 fputc(ch, current_dd->scr_file);
485 static char *fnormalize(struct pane *p safe, const char *str) safe
487 char *ret = strsave(p, str);
490 for (cp = ret ; cp && *cp ; cp++)
492 !strchr("/_-+=.,@#", *cp))
493 /* Don't like this char */
498 static void wait_for(struct display_data *dd safe)
500 struct pids **pp = &dd->pids;
503 struct pids *p = *pp;
504 if (waitpid(p->pid, NULL, WNOHANG) > 0) {
514 struct display_data *dd = &ci->home->data;
517 dd->suspended = False;
518 set_screen(ci->home);
524 DEF_CMD(nc_external_viewer)
526 struct pane *p = ci->home;
527 struct display_data *dd = &p->data;
528 char *disp = pane_attr_get(p, "DISPLAY");
529 char *disp_auth = pane_attr_get(p, "XAUTHORITY");
530 char *remote = pane_attr_get(p, "REMOTE_SESSION");
532 const char *path = ci->str;
542 switch (pid = fork()) {
546 setenv("DISPLAY", disp, 1);
548 setenv("XAUTHORITY", disp_auth, 1);
549 fd = open("/dev/null", O_RDWR);
557 execlp("xdg-open", "xdg-open", path, NULL);
559 default: /* parent */
560 pds = malloc(sizeof(*pds));
562 pds->next = dd->pids;
569 /* handle no-display case */
570 if (remote && strcmp(remote, "yes") == 0 &&
572 gethostname(buf, sizeof(buf)) == 0) {
573 struct addrinfo *res;
574 const struct addrinfo hints = {
575 .ai_flags = AI_CANONNAME,
577 if (getaddrinfo(buf, NULL, &hints, &res) == 0 &&
578 res && res->ai_canonname)
579 fqdn = strdup(res->ai_canonname);
584 ioctl(fileno(dd->scr_file), FIONREAD, &n);
586 n -= read(fileno(dd->scr_file), buf,
587 n <= (int)sizeof(buf) ? n : (int)sizeof(buf));
589 /* stay in raw mode */
593 /* Endwin doesn't seem to reset properly, at least on xfce-terminal.
597 tputs(dd->rs1, 1, nc_putc);
599 tputs(dd->rs2, 1, nc_putc);
601 tputs(dd->rs3, 1, nc_putc);
603 tputs(dd->clear, 1, nc_putc);
604 fflush(dd->scr_file);
606 fprintf(dd->scr_file, "# Consider copy-pasting following\r\n");
607 if (fqdn && path[0] == '/') {
608 /* File will not be local for the user, so help them copy it. */
609 const char *tmp = fnormalize(p, ci->str2 ?: "XXXXXX");
610 const char *fname = fnormalize(p, ci->str);
612 if (strcmp(fname, ci->str) != 0)
613 /* file name had unusuable chars, need to create safe name */
614 link(ci->str, fname);
615 fprintf(dd->scr_file, "f=`mktemp --tmpdir %s`; scp %s:%s $f ; ",
620 fprintf(dd->scr_file, "xdg-open %s\r\n", path);
621 fprintf(dd->scr_file, "# Press Enter to continue\r\n");
622 dd->suspended = True;
623 call_comm("event:timer", p, &ns_resume, 30*1000);
627 static void ncurses_stop(struct pane *p safe)
629 struct display_data *dd = &p->data;
632 /* disable bracketed-paste */
633 fprintf(dd->scr_file, "\033[?2004l");
634 fflush(dd->scr_file);
637 free(buf_final(&dd->paste_buf));
639 free(dd->paste_latest);
640 dd->paste_latest = NULL;
644 tputs(dd->rs1, 1, nc_putc);
646 tputs(dd->rs2, 1, nc_putc);
648 tputs(dd->rs3, 1, nc_putc);
649 fflush(dd->scr_file);
652 static void ncurses_end(struct pane *p safe)
654 struct display_data *dd = &p->data;
658 dd->did_close = True;
666 * hash table for colours and pairs
667 * key is r,g,b (0-1000) in 10bit fields,
668 * or fg,bg in 16 bit fields with bit 31 set
669 * content is colour number of colour pair number.
670 * We never delete entries, unless we delete everything.
677 #define COL_KEY(r,g,b) ((r<<20) | (g<<10) | b)
678 #define PAIR_KEY(fg, bg) ((1<<31) | (fg<<16) | bg)
679 #define hash_key(k) ((((k) * 0x61C88647) >> 20) & 0xff)
682 int next_col, next_pair;
683 struct chash *tbl[256];
686 static struct col_hash *safe hash_init(struct display_data *dd safe)
689 dd->col_hash = malloc(sizeof(*dd->col_hash));
690 memset(dd->col_hash, 0, sizeof(*dd->col_hash));
691 dd->col_hash->next_col = 16;
692 dd->col_hash->next_pair = 1;
697 static void hash_free(struct display_data *dd safe)
706 for (h = 0; h < 255; h++)
707 while ((c = ch->tbl[h]) != NULL) {
708 ch->tbl[h] = c->next;
715 static int find_col(struct display_data *dd safe, int rgb[])
717 if (0 /* dynamic colours */) {
718 struct col_hash *ch = hash_init(dd);
719 int k = COL_KEY(rgb[0], rgb[1], rgb[2]);
723 for (c = ch->tbl[h]; c; c = c->next)
726 c = malloc(sizeof(*c));
728 c->content = ch->next_col++;
729 c->next = ch->tbl[h];
731 init_color(c->content, rgb[0], rgb[1], rgb[2]);
734 /* If colour is grey, map to 1 of 26 for 0, 232 to 255, 15
735 * The 24 grey shades have bit values from 8 to 238, so the
736 * gap to white is a little bigger, but that probably doesn't
738 * Otherwise map to 6x6x6 rgb cube from 16
739 * Actual colours are biased bright, at 0,95,135,175,215,255
740 * with a 95 gap at bottom and 40 elsewhere.
741 * So we divide 5 and 2 half ranges, and merge bottom 2.
746 //printf("want %d,%d,%d\n", rgb[0], rgb[1], rgb[2]);
747 if (abs(rgb[0] - rgb[1]) < 10 &&
748 abs(rgb[1] - rgb[2]) < 10) {
749 /* grey - within 1% */
750 int v = (rgb[0] + rgb[1] + rgb[2]) / 3;
752 /* We divide the space in 24 ranges surrounding
753 * the grey values, and 2 half-ranges near black
754 * and white. So add half a range - 1000/50 -
755 * then divide by 1000/25 to get a number from 0 to 25.
757 v = (v + 1000/50) / (1000/25);
759 return 0; /* black */
761 return 15; /* white */
762 //printf(" grey %d\n", v + 231);
763 /* grey shades are from 232 to 255 inclusive */
766 for (h = 0; h < 3; h++) {
769 v = (v + 1000/12) / (1000/6);
770 /* v is from 0 to 6, we want up to 5
771 * with 0 and 1 merged
778 //printf(" color %d\n", c + 16);
783 static int to_pair(struct display_data *dd safe, int fg, int bg)
785 struct col_hash *ch = hash_init(dd);
786 int k = PAIR_KEY(fg, bg);
790 for (c = ch->tbl[h]; c; c = c->next)
793 c = malloc(sizeof(*c));
795 c->content = ch->next_pair++;
796 c->next = ch->tbl[h];
798 init_pair(c->content, fg, bg);
802 static int cvt_attrs(struct pane *p safe, struct pane *home safe,
803 const char *attrs, int *pairp safe, bool use_parent)
805 struct display_data *dd = &home->data;
810 int fg = COLOR_BLACK;
811 int bg = COLOR_WHITE+8;
816 } while (p->parent != p &&(pan = pane_panel(p, NULL)) == NULL);
817 if (pan && use_parent) {
818 /* Get 'default colours for this pane - set at clear */
819 int at = getbkgd(panel_window(pan));
820 int pair = PAIR_NUMBER(at);
822 pair_content(pair, &dfg, &dbg);
829 foreach_attr(a, v, attrs, NULL) {
830 if (amatch(a, "inverse"))
832 else if (amatch(a, "noinverse"))
834 else if (amatch(a, "bold"))
836 else if (amatch(a, "nobold"))
838 else if (amatch(a, "underline"))
840 else if (amatch(a, "nounderline"))
841 attr &= ~A_UNDERLINE;
842 else if (amatch(a, "fg") && v) {
843 struct call_return cr =
844 call_ret(all, "colour:map", home,
845 0, NULL, aupdate(&col, v));
846 int rgb[3] = {cr.i, cr.i2, cr.x};
847 fg = find_col(dd, rgb);
848 } else if (amatch(a, "bg") && v) {
849 struct call_return cr =
850 call_ret(all, "colour:map", home,
851 0, NULL, aupdate(&col, v));
852 int rgb[3] = {cr.i, cr.i2, cr.x};
853 bg = find_col(dd, rgb);
857 if (fg != COLOR_BLACK || bg != COLOR_WHITE+8)
858 *pairp = to_pair(dd, fg, bg);
862 static int make_cursor(int attr)
864 return attr ^ A_UNDERLINE;
867 DEF_CMD(nc_notify_display)
869 struct display_data *dd = &ci->home->data;
870 comm_call(ci->comm2, "callback:display", ci->home, dd->last_event);
876 struct pane *p = ci->home;
877 struct display_data *dd = &p->data;
880 fclose(dd->scr_file);
884 DEF_CMD(nc_pane_close)
888 set_screen(ci->home);
889 while ((pan = panel_above(pan)) != NULL)
890 if (panel_userptr(pan) == ci->focus)
893 WINDOW *win = panel_window(pan);
896 pane_damaged(ci->home, DAMAGED_POSTORDER);
901 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home)
905 while ((pan = panel_above(pan)) != NULL)
906 if (panel_userptr(pan) == p)
912 pan = new_panel(newwin(p->h, p->w, 0, 0));
913 set_panel_userptr(pan, p);
914 pane_add_notify(home, p, "Notify:Close");
921 struct pane *p = ci->home;
922 struct display_data *dd = &p->data;
925 int attr = cvt_attrs(ci->focus, p, ci->str, &pair, ci->str == NULL);
931 panel = pane_panel(ci->focus, p);
934 win = panel_window(panel);
936 if (h != ci->focus->h || w != ci->focus->w) {
937 wresize(win, ci->focus->h, ci->focus->w);
938 replace_panel(panel, win);
943 wbkgrndset(win, &cc);
947 pane_damaged(p, DAMAGED_POSTORDER);
951 DEF_CMD(nc_text_size)
953 int max_space = ci->num;
956 const char *str = ci->str;
960 while (str[0] != 0) {
961 wint_t wc = get_utf8(&str, NULL);
969 if (size <= max_space)
970 max_bytes = str - ci->str;
972 return comm_call(ci->comm2, "callback:size", ci->focus,
973 max_bytes, NULL, NULL,
974 0, NULL, NULL, size, 1);
977 DEF_CMD(nc_draw_text)
979 struct pane *p = ci->home;
981 int attr = cvt_attrs(ci->focus, p, ci->str2, &pair, True);
982 int cursor_offset = ci->num;
983 short x = ci->x, y = ci->y;
984 const char *str = ci->str;
989 while (str[0] != 0) {
990 int precurs = str <= ci->str + cursor_offset;
991 wint_t wc = get_utf8(&str, NULL);
993 if (wc == WEOF || wc == WERR)
998 if (precurs && str > ci->str + cursor_offset)
999 ncurses_text(ci->focus, p, wc, attr, pair, x, y, 1);
1001 ncurses_text(ci->focus, p, wc, attr, pair, x, y, 0);
1004 if (str == ci->str + cursor_offset)
1005 ncurses_text(ci->focus, p, ' ', 0, 0, x, y, 1);
1006 pane_damaged(p, DAMAGED_POSTORDER);
1010 DEF_CMD(nc_draw_image)
1012 /* 'str' identifies the image. Options are:
1013 * file:filename - load file from fs
1014 * comm:command - run command collecting bytes
1015 * 'num' is '16' if image should be stretched to fill pane
1016 * Otherwise it is the 'or' of
1017 * 0,1,2 for left/middle/right in x direction
1018 * 0,4,8 for top/middle/bottom in y direction
1019 * only one of these can be used as image will fill pane
1020 * in other direction.
1021 * If 'x' and 'y' are both positive, draw cursor box at
1022 * p->cx, p->cy of a size so that 'x' will fit across and
1023 * 'y' will fit down.
1025 struct pane *p = ci->home;
1026 struct display_data *dd = &p->data;
1028 bool stretch = ci->num & 16;
1030 int w = ci->focus->w, h = ci->focus->h * 2;
1031 int cx = -1, cy = -1;
1032 MagickBooleanType status;
1039 if (strstarts(ci->str, "file:")) {
1040 wd = NewMagickWand();
1041 status = MagickReadImage(wd, ci->str + 5);
1042 if (status == MagickFalse) {
1043 DestroyMagickWand(wd);
1046 } else if (strstarts(ci->str, "comm:")) {
1047 struct call_return cr;
1048 wd = NewMagickWand();
1049 cr = call_ret(bytes, ci->str+5, ci->focus, 0, NULL, ci->str2);
1051 DestroyMagickWand(wd);
1054 status = MagickReadImageBlob(wd, cr.s, cr.i);
1056 if (status == MagickFalse) {
1057 DestroyMagickWand(wd);
1063 MagickAutoOrientImage(wd);
1065 int ih = MagickGetImageHeight(wd);
1066 int iw = MagickGetImageWidth(wd);
1068 if (iw <= 0 || iw <= 0) {
1069 DestroyMagickWand(wd);
1072 if (iw * h > ih * w) {
1073 /* Image is wider than space, use less height */
1075 switch(pos & (8+4)) {
1076 case 4: /* center */
1077 y = (h - ih) / 2; break;
1078 case 8: /* bottom */
1081 /* Keep 'h' even! */
1084 /* image is too tall, use less width */
1086 switch (pos & (1+2)) {
1087 case 1: /* center */
1088 x = (w - iw) / 2; break;
1095 MagickAdaptiveResizeImage(wd, w, h);
1096 buf = malloc(h * w * 4);
1097 MagickExportImagePixels(wd, 0, 0, w, h, "RGBA", CharPixel, buf);
1099 if (ci->x > 0 && ci->y > 0 && ci->focus->cx >= 0) {
1100 /* We want a cursor */
1101 cx = x + ci->focus->cx;
1102 cy = y + ci->focus->cy;
1104 for (i = 0; i < h; i+= 2) {
1105 static const wint_t hilo = 0x2580; /* L'▀' */
1106 for (j = 0; j < w ; j+= 1) {
1107 unsigned char *p1 = buf + i*w*4 + j*4;
1108 unsigned char *p2 = buf + (i+1)*w*4 + j*4;
1109 int rgb1[3] = { p1[0]*1000/255, p1[1]*1000/255, p1[2]*1000/255 };
1110 int rgb2[3] = { p2[0]*1000/255, p2[1]*1000/255, p2[2]*1000/255 };
1111 int fg = find_col(dd, rgb1);
1112 int bg = find_col(dd, rgb2);
1114 if (p1[3] < 128 || p2[3] < 128) {
1118 struct pane *pn2 = ci->focus;
1119 PANEL *pan = pane_panel(pn2, NULL);
1121 while (!pan && pn2->parent != pn2) {
1123 pan = pane_panel(pn2, NULL);
1126 wgetbkgrnd(panel_window(pan), &cc);
1127 if (cc.ext_color == 0)
1128 /* default. This is light
1129 * gray rather then white,
1130 * but I think it is a good
1135 pair_content(cc.ext_color, &f, &b);
1142 /* FIXME this doesn't work because
1143 * render-line knows too much and gets it wrong.
1145 if (cx == x+j && cy == y + (i/2))
1146 ncurses_text(ci->focus, p, 'X', 0,
1150 ncurses_text(ci->focus, p, hilo, 0,
1151 to_pair(dd, fg, bg),
1158 DestroyMagickWand(wd);
1160 pane_damaged(ci->home, DAMAGED_POSTORDER);
1165 DEF_CMD(nc_image_size)
1167 MagickBooleanType status;
1173 if (strstarts(ci->str, "file:")) {
1174 wd = NewMagickWand();
1175 status = MagickReadImage(wd, ci->str + 5);
1176 if (status == MagickFalse) {
1177 DestroyMagickWand(wd);
1180 } else if (strstarts(ci->str, "comm:")) {
1181 struct call_return cr;
1182 wd = NewMagickWand();
1183 cr = call_ret(bytes, ci->str+5, ci->focus, 0, NULL, ci->str2);
1185 DestroyMagickWand(wd);
1188 status = MagickReadImageBlob(wd, cr.s, cr.i);
1190 if (status == MagickFalse) {
1191 DestroyMagickWand(wd);
1197 MagickAutoOrientImage(wd);
1198 ih = MagickGetImageHeight(wd);
1199 iw = MagickGetImageWidth(wd);
1201 DestroyMagickWand(wd);
1202 comm_call(ci->comm2, "callback:size", ci->focus,
1203 0, NULL, NULL, 0, NULL, NULL,
1208 DEF_CMD(nc_refresh_size)
1210 struct pane *p = ci->home;
1213 getmaxyx(stdscr, p->h, p->w);
1218 DEF_CMD(nc_refresh_post)
1220 struct pane *p = ci->home;
1221 struct display_data *dd = &p->data;
1230 /* Need to ensure stacking order and panel y,x position
1231 * is correct. FIXME it would be good if we could skip this
1234 pan = panel_above(NULL);
1237 p1 = (struct pane*) panel_userptr(pan);
1238 for (;(pan2 = panel_above(pan)) != NULL; pan = pan2) {
1239 struct pane *p2 = (struct pane*)panel_userptr(pan2);
1240 p1 = (struct pane*)panel_userptr(pan);
1244 if (p1->abs_z < p2->abs_z)
1246 if (p1->abs_z == p2->abs_z &&
1249 /* pan needs to be above pan2. All we can do is move it to
1250 * the top. Anything that needs to be above it will eventually
1254 /* Now the panel below pan might need to be over pan2 too... */
1255 pan = panel_below(pan2);
1260 /* As we need to crop pane against their parents, we cannot simply
1261 * use update_panels(). Instead we copy each to stdscr and refresh
1264 for (pan = NULL; (pan = panel_above(pan)) != NULL; ) {
1266 struct xy src, dest, destend;
1269 p1 = (void*)panel_userptr(pan);
1272 dest = pane_mapxy(p1, p, 0, 0, True);
1273 destend = pane_mapxy(p1, p, p1->w, p1->h, True);
1274 src = pane_mapxy(p1, p, 0, 0, False);
1275 src.x = dest.x - src.x;
1276 src.y = dest.y - src.y;
1277 win = panel_window(pan);
1278 getmaxyx(win, h, w);
1279 /* guard again accessing beyond boundary of win */
1280 if (destend.x > dest.x + (w - src.x))
1281 destend.x = dest.x + (w - src.x);
1282 if (destend.y > dest.y + (h - src.y))
1283 destend.y = dest.y - (h - src.y);
1284 copywin(win, stdscr, src.y, src.x,
1285 dest.y, dest.x, destend.y-1, destend.x-1, 0);
1287 /* place the cursor */
1290 while (p1 != p && (pan = pane_panel(p1, NULL)) == NULL)
1292 if (pan && p1->cx >= 0) {
1293 struct xy curs = pane_mapxy(p1, p, p1->cx, p1->cy, False);
1294 wmove(stdscr, curs.y, curs.x);
1295 } else if (p->cx >= 0)
1296 wmove(stdscr, p->cy, p->cx);
1298 record_screen(ci->home);
1302 static void ncurses_start(struct pane *p safe)
1304 struct display_data *dd = &p->data;
1308 use_default_colors();
1314 intrflush(stdscr, FALSE);
1315 keypad(stdscr, TRUE);
1316 mousemask(BUTTON1_PRESSED | BUTTON1_RELEASED |
1317 BUTTON2_PRESSED | BUTTON2_RELEASED |
1318 BUTTON3_PRESSED | BUTTON3_RELEASED |
1319 BUTTON4_PRESSED | BUTTON4_RELEASED |
1320 BUTTON5_PRESSED | BUTTON5_RELEASED |
1321 BUTTON_CTRL | BUTTON_SHIFT | BUTTON_ALT |
1322 REPORT_MOUSE_POSITION, NULL);
1325 /* Enable bracketed-paste */
1326 fprintf(dd->scr_file, "\033[?2004h");
1327 fflush(dd->scr_file);
1330 getmaxyx(stdscr, rows, cols);
1331 pane_resize(p, 0, 0, cols, rows);
1334 static struct pane *ncurses_init(struct pane *ed safe,
1335 const char *tty, const char *term)
1339 struct display_data *dd;
1344 if (tty && strcmp(tty, "-") != 0)
1345 f = fopen(tty, "r+");
1347 f = fdopen(1, "r+");
1350 scr = newterm(term, f, f);
1354 p = pane_register(ed, 1, &ncurses_handle.c);
1360 dd->is_xterm = (term && strstarts(term, "xterm"));
1366 area = dd->attr_buf;
1367 dd->rs1 = tgetstr("rs1", &area);
1369 dd->rs1 = tgetstr("is1", &area);
1370 dd->rs2 = tgetstr("rs2", &area);
1372 dd->rs2 = tgetstr("is2", &area);
1373 dd->rs3 = tgetstr("rs3", &area);
1375 dd->rs3 = tgetstr("is3", &area);
1376 dd->clear = tgetstr("clear", &area);
1378 call("editor:request:all-displays", p);
1379 if (!prepare_recrep(p)) {
1380 call_comm("event:read", p, &input_handle, fileno(f));
1381 if (!tty || strcmp(tty, "-") == 0)
1382 call_comm("event:signal", p, &handle_winch, SIGWINCH);
1387 REDEF_CMD(handle_winch)
1389 struct pane *p = ci->home;
1390 struct display_data *dd = &p->data;
1391 struct winsize size;
1392 ioctl(fileno(dd->scr_file), TIOCGWINSZ, &size);
1394 resize_term(size.ws_row, size.ws_col);
1396 pane_resize(p, 0, 0, size.ws_row, size.ws_col);
1400 DEF_CMD(force_redraw)
1402 struct pane *p = ci->home;
1406 /* full reset, as mosh sometimes gets confused */
1414 static void ncurses_text(struct pane *p safe, struct pane *display safe,
1415 wchar_t ch, int attr, int pair,
1416 short x, short y, short cursor)
1425 set_screen(display);
1427 if (pane_has_focus(p->z < 0 ? p->parent : p)) {
1428 /* Cursor is in-focus */
1429 struct xy curs = pane_mapxy(p, display, x, y, False);
1430 display->cx = curs.x;
1431 display->cy = curs.y;
1433 /* Cursor here, but not focus */
1434 attr = make_cursor(attr);
1437 cc.ext_color = pair;
1441 pan = pane_panel(p2, NULL);
1442 while (!pan && p2->parent != p2) {
1444 pan = pane_panel(p2, NULL);
1447 struct xy xy = pane_mapxy(p, p2, x, y, False);
1448 mvwadd_wch(panel_window(pan), xy.y, xy.x, &cc);
1452 static struct namelist {
1456 {KEY_DOWN, ":Down"},
1458 {KEY_LEFT, ":Left"},
1459 {KEY_RIGHT, ":Right"},
1460 {KEY_HOME, ":Home"},
1461 {KEY_BACKSPACE, ":Backspace"},
1462 {KEY_DL, ":DelLine"},
1463 {KEY_IL, ":InsLine"},
1466 {KEY_ENTER, ":Enter"},
1469 {KEY_NPAGE, ":Next"},
1470 {KEY_PPAGE, ":Prior"},
1472 {KEY_SDC, ":S:Del"},
1473 {KEY_SDL, ":S:DelLine"},
1474 {KEY_SEND, ":S:End"},
1475 {KEY_SHOME, ":S:Home"},
1476 {KEY_SLEFT, ":S:Left"},
1477 {KEY_SRIGHT, ":S:Right"},
1478 {KEY_BTAB, ":S:Tab"},
1482 { 0616, ":S:Prior"},
1484 { 01041, ":S:Home"},
1486 { 01066, ":S:Prior"},
1487 { 01015, ":S:Next"},
1489 { 01027, ":A:S:Home"},
1490 { 01022, ":A:S:End"},
1491 { 01046, ":A:S:Prior"},
1492 { 01047, ":A:S:Next"}, // ??
1494 { 01052, ":A:Prior"},
1495 { 01045, ":A:Next"},
1496 { 01026, ":A:Home"},
1499 { 01014, ":A:Down"},
1500 { 01040, ":A:Left"},
1501 { 01057, ":A:Right"},
1526 { 01114, ":Focus-in"},
1527 { 01115, ":Focus-out"},
1534 {'\177', ":Delete"},
1539 static char *find_name (struct namelist *l safe, wint_t c)
1542 for (i = 0; l[i].name; i++)
1548 static void send_key(int keytype, wint_t c, int alt, struct pane *p safe)
1550 struct display_data *dd = &p->data;
1552 char buf[100];/* FIXME */
1554 char *a = alt ? ":A" : "";
1556 if (keytype == KEY_CODE_YES) {
1557 n = find_name(key_names, c);
1559 LOG("Unknown ncurses key 0o%o", c);
1560 sprintf(buf, "%sNcurs-%o", a, c);
1561 } else if (strstarts(n, ":Focus-"))
1562 /* 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);
1578 call("Keystroke", p, 0, NULL, buf);
1581 static void do_send_mouse(struct pane *p safe, int x, int y, char *cmd safe,
1582 int button, char *mod, int type)
1585 struct display_data *dd = &p->data;
1587 record_mouse(p, cmd, x, y);
1588 ret = call("Mouse-event", p, button, NULL, cmd, type, NULL, mod, x, y);
1589 if (type == 1 && !dd->report_position) {
1591 fprintf(dd->scr_file, "\033[?1002h");
1592 fflush(dd->scr_file);
1594 dd->report_position = 1;
1595 } else if (type == 3 && ret <= 0) {
1597 fprintf(dd->scr_file, "\033[?1002l");
1598 fflush(dd->scr_file);
1600 dd->report_position = 0;
1604 static void send_mouse(MEVENT *mev safe, struct pane *p safe)
1606 struct display_data *dd = &p->data;
1612 /* MEVENT has lots of bits. We want a few numbers */
1613 for (b = 1 ; b <= (NCURSES_MOUSE_VERSION <= 1 ? 3 : 5); b++) {
1614 mmask_t s = mev->bstate;
1619 if (s & BUTTON_SHIFT) modf |= 1;
1620 if (s & BUTTON_CTRL) modf |= 2;
1621 if (s & BUTTON_ALT) modf |= 4;
1623 case 0: mod = ""; break;
1624 case 1: mod = ":S"; break;
1625 case 2: mod = ":C"; break;
1626 case 3: mod = ":C:S"; break;
1627 case 4: mod = ":A"; break;
1628 case 5: mod = ":A:S"; break;
1629 case 6: mod = ":A:C"; break;
1630 case 7: mod = ":A:C:S"; break;
1632 if (BUTTON_PRESS(s, b))
1633 action = "%s:Press-%d";
1634 else if (BUTTON_RELEASE(s, b)) {
1635 action = "%s:Release-%d";
1636 /* Modifiers only reported on button Press */
1640 snprintf(buf, sizeof(buf), action, mod, b);
1641 dd->last_event = time(NULL);
1642 do_send_mouse(p, x, y, buf, b, mod, BUTTON_PRESS(s,b) ? 1 : 2);
1644 if ((mev->bstate & REPORT_MOUSE_POSITION) &&
1645 dd->report_position)
1646 /* Motion doesn't update last_event */
1647 do_send_mouse(p, x, y, ":Motion", 0, "", 3);
1650 static void paste_start(struct pane *home safe)
1652 struct display_data *dd = &home->data;
1654 dd->paste_start = time(NULL);
1655 buf_init(&dd->paste_buf);
1658 static void paste_flush(struct pane *home safe)
1660 struct display_data *dd = &home->data;
1662 if (!dd->paste_start)
1664 free(dd->paste_latest);
1665 dd->paste_latest = buf_final(&dd->paste_buf);
1666 if (dd->paste_buf.len > 0)
1667 dd->paste_pending = 1;
1668 dd->paste_start = 0;
1671 static bool paste_recv(struct pane *home safe, int is_keycode, wint_t ch)
1673 struct display_data *dd = &home->data;
1675 if (dd->paste_start == 0)
1678 if (dd->paste_start < now || dd->paste_start > now + 2 ||
1679 is_keycode != OK || ch == KEY_MOUSE) {
1685 /* I really don't want carriage-returns... */
1687 buf_append(&dd->paste_buf, ch);
1688 if (ch == '~' && dd->paste_buf.len >= 6 &&
1690 buf_final(&dd->paste_buf) + dd->paste_buf.len - 6) == 0) {
1691 dd->paste_buf.len -= 6;
1697 DEF_CMD(nc_get_paste)
1699 struct display_data *dd = &ci->home->data;
1701 comm_call(ci->comm2, "cb", ci->focus,
1702 dd->paste_start, NULL, dd->paste_latest);
1706 REDEF_CMD(input_handle)
1708 struct pane *p = ci->home;
1709 struct display_data *dd = &p->data;
1710 static const char paste_seq[] = "\e[200~";
1713 int have_escape = 0;
1718 while ((is_keycode = get_wch(&c)) != ERR) {
1719 if (dd->suspended && c != KEY_MOUSE) {
1720 dd->suspended = False;
1722 call_comm("event:free", p, &ns_resume);
1723 /* swallow the key */
1726 if (paste_recv(p, is_keycode, c))
1728 if (c == KEY_MOUSE) {
1731 while (getmouse(&mev) != ERR) {
1732 if (dd->paste_pending &&
1733 mev.bstate == REPORT_MOUSE_POSITION) {
1734 /* xcfe-terminal is a bit weird.
1735 * It captures middle-press to
1736 * sanitise the paste, but lets
1737 * middle-release though. It comes
1738 * here as REPORT_MOUSE_POSTION
1739 * and we can use that to find the
1740 * position of the paste.
1741 * '6' is an unused button, and
1742 * ensures lib-input doesn't expect
1743 * matching press/release
1745 call("Mouse-event", ci->home,
1747 6, NULL, NULL, mev.x, mev.y);
1748 dd->paste_pending = 0;
1750 send_mouse(&mev, p);
1752 } else if (c == (wint_t)paste_seq[have_escape]) {
1754 if (!paste_seq[have_escape]) {
1758 } else if (have_escape == 1) {
1759 send_key(is_keycode, c, 1, p);
1761 } else if (have_escape) {
1762 send_key(OK, paste_seq[1], 1, p);
1763 for (i = 2; i < have_escape; i++)
1764 send_key(OK, paste_seq[i], 0, p);
1765 send_key(is_keycode, c, 0, p);
1768 send_key(is_keycode, c, 0, p);
1770 /* Don't know what other code might have done,
1771 * so re-set the screen
1775 if (have_escape == 1)
1776 send_key(is_keycode, '\e', 0, p);
1777 else if (have_escape > 1) {
1778 send_key(OK, paste_seq[1], 1, p);
1779 for (i = 2; i < have_escape; i++)
1780 send_key(OK, paste_seq[i], 0, p);
1782 if (dd->paste_pending == 2) {
1783 /* no mouse event to give postion, so treat as keyboard */
1784 call("Keystroke", ci->home, 0, NULL, ":Paste");
1785 dd->paste_pending = 0;
1786 } else if (dd->paste_pending == 1) {
1787 /* Wait for possible mouse-position update. */
1788 dd->paste_pending = 2;
1789 call_comm("event:timer", p, &input_handle, 200);
1794 DEF_CMD(display_ncurses)
1797 struct pane *ed = pane_root(ci->focus);
1798 const char *tty = ci->str;
1799 const char *term = ci->str2;
1802 term = "xterm-256color";
1804 p = ncurses_init(ed, tty, term);
1806 p = call_ret(pane, "editor:activate-display", p);
1807 if (p && ci->focus != ed)
1808 /* Assume ci->focus is a document */
1809 p = home_call_ret(pane, ci->focus, "doc:attach-view", p, 1);
1811 return comm_call(ci->comm2, "callback:display", p);
1816 void edlib_init(struct pane *ed safe)
1818 call_comm("global-set-command", ed, &display_ncurses, 0, NULL,
1819 "attach-display-ncurses");
1821 nc_map = key_alloc();
1822 key_add(nc_map, "Display:refresh", &force_redraw);
1823 key_add(nc_map, "Display:close", &nc_close_display);
1824 key_add(nc_map, "Display:set-noclose", &nc_set_noclose);
1825 key_add(nc_map, "Display: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);