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>
37 #include <wand/MagickWand.h>
39 // enums confuse sparse...
40 #define MagickBooleanType int
51 typedef char hash_t[MD5_DIGEST_SIZE*2+1];
55 #undef NCURSES_OK_ADDR
56 #define NCURSES_OK_ADDR(p) ((void*)0 != NCURSES_CAST(const void *, (p)))
66 struct col_hash *col_hash;
77 char *rs1, *rs2, *rs3, *clear;
83 char last_screen[MD5_DIGEST_SIZE*2+1];
84 char next_screen[MD5_DIGEST_SIZE*2+1];
85 /* The next event to generate when idle */
86 enum { DoNil, DoMouse, DoKey, DoCheck, DoClose} next_event;
90 int clears; /* counts of Draw:clear events */
94 static SCREEN *current_screen;
95 static void ncurses_text(struct pane *p safe, struct pane *display safe,
96 wchar_t ch, int attr, int pair,
97 short x, short y, short cursor);
98 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home);
99 DEF_CMD(input_handle);
100 DEF_CMD(handle_winch);
101 static struct map *nc_map;
102 DEF_LOOKUP_CMD(ncurses_handle, nc_map);
104 static struct display_data *current_dd;
105 static void set_screen(struct pane *p)
107 struct display_data *dd;
108 extern void *_nc_globals[100];
110 static int index = -1, offset=0;
113 if (current_screen && index >= 0)
114 _nc_globals[index] = NULL;
115 current_screen = NULL;
122 if (dd->scr == current_screen)
127 for (i=0; i<100; i++)
128 if (_nc_globals[i] < (void*)stdscr &&
129 _nc_globals[i]+4*(sizeof(void*)) >= (void*)stdscr) {
130 /* This is _nc_windowlist */
132 offset = ((void*)stdscr) - _nc_globals[i];
137 current_screen = dd->scr;
139 _nc_globals[index] = ((void*)stdscr) - offset;
146 static bool parse_event(struct pane *p safe);
147 static bool prepare_recrep(struct pane *p safe)
149 struct display_data *dd = p->data;
152 name = getenv("EDLIB_RECORD");
154 dd->log = fopen(name, "w");
155 name = getenv("EDLIB_REPLAY");
157 dd->input = fopen(name, "r");
158 if (getenv("EDLIB_PAUSE"))
159 sleep(atoi(getenv("EDLIB_PAUSE")));
167 static void close_recrep(struct pane *p safe)
169 struct display_data *dd = p->data;
172 fprintf(dd->log, "Close %d\n", dd->clears);
177 static void record_key(struct pane *p safe, char *key safe)
179 struct display_data *dd = p->data;
184 if (!strchr(key, '"'))
186 else if (!strchr(key, '\''))
188 else if (!strchr(key, '/'))
192 fprintf(dd->log, "Key %c%s%c\n", q,key,q);
196 static void record_mouse(struct pane *p safe, char *key safe, int x, int y)
198 struct display_data *dd = p->data;
202 if (!strchr(key, '"'))
204 else if (!strchr(key, '\''))
206 else if (!strchr(key, '/'))
210 fprintf(dd->log, "Mouse %c%s%c %d,%d\n", q,key,q, x, y);
214 static void record_screen(struct pane *p safe)
216 struct display_data *dd = p->data;
217 struct md5_state ctx;
218 uint16_t buf[CCHARW_MAX+5];
219 char out[MD5_DIGEST_SIZE*2+1];
222 if (!dd->log && !(dd->input && dd->next_event == DoCheck))
226 for (r = 0; r < p->h; r++)
227 for (c = 0; c < p->w; c++) {
229 wchar_t wc[CCHARW_MAX+2];
234 mvwin_wch(stdscr, r, c, &cc);
235 getcchar(&cc, wc, &a, &color, NULL);
236 pair_content(color, &fg, &bg);
237 buf[0] = htole16(fg);
238 buf[1] = htole16(bg);
239 for (l = 0; l < CCHARW_MAX && wc[l]; l++)
240 buf[l+3] = htole16(wc[l]);
242 LOG("%d,%d %d:%d:%d:%d %lc", c,r,fg,bg,l,wc[0], wc[0] > ' ' ? wc[0] : '?');
243 md5_update(&ctx, (uint8_t*)buf,
244 (l+3) * sizeof(uint16_t));
246 md5_final_txt(&ctx, out);
248 fprintf(dd->log, "Display %d,%d %s", p->w, p->h, out);
249 strcpy(dd->last_screen, out);
251 fprintf(dd->log, " %d,%d", p->cx, p->cy);
252 fprintf(dd->log, "\n");
255 if (dd->input && dd->input_sleeping) {
256 char *delay = getenv("EDLIB_REPLAY_DELAY");
257 call_comm("event:free", p, &next_evt);
259 call_comm("event:timer", p, &next_evt, atoi(delay));
261 call_comm("editor-on-idle", p, &next_evt);
265 static inline int match(char *line safe, char *w safe)
267 return strncmp(line, w, strlen(w)) == 0;
270 static char *copy_quote(char *line safe, char *buf safe)
276 if (q != '"' && q != '\'' && q != '/')
278 while (*line != q && *line)
286 static char *get_coord(char *line safe, struct xy *co safe)
293 v = strtol(line, &ep, 10);
294 if (!ep || ep == line || *ep != ',')
298 v = strtol(line, &ep, 10);
299 if (!ep || ep == line || (*ep && *ep != ' ' && *ep != '\n'))
305 static char *get_hash(char *line safe, hash_t hash safe)
310 for (i = 0; i < MD5_DIGEST_SIZE*2 && *line; i++)
317 static bool parse_event(struct pane *p safe)
319 struct display_data *dd = p->data;
323 dd->next_event = DoNil;
325 fgets(line, sizeof(line)-1, dd->input) == NULL)
327 else if (match(line, "Key ")) {
328 if (!copy_quote(line+4, dd->event_info))
330 dd->next_event = DoKey;
331 } else if (match(line, "Mouse ")) {
332 char *f = copy_quote(line+6, dd->event_info);
335 f = get_coord(f, &dd->event_pos);
338 dd->next_event = DoMouse;
339 } else if (match(line, "Display ")) {
340 char *f = get_coord(line+8, &dd->event_pos);
343 f = get_hash(f, dd->next_screen);
344 dd->next_event = DoCheck;
345 } else if (match(line, "Close")) {
346 dd->next_event = DoClose;
348 LOG("parse %s", line);
350 dd->input_sleeping = 1;
351 if (dd->next_event != DoCheck) {
352 char *delay = getenv("EDLIB_REPLAY_DELAY");
354 call_comm("event:timer", p, &next_evt, atoi(delay));
356 call_comm("editor-on-idle", p, &next_evt);
358 call_comm("event:timer", p, &next_evt, 10*1000);
364 struct pane *p = ci->home;
365 struct display_data *dd = p->data;
366 int button = 0, type = 0;
368 dd->input_sleeping = 0;
369 switch(dd->next_event) {
371 record_key(p, dd->event_info);
372 call("Keystroke", p, 0, NULL, dd->event_info);
375 record_mouse(p, dd->event_info, dd->event_pos.x,
377 if (strstr(dd->event_info, ":Press"))
379 else if (strstr(dd->event_info, ":Release"))
381 else if (strstr(dd->event_info, ":Motion"))
383 if (type == 1 || type == 2) {
384 char *e = dd->event_info + strlen(dd->event_info) - 1;
387 call("Mouse-event", p, button, NULL, dd->event_info,
389 dd->event_pos.x, dd->event_pos.y);
392 /* No point checking, just do a diff against new trace log. */
393 /* not; (strcmp(dd->next_screen, dd->last_screen) != 0) */
396 call("event:deactivate", p);
400 call_comm("event:read", p, &input_handle, 0);
401 call_comm("event:signal", p, &handle_winch, SIGWINCH);
408 static inline bool prepare_recrep(struct pane *p safe) {return False;}
409 static inline void record_key(struct pane *p safe, char *key) {}
410 static inline void record_mouse(struct pane *p safe, char *key safe,
412 static inline void record_screen(struct pane *p safe) {}
413 static inline void close_recrep(struct pane *p safe) {}
418 struct call_return *cr = container_of(ci->comm, struct call_return, c);
424 static void ncurses_end(struct pane *p safe);
426 DEF_CMD(nc_close_display)
428 /* If this is only display, then refuse to close this one */
429 struct call_return cr;
430 struct display_data *dd = ci->home->data;
433 call("Message", ci->focus, 0, NULL, dd->noclose);
439 call_comm("editor:notify:all-displays", ci->focus, &cr.c);
441 /* Need to call ncurses_end() before we send a Notify:Close
442 * notification, else server exists too early
444 ncurses_end(ci->home);
445 pane_close(ci->home);
447 call("Message", ci->focus, 0, NULL,
448 "Cannot close only window.");
452 DEF_CMD(nc_set_noclose)
454 struct display_data *dd = ci->home->data;
459 dd->noclose = strdup(ci->str);
463 static int nc_putc(int ch)
466 fputc(ch, current_dd->scr_file);
470 static char *fnormalize(struct pane *p safe, const char *str) safe
472 char *ret = strsave(p, str);
475 for (cp = ret ; cp && *cp ; cp++)
477 !strchr("/_-+=.,@#", *cp))
478 /* Don't like this char */
483 DEF_CMD(nc_external_viewer)
485 struct pane *p = ci->home;
486 struct display_data *dd = p->data;
487 char *disp = pane_attr_get(p, "DISPLAY");
488 char *disp_auth = pane_attr_get(p, "XAUTHORITY");
489 char *remote = pane_attr_get(p, "REMOTE_SESSION");
491 const char *path = ci->str;
500 switch (pid = fork()) {
504 setenv("DISPLAY", disp, 1);
506 setenv("XAUTHORITY", disp_auth, 1);
507 fd = open("/dev/null", O_RDWR);
515 execlp("xdg-open", "xdg-open", path, NULL);
517 default: /* parent */
518 /* FIXME record pid?? */
523 /* handle no-display case */
524 if (remote && strcmp(remote, "yes") == 0 &&
526 gethostname(buf, sizeof(buf)) == 0) {
527 struct addrinfo *res;
528 const struct addrinfo hints = {
529 .ai_flags = AI_CANONNAME,
531 if (getaddrinfo(buf, NULL, &hints, &res) == 0 &&
532 res && res->ai_canonname)
533 fqdn = strdup(res->ai_canonname);
538 ioctl(fileno(dd->scr_file), FIONREAD, &n);
540 n -= read(fileno(dd->scr_file), buf,
541 n <= (int)sizeof(buf) ? n : (int)sizeof(buf));
544 /* Endwin doesn't seem to reset properly, at least on xfce-terminal.
548 tputs(dd->rs1, 1, nc_putc);
550 tputs(dd->rs2, 1, nc_putc);
552 tputs(dd->rs3, 1, nc_putc);
554 tputs(dd->clear, 1, nc_putc);
555 fflush(dd->scr_file);
557 fprintf(dd->scr_file, "# Consider copy-pasting following\n");
558 if (fqdn && path[0] == '/') {
559 /* File will not be local for the user, so help them copy it. */
560 const char *tmp = fnormalize(p, ci->str2 ?: "XXXXXX");
561 const char *fname = fnormalize(p, ci->str);
563 if (strcmp(fname, ci->str) != 0)
564 /* file name had unusuable chars, need to create safe name */
565 link(ci->str, fname);
566 fprintf(dd->scr_file, "f=`mktemp --tmpdir %s`; scp %s:%s $f ; ",
570 fprintf(dd->scr_file, "xdg-open %s\n", path);
571 fprintf(dd->scr_file, "# Press Enter to continue\n");
572 n = read(fileno(dd->scr_file), buf, sizeof(buf));
578 static void ncurses_stop(struct pane *p safe)
580 struct display_data *dd = p->data;
583 /* disable bracketed-paste */
584 fprintf(dd->scr_file, "\033[?2004l");
585 fflush(dd->scr_file);
588 free(buf_final(&dd->paste_buf));
590 free(dd->paste_latest);
591 dd->paste_latest = NULL;
595 tputs(dd->rs1, 1, nc_putc);
597 tputs(dd->rs2, 1, nc_putc);
599 tputs(dd->rs3, 1, nc_putc);
600 fflush(dd->scr_file);
603 static void ncurses_end(struct pane *p safe)
605 struct display_data *dd = p->data;
609 dd->did_close = True;
617 * hash table for colours and pairs
618 * key is r,g,b (0-1000) in 10bit fields,
619 * or fg,bg in 16 bit fields with bit 31 set
620 * content is colour number of colour pair number.
621 * We never delete entries, unless we delete everything.
628 #define COL_KEY(r,g,b) ((r<<20) | (g<<10) | b)
629 #define PAIR_KEY(fg, bg) ((1<<31) | (fg<<16) | bg)
630 #define hash_key(k) ((((k) * 0x61C88647) >> 20) & 0xff)
633 int next_col, next_pair;
634 struct chash *tbl[256];
637 static struct col_hash *safe hash_init(struct display_data *dd safe)
640 dd->col_hash = malloc(sizeof(*dd->col_hash));
641 memset(dd->col_hash, 0, sizeof(*dd->col_hash));
642 dd->col_hash->next_col = 16;
643 dd->col_hash->next_pair = 1;
648 static void hash_free(struct display_data *dd safe)
657 for (h = 0; h < 255; h++)
658 while ((c = ch->tbl[h]) != NULL) {
659 ch->tbl[h] = c->next;
666 static int find_col(struct display_data *dd safe, int rgb[])
668 if (0 /* dynamic colours */) {
669 struct col_hash *ch = hash_init(dd);
670 int k = COL_KEY(rgb[0], rgb[1], rgb[2]);
674 for (c = ch->tbl[h]; c; c = c->next)
677 c = malloc(sizeof(*c));
679 c->content = ch->next_col++;
680 c->next = ch->tbl[h];
682 init_color(c->content, rgb[0], rgb[1], rgb[2]);
685 /* If colour is grey, map to 1 of 26 for 0, 232 to 255, 15
686 * The 24 grey shades have bit values from 8 to 238, so the
687 * gap to white is a little bigger, but that probably doesn't
689 * Otherwise map to 6x6x6 rgb cube from 16
690 * Actual colours are biased bright, at 0,95,135,175,215,255
691 * with a 95 gap at bottom and 40 elsewhere.
692 * So we divide 5 and 2 half ranges, and merge bottom 2.
697 //printf("want %d,%d,%d\n", rgb[0], rgb[1], rgb[2]);
698 if (abs(rgb[0] - rgb[1]) < 10 &&
699 abs(rgb[1] - rgb[2]) < 10) {
700 /* grey - within 1% */
701 int v = (rgb[0] + rgb[1] + rgb[2]) / 3;
703 /* We divide the space in 24 ranges surrounding
704 * the grey values, and 2 half-ranges near black
705 * and white. So add half a range - 1000/50 -
706 * then divide by 1000/25 to get a number from 0 to 25.
708 v = (v + 1000/50) / (1000/25);
710 return 0; /* black */
712 return 15; /* white */
713 //printf(" grey %d\n", v + 231);
714 /* grey shades are from 232 to 255 inclusive */
717 for (h = 0; h < 3; h++) {
720 v = (v + 1000/12) / (1000/6);
721 /* v is from 0 to 6, we want up to 5
722 * with 0 and 1 merged
729 //printf(" color %d\n", c + 16);
734 static int to_pair(struct display_data *dd safe, int fg, int bg)
736 struct col_hash *ch = hash_init(dd);
737 int k = PAIR_KEY(fg, bg);
741 for (c = ch->tbl[h]; c; c = c->next)
744 c = malloc(sizeof(*c));
746 c->content = ch->next_pair++;
747 c->next = ch->tbl[h];
749 init_pair(c->content, fg, bg);
753 static int cvt_attrs(struct pane *p safe, struct pane *home safe,
754 const char *attrs, int *pairp safe, bool use_parent)
756 struct display_data *dd = home->data;
761 int fg = COLOR_BLACK;
762 int bg = COLOR_WHITE+8;
767 } while (p->parent != p &&(pan = pane_panel(p, NULL)) == NULL);
768 if (pan && use_parent) {
769 /* Get 'default colours for this pane - set at clear */
770 int at = getbkgd(panel_window(pan));
771 int pair = PAIR_NUMBER(at);
773 pair_content(pair, &dfg, &dbg);
789 strncpy(tmp, a, c-a);
791 if (strcmp(tmp, "inverse")==0) attr |= A_STANDOUT;
792 else if (strcmp(tmp, "noinverse")==0) attr &= ~A_STANDOUT;
793 else if (strcmp(tmp, "bold")==0) attr |= A_BOLD;
794 else if (strcmp(tmp, "nobold")==0) attr &= ~A_BOLD;
795 else if (strcmp(tmp, "underline")==0) attr |= A_UNDERLINE;
796 else if (strcmp(tmp, "nounderline")==0) attr &= ~A_UNDERLINE;
797 else if (strncmp(tmp, "fg:", 3) == 0) {
798 struct call_return cr =
799 call_ret(all, "colour:map", home,
801 int rgb[3] = {cr.i, cr.i2, cr.x};
802 fg = find_col(dd, rgb);
803 } else if (strncmp(tmp, "bg:", 3) == 0) {
804 struct call_return cr =
805 call_ret(all, "colour:map", home,
807 int rgb[3] = {cr.i, cr.i2, cr.x};
808 bg = find_col(dd, rgb);
812 if (fg != COLOR_BLACK || bg != COLOR_WHITE+8)
813 *pairp = to_pair(dd, fg, bg);
817 static int make_cursor(int attr)
819 return attr ^ A_UNDERLINE;
822 DEF_CMD(nc_notify_display)
824 struct display_data *dd = ci->home->data;
825 comm_call(ci->comm2, "callback:display", ci->home, dd->last_event);
831 struct pane *p = ci->home;
832 struct display_data *dd = p->data;
835 fclose(dd->scr_file);
839 DEF_CMD(nc_pane_close)
843 set_screen(ci->home);
844 while ((pan = panel_above(pan)) != NULL)
845 if (panel_userptr(pan) == ci->focus)
848 WINDOW *win = panel_window(pan);
851 pane_damaged(ci->home, DAMAGED_POSTORDER);
856 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home)
860 while ((pan = panel_above(pan)) != NULL)
861 if (panel_userptr(pan) == p)
867 pan = new_panel(newwin(p->h, p->w, 0, 0));
868 set_panel_userptr(pan, p);
869 pane_add_notify(home, p, "Notify:Close");
876 struct pane *p = ci->home;
877 struct display_data *dd = p->data;
880 int attr = cvt_attrs(ci->focus, p, ci->str, &pair, ci->str == NULL);
886 panel = pane_panel(ci->focus, p);
889 win = panel_window(panel);
891 if (h != ci->focus->h || w != ci->focus->w) {
892 wresize(win, ci->focus->h, ci->focus->w);
893 replace_panel(panel, win);
898 wbkgrndset(win, &cc);
902 pane_damaged(p, DAMAGED_POSTORDER);
906 DEF_CMD(nc_text_size)
908 int max_space = ci->num;
911 const char *str = ci->str;
915 while (str[0] != 0) {
916 wint_t wc = get_utf8(&str, NULL);
924 if (size <= max_space)
925 max_bytes = str - ci->str;
927 return comm_call(ci->comm2, "callback:size", ci->focus,
928 max_bytes, NULL, NULL,
929 0, NULL, NULL, size, 1);
932 DEF_CMD(nc_draw_text)
934 struct pane *p = ci->home;
936 int attr = cvt_attrs(ci->focus, p, ci->str2, &pair, True);
937 int cursor_offset = ci->num;
938 short x = ci->x, y = ci->y;
939 const char *str = ci->str;
944 while (str[0] != 0) {
945 int precurs = str <= ci->str + cursor_offset;
946 wint_t wc = get_utf8(&str, NULL);
948 if (wc == WEOF || wc == WERR)
953 if (precurs && str > ci->str + cursor_offset)
954 ncurses_text(ci->focus, p, wc, attr, pair, x, y, 1);
956 ncurses_text(ci->focus, p, wc, attr, pair, x, y, 0);
959 if (str == ci->str + cursor_offset)
960 ncurses_text(ci->focus, p, ' ', 0, 0, x, y, 1);
961 pane_damaged(p, DAMAGED_POSTORDER);
965 DEF_CMD(nc_draw_image)
967 /* 'str' identifies the image. Options are:
968 * file:filename - load file from fs
969 * comm:command - run command collecting bytes
970 * 'num' is '16' if image should be stretched to fill pane
971 * Otherwise it is the 'or' of
972 * 0,1,2 for left/middle/right in x direction
973 * 0,4,8 for top/middle/bottom in y direction
974 * only one of these can be used as image will fill pane
975 * in other direction.
976 * If 'x' and 'y' are both positive, draw cursor box at
977 * p->cx, p->cy of a size so that 'x' will fit across and
980 struct pane *p = ci->home;
981 struct display_data *dd = p->data;
983 bool stretch = ci->num & 16;
985 int w = ci->focus->w, h = ci->focus->h * 2;
986 int cx = -1, cy = -1;
987 MagickBooleanType status;
994 if (strncmp(ci->str, "file:", 5) == 0) {
995 wd = NewMagickWand();
996 status = MagickReadImage(wd, ci->str + 5);
997 if (status == MagickFalse) {
998 DestroyMagickWand(wd);
1001 } else if (strncmp(ci->str, "comm:", 5) == 0) {
1002 struct call_return cr;
1003 wd = NewMagickWand();
1004 cr = call_ret(bytes, ci->str+5, ci->focus, 0, NULL, ci->str2);
1006 DestroyMagickWand(wd);
1009 status = MagickReadImageBlob(wd, cr.s, cr.i);
1011 if (status == MagickFalse) {
1012 DestroyMagickWand(wd);
1018 MagickAutoOrientImage(wd);
1020 int ih = MagickGetImageHeight(wd);
1021 int iw = MagickGetImageWidth(wd);
1023 if (iw <= 0 || iw <= 0) {
1024 DestroyMagickWand(wd);
1027 if (iw * h > ih * w) {
1028 /* Image is wider than space, use less height */
1030 switch(pos & (8+4)) {
1031 case 4: /* center */
1032 y = (h - ih) / 2; break;
1033 case 8: /* bottom */
1036 /* Keep 'h' even! */
1039 /* image is too tall, use less width */
1041 switch (pos & (1+2)) {
1042 case 1: /* center */
1043 x = (w - iw) / 2; break;
1050 MagickAdaptiveResizeImage(wd, w, h);
1051 buf = malloc(h * w * 4);
1052 MagickExportImagePixels(wd, 0, 0, w, h, "RGBA", CharPixel, buf);
1054 if (ci->x > 0 && ci->y > 0 && ci->focus->cx >= 0) {
1055 /* We want a cursor */
1056 cx = x + ci->focus->cx;
1057 cy = y + ci->focus->cy;
1059 for (i = 0; i < h; i+= 2) {
1060 static wint_t hilo = 0x2580; /* L'▀' */
1061 for (j = 0; j < w ; j+= 1) {
1062 unsigned char *p1 = buf + i*w*4 + j*4;
1063 unsigned char *p2 = buf + (i+1)*w*4 + j*4;
1064 int rgb1[3] = { p1[0]*1000/255, p1[1]*1000/255, p1[2]*1000/255 };
1065 int rgb2[3] = { p2[0]*1000/255, p2[1]*1000/255, p2[2]*1000/255 };
1066 int fg = find_col(dd, rgb1);
1067 int bg = find_col(dd, rgb2);
1069 if (p1[3] < 128 || p2[3] < 128) {
1073 struct pane *pn2 = ci->focus;
1074 PANEL *pan = pane_panel(pn2, NULL);
1076 while (!pan && pn2->parent != pn2) {
1078 pan = pane_panel(pn2, NULL);
1081 wgetbkgrnd(panel_window(pan), &cc);
1082 if (cc.ext_color == 0)
1083 /* default. This is light
1084 * gray rather then white,
1085 * but I think it is a good
1090 pair_content(cc.ext_color, &f, &b);
1097 /* FIXME this doesn't work because
1098 * render-line knows too much and gets it wrong.
1100 if (cx == x+j && cy == y + (i/2))
1101 ncurses_text(ci->focus, p, 'X', 0,
1105 ncurses_text(ci->focus, p, hilo, 0,
1106 to_pair(dd, fg, bg),
1113 DestroyMagickWand(wd);
1115 pane_damaged(ci->home, DAMAGED_POSTORDER);
1120 DEF_CMD(nc_image_size)
1122 MagickBooleanType status;
1128 if (strncmp(ci->str, "file:", 5) == 0) {
1129 wd = NewMagickWand();
1130 status = MagickReadImage(wd, ci->str + 5);
1131 if (status == MagickFalse) {
1132 DestroyMagickWand(wd);
1135 } else if (strncmp(ci->str, "comm:", 5) == 0) {
1136 struct call_return cr;
1137 wd = NewMagickWand();
1138 cr = call_ret(bytes, ci->str+5, ci->focus, 0, NULL, ci->str2);
1140 DestroyMagickWand(wd);
1143 status = MagickReadImageBlob(wd, cr.s, cr.i);
1145 if (status == MagickFalse) {
1146 DestroyMagickWand(wd);
1152 MagickAutoOrientImage(wd);
1153 ih = MagickGetImageHeight(wd);
1154 iw = MagickGetImageWidth(wd);
1156 DestroyMagickWand(wd);
1157 comm_call(ci->comm2, "callback:size", ci->focus,
1158 0, NULL, NULL, 0, NULL, NULL,
1163 DEF_CMD(nc_refresh_size)
1165 struct pane *p = ci->home;
1168 getmaxyx(stdscr, p->h, p->w);
1173 DEF_CMD(nc_refresh_post)
1175 struct pane *p = ci->home;
1181 /* Need to ensure stacking order and panel y,x position
1182 * is correct. FIXME it would be good if we could skip this
1185 pan = panel_above(NULL);
1188 p1 = (struct pane*) panel_userptr(pan);
1189 for (;(pan2 = panel_above(pan)) != NULL; pan = pan2) {
1190 struct pane *p2 = (struct pane*)panel_userptr(pan2);
1191 p1 = (struct pane*)panel_userptr(pan);
1195 if (p1->abs_z < p2->abs_z)
1197 if (p1->abs_z == p2->abs_z &&
1200 /* pan needs to be above pan2. All we can do is move it to
1201 * the top. Anything that needs to be above it will eventually
1205 /* Now the panel below pan might need to be over pan2 too... */
1206 pan = panel_below(pan2);
1211 /* As we need to crop pane against their parents, we cannot simply
1212 * use update_panels(). Instead we copy each to stdscr and refresh
1215 for (pan = NULL; (pan = panel_above(pan)) != NULL; ) {
1217 struct xy src, dest, destend;
1220 p1 = (void*)panel_userptr(pan);
1223 dest = pane_mapxy(p1, p, 0, 0, True);
1224 destend = pane_mapxy(p1, p, p1->w, p1->h, True);
1225 src = pane_mapxy(p1, p, 0, 0, False);
1226 src.x = dest.x - src.x;
1227 src.y = dest.y - src.y;
1228 win = panel_window(pan);
1229 getmaxyx(win, h, w);
1230 /* guard again accessing beyond boundary of win */
1231 if (destend.x > dest.x + (w - src.x))
1232 destend.x = dest.x + (w - src.x);
1233 if (destend.y > dest.y + (h - src.y))
1234 destend.y = dest.y - (h - src.y);
1235 copywin(win, stdscr, src.y, src.x,
1236 dest.y, dest.x, destend.y-1, destend.x-1, 0);
1238 /* place the cursor */
1241 while (p1 != p && (pan = pane_panel(p1, NULL)) == NULL)
1243 if (pan && p1->cx >= 0) {
1244 struct xy curs = pane_mapxy(p1, p, p1->cx, p1->cy, False);
1245 wmove(stdscr, curs.y, curs.x);
1246 } else if (p->cx >= 0)
1247 wmove(stdscr, p->cy, p->cx);
1249 record_screen(ci->home);
1253 static void ncurses_start(struct pane *p safe)
1255 struct display_data *dd = p->data;
1259 use_default_colors();
1265 intrflush(stdscr, FALSE);
1266 keypad(stdscr, TRUE);
1267 mousemask(BUTTON1_PRESSED | BUTTON1_RELEASED |
1268 BUTTON2_PRESSED | BUTTON2_RELEASED |
1269 BUTTON3_PRESSED | BUTTON3_RELEASED |
1270 BUTTON4_PRESSED | BUTTON4_RELEASED |
1271 BUTTON5_PRESSED | BUTTON5_RELEASED |
1272 BUTTON_CTRL | BUTTON_SHIFT | BUTTON_ALT |
1273 REPORT_MOUSE_POSITION, NULL);
1276 /* Enable bracketed-paste */
1277 fprintf(dd->scr_file, "\033[?2004h");
1278 fflush(dd->scr_file);
1281 getmaxyx(stdscr, rows, cols);
1282 pane_resize(p, 0, 0, cols, rows);
1285 static struct pane *ncurses_init(struct pane *ed,
1286 const char *tty, const char *term)
1290 struct display_data *dd;
1295 if (tty && strcmp(tty, "-") != 0)
1296 f = fopen(tty, "r+");
1298 f = fdopen(1, "r+");
1301 scr = newterm(term, f, f);
1308 dd->is_xterm = (term && strncmp(term, "xterm", 5) == 0);
1310 p = pane_register(ed, 1, &ncurses_handle.c, dd);
1319 area = dd->attr_buf;
1320 dd->rs1 = tgetstr("rs1", &area);
1322 dd->rs1 = tgetstr("is1", &area);
1323 dd->rs2 = tgetstr("rs2", &area);
1325 dd->rs2 = tgetstr("is2", &area);
1326 dd->rs3 = tgetstr("rs3", &area);
1328 dd->rs3 = tgetstr("is3", &area);
1329 dd->clear = tgetstr("clear", &area);
1331 call("editor:request:all-displays", p);
1332 if (!prepare_recrep(p)) {
1333 call_comm("event:read", p, &input_handle, fileno(f));
1334 if (!tty || strcmp(tty, "-") == 0)
1335 call_comm("event:signal", p, &handle_winch, SIGWINCH);
1340 REDEF_CMD(handle_winch)
1342 struct pane *p = ci->home;
1343 struct display_data *dd = p->data;
1344 struct winsize size;
1345 ioctl(fileno(dd->scr_file), TIOCGWINSZ, &size);
1347 resize_term(size.ws_row, size.ws_col);
1349 pane_resize(p, 0, 0, size.ws_row, size.ws_col);
1353 DEF_CMD(force_redraw)
1355 struct pane *p = ci->home;
1359 /* full reset, as mosh sometimes gets confused */
1367 static void ncurses_text(struct pane *p safe, struct pane *display safe,
1368 wchar_t ch, int attr, int pair,
1369 short x, short y, short cursor)
1378 set_screen(display);
1380 if (pane_has_focus(p->z < 0 ? p->parent : p)) {
1381 /* Cursor is in-focus */
1382 struct xy curs = pane_mapxy(p, display, x, y, False);
1383 display->cx = curs.x;
1384 display->cy = curs.y;
1386 /* Cursor here, but not focus */
1387 attr = make_cursor(attr);
1390 cc.ext_color = pair;
1394 pan = pane_panel(p2, NULL);
1395 while (!pan && p2->parent != p2) {
1397 pan = pane_panel(p2, NULL);
1400 struct xy xy = pane_mapxy(p, p2, x, y, False);
1401 mvwadd_wch(panel_window(pan), xy.y, xy.x, &cc);
1405 static struct namelist {
1409 {KEY_DOWN, ":Down"},
1411 {KEY_LEFT, ":Left"},
1412 {KEY_RIGHT, ":Right"},
1413 {KEY_HOME, ":Home"},
1414 {KEY_BACKSPACE, ":Backspace"},
1415 {KEY_DL, ":DelLine"},
1416 {KEY_IL, ":InsLine"},
1419 {KEY_ENTER, ":Enter"},
1422 {KEY_NPAGE, ":Next"},
1423 {KEY_PPAGE, ":Prior"},
1425 {KEY_SDC, ":S:Del"},
1426 {KEY_SDL, ":S:DelLine"},
1427 {KEY_SEND, ":S:End"},
1428 {KEY_SHOME, ":S:Home"},
1429 {KEY_SLEFT, ":S:Left"},
1430 {KEY_SRIGHT, ":S:Right"},
1431 {KEY_BTAB, ":S:Tab"},
1435 { 0616, ":S:Prior"},
1437 { 01041, ":S:Home"},
1439 { 01066, ":S:Prior"},
1440 { 01015, ":S:Next"},
1442 { 01027, ":A:S:Home"},
1443 { 01022, ":A:S:End"},
1444 { 01046, ":A:S:Prior"},
1445 { 01047, ":A:S:Next"}, // ??
1447 { 01052, ":A:Prior"},
1448 { 01045, ":A:Next"},
1449 { 01026, ":A:Home"},
1452 { 01014, ":A:Down"},
1453 { 01040, ":A:Left"},
1454 { 01057, ":A:Right"},
1485 {'\177', ":Delete"},
1490 static char *find_name (struct namelist *l safe, wint_t c)
1493 for (i = 0; l[i].name; i++)
1499 static void send_key(int keytype, wint_t c, int alt, struct pane *p safe)
1501 struct display_data *dd = p->data;
1503 char buf[100];/* FIXME */
1505 char *a = alt ? ":A" : "";
1507 if (keytype == KEY_CODE_YES) {
1508 n = find_name(key_names, c);
1510 sprintf(buf, "%sNcurs-%o", a, c);
1512 strcat(strcpy(buf, a), n);
1514 n = find_name(char_names, c);
1516 sprintf(buf, "%s%s", a, n);
1517 else if (c < ' ' || c == 0x7f)
1518 sprintf(buf, "%s:C-%c",
1521 sprintf(buf, "%s-%s", a, put_utf8(t, c));
1524 dd->last_event = time(NULL);
1526 call("Keystroke", p, 0, NULL, buf);
1529 static void do_send_mouse(struct pane *p safe, int x, int y, char *cmd safe,
1530 int button, char *mod, int type)
1533 struct display_data *dd = p->data;
1535 record_mouse(p, cmd, x, y);
1536 ret = call("Mouse-event", p, button, NULL, cmd, type, NULL, mod, x, y);
1537 if (type == 1 && !dd->report_position) {
1539 fprintf(dd->scr_file, "\033[?1002h");
1540 fflush(dd->scr_file);
1542 dd->report_position = 1;
1543 } else if (type == 3 && ret <= 0) {
1545 fprintf(dd->scr_file, "\033[?1002l");
1546 fflush(dd->scr_file);
1548 dd->report_position = 0;
1552 static void send_mouse(MEVENT *mev safe, struct pane *p safe)
1554 struct display_data *dd = p->data;
1560 /* MEVENT has lots of bits. We want a few numbers */
1561 for (b = 1 ; b <= (NCURSES_MOUSE_VERSION <= 1 ? 3 : 5); b++) {
1562 mmask_t s = mev->bstate;
1567 if (s & BUTTON_SHIFT) modf |= 1;
1568 if (s & BUTTON_CTRL) modf |= 2;
1569 if (s & BUTTON_ALT) modf |= 4;
1571 case 0: mod = ""; break;
1572 case 1: mod = ":S"; break;
1573 case 2: mod = ":C"; break;
1574 case 3: mod = ":C:S"; break;
1575 case 4: mod = ":A"; break;
1576 case 5: mod = ":A:S"; break;
1577 case 6: mod = ":A:C"; break;
1578 case 7: mod = ":A:C:S"; break;
1580 if (BUTTON_PRESS(s, b))
1581 action = "%s:Press-%d";
1582 else if (BUTTON_RELEASE(s, b)) {
1583 action = "%s:Release-%d";
1584 /* Modifiers only reported on button Press */
1588 snprintf(buf, sizeof(buf), action, mod, b);
1589 dd->last_event = time(NULL);
1590 do_send_mouse(p, x, y, buf, b, mod, BUTTON_PRESS(s,b) ? 1 : 2);
1592 if ((mev->bstate & REPORT_MOUSE_POSITION) &&
1593 dd->report_position)
1594 /* Motion doesn't update last_event */
1595 do_send_mouse(p, x, y, ":Motion", 0, "", 3);
1598 static void paste_start(struct pane *home safe)
1600 struct display_data *dd = home->data;
1602 dd->paste_start = time(NULL);
1603 buf_init(&dd->paste_buf);
1606 static void paste_flush(struct pane *home safe)
1608 struct display_data *dd = home->data;
1610 if (!dd->paste_start)
1612 free(dd->paste_latest);
1613 dd->paste_latest = buf_final(&dd->paste_buf);
1614 if (dd->paste_buf.len > 0)
1615 dd->paste_pending = 1;
1616 dd->paste_start = 0;
1619 static bool paste_recv(struct pane *home safe, int is_keycode, wint_t ch)
1621 struct display_data *dd = home->data;
1623 if (dd->paste_start == 0)
1626 if (dd->paste_start < now || dd->paste_start > now + 2 ||
1627 is_keycode != OK || ch == KEY_MOUSE) {
1633 /* I really don't want carriage-returns... */
1635 buf_append(&dd->paste_buf, ch);
1636 if (ch == '~' && dd->paste_buf.len >= 6 &&
1638 buf_final(&dd->paste_buf) + dd->paste_buf.len - 6) == 0) {
1639 dd->paste_buf.len -= 6;
1645 DEF_CMD(nc_get_paste)
1647 struct display_data *dd = ci->home->data;
1649 comm_call(ci->comm2, "cb", ci->focus,
1650 dd->paste_start, NULL, dd->paste_latest);
1654 REDEF_CMD(input_handle)
1656 struct pane *p = ci->home;
1657 struct display_data *dd = p->data;
1658 static const char paste_seq[] = "\e[200~";
1661 int have_escape = 0;
1664 if (!(void*)p->data)
1665 /* already closed */
1668 while ((is_keycode = get_wch(&c)) != ERR) {
1669 if (paste_recv(p, is_keycode, c))
1671 if (c == KEY_MOUSE) {
1674 while (getmouse(&mev) != ERR) {
1675 if (dd->paste_pending &&
1676 mev.bstate == REPORT_MOUSE_POSITION) {
1677 /* xcfe-terminal is a bit weird.
1678 * It captures middle-press to
1679 * sanitise the paste, but lets
1680 * middle-release though. It comes
1681 * here as REPORT_MOUSE_POSTION
1682 * and we can use that to find the
1683 * position of the paste.
1684 * '6' is an unused button, and
1685 * ensures lib-input doesn't expect
1686 * matching press/release
1688 call("Mouse-event", ci->home,
1690 6, NULL, NULL, mev.x, mev.y);
1691 dd->paste_pending = 0;
1693 send_mouse(&mev, p);
1695 } else if (c == (wint_t)paste_seq[have_escape]) {
1697 if (!paste_seq[have_escape]) {
1701 } else if (have_escape == 1) {
1702 send_key(is_keycode, c, 1, p);
1704 } else if (have_escape) {
1705 send_key(OK, paste_seq[1], 1, p);
1706 for (i = 2; i < have_escape; i++)
1707 send_key(OK, paste_seq[i], 0, p);
1708 send_key(is_keycode, c, 0, p);
1711 send_key(is_keycode, c, 0, p);
1713 /* Don't know what other code might have done,
1714 * so re-set the screen
1718 if (have_escape == 1)
1719 send_key(is_keycode, '\e', 0, p);
1720 else if (have_escape > 1) {
1721 send_key(OK, paste_seq[1], 1, p);
1722 for (i = 2; i < have_escape; i++)
1723 send_key(OK, paste_seq[i], 0, p);
1725 if (dd->paste_pending == 2) {
1726 /* no mouse event to give postion, so treat as keyboard */
1727 call("Keystroke", ci->home, 0, NULL, ":Paste");
1728 dd->paste_pending = 0;
1729 } else if (dd->paste_pending == 1) {
1730 /* Wait for possible mouse-position update. */
1731 dd->paste_pending = 2;
1732 call_comm("event:timer", p, &input_handle, 200);
1737 DEF_CMD(display_ncurses)
1740 const char *tty = ci->str;
1741 const char *term = ci->str2;
1744 term = "xterm-256color";
1746 p = ncurses_init(ci->focus, tty, term);
1748 return comm_call(ci->comm2, "callback:display", p);
1753 void edlib_init(struct pane *ed safe)
1755 call_comm("global-set-command", ed, &display_ncurses, 0, NULL,
1756 "attach-display-ncurses");
1758 nc_map = key_alloc();
1759 key_add(nc_map, "Display:refresh", &force_redraw);
1760 key_add(nc_map, "Display:close", &nc_close_display);
1761 key_add(nc_map, "Display:set-noclose", &nc_set_noclose);
1762 key_add(nc_map, "Display:external-viewer", &nc_external_viewer);
1763 key_add(nc_map, "Close", &nc_close);
1764 key_add(nc_map, "Free", &edlib_do_free);
1765 key_add(nc_map, "Draw:clear", &nc_clear);
1766 key_add(nc_map, "Draw:text-size", &nc_text_size);
1767 key_add(nc_map, "Draw:text", &nc_draw_text);
1769 key_add(nc_map, "Draw:image", &nc_draw_image);
1770 key_add(nc_map, "Draw:image-size", &nc_image_size);
1772 key_add(nc_map, "Refresh:size", &nc_refresh_size);
1773 key_add(nc_map, "Refresh:postorder", &nc_refresh_post);
1774 key_add(nc_map, "Paste:get", &nc_get_paste);
1775 key_add(nc_map, "all-displays", &nc_notify_display);
1776 key_add(nc_map, "Sig:Winch", &handle_winch);
1777 key_add(nc_map, "Notify:Close", &nc_pane_close);