2 * Copyright Neil Brown ©2015-2021 <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>
44 typedef char hash_t[MD5_DIGEST_SIZE*2+1];
48 #undef NCURSES_OK_ADDR
49 #define NCURSES_OK_ADDR(p) ((void*)0 != NCURSES_CAST(const void *, (p)))
59 struct col_hash *col_hash;
70 char *rs1, *rs2, *rs3, *clear;
76 char last_screen[MD5_DIGEST_SIZE*2+1];
77 char next_screen[MD5_DIGEST_SIZE*2+1];
78 /* The next event to generate when idle */
79 enum { DoNil, DoMouse, DoKey, DoCheck, DoClose} next_event;
83 int clears; /* counts of Draw:clear events */
87 static SCREEN *current_screen;
88 static void ncurses_text(struct pane *p safe, struct pane *display safe,
89 wchar_t ch, int attr, short x, short y, short cursor);
90 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home);
91 DEF_CMD(input_handle);
92 DEF_CMD(handle_winch);
93 static struct map *nc_map;
94 DEF_LOOKUP_CMD(ncurses_handle, nc_map);
96 static struct display_data *current_dd;
97 static void set_screen(struct pane *p)
99 struct display_data *dd;
100 extern void *_nc_globals[100];
102 static int index = -1, offset=0;
105 if (current_screen && index >= 0)
106 _nc_globals[index] = NULL;
107 current_screen = NULL;
114 if (dd->scr == current_screen)
119 for (i=0; i<100; i++)
120 if (_nc_globals[i] < (void*)stdscr &&
121 _nc_globals[i]+4*(sizeof(void*)) >= (void*)stdscr) {
122 /* This is _nc_windowlist */
124 offset = ((void*)stdscr) - _nc_globals[i];
129 current_screen = dd->scr;
131 _nc_globals[index] = ((void*)stdscr) - offset;
138 static bool parse_event(struct pane *p safe);
139 static bool prepare_recrep(struct pane *p safe)
141 struct display_data *dd = p->data;
144 name = getenv("EDLIB_RECORD");
146 dd->log = fopen(name, "w");
147 name = getenv("EDLIB_REPLAY");
149 dd->input = fopen(name, "r");
150 if (getenv("EDLIB_PAUSE"))
151 sleep(atoi(getenv("EDLIB_PAUSE")));
159 static void close_recrep(struct pane *p safe)
161 struct display_data *dd = p->data;
164 fprintf(dd->log, "Close %d\n", dd->clears);
169 static void record_key(struct pane *p safe, char *key safe)
171 struct display_data *dd = p->data;
176 if (!strchr(key, '"'))
178 else if (!strchr(key, '\''))
180 else if (!strchr(key, '/'))
184 fprintf(dd->log, "Key %c%s%c\n", q,key,q);
188 static void record_mouse(struct pane *p safe, char *key safe, int x, int y)
190 struct display_data *dd = p->data;
194 if (!strchr(key, '"'))
196 else if (!strchr(key, '\''))
198 else if (!strchr(key, '/'))
202 fprintf(dd->log, "Mouse %c%s%c %d,%d\n", q,key,q, x, y);
206 static void record_screen(struct pane *p safe)
208 struct display_data *dd = p->data;
209 struct md5_state ctx;
210 uint16_t buf[CCHARW_MAX+5];
211 char out[MD5_DIGEST_SIZE*2+1];
214 if (!dd->log && !(dd->input && dd->next_event == DoCheck))
218 for (r = 0; r < p->h; r++)
219 for (c = 0; c < p->w; c++) {
221 wchar_t wc[CCHARW_MAX+2];
226 mvwin_wch(stdscr, r, c, &cc);
227 getcchar(&cc, wc, &a, &color, NULL);
228 pair_content(color, &fg, &bg);
229 buf[0] = htole16(fg);
230 buf[1] = htole16(bg);
231 for (l = 0; l < CCHARW_MAX && wc[l]; l++)
232 buf[l+3] = htole16(wc[l]);
234 LOG("%d,%d %d:%d:%d:%d %lc", c,r,fg,bg,l,wc[0], wc[0] > ' ' ? wc[0] : '?');
235 md5_update(&ctx, (uint8_t*)buf,
236 (l+3) * sizeof(uint16_t));
238 md5_final_txt(&ctx, out);
240 fprintf(dd->log, "Display %d,%d %s", p->w, p->h, out);
241 strcpy(dd->last_screen, out);
243 fprintf(dd->log, " %d,%d", p->cx, p->cy);
244 fprintf(dd->log, "\n");
247 if (dd->input && dd->input_sleeping) {
248 char *delay = getenv("EDLIB_REPLAY_DELAY");
249 call_comm("event:free", p, &next_evt);
251 call_comm("event:timer", p, &next_evt, atoi(delay));
253 call_comm("editor-on-idle", p, &next_evt);
257 static inline int match(char *line safe, char *w safe)
259 return strncmp(line, w, strlen(w)) == 0;
262 static char *copy_quote(char *line safe, char *buf safe)
268 if (q != '"' && q != '\'' && q != '/')
270 while (*line != q && *line)
278 static char *get_coord(char *line safe, struct xy *co safe)
285 v = strtol(line, &ep, 10);
286 if (!ep || ep == line || *ep != ',')
290 v = strtol(line, &ep, 10);
291 if (!ep || ep == line || (*ep && *ep != ' ' && *ep != '\n'))
297 static char *get_hash(char *line safe, hash_t hash safe)
302 for (i = 0; i < MD5_DIGEST_SIZE*2 && *line; i++)
309 static bool parse_event(struct pane *p safe)
311 struct display_data *dd = p->data;
315 dd->next_event = DoNil;
317 fgets(line, sizeof(line)-1, dd->input) == NULL)
319 else if (match(line, "Key ")) {
320 if (!copy_quote(line+4, dd->event_info))
322 dd->next_event = DoKey;
323 } else if (match(line, "Mouse ")) {
324 char *f = copy_quote(line+6, dd->event_info);
327 f = get_coord(f, &dd->event_pos);
330 dd->next_event = DoMouse;
331 } else if (match(line, "Display ")) {
332 char *f = get_coord(line+8, &dd->event_pos);
335 f = get_hash(f, dd->next_screen);
336 dd->next_event = DoCheck;
337 } else if (match(line, "Close")) {
338 dd->next_event = DoClose;
340 LOG("parse %s", line);
342 dd->input_sleeping = 1;
343 if (dd->next_event != DoCheck) {
344 char *delay = getenv("EDLIB_REPLAY_DELAY");
346 call_comm("event:timer", p, &next_evt, atoi(delay));
348 call_comm("editor-on-idle", p, &next_evt);
350 call_comm("event:timer", p, &next_evt, 10*1000);
356 struct pane *p = ci->home;
357 struct display_data *dd = p->data;
358 int button = 0, type = 0;
360 dd->input_sleeping = 0;
361 switch(dd->next_event) {
363 record_key(p, dd->event_info);
364 call("Keystroke", p, 0, NULL, dd->event_info);
367 record_mouse(p, dd->event_info, dd->event_pos.x,
369 if (strstr(dd->event_info, ":Press"))
371 else if (strstr(dd->event_info, ":Release"))
373 else if (strstr(dd->event_info, ":Motion"))
375 if (type == 1 || type == 2) {
376 char *e = dd->event_info + strlen(dd->event_info) - 1;
379 call("Mouse-event", p, button, NULL, dd->event_info,
381 dd->event_pos.x, dd->event_pos.y);
384 /* No point checking, just do a diff against new trace log. */
385 /* not; (strcmp(dd->next_screen, dd->last_screen) != 0) */
388 call("event:deactivate", p);
392 call_comm("event:read", p, &input_handle, 0);
393 call_comm("event:signal", p, &handle_winch, SIGWINCH);
400 static inline bool prepare_recrep(struct pane *p safe) {return False;}
401 static inline void record_key(struct pane *p safe, char *key) {}
402 static inline void record_mouse(struct pane *p safe, char *key safe,
404 static inline void record_screen(struct pane *p safe) {}
405 static inline void close_recrep(struct pane *p safe) {}
410 struct call_return *cr = container_of(ci->comm, struct call_return, c);
416 static void ncurses_end(struct pane *p safe);
418 DEF_CMD(nc_close_display)
420 /* If this is only display, then refuse to close this one */
421 struct call_return cr;
422 struct display_data *dd = ci->home->data;
425 call("Message", ci->focus, 0, NULL, dd->noclose);
431 call_comm("editor:notify:all-displays", ci->focus, &cr.c);
433 /* Need to call ncurses_end() before we send a Notify:Close
434 * notification, else server exists too early
436 ncurses_end(ci->home);
437 pane_close(ci->home);
439 call("Message", ci->focus, 0, NULL,
440 "Cannot close only window.");
444 DEF_CMD(nc_set_noclose)
446 struct display_data *dd = ci->home->data;
451 dd->noclose = strdup(ci->str);
455 static int nc_putc(int ch)
458 fputc(ch, current_dd->scr_file);
462 static char *fnormalize(struct pane *p safe, const char *str) safe
464 char *ret = strsave(p, str);
467 for (cp = ret ; cp && *cp ; cp++)
469 !strchr("/_-+=.,@#", *cp))
470 /* Don't like this char */
475 DEF_CMD(nc_external_viewer)
477 struct pane *p = ci->home;
478 struct display_data *dd = p->data;
479 char *disp = pane_attr_get(p, "DISPLAY");
480 char *remote = pane_attr_get(p, "REMOTE_SESSION");
482 const char *path = ci->str;
491 switch (pid = fork()) {
495 setenv("DISPLAY", disp, 1);
496 fd = open("/dev/null", O_RDWR);
504 execlp("xdg-open", "xdg-open", path, NULL);
506 default: /* parent */
507 /* FIXME record pid?? */
512 /* handle no-display case */
513 if (remote && strcmp(remote, "yes") == 0 &&
515 gethostname(buf, sizeof(buf)) == 0) {
516 struct addrinfo *res;
517 const struct addrinfo hints = {
518 .ai_flags = AI_CANONNAME,
520 if (getaddrinfo(buf, NULL, &hints, &res) == 0 &&
521 res && res->ai_canonname)
522 fqdn = strdup(res->ai_canonname);
527 ioctl(fileno(dd->scr_file), FIONREAD, &n);
529 n -= read(fileno(dd->scr_file), buf,
530 n <= (int)sizeof(buf) ? n : (int)sizeof(buf));
533 /* Endwin doesn't seem to reset properly, at least on xfce-terminal.
537 tputs(dd->rs1, 1, nc_putc);
539 tputs(dd->rs2, 1, nc_putc);
541 tputs(dd->rs3, 1, nc_putc);
543 tputs(dd->clear, 1, nc_putc);
544 fflush(dd->scr_file);
546 fprintf(dd->scr_file, "# Consider copy-pasting following\n");
547 if (fqdn && path[0] == '/') {
548 /* File will not be local for the user, so help them copy it. */
549 const char *tmp = fnormalize(p, ci->str2 ?: "XXXXXX");
550 const char *fname = fnormalize(p, ci->str);
552 if (strcmp(fname, ci->str) != 0)
553 /* file name had unusuable chars, need to create safe name */
554 link(ci->str, fname);
555 fprintf(dd->scr_file, "f=`mktemp --tmpdir %s`; scp %s:%s $f ; ",
559 fprintf(dd->scr_file, "xdg-open %s\n", path);
560 fprintf(dd->scr_file, "# Press Enter to continue\n");
561 n = read(fileno(dd->scr_file), buf, sizeof(buf));
567 static void ncurses_end(struct pane *p safe)
569 struct display_data *dd = p->data;
573 dd->did_close = True;
578 /* disable bracketed-paste */
579 fprintf(dd->scr_file, "\033[?2004l");
580 fflush(dd->scr_file);
583 free(buf_final(&dd->paste_buf));
584 free(dd->paste_latest);
588 tputs(dd->rs1, 1, nc_putc);
590 tputs(dd->rs2, 1, nc_putc);
592 tputs(dd->rs3, 1, nc_putc);
593 fflush(dd->scr_file);
597 * hash table for colours and pairs
598 * key is r,g,b (0-1000) in 10bit fields,
599 * or fg,bg in 16 bit fields with bit 31 set
600 * content is colour number of colour pair number.
601 * We never delete entries, unless we delete everything.
608 #define COL_KEY(r,g,b) ((r<<20) | (g<<10) | b)
609 #define PAIR_KEY(fg, bg) ((1<<31) | (fg<<16) | bg)
610 #define hash_key(k) ((((k) * 0x61C88647) >> 20) & 0xff)
613 int next_col, next_pair;
614 struct chash *tbl[256];
617 static struct col_hash *safe hash_init(struct display_data *dd safe)
620 dd->col_hash = malloc(sizeof(*dd->col_hash));
621 memset(dd->col_hash, 0, sizeof(*dd->col_hash));
622 dd->col_hash->next_col = 16;
623 dd->col_hash->next_pair = 1;
628 static void hash_free(struct display_data *dd safe)
637 for (h = 0; h < 255; h++)
638 while ((c = ch->tbl[h]) != NULL) {
639 ch->tbl[h] = c->next;
646 static int find_col(struct display_data *dd safe, int rgb[])
648 if (0 /* dynamic colours */) {
649 struct col_hash *ch = hash_init(dd);
650 int k = COL_KEY(rgb[0], rgb[1], rgb[2]);
654 for (c = ch->tbl[h]; c; c = c->next)
657 c = malloc(sizeof(*c));
659 c->content = ch->next_col++;
660 c->next = ch->tbl[h];
662 init_color(c->content, rgb[0], rgb[1], rgb[2]);
665 /* If colour is grey, map to 1 of 26 for 0, 232 to 255, 15
666 * The 24 grey shades have bit values from 8 to 238, so the
667 * gap to white is a little bigger, but that probably doesn't
669 * Otherwise map to 6x6x6 rgb cube from 16
670 * Actual colours are biased bright, at 0,95,135,175,215,255
671 * with a 95 gap at bottom and 40 elsewhere.
672 * So we divide 5 and 2 half ranges, and merge bottom 2.
677 //printf("want %d,%d,%d\n", rgb[0], rgb[1], rgb[2]);
678 if (abs(rgb[0] - rgb[1]) < 10 &&
679 abs(rgb[1] - rgb[2]) < 10) {
680 /* grey - within 1% */
681 int v = (rgb[0] + rgb[1] + rgb[2]) / 3;
683 /* We divide the space in 24 ranges surrounding
684 * the grey values, and 2 half-ranges near black
685 * and white. So add half a range - 1000/50 -
686 * then divide by 1000/25 to get a number from 0 to 25.
688 v = (v + 1000/50) / (1000/25);
690 return 0; /* black */
692 return 15; /* white */
693 //printf(" grey %d\n", v + 231);
694 /* grey shades are from 232 to 255 inclusive */
697 for (h = 0; h < 3; h++) {
700 v = (v + 1000/12) / (1000/6);
701 /* v is from 0 to 6, we want up to 5
702 * with 0 and 1 merged
709 //printf(" color %d\n", c + 16);
714 static int to_pair(struct display_data *dd safe, int fg, int bg)
716 struct col_hash *ch = hash_init(dd);
717 int k = PAIR_KEY(fg, bg);
721 for (c = ch->tbl[h]; c; c = c->next)
724 c = malloc(sizeof(*c));
726 c->content = ch->next_pair++;
727 c->next = ch->tbl[h];
729 init_pair(c->content, fg, bg);
733 static int cvt_attrs(struct pane *p safe, struct pane *home safe,
734 const char *attrs, bool use_parent)
736 struct display_data *dd = home->data;
741 int fg = COLOR_BLACK;
742 int bg = COLOR_WHITE+8;
747 } while (p->parent != p &&(pan = pane_panel(p, NULL)) == NULL);
748 if (pan && use_parent) {
749 /* Get 'default colours for this pane - set at clear */
750 int at = getbkgd(panel_window(pan));
751 int pair = PAIR_NUMBER(at);
753 pair_content(pair, &dfg, &dbg);
769 strncpy(tmp, a, c-a);
771 if (strcmp(tmp, "inverse")==0) attr |= A_STANDOUT;
772 else if (strcmp(tmp, "noinverse")==0) attr &= ~A_STANDOUT;
773 else if (strcmp(tmp, "bold")==0) attr |= A_BOLD;
774 else if (strcmp(tmp, "nobold")==0) attr &= ~A_BOLD;
775 else if (strcmp(tmp, "underline")==0) attr |= A_UNDERLINE;
776 else if (strcmp(tmp, "nounderline")==0) attr &= ~A_UNDERLINE;
777 else if (strncmp(tmp, "fg:", 3) == 0) {
778 struct call_return cr =
779 call_ret(all, "colour:map", home,
781 int rgb[3] = {cr.i, cr.i2, cr.x};
782 fg = find_col(dd, rgb);
783 } else if (strncmp(tmp, "bg:", 3) == 0) {
784 struct call_return cr =
785 call_ret(all, "colour:map", home,
787 int rgb[3] = {cr.i, cr.i2, cr.x};
788 bg = find_col(dd, rgb);
792 if (fg != COLOR_BLACK || bg != COLOR_WHITE+8)
793 attr |= COLOR_PAIR(to_pair(dd, fg, bg));
797 static int make_cursor(int attr)
799 return attr ^ A_UNDERLINE;
802 DEF_CMD(nc_notify_display)
804 struct display_data *dd = ci->home->data;
805 comm_call(ci->comm2, "callback:display", ci->home, dd->last_event);
811 struct pane *p = ci->home;
812 struct display_data *dd = p->data;
815 fclose(dd->scr_file);
819 DEF_CMD(nc_pane_close)
823 set_screen(ci->home);
824 while ((pan = panel_above(pan)) != NULL)
825 if (panel_userptr(pan) == ci->focus)
828 WINDOW *win = panel_window(pan);
831 pane_damaged(ci->home, DAMAGED_POSTORDER);
836 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home)
840 while ((pan = panel_above(pan)) != NULL)
841 if (panel_userptr(pan) == p)
847 pan = new_panel(newwin(p->h, p->w, 0, 0));
848 set_panel_userptr(pan, p);
849 pane_add_notify(home, p, "Notify:Close");
856 struct pane *p = ci->home;
857 struct display_data *dd = p->data;
858 int attr = cvt_attrs(ci->focus, p, ci->str, ci->str == NULL);
864 panel = pane_panel(ci->focus, p);
867 win = panel_window(panel);
869 if (h != ci->focus->h || w != ci->focus->w) {
870 wresize(win, ci->focus->h, ci->focus->w);
871 replace_panel(panel, win);
877 pane_damaged(p, DAMAGED_POSTORDER);
881 DEF_CMD(nc_text_size)
883 int max_space = ci->num;
886 const char *str = ci->str;
890 while (str[0] != 0) {
891 wint_t wc = get_utf8(&str, NULL);
899 if (size <= max_space)
900 max_bytes = str - ci->str;
902 return comm_call(ci->comm2, "callback:size", ci->focus,
903 max_bytes, NULL, NULL,
904 0, NULL, NULL, size, 1);
907 DEF_CMD(nc_draw_text)
909 struct pane *p = ci->home;
910 int attr = cvt_attrs(ci->focus, p, ci->str2, True);
911 int cursor_offset = ci->num;
912 short x = ci->x, y = ci->y;
913 const char *str = ci->str;
918 while (str[0] != 0) {
919 int precurs = str <= ci->str + cursor_offset;
920 wint_t wc = get_utf8(&str, NULL);
922 if (wc == WEOF || wc == WERR)
927 if (precurs && str > ci->str + cursor_offset)
928 ncurses_text(ci->focus, p, wc, attr, x, y, 1);
930 ncurses_text(ci->focus, p, wc, attr, x, y, 0);
933 if (str == ci->str + cursor_offset)
934 ncurses_text(ci->focus, p, ' ', 0, x, y, 1);
935 pane_damaged(p, DAMAGED_POSTORDER);
939 DEF_CMD(nc_refresh_size)
941 struct pane *p = ci->home;
944 getmaxyx(stdscr, p->h, p->w);
949 DEF_CMD(nc_refresh_post)
951 struct pane *p = ci->home;
957 /* Need to ensure stacking order and panel y,x position
958 * is correct. FIXME it would be good if we could skip this
961 pan = panel_above(NULL);
964 p1 = (struct pane*) panel_userptr(pan);
965 for (;(pan2 = panel_above(pan)) != NULL; pan = pan2) {
966 struct pane *p2 = (struct pane*)panel_userptr(pan2);
967 p1 = (struct pane*)panel_userptr(pan);
971 if (p1->abs_z < p2->abs_z)
973 if (p1->abs_z == p2->abs_z &&
976 /* pan needs to be above pan2. All we can do is move it to
977 * the top. Anything that needs to be above it will eventually
981 /* Now the panel below pan might need to be over pan2 too... */
982 pan = panel_below(pan2);
987 /* As we need to crop pane against their parents, we cannot simply
988 * use update_panels(). Instead we copy each to stdscr and refresh
991 for (pan = NULL; (pan = panel_above(pan)) != NULL; ) {
993 struct xy src, dest, destend;
996 p1 = (void*)panel_userptr(pan);
999 dest = pane_mapxy(p1, p, 0, 0, True);
1000 destend = pane_mapxy(p1, p, p1->w, p1->h, True);
1001 src = pane_mapxy(p1, p, 0, 0, False);
1002 src.x = dest.x - src.x;
1003 src.y = dest.y - src.y;
1004 win = panel_window(pan);
1005 getmaxyx(win, h, w);
1006 /* guard again accessing beyond boundary of win */
1007 if (destend.x > dest.x + (w - src.x))
1008 destend.x = dest.x + (w - src.x);
1009 if (destend.y > dest.y + (h - src.y))
1010 destend.y = dest.y - (h - src.y);
1011 copywin(win, stdscr, src.y, src.x,
1012 dest.y, dest.x, destend.y-1, destend.x-1, 0);
1014 /* place the cursor */
1017 while (p1 != p && (pan = pane_panel(p1, NULL)) == NULL)
1019 if (pan && p1->cx >= 0) {
1020 struct xy curs = pane_mapxy(p1, p, p1->cx, p1->cy, False);
1021 wmove(stdscr, curs.y, curs.x);
1022 } else if (p->cx >= 0)
1023 wmove(stdscr, p->cy, p->cx);
1025 record_screen(ci->home);
1029 static struct pane *ncurses_init(struct pane *ed,
1030 const char *tty, const char *term)
1034 struct display_data *dd;
1040 if (tty && strcmp(tty, "-") != 0)
1041 f = fopen(tty, "r+");
1043 f = fdopen(1, "r+");
1046 scr = newterm(term, f, f);
1053 dd->is_xterm = (term && strncmp(term, "xterm", 5) == 0);
1055 p = pane_register(ed, 1, &ncurses_handle.c, dd);
1063 use_default_colors();
1069 intrflush(stdscr, FALSE);
1070 keypad(stdscr, TRUE);
1071 mousemask(BUTTON1_PRESSED | BUTTON1_RELEASED |
1072 BUTTON2_PRESSED | BUTTON2_RELEASED |
1073 BUTTON3_PRESSED | BUTTON3_RELEASED |
1074 BUTTON4_PRESSED | BUTTON4_RELEASED |
1075 BUTTON5_PRESSED | BUTTON5_RELEASED |
1076 BUTTON_CTRL | BUTTON_SHIFT | BUTTON_ALT |
1077 REPORT_MOUSE_POSITION, NULL);
1080 /* Enable bracketed-paste */
1081 fprintf(dd->scr_file, "\033[?2004h");
1082 fflush(dd->scr_file);
1085 getmaxyx(stdscr, rows, cols);
1086 pane_resize(p, 0, 0, cols, rows);
1088 area = dd->attr_buf;
1089 dd->rs1 = tgetstr("rs1", &area);
1091 dd->rs1 = tgetstr("is1", &area);
1092 dd->rs2 = tgetstr("rs2", &area);
1094 dd->rs2 = tgetstr("is2", &area);
1095 dd->rs3 = tgetstr("rs3", &area);
1097 dd->rs3 = tgetstr("is3", &area);
1098 dd->clear = tgetstr("clear", &area);
1100 call("editor:request:all-displays", p);
1101 if (!prepare_recrep(p)) {
1102 call_comm("event:read", p, &input_handle, fileno(f));
1103 if (!tty || strcmp(tty, "-") == 0)
1104 call_comm("event:signal", p, &handle_winch, SIGWINCH);
1109 REDEF_CMD(handle_winch)
1111 struct pane *p = ci->home;
1112 struct display_data *dd = p->data;
1113 struct winsize size;
1114 ioctl(fileno(dd->scr_file), TIOCGWINSZ, &size);
1116 resize_term(size.ws_row, size.ws_col);
1118 pane_resize(p, 0, 0, size.ws_row, size.ws_col);
1122 DEF_CMD(force_redraw)
1124 struct pane *p = ci->home;
1131 static void ncurses_text(struct pane *p safe, struct pane *display safe,
1132 wchar_t ch, int attr, short x, short y, short cursor)
1141 set_screen(display);
1143 if (pane_has_focus(p->z < 0 ? p->parent : p)) {
1144 /* Cursor is in-focus */
1145 struct xy curs = pane_mapxy(p, display, x, y, False);
1146 display->cx = curs.x;
1147 display->cy = curs.y;
1149 /* Cursor here, but not focus */
1150 attr = make_cursor(attr);
1156 pan = pane_panel(p2, NULL);
1157 while (!pan && p2->parent != p2) {
1159 pan = pane_panel(p2, NULL);
1162 struct xy xy = pane_mapxy(p, p2, x, y, False);
1163 mvwadd_wch(panel_window(pan), xy.y, xy.x, &cc);
1167 static struct namelist {
1171 {KEY_DOWN, ":Down"},
1173 {KEY_LEFT, ":Left"},
1174 {KEY_RIGHT, ":Right"},
1175 {KEY_HOME, ":Home"},
1176 {KEY_BACKSPACE, ":Backspace"},
1177 {KEY_DL, ":DelLine"},
1178 {KEY_IL, ":InsLine"},
1181 {KEY_ENTER, ":Enter"},
1184 {KEY_NPAGE, ":Next"},
1185 {KEY_PPAGE, ":Prior"},
1187 {KEY_SDC, ":S:Del"},
1188 {KEY_SDL, ":S:DelLine"},
1189 {KEY_SEND, ":S:End"},
1190 {KEY_SHOME, ":S:Home"},
1191 {KEY_SLEFT, ":S:Left"},
1192 {KEY_SRIGHT, ":S:Right"},
1193 {KEY_BTAB, ":S:Tab"},
1197 { 0616, ":S:Prior"},
1199 { 01041, ":S:Home"},
1201 { 01066, ":S:Prior"},
1202 { 01015, ":S:Next"},
1204 { 01027, ":A:S:Home"},
1205 { 01022, ":A:S:End"},
1206 { 01046, ":A:S:Prior"},
1207 { 01047, ":A:S:Next"}, // ??
1209 { 01052, ":A:Prior"},
1210 { 01045, ":A:Next"},
1211 { 01026, ":A:Home"},
1214 { 01014, ":A:Down"},
1215 { 01040, ":A:Left"},
1216 { 01057, ":A:Right"},
1247 {'\177', ":Delete"},
1252 static char *find_name (struct namelist *l safe, wint_t c)
1255 for (i = 0; l[i].name; i++)
1261 static void send_key(int keytype, wint_t c, int alt, struct pane *p safe)
1263 struct display_data *dd = p->data;
1265 char buf[100];/* FIXME */
1267 char *a = alt ? ":A" : "";
1269 if (keytype == KEY_CODE_YES) {
1270 n = find_name(key_names, c);
1272 sprintf(buf, "%sNcurs-%o", a, c);
1274 strcat(strcpy(buf, a), n);
1276 n = find_name(char_names, c);
1278 sprintf(buf, "%s%s", a, n);
1279 else if (c < ' ' || c == 0x7f)
1280 sprintf(buf, "%s:C-%c",
1283 sprintf(buf, "%s-%s", a, put_utf8(t, c));
1286 dd->last_event = time(NULL);
1288 call("Keystroke", p, 0, NULL, buf);
1291 static void do_send_mouse(struct pane *p safe, int x, int y, char *cmd safe,
1292 int button, char *mod, int type)
1295 struct display_data *dd = p->data;
1297 record_mouse(p, cmd, x, y);
1298 ret = call("Mouse-event", p, button, NULL, cmd, type, NULL, mod, x, y);
1299 if (type == 1 && !dd->report_position) {
1301 fprintf(dd->scr_file, "\033[?1002h");
1302 fflush(dd->scr_file);
1304 dd->report_position = 1;
1305 } else if (type == 3 && ret <= 0) {
1307 fprintf(dd->scr_file, "\033[?1002l");
1308 fflush(dd->scr_file);
1310 dd->report_position = 0;
1314 static void send_mouse(MEVENT *mev safe, struct pane *p safe)
1316 struct display_data *dd = p->data;
1322 /* MEVENT has lots of bits. We want a few numbers */
1323 for (b = 1 ; b <= (NCURSES_MOUSE_VERSION <= 1 ? 3 : 5); b++) {
1324 mmask_t s = mev->bstate;
1329 if (s & BUTTON_SHIFT) modf |= 1;
1330 if (s & BUTTON_CTRL) modf |= 2;
1331 if (s & BUTTON_ALT) modf |= 4;
1333 case 0: mod = ""; break;
1334 case 1: mod = ":S"; break;
1335 case 2: mod = ":C"; break;
1336 case 3: mod = ":C:S"; break;
1337 case 4: mod = ":A"; break;
1338 case 5: mod = ":A:S"; break;
1339 case 6: mod = ":A:C"; break;
1340 case 7: mod = ":A:C:S"; break;
1342 if (BUTTON_PRESS(s, b))
1343 action = "%s:Press-%d";
1344 else if (BUTTON_RELEASE(s, b)) {
1345 action = "%s:Release-%d";
1346 /* Modifiers only reported on button Press */
1350 snprintf(buf, sizeof(buf), action, mod, b);
1351 dd->last_event = time(NULL);
1352 do_send_mouse(p, x, y, buf, b, mod, BUTTON_PRESS(s,b) ? 1 : 2);
1354 if ((mev->bstate & REPORT_MOUSE_POSITION) &&
1355 dd->report_position)
1356 /* Motion doesn't update last_event */
1357 do_send_mouse(p, x, y, ":Motion", 0, "", 3);
1360 static void paste_start(struct pane *home safe)
1362 struct display_data *dd = home->data;
1364 dd->paste_start = time(NULL);
1365 buf_init(&dd->paste_buf);
1368 static void paste_flush(struct pane *home safe)
1370 struct display_data *dd = home->data;
1372 if (!dd->paste_start)
1374 free(dd->paste_latest);
1375 dd->paste_latest = buf_final(&dd->paste_buf);
1376 if (dd->paste_buf.len > 0)
1377 dd->paste_pending = 1;
1378 dd->paste_start = 0;
1381 static bool paste_recv(struct pane *home safe, int is_keycode, wint_t ch)
1383 struct display_data *dd = home->data;
1385 if (dd->paste_start == 0)
1388 if (dd->paste_start < now || dd->paste_start > now + 2 ||
1389 is_keycode != OK || ch == KEY_MOUSE) {
1395 /* I really don't want carriage-returns... */
1397 buf_append(&dd->paste_buf, ch);
1398 if (ch == '~' && dd->paste_buf.len >= 6 &&
1400 buf_final(&dd->paste_buf) + dd->paste_buf.len - 6) == 0) {
1401 dd->paste_buf.len -= 6;
1407 DEF_CMD(nc_get_paste)
1409 struct display_data *dd = ci->home->data;
1411 comm_call(ci->comm2, "cb", ci->focus,
1412 dd->paste_start, NULL, dd->paste_latest);
1416 REDEF_CMD(input_handle)
1418 struct pane *p = ci->home;
1419 struct display_data *dd = p->data;
1420 static const char paste_seq[] = "\e[200~";
1423 int have_escape = 0;
1426 if (!(void*)p->data)
1427 /* already closed */
1430 while ((is_keycode = get_wch(&c)) != ERR) {
1431 if (paste_recv(p, is_keycode, c))
1433 if (c == KEY_MOUSE) {
1436 while (getmouse(&mev) != ERR) {
1437 if (dd->paste_pending &&
1438 mev.bstate == REPORT_MOUSE_POSITION) {
1439 /* xcfe-terminal is a bit weird.
1440 * It captures middle-press to
1441 * sanitise the paste, but lets
1442 * middle-release though. It comes
1443 * here as REPORT_MOUSE_POSTION
1444 * and we can use that to find the
1445 * position of the paste.
1446 * '6' is an unused button, and
1447 * ensures lib-input doesn't expect
1448 * matching press/release
1450 call("Mouse-event", ci->home,
1452 6, NULL, NULL, mev.x, mev.y);
1453 dd->paste_pending = 0;
1455 send_mouse(&mev, p);
1457 } else if (c == (wint_t)paste_seq[have_escape]) {
1459 if (!paste_seq[have_escape]) {
1463 } else if (have_escape == 1) {
1464 send_key(is_keycode, c, 1, p);
1466 } else if (have_escape) {
1467 send_key(OK, paste_seq[1], 1, p);
1468 for (i = 2; i < have_escape; i++)
1469 send_key(OK, paste_seq[i], 0, p);
1470 send_key(is_keycode, c, 0, p);
1473 send_key(is_keycode, c, 0, p);
1475 /* Don't know what other code might have done,
1476 * so re-set the screen
1480 if (have_escape == 1)
1481 send_key(is_keycode, '\e', 0, p);
1482 else if (have_escape > 1) {
1483 send_key(OK, paste_seq[1], 1, p);
1484 for (i = 2; i < have_escape; i++)
1485 send_key(OK, paste_seq[i], 0, p);
1487 if (dd->paste_pending == 2) {
1488 /* no mouse event to give postion, so treat as keyboard */
1489 call("Keystroke", ci->home, 0, NULL, ":Paste");
1490 dd->paste_pending = 0;
1491 } else if (dd->paste_pending == 1) {
1492 /* Wait for possible mouse-position update. */
1493 dd->paste_pending = 2;
1494 call_comm("event:timer", p, &input_handle, 200);
1499 DEF_CMD(display_ncurses)
1502 const char *tty = ci->str;
1503 const char *term = ci->str2;
1506 term = "xterm-256color";
1508 p = ncurses_init(ci->focus, tty, term);
1510 return comm_call(ci->comm2, "callback:display", p);
1515 void edlib_init(struct pane *ed safe)
1517 call_comm("global-set-command", ed, &display_ncurses, 0, NULL,
1518 "attach-display-ncurses");
1520 nc_map = key_alloc();
1521 key_add(nc_map, "Display:refresh", &force_redraw);
1522 key_add(nc_map, "Display:close", &nc_close_display);
1523 key_add(nc_map, "Display:set-noclose", &nc_set_noclose);
1524 key_add(nc_map, "Display:external-viewer", &nc_external_viewer);
1525 key_add(nc_map, "Close", &nc_close);
1526 key_add(nc_map, "Free", &edlib_do_free);
1527 key_add(nc_map, "Draw:clear", &nc_clear);
1528 key_add(nc_map, "Draw:text-size", &nc_text_size);
1529 key_add(nc_map, "Draw:text", &nc_draw_text);
1530 key_add(nc_map, "Refresh:size", &nc_refresh_size);
1531 key_add(nc_map, "Refresh:postorder", &nc_refresh_post);
1532 key_add(nc_map, "Paste:get", &nc_get_paste);
1533 key_add(nc_map, "all-displays", &nc_notify_display);
1534 key_add(nc_map, "Sig:Winch", &handle_winch);
1535 key_add(nc_map, "Notify:Close", &nc_pane_close);