2 * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * ncurses front end for edlib.
7 * There is currently only support for a single terminal window
8 * which provides a single pane.
10 * Rendering operations are:
11 * draw text with attributes at location
12 * erase with attributes in rectangle
20 #define _XOPEN_SOURCE_EXTENDED
21 #ifndef _DEFAULT_SOURCE
22 #define _DEFAULT_SOURCE
34 #include <sys/ioctl.h>
38 #include <wand/MagickWand.h>
40 // enums confuse sparse...
41 #define MagickBooleanType int
46 #define PANE_DATA_TYPE struct display_data
53 typedef char hash_t[MD5_DIGEST_SIZE*2+1];
57 #undef NCURSES_OK_ADDR
58 #define NCURSES_OK_ADDR(p) ((void*)0 != NCURSES_CAST(const void *, (p)))
67 struct col_hash *col_hash;
84 char *rs1, *rs2, *rs3, *clear;
90 /* Sometimes I get duplicate Display lines, but not consistently.
91 * To avoid these, record last, filter repeats.
94 char last_screen[MD5_DIGEST_SIZE*2+1];
95 char next_screen[MD5_DIGEST_SIZE*2+1];
96 /* The next event to generate when idle */
97 enum { DoNil, DoMouse, DoKey, DoCheck, DoClose} next_event;
101 int clears; /* counts of Draw:clear events */
104 #include "core-pane.h"
106 static SCREEN *current_screen;
107 static void ncurses_text(struct pane *p safe, struct pane *display safe,
108 wchar_t ch, int attr, int pair,
109 short x, short y, short cursor);
110 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home);
111 DEF_CMD(input_handle);
112 DEF_CMD(handle_winch);
113 static struct map *nc_map;
114 DEF_LOOKUP_CMD(ncurses_handle, nc_map);
116 static struct display_data *current_dd;
117 static void set_screen(struct pane *p)
119 struct display_data *dd;
120 extern void *_nc_globals[100];
122 static int index = -1, offset=0;
125 if (current_screen && index >= 0)
126 _nc_globals[index] = NULL;
127 current_screen = NULL;
134 if (dd->scr == current_screen)
139 for (i=0; i<100; i++)
140 if (_nc_globals[i] < (void*)stdscr &&
141 _nc_globals[i]+4*(sizeof(void*)) >= (void*)stdscr) {
142 /* This is _nc_windowlist */
144 offset = ((void*)stdscr) - _nc_globals[i];
149 current_screen = dd->scr;
151 _nc_globals[index] = ((void*)stdscr) - offset;
158 static bool parse_event(struct pane *p safe);
159 static bool prepare_recrep(struct pane *p safe)
161 struct display_data *dd = p->data;
164 name = getenv("EDLIB_RECORD");
166 dd->log = fopen(name, "w");
167 name = getenv("EDLIB_REPLAY");
169 dd->input = fopen(name, "r");
170 if (getenv("EDLIB_PAUSE"))
171 sleep(atoi(getenv("EDLIB_PAUSE")));
179 static void close_recrep(struct pane *p safe)
181 struct display_data *dd = p->data;
184 fprintf(dd->log, "Close %d\n", dd->clears);
189 static void record_key(struct pane *p safe, char *key safe)
191 struct display_data *dd = p->data;
196 if (!strchr(key, '"'))
198 else if (!strchr(key, '\''))
200 else if (!strchr(key, '/'))
204 fprintf(dd->log, "Key %c%s%c\n", q,key,q);
205 dd->last_cx = -2; /* Force next Display to be shown */
209 static void record_mouse(struct pane *p safe, char *key safe, int x, int y)
211 struct display_data *dd = p->data;
215 if (!strchr(key, '"'))
217 else if (!strchr(key, '\''))
219 else if (!strchr(key, '/'))
223 fprintf(dd->log, "Mouse %c%s%c %d,%d\n", q,key,q, x, y);
224 dd->last_cx = -2; /* Force next Display to be shown */
228 static void record_screen(struct pane *p safe)
230 struct display_data *dd = p->data;
231 struct md5_state ctx;
232 uint16_t buf[CCHARW_MAX+5];
233 char out[MD5_DIGEST_SIZE*2+1];
236 if (!dd->log && !(dd->input && dd->next_event == DoCheck))
240 for (r = 0; r < p->h; r++)
241 for (c = 0; c < p->w; c++) {
243 wchar_t wc[CCHARW_MAX+2];
248 mvwin_wch(stdscr, r, c, &cc);
249 getcchar(&cc, wc, &a, &color, NULL);
250 pair_content(color, &fg, &bg);
251 buf[0] = htole16(fg);
252 buf[1] = htole16(bg);
253 for (l = 0; l < CCHARW_MAX && wc[l]; l++)
254 buf[l+3] = htole16(wc[l]);
256 LOG("%d,%d %d:%d:%d:%d %lc", c,r,fg,bg,l,wc[0], wc[0] > ' ' ? wc[0] : '?');
257 md5_update(&ctx, (uint8_t*)buf,
258 (l+3) * sizeof(uint16_t));
260 md5_final_txt(&ctx, out);
261 if (strcmp(out, dd->last_screen) == 0 &&
262 p->cx == dd->last_cx && p->cy == dd->last_cy) {
263 /* No change - filter it */
265 } else if (dd->log) {
266 fprintf(dd->log, "Display %d,%d %s", p->w, p->h, out);
268 fprintf(dd->log, " %d,%d", p->cx, p->cy);
269 fprintf(dd->log, "\n");
271 strcpy(dd->last_screen, out);
272 dd->last_cx = p->cx; dd->last_cy = p->cy;
274 if (dd->input && dd->input_sleeping) {
275 char *delay = getenv("EDLIB_REPLAY_DELAY");
276 call_comm("event:free", p, &next_evt);
278 call_comm("event:timer", p, &next_evt, atoi(delay));
280 call_comm("event:on-idle", p, &next_evt);
284 static char *copy_quote(char *line safe, char *buf safe)
290 if (q != '"' && q != '\'' && q != '/')
292 while (*line != q && *line)
300 static char *get_coord(char *line safe, struct xy *co safe)
307 v = strtol(line, &ep, 10);
308 if (!ep || ep == line || *ep != ',')
312 v = strtol(line, &ep, 10);
313 if (!ep || ep == line || (*ep && *ep != ' ' && *ep != '\n'))
319 static char *get_hash(char *line safe, hash_t hash safe)
324 for (i = 0; i < MD5_DIGEST_SIZE*2 && *line; i++)
331 static bool parse_event(struct pane *p safe)
333 struct display_data *dd = p->data;
337 dd->next_event = DoNil;
339 fgets(line, sizeof(line)-1, dd->input) == NULL)
341 else if (strstarts(line, "Key ")) {
342 if (!copy_quote(line+4, dd->event_info))
344 dd->next_event = DoKey;
345 } else if (strstarts(line, "Mouse ")) {
346 char *f = copy_quote(line+6, dd->event_info);
349 f = get_coord(f, &dd->event_pos);
352 dd->next_event = DoMouse;
353 } else if (strstarts(line, "Display ")) {
354 char *f = get_coord(line+8, &dd->event_pos);
357 f = get_hash(f, dd->next_screen);
358 dd->next_event = DoCheck;
359 } else if (strstarts(line, "Close")) {
360 dd->next_event = DoClose;
362 LOG("parse %s", line);
364 dd->input_sleeping = 1;
365 if (dd->next_event != DoCheck) {
366 char *delay = getenv("EDLIB_REPLAY_DELAY");
368 call_comm("event:timer", p, &next_evt, atoi(delay));
370 call_comm("event:on-idle", p, &next_evt);
372 call_comm("event:timer", p, &next_evt, 10*1000);
378 struct pane *p = ci->home;
379 struct display_data *dd = p->data;
380 int button = 0, type = 0;
382 dd->input_sleeping = 0;
383 switch(dd->next_event) {
385 record_key(p, dd->event_info);
386 call("Keystroke", p, 0, NULL, dd->event_info);
389 record_mouse(p, dd->event_info, dd->event_pos.x,
391 if (strstr(dd->event_info, ":Press"))
393 else if (strstr(dd->event_info, ":Release"))
395 else if (strstr(dd->event_info, ":Motion"))
397 if (type == 1 || type == 2) {
398 char *e = dd->event_info + strlen(dd->event_info) - 1;
401 call("Mouse-event", p, button, NULL, dd->event_info,
403 dd->event_pos.x, dd->event_pos.y);
406 /* No point checking, just do a diff against new trace log. */
407 /* not; (strcmp(dd->next_screen, dd->last_screen) != 0) */
410 call("event:deactivate", p);
414 call_comm("event:read", p, &input_handle, 0);
415 call_comm("event:signal", p, &handle_winch, SIGWINCH);
422 static inline bool prepare_recrep(struct pane *p safe) {return False;}
423 static inline void record_key(struct pane *p safe, char *key) {}
424 static inline void record_mouse(struct pane *p safe, char *key safe,
426 static inline void record_screen(struct pane *p safe) {}
427 static inline void close_recrep(struct pane *p safe) {}
432 struct call_return *cr = container_of(ci->comm, struct call_return, c);
438 static void ncurses_end(struct pane *p safe);
440 DEF_CMD(nc_close_display)
442 /* If this is only display, then refuse to close this one */
443 struct call_return cr;
444 char *nc = pane_attr_get(ci->home, "no-close");
447 call("Message", ci->focus, 0, NULL, nc);
453 call_comm("editor:notify:all-displays", ci->focus, &cr.c);
455 /* Need to call ncurses_end() before we send a Notify:Close
456 * notification, else server exits too early
458 ncurses_end(ci->home);
461 call("Message", ci->focus, 0, NULL,
462 "Cannot close only window.");
466 static int nc_putc(int ch)
469 fputc(ch, current_dd->scr_file);
473 static char *fnormalize(struct pane *p safe, const char *str) safe
475 char *ret = strsave(p, str);
478 for (cp = ret ; cp && *cp ; cp++)
480 !strchr("/_-+=.,@#", *cp))
481 /* Don't like this char */
486 static void wait_for(struct display_data *dd safe)
488 struct pids **pp = &dd->pids;
491 struct pids *p = *pp;
492 if (waitpid(p->pid, NULL, WNOHANG) > 0) {
502 struct display_data *dd = ci->home->data;
505 dd->suspended = False;
506 set_screen(ci->home);
512 DEF_CMD(nc_external_viewer)
514 struct pane *p = ci->home;
515 struct display_data *dd = p->data;
516 char *disp = pane_attr_get(p, "DISPLAY");
517 char *disp_auth = pane_attr_get(p, "XAUTHORITY");
518 char *remote = pane_attr_get(p, "REMOTE_SESSION");
520 const char *path = ci->str;
530 switch (pid = fork()) {
534 setenv("DISPLAY", disp, 1);
536 setenv("XAUTHORITY", disp_auth, 1);
537 fd = open("/dev/null", O_RDWR);
545 execlp("xdg-open", "xdg-open", path, NULL);
547 default: /* parent */
548 pds = malloc(sizeof(*pds));
550 pds->next = dd->pids;
557 /* handle no-display case */
558 if (remote && strcmp(remote, "yes") == 0 &&
560 gethostname(buf, sizeof(buf)) == 0) {
561 struct addrinfo *res;
562 const struct addrinfo hints = {
563 .ai_flags = AI_CANONNAME,
565 if (getaddrinfo(buf, NULL, &hints, &res) == 0 &&
566 res && res->ai_canonname)
567 fqdn = strdup(res->ai_canonname);
572 ioctl(fileno(dd->scr_file), FIONREAD, &n);
574 n -= read(fileno(dd->scr_file), buf,
575 n <= (int)sizeof(buf) ? n : (int)sizeof(buf));
577 /* stay in raw mode */
581 /* Endwin doesn't seem to reset properly, at least on xfce-terminal.
585 tputs(dd->rs1, 1, nc_putc);
587 tputs(dd->rs2, 1, nc_putc);
589 tputs(dd->rs3, 1, nc_putc);
591 tputs(dd->clear, 1, nc_putc);
592 fflush(dd->scr_file);
594 fprintf(dd->scr_file, "# Consider copy-pasting following\r\n");
595 if (fqdn && path[0] == '/') {
596 /* File will not be local for the user, so help them copy it. */
597 const char *tmp = fnormalize(p, ci->str2 ?: "XXXXXX");
598 const char *fname = fnormalize(p, ci->str);
600 if (strcmp(fname, ci->str) != 0)
601 /* file name had unusuable chars, need to create safe name */
602 link(ci->str, fname);
603 fprintf(dd->scr_file, "f=`mktemp --tmpdir %s`; scp %s:%s $f ; ",
608 fprintf(dd->scr_file, "xdg-open %s\r\n", path);
609 fprintf(dd->scr_file, "# Press Enter to continue\r\n");
610 dd->suspended = True;
611 call_comm("event:timer", p, &ns_resume, 30*1000);
615 static void ncurses_stop(struct pane *p safe)
617 struct display_data *dd = p->data;
620 /* disable bracketed-paste */
621 fprintf(dd->scr_file, "\033[?2004l");
622 fflush(dd->scr_file);
625 free(buf_final(&dd->paste_buf));
627 free(dd->paste_latest);
628 dd->paste_latest = NULL;
632 tputs(dd->rs1, 1, nc_putc);
634 tputs(dd->rs2, 1, nc_putc);
636 tputs(dd->rs3, 1, nc_putc);
637 fflush(dd->scr_file);
640 static void ncurses_end(struct pane *p safe)
642 struct display_data *dd = p->data;
646 dd->did_close = True;
654 * hash table for colours and pairs
655 * key is r,g,b (0-1000) in 10bit fields,
656 * or fg,bg in 16 bit fields with bit 31 set
657 * content is colour number of colour pair number.
658 * We never delete entries, unless we delete everything.
665 #define COL_KEY(r,g,b) ((r<<20) | (g<<10) | b)
666 #define PAIR_KEY(fg, bg) ((1<<31) | (fg<<16) | bg)
667 #define hash_key(k) ((((k) * 0x61C88647) >> 20) & 0xff)
670 int next_col, next_pair;
671 struct chash *tbl[256];
674 static struct col_hash *safe hash_init(struct display_data *dd safe)
677 dd->col_hash = malloc(sizeof(*dd->col_hash));
678 memset(dd->col_hash, 0, sizeof(*dd->col_hash));
679 dd->col_hash->next_col = 16;
680 dd->col_hash->next_pair = 1;
685 static void hash_free(struct display_data *dd safe)
694 for (h = 0; h < 255; h++)
695 while ((c = ch->tbl[h]) != NULL) {
696 ch->tbl[h] = c->next;
703 static int find_col(struct display_data *dd safe, int rgb[])
705 if (0 /* dynamic colours */) {
706 struct col_hash *ch = hash_init(dd);
707 int k = COL_KEY(rgb[0], rgb[1], rgb[2]);
711 for (c = ch->tbl[h]; c; c = c->next)
714 c = malloc(sizeof(*c));
716 c->content = ch->next_col++;
717 c->next = ch->tbl[h];
719 init_color(c->content, rgb[0], rgb[1], rgb[2]);
722 /* If colour is grey, map to 1 of 26 for 0, 232 to 255, 15
723 * The 24 grey shades have bit values from 8 to 238, so the
724 * gap to white is a little bigger, but that probably doesn't
726 * Otherwise map to 6x6x6 rgb cube from 16
727 * Actual colours are biased bright, at 0,95,135,175,215,255
728 * with a 95 gap at bottom and 40 elsewhere.
729 * So we divide 5 and 2 half ranges, and merge bottom 2.
734 //printf("want %d,%d,%d\n", rgb[0], rgb[1], rgb[2]);
735 if (abs(rgb[0] - rgb[1]) < 10 &&
736 abs(rgb[1] - rgb[2]) < 10) {
737 /* grey - within 1% */
738 int v = (rgb[0] + rgb[1] + rgb[2]) / 3;
740 /* We divide the space in 24 ranges surrounding
741 * the grey values, and 2 half-ranges near black
742 * and white. So add half a range - 1000/50 -
743 * then divide by 1000/25 to get a number from 0 to 25.
745 v = (v + 1000/50) / (1000/25);
747 return 0; /* black */
749 return 15; /* white */
750 //printf(" grey %d\n", v + 231);
751 /* grey shades are from 232 to 255 inclusive */
754 for (h = 0; h < 3; h++) {
757 v = (v + 1000/12) / (1000/6);
758 /* v is from 0 to 6, we want up to 5
759 * with 0 and 1 merged
766 //printf(" color %d\n", c + 16);
771 static int to_pair(struct display_data *dd safe, int fg, int bg)
773 struct col_hash *ch = hash_init(dd);
774 int k = PAIR_KEY(fg, bg);
778 for (c = ch->tbl[h]; c; c = c->next)
781 c = malloc(sizeof(*c));
783 c->content = ch->next_pair++;
784 c->next = ch->tbl[h];
786 init_pair(c->content, fg, bg);
790 static int cvt_attrs(struct pane *p safe, struct pane *home safe,
791 const char *attrs, int *pairp safe)
793 struct display_data *dd = home->data;
798 int fg = COLOR_BLACK;
799 int bg = COLOR_WHITE+8;
802 while (p->parent != p &&(pan = pane_panel(p, NULL)) == NULL)
805 /* Get 'default colours for this pane - set at clear */
806 int at = getbkgd(panel_window(pan));
807 int pair = PAIR_NUMBER(at);
809 pair_content(pair, &dfg, &dbg);
816 foreach_attr(a, v, attrs, NULL) {
817 if (amatch(a, "inverse"))
819 else if (amatch(a, "noinverse"))
821 else if (amatch(a, "bold"))
823 else if (amatch(a, "nobold"))
825 else if (amatch(a, "underline"))
827 else if (amatch(a, "nounderline"))
828 attr &= ~A_UNDERLINE;
829 else if (amatch(a, "fg") && v) {
830 struct call_return cr =
831 call_ret(all, "colour:map", home,
832 0, NULL, aupdate(&col, v));
833 int rgb[3] = {cr.i, cr.i2, cr.x};
834 fg = find_col(dd, rgb);
835 } else if (amatch(a, "bg") && v) {
836 struct call_return cr =
837 call_ret(all, "colour:map", home,
838 0, NULL, aupdate(&col, v));
839 int rgb[3] = {cr.i, cr.i2, cr.x};
840 bg = find_col(dd, rgb);
844 if (fg != COLOR_BLACK || bg != COLOR_WHITE+8)
845 *pairp = to_pair(dd, fg, bg);
849 static int make_cursor(int attr)
851 return attr ^ A_UNDERLINE;
854 DEF_CMD(nc_notify_display)
856 struct display_data *dd = ci->home->data;
857 comm_call(ci->comm2, "callback:display", ci->home, dd->last_event);
861 DEF_CMD_CLOSED(nc_close)
863 struct pane *p = ci->home;
864 struct display_data *dd = p->data;
867 fclose(dd->scr_file);
871 DEF_CMD(nc_pane_close)
875 set_screen(ci->home);
876 while ((pan = panel_above(pan)) != NULL)
877 if (panel_userptr(pan) == ci->focus)
880 WINDOW *win = panel_window(pan);
883 pane_damaged(ci->home, DAMAGED_POSTORDER);
888 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home)
892 while ((pan = panel_above(pan)) != NULL)
893 if (panel_userptr(pan) == p)
899 pan = new_panel(newwin(p->h, p->w, 0, 0));
900 set_panel_userptr(pan, p);
901 pane_add_notify(home, p, "Notify:Close");
908 struct pane *p = ci->home;
909 struct display_data *dd = p->data;
912 /* default come from parent when clearing pane */
913 int attr = cvt_attrs(ci->focus->parent, p, ci->str, &pair);
919 panel = pane_panel(ci->focus, p);
922 win = panel_window(panel);
924 if (h != ci->focus->h || w != ci->focus->w) {
925 wresize(win, ci->focus->h, ci->focus->w);
926 replace_panel(panel, win);
931 wbkgrndset(win, &cc);
935 pane_damaged(p, DAMAGED_POSTORDER);
939 DEF_CMD(nc_text_size)
941 int max_space = ci->num;
944 const char *str = ci->str;
948 while (str[0] != 0) {
949 wint_t wc = get_utf8(&str, NULL);
957 if (size <= max_space)
958 max_bytes = str - ci->str;
960 return comm_call(ci->comm2, "callback:size", ci->focus,
961 max_bytes, NULL, NULL,
962 0, NULL, NULL, size, 1);
965 DEF_CMD(nc_draw_text)
967 struct pane *p = ci->home;
969 int attr = cvt_attrs(ci->focus, p, ci->str2, &pair);
970 int cursor_offset = ci->num;
971 short x = ci->x, y = ci->y;
972 const char *str = ci->str;
977 while (str[0] != 0) {
978 int precurs = str <= ci->str + cursor_offset;
979 wint_t wc = get_utf8(&str, NULL);
981 if (wc == WEOF || wc == WERR)
986 if (precurs && str > ci->str + cursor_offset)
987 ncurses_text(ci->focus, p, wc, attr, pair, x, y, 1);
989 ncurses_text(ci->focus, p, wc, attr, pair, x, y, 0);
992 if (str == ci->str + cursor_offset)
993 ncurses_text(ci->focus, p, ' ', 0, 0, x, y, 1);
994 pane_damaged(p, DAMAGED_POSTORDER);
1000 MagickWand *wd safe;
1003 struct pane *p safe;
1006 DEF_CB(nc_draw_image_cb)
1008 struct di_info *dii = container_of(ci->comm, struct di_info, c);
1009 struct display_data *dd = dii->p->data;
1013 switch (ci->key[0]) {
1014 case 'w': /* width */
1015 return MagickGetImageWidth(dii->wd);
1016 case 'h': /* height */
1017 return MagickGetImageHeight(dii->wd);
1018 case 's': /* scale */
1019 MagickAdaptiveResizeImage(dii->wd, ci->num, ci->num2);
1021 case 'c': /* crop or cursor */
1022 if (ci->key[1] != 'u') {
1031 /* FIXME this doesn't work because
1032 * render-line knows too much and gets it wrong.
1034 ncurses_text(ci->focus, dii->p, 'X', 0,
1037 (ci->y + dii->xo)/2, 1);
1040 case 'd': /* draw */
1041 if (dii->w <= 0 || dii->h <= 0)
1043 buf = malloc(dii->h * dii->w * 4);
1045 MagickExportImagePixels(dii->wd, dii->x, dii->y,
1047 "RGBA", CharPixel, buf);
1049 for (i = 0; i < dii->h; i+= 2) {
1050 static const wint_t hilo = 0x2580; /* L'▀' */
1051 for (j = 0; j < dii->w ; j+= 1) {
1052 unsigned char *p1 = buf + i*dii->w*4 + j*4;
1053 unsigned char *p2 = buf + (i+1)*dii->w*4 + j*4;
1054 int rgb1[3] = { p1[0]*1000/255, p1[1]*1000/255, p1[2]*1000/255 };
1055 int rgb2[3] = { p2[0]*1000/255, p2[1]*1000/255, p2[2]*1000/255 };
1056 int fg = find_col(dd, rgb1);
1057 int bg = find_col(dd, rgb2);
1059 if (p1[3] < 128 || p2[3] < 128) {
1063 struct pane *pn2 = ci->focus;
1064 PANEL *pan = pane_panel(pn2, NULL);
1066 while (!pan && pn2->parent != pn2) {
1068 pan = pane_panel(pn2, NULL);
1071 wgetbkgrnd(panel_window(pan), &cc);
1072 if (cc.ext_color == 0)
1073 /* default. This is light
1074 * gray rather then white,
1075 * but I think it is a good
1080 pair_content(cc.ext_color, &f, &b);
1087 ncurses_text(ci->focus, dii->p, hilo, 0,
1088 to_pair(dd, fg, bg),
1089 ci->num + dii->xo + j,
1090 (ci->num2 + dii->yo + i)/2,
1101 DEF_CMD(nc_draw_image)
1103 /* 'str' identifies the image. Options are:
1104 * file:filename - load file from fs
1105 * comm:command - run command collecting bytes
1106 * 'str2' and numbers are handled by Draw:scale-image.
1108 struct pane *p = ci->home;
1109 MagickBooleanType status;
1110 MagickWand *wd = NULL;
1115 if (strstarts(ci->str, "file:")) {
1116 wd = NewMagickWand();
1117 status = MagickReadImage(wd, ci->str + 5);
1118 if (status == MagickFalse) {
1119 DestroyMagickWand(wd);
1122 } else if (strstarts(ci->str, "comm:")) {
1123 struct call_return cr;
1124 wd = NewMagickWand();
1125 cr = call_ret(bytes, ci->str+5, ci->focus);
1127 DestroyMagickWand(wd);
1130 status = MagickReadImageBlob(wd, cr.s, cr.i);
1132 if (status == MagickFalse) {
1133 DestroyMagickWand(wd);
1141 MagickAutoOrientImage(wd);
1142 dii.c = nc_draw_image_cb;
1145 dii.w = dii.h = dii.x = dii.y = dii.xo = dii.yo = 0;
1146 call("Draw:scale-image", ci->focus,
1147 ci->num, NULL, NULL, ci->num2, NULL, ci->str2,
1148 ci->x, ci->y, &dii.c);
1150 DestroyMagickWand(wd);
1152 pane_damaged(ci->home, DAMAGED_POSTORDER);
1157 DEF_CMD(nc_image_size)
1159 MagickBooleanType status;
1165 if (strstarts(ci->str, "file:")) {
1166 wd = NewMagickWand();
1167 status = MagickReadImage(wd, ci->str + 5);
1168 if (status == MagickFalse) {
1169 DestroyMagickWand(wd);
1172 } else if (strstarts(ci->str, "comm:")) {
1173 struct call_return cr;
1174 wd = NewMagickWand();
1175 cr = call_ret(bytes, ci->str+5, ci->focus);
1177 DestroyMagickWand(wd);
1180 status = MagickReadImageBlob(wd, cr.s, cr.i);
1182 if (status == MagickFalse) {
1183 DestroyMagickWand(wd);
1189 MagickAutoOrientImage(wd);
1190 ih = MagickGetImageHeight(wd);
1191 iw = MagickGetImageWidth(wd);
1193 DestroyMagickWand(wd);
1194 comm_call(ci->comm2, "callback:size", ci->focus,
1195 0, NULL, NULL, 0, NULL, NULL,
1200 DEF_CMD(nc_refresh_size)
1202 struct pane *p = ci->home;
1205 getmaxyx(stdscr, p->h, p->w);
1210 DEF_CMD(nc_refresh_post)
1212 struct pane *p = ci->home;
1213 struct display_data *dd = p->data;
1222 /* Need to ensure stacking order and panel y,x position
1223 * is correct. FIXME it would be good if we could skip this
1226 pan = panel_above(NULL);
1229 p1 = (struct pane*) panel_userptr(pan);
1230 for (;(pan2 = panel_above(pan)) != NULL; pan = pan2) {
1231 struct pane *p2 = (struct pane*)panel_userptr(pan2);
1232 p1 = (struct pane*)panel_userptr(pan);
1236 if (p1->abs_z < p2->abs_z)
1238 if (p1->abs_z == p2->abs_z &&
1241 /* pan needs to be above pan2. All we can do is move it to
1242 * the top. Anything that needs to be above it will eventually
1246 /* Now the panel below pan might need to be over pan2 too... */
1247 pan = panel_below(pan2);
1252 /* As we need to crop pane against their parents, we cannot simply
1253 * use update_panels(). Instead we copy each to stdscr and refresh
1256 for (pan = NULL; (pan = panel_above(pan)) != NULL; ) {
1258 struct xy src, dest, destend;
1261 p1 = (void*)panel_userptr(pan);
1264 dest = pane_mapxy(p1, p, 0, 0, True);
1265 destend = pane_mapxy(p1, p, p1->w, p1->h, True);
1266 src = pane_mapxy(p1, p, 0, 0, False);
1267 src.x = dest.x - src.x;
1268 src.y = dest.y - src.y;
1269 win = panel_window(pan);
1270 getmaxyx(win, h, w);
1271 /* guard again accessing beyond boundary of win */
1272 if (destend.x > dest.x + (w - src.x))
1273 destend.x = dest.x + (w - src.x);
1274 if (destend.y > dest.y + (h - src.y))
1275 destend.y = dest.y - (h - src.y);
1276 copywin(win, stdscr, src.y, src.x,
1277 dest.y, dest.x, destend.y-1, destend.x-1, 0);
1279 /* place the cursor */
1282 while (p1 != p && (pan = pane_panel(p1, NULL)) == NULL)
1284 if (pan && p1->cx >= 0) {
1285 struct xy curs = pane_mapxy(p1, p, p1->cx, p1->cy, False);
1286 wmove(stdscr, curs.y, curs.x);
1287 } else if (p->cx >= 0)
1288 wmove(stdscr, p->cy, p->cx);
1290 record_screen(ci->home);
1294 static void ncurses_start(struct pane *p safe)
1296 struct display_data *dd = p->data;
1300 use_default_colors();
1306 intrflush(stdscr, FALSE);
1307 keypad(stdscr, TRUE);
1308 mousemask(BUTTON1_PRESSED | BUTTON1_RELEASED |
1309 BUTTON2_PRESSED | BUTTON2_RELEASED |
1310 BUTTON3_PRESSED | BUTTON3_RELEASED |
1311 BUTTON4_PRESSED | BUTTON4_RELEASED |
1312 BUTTON5_PRESSED | BUTTON5_RELEASED |
1313 BUTTON_CTRL | BUTTON_SHIFT | BUTTON_ALT |
1314 REPORT_MOUSE_POSITION, NULL);
1317 /* Enable bracketed-paste */
1318 fprintf(dd->scr_file, "\033[?2004h");
1319 fflush(dd->scr_file);
1322 getmaxyx(stdscr, rows, cols);
1323 pane_resize(p, 0, 0, cols, rows);
1326 static struct pane *ncurses_init(struct pane *ed safe,
1327 const char *tty, const char *term)
1331 struct display_data *dd;
1336 if (tty && strcmp(tty, "-") != 0)
1337 f = fopen(tty, "r+");
1339 f = fdopen(1, "r+");
1342 scr = newterm(term, f, f);
1346 p = pane_register(ed, 1, &ncurses_handle.c);
1352 dd->is_xterm = (term && strstarts(term, "xterm"));
1354 attr_set_str(&p->attrs, "Display:pixels", "1x2");
1360 area = dd->attr_buf;
1361 dd->rs1 = tgetstr("rs1", &area);
1363 dd->rs1 = tgetstr("is1", &area);
1364 dd->rs2 = tgetstr("rs2", &area);
1366 dd->rs2 = tgetstr("is2", &area);
1367 dd->rs3 = tgetstr("rs3", &area);
1369 dd->rs3 = tgetstr("is3", &area);
1370 dd->clear = tgetstr("clear", &area);
1372 call("editor:request:all-displays", p);
1373 if (!prepare_recrep(p)) {
1374 call_comm("event:read", p, &input_handle, fileno(f));
1375 if (!tty || strcmp(tty, "-") == 0)
1376 call_comm("event:signal", p, &handle_winch, SIGWINCH);
1381 REDEF_CMD(handle_winch)
1383 struct pane *p = ci->home;
1384 struct display_data *dd = p->data;
1385 struct winsize size;
1386 ioctl(fileno(dd->scr_file), TIOCGWINSZ, &size);
1388 resize_term(size.ws_row, size.ws_col);
1390 pane_resize(p, 0, 0, size.ws_row, size.ws_col);
1394 DEF_CMD(force_redraw)
1396 struct pane *p = ci->home;
1400 /* full reset, as mosh sometimes gets confused */
1408 static void ncurses_text(struct pane *p safe, struct pane *display safe,
1409 wchar_t ch, int attr, int pair,
1410 short x, short y, short cursor)
1419 set_screen(display);
1421 if (pane_has_focus(p->z < 0 ? p->parent : p)) {
1422 /* Cursor is in-focus */
1423 struct xy curs = pane_mapxy(p, display, x, y, False);
1424 display->cx = curs.x;
1425 display->cy = curs.y;
1427 /* Cursor here, but not focus */
1428 attr = make_cursor(attr);
1431 cc.ext_color = pair;
1435 pan = pane_panel(p2, NULL);
1436 while (!pan && p2->parent != p2) {
1438 pan = pane_panel(p2, NULL);
1441 struct xy xy = pane_mapxy(p, p2, x, y, False);
1442 mvwadd_wch(panel_window(pan), xy.y, xy.x, &cc);
1446 static struct namelist {
1450 {KEY_DOWN, ":Down"},
1452 {KEY_LEFT, ":Left"},
1453 {KEY_RIGHT, ":Right"},
1454 {KEY_HOME, ":Home"},
1455 {KEY_BACKSPACE, ":Backspace"},
1456 {KEY_DL, ":DelLine"},
1457 {KEY_IL, ":InsLine"},
1460 {KEY_ENTER, ":Enter"},
1463 {KEY_NPAGE, ":Next"},
1464 {KEY_PPAGE, ":Prior"},
1466 {KEY_SDC, ":S:Del"},
1467 {KEY_SDL, ":S:DelLine"},
1468 {KEY_SEND, ":S:End"},
1469 {KEY_SHOME, ":S:Home"},
1470 {KEY_SLEFT, ":S:Left"},
1471 {KEY_SRIGHT, ":S:Right"},
1472 {KEY_BTAB, ":S:Tab"},
1476 { 0616, ":S:Prior"},
1478 { 01041, ":S:Home"},
1480 { 01066, ":S:Prior"},
1481 { 01015, ":S:Next"},
1483 { 01027, ":A:S:Home"},
1484 { 01022, ":A:S:End"},
1485 { 01046, ":A:S:Prior"},
1486 { 01047, ":A:S:Next"}, // ??
1488 { 01052, ":A:Prior"},
1489 { 01045, ":A:Next"},
1490 { 01026, ":A:Home"},
1493 { 01014, ":A:Down"},
1494 { 01040, ":A:Left"},
1495 { 01057, ":A:Right"},
1520 { 01114, ":Focus-in"},
1521 { 01115, ":Focus-out"},
1528 {'\177', ":Delete"},
1533 static char *find_name (struct namelist *l safe, wint_t c)
1536 for (i = 0; l[i].name; i++)
1542 static void send_key(int keytype, wint_t c, int alt, struct pane *p safe)
1544 struct display_data *dd = p->data;
1546 char buf[100];/* FIXME */
1548 char *a = alt ? ":A" : "";
1550 if (keytype == KEY_CODE_YES) {
1551 n = find_name(key_names, c);
1553 LOG("Unknown ncurses key 0o%o", c);
1554 sprintf(buf, "%sNcurs-%o", a, c);
1555 } else if (strstarts(n, ":Focus-"))
1556 /* Ignore focus changes for now */
1559 strcat(strcpy(buf, a), n);
1561 n = find_name(char_names, c);
1563 sprintf(buf, "%s%s", a, n);
1564 else if (c < ' ' || c == 0x7f)
1565 sprintf(buf, "%s:C-%c",
1568 sprintf(buf, "%s-%s", a, put_utf8(t, c));
1571 dd->last_event = time(NULL);
1574 call("Keystroke", p, 0, NULL, buf);
1578 static void do_send_mouse(struct pane *p safe, int x, int y, char *cmd safe,
1579 int button, char *mod, int type)
1582 struct display_data *dd = p->data;
1584 record_mouse(p, cmd, x, y);
1585 ret = call("Mouse-event", p, button, NULL, cmd, type, NULL, mod, x, y);
1586 if (type == 1 && !dd->report_position) {
1588 fprintf(dd->scr_file, "\033[?1002h");
1589 fflush(dd->scr_file);
1591 dd->report_position = 1;
1592 } else if (type == 3 && ret <= 0) {
1594 fprintf(dd->scr_file, "\033[?1002l");
1595 fflush(dd->scr_file);
1597 dd->report_position = 0;
1601 static void send_mouse(MEVENT *mev safe, struct pane *p safe)
1603 struct display_data *dd = p->data;
1609 /* MEVENT has lots of bits. We want a few numbers */
1610 for (b = 1 ; b <= (NCURSES_MOUSE_VERSION <= 1 ? 3 : 5); b++) {
1611 mmask_t s = mev->bstate;
1616 if (s & BUTTON_SHIFT) modf |= 1;
1617 if (s & BUTTON_CTRL) modf |= 2;
1618 if (s & BUTTON_ALT) modf |= 4;
1620 case 0: mod = ""; break;
1621 case 1: mod = ":S"; break;
1622 case 2: mod = ":C"; break;
1623 case 3: mod = ":C:S"; break;
1624 case 4: mod = ":A"; break;
1625 case 5: mod = ":A:S"; break;
1626 case 6: mod = ":A:C"; break;
1627 case 7: mod = ":A:C:S"; break;
1629 if (BUTTON_PRESS(s, b))
1630 action = "%s:Press-%d";
1631 else if (BUTTON_RELEASE(s, b)) {
1632 action = "%s:Release-%d";
1633 /* Modifiers only reported on button Press */
1637 snprintf(buf, sizeof(buf), action, mod, b);
1638 dd->last_event = time(NULL);
1639 do_send_mouse(p, x, y, buf, b, mod, BUTTON_PRESS(s,b) ? 1 : 2);
1641 if ((mev->bstate & REPORT_MOUSE_POSITION) &&
1642 dd->report_position)
1643 /* Motion doesn't update last_event */
1644 do_send_mouse(p, x, y, ":Motion", 0, "", 3);
1647 static void paste_start(struct pane *home safe)
1649 struct display_data *dd = home->data;
1651 dd->paste_start = time(NULL);
1652 buf_init(&dd->paste_buf);
1655 static void paste_flush(struct pane *home safe)
1657 struct display_data *dd = home->data;
1659 if (!dd->paste_start)
1661 free(dd->paste_latest);
1662 dd->paste_latest = buf_final(&dd->paste_buf);
1663 if (dd->paste_buf.len > 0)
1664 dd->paste_pending = 1;
1665 dd->paste_start = 0;
1668 static bool paste_recv(struct pane *home safe, int is_keycode, wint_t ch)
1670 struct display_data *dd = home->data;
1672 if (dd->paste_start == 0)
1675 if (dd->paste_start < now || dd->paste_start > now + 2 ||
1676 is_keycode != OK || ch == KEY_MOUSE) {
1682 /* I really don't want carriage-returns... */
1684 buf_append(&dd->paste_buf, ch);
1685 if (ch == '~' && dd->paste_buf.len >= 6 &&
1687 buf_final(&dd->paste_buf) + dd->paste_buf.len - 6) == 0) {
1688 dd->paste_buf.len -= 6;
1694 DEF_CMD(nc_get_paste)
1696 struct display_data *dd = ci->home->data;
1698 comm_call(ci->comm2, "cb", ci->focus,
1699 dd->paste_start, NULL, dd->paste_latest);
1703 REDEF_CMD(input_handle)
1705 struct pane *p = ci->home;
1706 struct display_data *dd = p->data;
1707 static const char paste_seq[] = "\e[200~";
1710 int have_escape = 0;
1715 while ((is_keycode = get_wch(&c)) != ERR) {
1716 if (dd->suspended && c != KEY_MOUSE) {
1717 dd->suspended = False;
1719 call_comm("event:free", p, &ns_resume);
1720 /* swallow the key */
1723 if (paste_recv(p, is_keycode, c))
1725 if (c == KEY_MOUSE) {
1728 while (getmouse(&mev) != ERR) {
1729 if (dd->paste_pending &&
1730 mev.bstate == REPORT_MOUSE_POSITION) {
1731 /* xcfe-terminal is a bit weird.
1732 * It captures middle-press to
1733 * sanitise the paste, but lets
1734 * middle-release though. It comes
1735 * here as REPORT_MOUSE_POSTION
1736 * and we can use that to find the
1737 * position of the paste.
1738 * '6' is an unused button, and
1739 * ensures lib-input doesn't expect
1740 * matching press/release
1742 call("Mouse-event", ci->home,
1744 6, NULL, NULL, mev.x, mev.y);
1745 dd->paste_pending = 0;
1747 send_mouse(&mev, p);
1749 } else if (c == (wint_t)paste_seq[have_escape]) {
1751 if (!paste_seq[have_escape]) {
1755 } else if (have_escape == 1) {
1756 send_key(is_keycode, c, 1, p);
1758 } else if (have_escape) {
1759 send_key(OK, paste_seq[1], 1, p);
1760 for (i = 2; i < have_escape; i++)
1761 send_key(OK, paste_seq[i], 0, p);
1762 send_key(is_keycode, c, 0, p);
1765 send_key(is_keycode, c, 0, p);
1767 /* Don't know what other code might have done,
1768 * so re-set the screen
1772 if (have_escape == 1)
1773 send_key(is_keycode, '\e', 0, p);
1774 else if (have_escape > 1) {
1775 send_key(OK, paste_seq[1], 1, p);
1776 for (i = 2; i < have_escape; i++)
1777 send_key(OK, paste_seq[i], 0, p);
1779 if (dd->paste_pending == 2) {
1780 /* no mouse event to give postion, so treat as keyboard */
1781 call("Keystroke", ci->home, 0, NULL, ":Paste");
1782 dd->paste_pending = 0;
1783 } else if (dd->paste_pending == 1) {
1784 /* Wait for possible mouse-position update. */
1785 dd->paste_pending = 2;
1786 call_comm("event:timer", p, &input_handle, 200);
1791 DEF_CMD(display_ncurses)
1794 struct pane *ed = pane_root(ci->focus);
1795 const char *tty = ci->str;
1796 const char *term = ci->str2;
1799 term = "xterm-256color";
1801 p = ncurses_init(ed, tty, term);
1803 p = call_ret(pane, "editor:activate-display", p);
1804 if (p && ci->focus != ed)
1805 /* Assume ci->focus is a document */
1806 p = home_call_ret(pane, ci->focus, "doc:attach-view", p, 1);
1808 return comm_call(ci->comm2, "callback:display", p);
1813 void edlib_init(struct pane *ed safe)
1815 call_comm("global-set-command", ed, &display_ncurses, 0, NULL,
1816 "attach-display-ncurses");
1818 nc_map = key_alloc();
1819 key_add(nc_map, "window:refresh", &force_redraw);
1820 key_add(nc_map, "window:close", &nc_close_display);
1821 key_add(nc_map, "window:external-viewer", &nc_external_viewer);
1822 key_add(nc_map, "Close", &nc_close);
1823 key_add(nc_map, "Draw:clear", &nc_clear);
1824 key_add(nc_map, "Draw:text-size", &nc_text_size);
1825 key_add(nc_map, "Draw:text", &nc_draw_text);
1827 key_add(nc_map, "Draw:image", &nc_draw_image);
1828 key_add(nc_map, "Draw:image-size", &nc_image_size);
1830 key_add(nc_map, "Refresh:size", &nc_refresh_size);
1831 key_add(nc_map, "Refresh:postorder", &nc_refresh_post);
1832 key_add(nc_map, "Paste:get", &nc_get_paste);
1833 key_add(nc_map, "all-displays", &nc_notify_display);
1834 key_add(nc_map, "Sig:Winch", &handle_winch);
1835 key_add(nc_map, "Notify:Close", &nc_pane_close);