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);
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 * 'str2' container 'mode' information.
1004 * By default the image is placed centrally in the pane
1005 * and scaled to use either fully height or fully width.
1006 * Various letters modify this:
1007 * 'S' - stretch to use full height *and* full width
1008 * 'L' - place on left if full width isn't used
1009 * 'R' - place on right if full width isn't used
1010 * 'T' - place at top if full height isn't used
1011 * 'B' - place at bottom if full height isn't used.
1013 * If 'x' and 'y' are both positive, draw cursor box at
1014 * p->cx, p->cy of a size so that 'x' will fit across and
1015 * 'y' will fit down.
1017 struct pane *p = ci->home;
1018 struct display_data *dd = p->data;
1020 const char *mode = ci->str2 ?: "";
1021 bool stretch = strchr(mode, 'S');
1022 int w = ci->focus->w, h = ci->focus->h * 2;
1023 int cx = -1, cy = -1;
1024 MagickBooleanType status;
1031 if (strstarts(ci->str, "file:")) {
1032 wd = NewMagickWand();
1033 status = MagickReadImage(wd, ci->str + 5);
1034 if (status == MagickFalse) {
1035 DestroyMagickWand(wd);
1038 } else if (strstarts(ci->str, "comm:")) {
1039 struct call_return cr;
1040 wd = NewMagickWand();
1041 cr = call_ret(bytes, ci->str+5, ci->focus);
1043 DestroyMagickWand(wd);
1046 status = MagickReadImageBlob(wd, cr.s, cr.i);
1048 if (status == MagickFalse) {
1049 DestroyMagickWand(wd);
1055 MagickAutoOrientImage(wd);
1057 int ih = MagickGetImageHeight(wd);
1058 int iw = MagickGetImageWidth(wd);
1060 if (iw <= 0 || iw <= 0) {
1061 DestroyMagickWand(wd);
1064 if (iw * h > ih * w) {
1065 /* Image is wider than space, use less height */
1067 if (strchr(mode, 'B'))
1070 else if (!strchr(mode, 'T'))
1073 /* Keep 'h' even! */
1076 /* image is too tall, use less width */
1078 if (strchr(mode, 'R'))
1081 else if (!strchr(mode, 'L'))
1086 MagickAdaptiveResizeImage(wd, w, h);
1087 buf = malloc(h * w * 4);
1088 MagickExportImagePixels(wd, 0, 0, w, h, "RGBA", CharPixel, buf);
1090 if (ci->x > 0 && ci->y > 0 && ci->focus->cx >= 0) {
1091 /* We want a cursor */
1092 cx = x + ci->focus->cx;
1093 cy = y + ci->focus->cy;
1095 for (i = 0; i < h; i+= 2) {
1096 static const wint_t hilo = 0x2580; /* L'▀' */
1097 for (j = 0; j < w ; j+= 1) {
1098 unsigned char *p1 = buf + i*w*4 + j*4;
1099 unsigned char *p2 = buf + (i+1)*w*4 + j*4;
1100 int rgb1[3] = { p1[0]*1000/255, p1[1]*1000/255, p1[2]*1000/255 };
1101 int rgb2[3] = { p2[0]*1000/255, p2[1]*1000/255, p2[2]*1000/255 };
1102 int fg = find_col(dd, rgb1);
1103 int bg = find_col(dd, rgb2);
1105 if (p1[3] < 128 || p2[3] < 128) {
1109 struct pane *pn2 = ci->focus;
1110 PANEL *pan = pane_panel(pn2, NULL);
1112 while (!pan && pn2->parent != pn2) {
1114 pan = pane_panel(pn2, NULL);
1117 wgetbkgrnd(panel_window(pan), &cc);
1118 if (cc.ext_color == 0)
1119 /* default. This is light
1120 * gray rather then white,
1121 * but I think it is a good
1126 pair_content(cc.ext_color, &f, &b);
1133 /* FIXME this doesn't work because
1134 * render-line knows too much and gets it wrong.
1136 if (cx == x+j && cy == y + (i/2))
1137 ncurses_text(ci->focus, p, 'X', 0,
1141 ncurses_text(ci->focus, p, hilo, 0,
1142 to_pair(dd, fg, bg),
1149 DestroyMagickWand(wd);
1151 pane_damaged(ci->home, DAMAGED_POSTORDER);
1156 DEF_CMD(nc_image_size)
1158 MagickBooleanType status;
1164 if (strstarts(ci->str, "file:")) {
1165 wd = NewMagickWand();
1166 status = MagickReadImage(wd, ci->str + 5);
1167 if (status == MagickFalse) {
1168 DestroyMagickWand(wd);
1171 } else if (strstarts(ci->str, "comm:")) {
1172 struct call_return cr;
1173 wd = NewMagickWand();
1174 cr = call_ret(bytes, ci->str+5, ci->focus);
1176 DestroyMagickWand(wd);
1179 status = MagickReadImageBlob(wd, cr.s, cr.i);
1181 if (status == MagickFalse) {
1182 DestroyMagickWand(wd);
1188 MagickAutoOrientImage(wd);
1189 ih = MagickGetImageHeight(wd);
1190 iw = MagickGetImageWidth(wd);
1192 DestroyMagickWand(wd);
1193 comm_call(ci->comm2, "callback:size", ci->focus,
1194 0, NULL, NULL, 0, NULL, NULL,
1199 DEF_CMD(nc_refresh_size)
1201 struct pane *p = ci->home;
1204 getmaxyx(stdscr, p->h, p->w);
1209 DEF_CMD(nc_refresh_post)
1211 struct pane *p = ci->home;
1212 struct display_data *dd = p->data;
1221 /* Need to ensure stacking order and panel y,x position
1222 * is correct. FIXME it would be good if we could skip this
1225 pan = panel_above(NULL);
1228 p1 = (struct pane*) panel_userptr(pan);
1229 for (;(pan2 = panel_above(pan)) != NULL; pan = pan2) {
1230 struct pane *p2 = (struct pane*)panel_userptr(pan2);
1231 p1 = (struct pane*)panel_userptr(pan);
1235 if (p1->abs_z < p2->abs_z)
1237 if (p1->abs_z == p2->abs_z &&
1240 /* pan needs to be above pan2. All we can do is move it to
1241 * the top. Anything that needs to be above it will eventually
1245 /* Now the panel below pan might need to be over pan2 too... */
1246 pan = panel_below(pan2);
1251 /* As we need to crop pane against their parents, we cannot simply
1252 * use update_panels(). Instead we copy each to stdscr and refresh
1255 for (pan = NULL; (pan = panel_above(pan)) != NULL; ) {
1257 struct xy src, dest, destend;
1260 p1 = (void*)panel_userptr(pan);
1263 dest = pane_mapxy(p1, p, 0, 0, True);
1264 destend = pane_mapxy(p1, p, p1->w, p1->h, True);
1265 src = pane_mapxy(p1, p, 0, 0, False);
1266 src.x = dest.x - src.x;
1267 src.y = dest.y - src.y;
1268 win = panel_window(pan);
1269 getmaxyx(win, h, w);
1270 /* guard again accessing beyond boundary of win */
1271 if (destend.x > dest.x + (w - src.x))
1272 destend.x = dest.x + (w - src.x);
1273 if (destend.y > dest.y + (h - src.y))
1274 destend.y = dest.y - (h - src.y);
1275 copywin(win, stdscr, src.y, src.x,
1276 dest.y, dest.x, destend.y-1, destend.x-1, 0);
1278 /* place the cursor */
1281 while (p1 != p && (pan = pane_panel(p1, NULL)) == NULL)
1283 if (pan && p1->cx >= 0) {
1284 struct xy curs = pane_mapxy(p1, p, p1->cx, p1->cy, False);
1285 wmove(stdscr, curs.y, curs.x);
1286 } else if (p->cx >= 0)
1287 wmove(stdscr, p->cy, p->cx);
1289 record_screen(ci->home);
1293 static void ncurses_start(struct pane *p safe)
1295 struct display_data *dd = p->data;
1299 use_default_colors();
1305 intrflush(stdscr, FALSE);
1306 keypad(stdscr, TRUE);
1307 mousemask(BUTTON1_PRESSED | BUTTON1_RELEASED |
1308 BUTTON2_PRESSED | BUTTON2_RELEASED |
1309 BUTTON3_PRESSED | BUTTON3_RELEASED |
1310 BUTTON4_PRESSED | BUTTON4_RELEASED |
1311 BUTTON5_PRESSED | BUTTON5_RELEASED |
1312 BUTTON_CTRL | BUTTON_SHIFT | BUTTON_ALT |
1313 REPORT_MOUSE_POSITION, NULL);
1316 /* Enable bracketed-paste */
1317 fprintf(dd->scr_file, "\033[?2004h");
1318 fflush(dd->scr_file);
1321 getmaxyx(stdscr, rows, cols);
1322 pane_resize(p, 0, 0, cols, rows);
1325 static struct pane *ncurses_init(struct pane *ed safe,
1326 const char *tty, const char *term)
1330 struct display_data *dd;
1335 if (tty && strcmp(tty, "-") != 0)
1336 f = fopen(tty, "r+");
1338 f = fdopen(1, "r+");
1341 scr = newterm(term, f, f);
1345 p = pane_register(ed, 1, &ncurses_handle.c);
1351 dd->is_xterm = (term && strstarts(term, "xterm"));
1357 area = dd->attr_buf;
1358 dd->rs1 = tgetstr("rs1", &area);
1360 dd->rs1 = tgetstr("is1", &area);
1361 dd->rs2 = tgetstr("rs2", &area);
1363 dd->rs2 = tgetstr("is2", &area);
1364 dd->rs3 = tgetstr("rs3", &area);
1366 dd->rs3 = tgetstr("is3", &area);
1367 dd->clear = tgetstr("clear", &area);
1369 call("editor:request:all-displays", p);
1370 if (!prepare_recrep(p)) {
1371 call_comm("event:read", p, &input_handle, fileno(f));
1372 if (!tty || strcmp(tty, "-") == 0)
1373 call_comm("event:signal", p, &handle_winch, SIGWINCH);
1378 REDEF_CMD(handle_winch)
1380 struct pane *p = ci->home;
1381 struct display_data *dd = p->data;
1382 struct winsize size;
1383 ioctl(fileno(dd->scr_file), TIOCGWINSZ, &size);
1385 resize_term(size.ws_row, size.ws_col);
1387 pane_resize(p, 0, 0, size.ws_row, size.ws_col);
1391 DEF_CMD(force_redraw)
1393 struct pane *p = ci->home;
1397 /* full reset, as mosh sometimes gets confused */
1405 static void ncurses_text(struct pane *p safe, struct pane *display safe,
1406 wchar_t ch, int attr, int pair,
1407 short x, short y, short cursor)
1416 set_screen(display);
1418 if (pane_has_focus(p->z < 0 ? p->parent : p)) {
1419 /* Cursor is in-focus */
1420 struct xy curs = pane_mapxy(p, display, x, y, False);
1421 display->cx = curs.x;
1422 display->cy = curs.y;
1424 /* Cursor here, but not focus */
1425 attr = make_cursor(attr);
1428 cc.ext_color = pair;
1432 pan = pane_panel(p2, NULL);
1433 while (!pan && p2->parent != p2) {
1435 pan = pane_panel(p2, NULL);
1438 struct xy xy = pane_mapxy(p, p2, x, y, False);
1439 mvwadd_wch(panel_window(pan), xy.y, xy.x, &cc);
1443 static struct namelist {
1447 {KEY_DOWN, ":Down"},
1449 {KEY_LEFT, ":Left"},
1450 {KEY_RIGHT, ":Right"},
1451 {KEY_HOME, ":Home"},
1452 {KEY_BACKSPACE, ":Backspace"},
1453 {KEY_DL, ":DelLine"},
1454 {KEY_IL, ":InsLine"},
1457 {KEY_ENTER, ":Enter"},
1460 {KEY_NPAGE, ":Next"},
1461 {KEY_PPAGE, ":Prior"},
1463 {KEY_SDC, ":S:Del"},
1464 {KEY_SDL, ":S:DelLine"},
1465 {KEY_SEND, ":S:End"},
1466 {KEY_SHOME, ":S:Home"},
1467 {KEY_SLEFT, ":S:Left"},
1468 {KEY_SRIGHT, ":S:Right"},
1469 {KEY_BTAB, ":S:Tab"},
1473 { 0616, ":S:Prior"},
1475 { 01041, ":S:Home"},
1477 { 01066, ":S:Prior"},
1478 { 01015, ":S:Next"},
1480 { 01027, ":A:S:Home"},
1481 { 01022, ":A:S:End"},
1482 { 01046, ":A:S:Prior"},
1483 { 01047, ":A:S:Next"}, // ??
1485 { 01052, ":A:Prior"},
1486 { 01045, ":A:Next"},
1487 { 01026, ":A:Home"},
1490 { 01014, ":A:Down"},
1491 { 01040, ":A:Left"},
1492 { 01057, ":A:Right"},
1517 { 01114, ":Focus-in"},
1518 { 01115, ":Focus-out"},
1525 {'\177', ":Delete"},
1530 static char *find_name (struct namelist *l safe, wint_t c)
1533 for (i = 0; l[i].name; i++)
1539 static void send_key(int keytype, wint_t c, int alt, struct pane *p safe)
1541 struct display_data *dd = p->data;
1543 char buf[100];/* FIXME */
1545 char *a = alt ? ":A" : "";
1547 if (keytype == KEY_CODE_YES) {
1548 n = find_name(key_names, c);
1550 LOG("Unknown ncurses key 0o%o", c);
1551 sprintf(buf, "%sNcurs-%o", a, c);
1552 } else if (strstarts(n, ":Focus-"))
1553 /* Ignore focus changes for now */
1556 strcat(strcpy(buf, a), n);
1558 n = find_name(char_names, c);
1560 sprintf(buf, "%s%s", a, n);
1561 else if (c < ' ' || c == 0x7f)
1562 sprintf(buf, "%s:C-%c",
1565 sprintf(buf, "%s-%s", a, put_utf8(t, c));
1568 dd->last_event = time(NULL);
1571 call("Keystroke", p, 0, NULL, buf);
1575 static void do_send_mouse(struct pane *p safe, int x, int y, char *cmd safe,
1576 int button, char *mod, int type)
1579 struct display_data *dd = p->data;
1581 record_mouse(p, cmd, x, y);
1582 ret = call("Mouse-event", p, button, NULL, cmd, type, NULL, mod, x, y);
1583 if (type == 1 && !dd->report_position) {
1585 fprintf(dd->scr_file, "\033[?1002h");
1586 fflush(dd->scr_file);
1588 dd->report_position = 1;
1589 } else if (type == 3 && ret <= 0) {
1591 fprintf(dd->scr_file, "\033[?1002l");
1592 fflush(dd->scr_file);
1594 dd->report_position = 0;
1598 static void send_mouse(MEVENT *mev safe, struct pane *p safe)
1600 struct display_data *dd = p->data;
1606 /* MEVENT has lots of bits. We want a few numbers */
1607 for (b = 1 ; b <= (NCURSES_MOUSE_VERSION <= 1 ? 3 : 5); b++) {
1608 mmask_t s = mev->bstate;
1613 if (s & BUTTON_SHIFT) modf |= 1;
1614 if (s & BUTTON_CTRL) modf |= 2;
1615 if (s & BUTTON_ALT) modf |= 4;
1617 case 0: mod = ""; break;
1618 case 1: mod = ":S"; break;
1619 case 2: mod = ":C"; break;
1620 case 3: mod = ":C:S"; break;
1621 case 4: mod = ":A"; break;
1622 case 5: mod = ":A:S"; break;
1623 case 6: mod = ":A:C"; break;
1624 case 7: mod = ":A:C:S"; break;
1626 if (BUTTON_PRESS(s, b))
1627 action = "%s:Press-%d";
1628 else if (BUTTON_RELEASE(s, b)) {
1629 action = "%s:Release-%d";
1630 /* Modifiers only reported on button Press */
1634 snprintf(buf, sizeof(buf), action, mod, b);
1635 dd->last_event = time(NULL);
1636 do_send_mouse(p, x, y, buf, b, mod, BUTTON_PRESS(s,b) ? 1 : 2);
1638 if ((mev->bstate & REPORT_MOUSE_POSITION) &&
1639 dd->report_position)
1640 /* Motion doesn't update last_event */
1641 do_send_mouse(p, x, y, ":Motion", 0, "", 3);
1644 static void paste_start(struct pane *home safe)
1646 struct display_data *dd = home->data;
1648 dd->paste_start = time(NULL);
1649 buf_init(&dd->paste_buf);
1652 static void paste_flush(struct pane *home safe)
1654 struct display_data *dd = home->data;
1656 if (!dd->paste_start)
1658 free(dd->paste_latest);
1659 dd->paste_latest = buf_final(&dd->paste_buf);
1660 if (dd->paste_buf.len > 0)
1661 dd->paste_pending = 1;
1662 dd->paste_start = 0;
1665 static bool paste_recv(struct pane *home safe, int is_keycode, wint_t ch)
1667 struct display_data *dd = home->data;
1669 if (dd->paste_start == 0)
1672 if (dd->paste_start < now || dd->paste_start > now + 2 ||
1673 is_keycode != OK || ch == KEY_MOUSE) {
1679 /* I really don't want carriage-returns... */
1681 buf_append(&dd->paste_buf, ch);
1682 if (ch == '~' && dd->paste_buf.len >= 6 &&
1684 buf_final(&dd->paste_buf) + dd->paste_buf.len - 6) == 0) {
1685 dd->paste_buf.len -= 6;
1691 DEF_CMD(nc_get_paste)
1693 struct display_data *dd = ci->home->data;
1695 comm_call(ci->comm2, "cb", ci->focus,
1696 dd->paste_start, NULL, dd->paste_latest);
1700 REDEF_CMD(input_handle)
1702 struct pane *p = ci->home;
1703 struct display_data *dd = p->data;
1704 static const char paste_seq[] = "\e[200~";
1707 int have_escape = 0;
1712 while ((is_keycode = get_wch(&c)) != ERR) {
1713 if (dd->suspended && c != KEY_MOUSE) {
1714 dd->suspended = False;
1716 call_comm("event:free", p, &ns_resume);
1717 /* swallow the key */
1720 if (paste_recv(p, is_keycode, c))
1722 if (c == KEY_MOUSE) {
1725 while (getmouse(&mev) != ERR) {
1726 if (dd->paste_pending &&
1727 mev.bstate == REPORT_MOUSE_POSITION) {
1728 /* xcfe-terminal is a bit weird.
1729 * It captures middle-press to
1730 * sanitise the paste, but lets
1731 * middle-release though. It comes
1732 * here as REPORT_MOUSE_POSTION
1733 * and we can use that to find the
1734 * position of the paste.
1735 * '6' is an unused button, and
1736 * ensures lib-input doesn't expect
1737 * matching press/release
1739 call("Mouse-event", ci->home,
1741 6, NULL, NULL, mev.x, mev.y);
1742 dd->paste_pending = 0;
1744 send_mouse(&mev, p);
1746 } else if (c == (wint_t)paste_seq[have_escape]) {
1748 if (!paste_seq[have_escape]) {
1752 } else if (have_escape == 1) {
1753 send_key(is_keycode, c, 1, p);
1755 } else if (have_escape) {
1756 send_key(OK, paste_seq[1], 1, p);
1757 for (i = 2; i < have_escape; i++)
1758 send_key(OK, paste_seq[i], 0, p);
1759 send_key(is_keycode, c, 0, p);
1762 send_key(is_keycode, c, 0, p);
1764 /* Don't know what other code might have done,
1765 * so re-set the screen
1769 if (have_escape == 1)
1770 send_key(is_keycode, '\e', 0, p);
1771 else if (have_escape > 1) {
1772 send_key(OK, paste_seq[1], 1, p);
1773 for (i = 2; i < have_escape; i++)
1774 send_key(OK, paste_seq[i], 0, p);
1776 if (dd->paste_pending == 2) {
1777 /* no mouse event to give postion, so treat as keyboard */
1778 call("Keystroke", ci->home, 0, NULL, ":Paste");
1779 dd->paste_pending = 0;
1780 } else if (dd->paste_pending == 1) {
1781 /* Wait for possible mouse-position update. */
1782 dd->paste_pending = 2;
1783 call_comm("event:timer", p, &input_handle, 200);
1788 DEF_CMD(display_ncurses)
1791 struct pane *ed = pane_root(ci->focus);
1792 const char *tty = ci->str;
1793 const char *term = ci->str2;
1796 term = "xterm-256color";
1798 p = ncurses_init(ed, tty, term);
1800 p = call_ret(pane, "editor:activate-display", p);
1801 if (p && ci->focus != ed)
1802 /* Assume ci->focus is a document */
1803 p = home_call_ret(pane, ci->focus, "doc:attach-view", p, 1);
1805 return comm_call(ci->comm2, "callback:display", p);
1810 void edlib_init(struct pane *ed safe)
1812 call_comm("global-set-command", ed, &display_ncurses, 0, NULL,
1813 "attach-display-ncurses");
1815 nc_map = key_alloc();
1816 key_add(nc_map, "window:refresh", &force_redraw);
1817 key_add(nc_map, "window:close", &nc_close_display);
1818 key_add(nc_map, "window:external-viewer", &nc_external_viewer);
1819 key_add(nc_map, "Close", &nc_close);
1820 key_add(nc_map, "Draw:clear", &nc_clear);
1821 key_add(nc_map, "Draw:text-size", &nc_text_size);
1822 key_add(nc_map, "Draw:text", &nc_draw_text);
1824 key_add(nc_map, "Draw:image", &nc_draw_image);
1825 key_add(nc_map, "Draw:image-size", &nc_image_size);
1827 key_add(nc_map, "Refresh:size", &nc_refresh_size);
1828 key_add(nc_map, "Refresh:postorder", &nc_refresh_post);
1829 key_add(nc_map, "Paste:get", &nc_get_paste);
1830 key_add(nc_map, "all-displays", &nc_notify_display);
1831 key_add(nc_map, "Sig:Winch", &handle_winch);
1832 key_add(nc_map, "Notify:Close", &nc_pane_close);