]> git.neil.brown.name Git - edlib.git/blob - display-ncurses.c
ncurses: set mouseinterval back to zero.
[edlib.git] / display-ncurses.c
1 /*
2  * Copyright Neil Brown ©2015-2021 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * ncurses front end for edlib.
6  *
7  * There is currently only support for a single terminal window
8  * which provides a single pane.
9  *
10  * Rendering operations are:
11  *  draw text with attributes at location
12  *  erase with attributes in rectangle
13  */
14
15 #define RECORD_REPLAY
16
17 #ifndef _XOPEN_SOURCE
18 #define _XOPEN_SOURCE
19 #endif
20 #define _XOPEN_SOURCE_EXTENDED
21 #ifndef _DEFAULT_SOURCE
22 #define _DEFAULT_SOURCE
23 #endif
24
25 #include <stdlib.h>
26 #include <fcntl.h>
27 #include <time.h>
28 #include <curses.h>
29 #include <panel.h>
30 #include <string.h>
31 #include <locale.h>
32 #include <ctype.h>
33 #include <signal.h>
34 #include <sys/ioctl.h>
35 #include <term.h>
36 #include <netdb.h>
37
38 #include "core.h"
39
40 #ifdef RECORD_REPLAY
41 #include <unistd.h>
42 #include <stdio.h>
43 #include "md5.h"
44 typedef char hash_t[MD5_DIGEST_SIZE*2+1];
45 #endif
46
47 #ifdef __CHECKER__
48 #undef NCURSES_OK_ADDR
49 #define NCURSES_OK_ADDR(p) ((void*)0 != NCURSES_CAST(const void *, (p)))
50 #endif
51
52 struct col_hash;
53
54 struct display_data {
55         SCREEN                  *scr;
56         FILE                    *scr_file;
57         int                     is_xterm;
58         char                    *noclose;
59         struct col_hash         *col_hash;
60         int                     report_position;
61         long                    last_event;
62
63         char                    *rs1, *rs2, *rs3, *clear;
64         char                    attr_buf[1024];
65         #ifdef RECORD_REPLAY
66         FILE                    *log;
67         FILE                    *input;
68         char                    last_screen[MD5_DIGEST_SIZE*2+1];
69         char                    next_screen[MD5_DIGEST_SIZE*2+1];
70         /* The next event to generate when idle */
71         enum { DoNil, DoMouse, DoKey, DoCheck, DoClose} next_event;
72         char                    event_info[30];
73         struct xy               event_pos;
74         #endif
75 };
76
77 static SCREEN *current_screen;
78 static void ncurses_text(struct pane *p safe, struct pane *display safe,
79                          wchar_t ch, int attr, short x, short y, short cursor);
80 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home);
81 DEF_CMD(input_handle);
82 DEF_CMD(handle_winch);
83 static struct map *nc_map;
84 DEF_LOOKUP_CMD(ncurses_handle, nc_map);
85
86 static struct display_data *current_dd;
87 static void set_screen(struct pane *p)
88 {
89         struct display_data *dd;
90         extern void *_nc_globals[100];
91         int i;
92         static int index = -1, offset=0;
93
94         if (!p) {
95                 if (current_screen && index >= 0)
96                         _nc_globals[index] = NULL;
97                 current_screen = NULL;
98                 return;
99         }
100         dd = p->data;
101         current_dd = dd;
102         if (!dd)
103                 return;
104         if (dd->scr == current_screen)
105                 return;
106
107         if (index == -1) {
108                 index = -2;
109                 for (i=0; i<100; i++)
110                         if (_nc_globals[i] < (void*)stdscr &&
111                             _nc_globals[i]+4*(sizeof(void*)) >= (void*)stdscr) {
112                                 /* This is _nc_windowlist */
113                                 index = i;
114                                 offset = ((void*)stdscr) - _nc_globals[i];
115                         }
116         }
117
118         set_term(dd->scr);
119         current_screen = dd->scr;
120         if (index >= 0) {
121                 _nc_globals[index] = ((void*)stdscr) - offset;
122         }
123 }
124
125 #ifdef RECORD_REPLAY
126 DEF_CMD(next_evt);
127 DEF_CMD(abort_replay);
128
129 static bool parse_event(struct pane *p safe);
130 static bool prepare_recrep(struct pane *p safe)
131 {
132         struct display_data *dd = p->data;
133         char *name;
134
135         name = getenv("EDLIB_RECORD");
136         if (name)
137                 dd->log = fopen(name, "w");
138         name = getenv("EDLIB_REPLAY");
139         if (name)
140                 dd->input = fopen(name, "r");
141         if (getenv("EDLIB_PAUSE"))
142                 sleep(atoi(getenv("EDLIB_PAUSE")));
143         if (dd->input) {
144                 parse_event(p);
145                 return True;
146         }
147         return False;
148 }
149
150 static void close_recrep(struct pane *p safe)
151 {
152         struct display_data *dd = p->data;
153
154         if (dd->log) {
155                 fprintf(dd->log, "Close\n");
156                 fclose(dd->log);
157         }
158 }
159
160 static void record_key(struct pane *p safe, char *key safe)
161 {
162         struct display_data *dd = p->data;
163         char q;
164
165         if (!dd->log)
166                 return;
167         if (!strchr(key, '"'))
168                 q = '"';
169         else if (!strchr(key, '\''))
170                 q = '\'';
171         else if (!strchr(key, '/'))
172                 q = '/';
173         else
174                 return;
175         fprintf(dd->log, "Key %c%s%c\n", q,key,q);
176 }
177
178 static void record_mouse(struct pane *p safe, char *key safe, int x, int y)
179 {
180         struct display_data *dd = p->data;
181         char q;
182         if (!dd->log)
183                 return;
184         if (!strchr(key, '"'))
185                 q = '"';
186         else if (!strchr(key, '\''))
187                 q = '\'';
188         else if (!strchr(key, '/'))
189                 q = '/';
190         else
191                 return;
192         fprintf(dd->log, "Mouse %c%s%c %d,%d\n", q,key,q, x, y);
193 }
194
195 static void record_screen(struct pane *p safe)
196 {
197         struct display_data *dd = p->data;
198         struct md5_state ctx;
199         uint16_t buf[CCHARW_MAX+5];
200         char out[MD5_DIGEST_SIZE*2+1];
201         int r,c;
202
203         if (!dd->log && !(dd->input && dd->next_event == DoCheck))
204                 return;
205         set_screen(p);
206         md5_init(&ctx);
207         for (r = 0; r < p->h; r++)
208                 for (c = 0; c < p->w; c++) {
209                         cchar_t cc;
210                         wchar_t wc[CCHARW_MAX+2];
211                         attr_t a;
212                         short color, fg, bg;
213                         int l;
214
215                         mvwin_wch(stdscr, r, c, &cc);
216                         getcchar(&cc, wc, &a, &color, NULL);
217                         pair_content(color, &fg, &bg);
218                         buf[0] = htole16(fg);
219                         buf[1] = htole16(bg);
220                         for (l = 0; l < CCHARW_MAX && wc[l]; l++)
221                                 buf[l+3] = htole16(wc[l]);
222                         buf[2] = htole16(l);
223                         LOG("%d,%d %d:%d:%d:%d %lc", c,r,fg,bg,l,wc[0], wc[0] > ' ' ? wc[0] : '?');
224                         md5_update(&ctx, (uint8_t*)buf,
225                                    (l+3) * sizeof(uint16_t));
226                 }
227         md5_final_txt(&ctx, out);
228         if (dd->log) {
229                 fprintf(dd->log, "Display %d,%d %s", p->w, p->h, out);
230                 strcpy(dd->last_screen, out);
231                 if (p->cx >= 0)
232                         fprintf(dd->log, " %d,%d", p->cx, p->cy);
233                 fprintf(dd->log, "\n");
234         }
235         if (dd->input && dd->next_event == DoCheck) {
236                 call_comm("event:free", p, &abort_replay);
237 //              if (strcmp(dd->last_screen, dd->next_screen) != 0)
238 //                      dd->next_event = DoClose;
239                 call_comm("editor-on-idle", p, &next_evt);
240         }
241 }
242
243 static inline int match(char *line safe, char *w safe)
244 {
245         return strncmp(line, w, strlen(w)) == 0;
246 }
247
248 static char *copy_quote(char *line safe, char *buf safe)
249 {
250         char q;
251         while (*line == ' ')
252                 line++;
253         q = *line++;
254         if (q != '"' && q != '\'' && q != '/')
255                 return NULL;
256         while (*line != q && *line)
257                 *buf++ = *line++;
258         if (!*line)
259                 return NULL;
260         *buf = '\0';
261         return line+1;
262 }
263
264 static char *get_coord(char *line safe, struct xy *co safe)
265 {
266         long v;
267         char *ep;
268
269         while (*line == ' ')
270                 line ++;
271         v = strtol(line, &ep, 10);
272         if (!ep || ep == line || *ep != ',')
273                 return NULL;
274         co->x = v;
275         line = ep+1;
276         v = strtol(line, &ep, 10);
277         if (!ep || ep == line || (*ep && *ep != ' ' && *ep != '\n'))
278                 return NULL;
279         co->y = v;
280         return ep;
281 }
282
283 static char *get_hash(char *line safe, hash_t hash safe)
284 {
285         int i;
286         while (*line == ' ')
287                 line++;
288         for (i = 0; i < MD5_DIGEST_SIZE*2 && *line; i++)
289                 hash[i] = *line++;
290         if (!*line)
291                 return NULL;
292         return line;
293 }
294
295 REDEF_CMD(abort_replay)
296 {
297         struct display_data *dd = ci->home->data;
298
299         dd->next_event = DoClose;
300         return next_evt_func(ci);
301 }
302
303 static bool parse_event(struct pane *p safe)
304 {
305         struct display_data *dd = p->data;
306         char line[80];
307
308         line[79] = 0;
309         dd->next_event = DoNil;
310         if (!dd->input ||
311             fgets(line, sizeof(line)-1, dd->input) == NULL)
312                 line[0]=0;
313         else if (match(line, "Key ")) {
314                 if (!copy_quote(line+4, dd->event_info))
315                         return False;
316                 dd->next_event = DoKey;
317         } else if (match(line, "Mouse ")) {
318                 char *f = copy_quote(line+6, dd->event_info);
319                 if (!f)
320                         return False;
321                 f = get_coord(f, &dd->event_pos);
322                 if (!f)
323                         return False;
324                 dd->next_event = DoMouse;
325         } else if (match(line, "Display ")) {
326                 char *f = get_coord(line+8, &dd->event_pos);
327                 if (!f)
328                         return False;
329                 f = get_hash(f, dd->next_screen);
330                 dd->next_event = DoCheck;
331         } else if (match(line, "Close")) {
332                 dd->next_event = DoClose;
333         }
334         LOG("parse %s", line);
335
336         if (dd->next_event != DoCheck)
337                 call_comm("editor-on-idle", p, &next_evt);
338         else
339                 call_comm("event:timer", p, &abort_replay, 10*1000);
340         return True;
341 }
342
343 REDEF_CMD(next_evt)
344 {
345         struct pane *p = ci->home;
346         struct display_data *dd = p->data;
347         int button = 0, type = 0;
348         char *delay;
349
350         delay = getenv("EDLIB_REPLAY_DELAY");
351         if (delay && dd->next_event != DoCheck)
352                 usleep(atoi(delay)*1000);
353
354         switch(dd->next_event) {
355         case DoKey:
356                 record_key(p, dd->event_info);
357                 call("Keystroke", p, 0, NULL, dd->event_info);
358                 break;
359         case DoMouse:
360                 record_mouse(p, dd->event_info, dd->event_pos.x,
361                              dd->event_pos.y);
362                 if (strstr(dd->event_info, ":Press"))
363                         type = 1;
364                 else if (strstr(dd->event_info, ":Release"))
365                         type = 2;
366                 else if (strstr(dd->event_info, ":Motion"))
367                         type = 3;
368                 if (type == 1 || type == 2) {
369                         char *e = dd->event_info + strlen(dd->event_info) - 1;
370                         button = atoi(e);
371                 }
372                 call("Mouse-event", p, button, NULL, dd->event_info,
373                      type, NULL, NULL,
374                      dd->event_pos.x, dd->event_pos.y);
375                 break;
376         case DoCheck:
377                 /* No point checking, just do a diff against new trace log. */
378                 /* not; (strcmp(dd->next_screen, dd->last_screen) != 0) */
379                 break;
380         case DoClose:
381                 call("event:deactivate", p);
382                 pane_close(p);
383                 return 1;
384         case DoNil:
385                 call_comm("event:read", p, &input_handle, 0);
386                 call_comm("event:signal", p, &handle_winch, SIGWINCH);
387                 return 1;
388         }
389         parse_event(p);
390         return 1;
391 }
392 #else
393 static inline bool  prepare_recrep(struct pane *p safe) {return False;}
394 static inline void record_key(struct pane *p safe, char *key) {}
395 static inline void record_mouse(struct pane *p safe, char *key safe,
396                                 int x, int y) {}
397 static inline void record_screen(struct pane *p safe) {}
398 static inline void close_recrep(struct pane *p safe) {}
399 #endif
400
401 DEF_CB(cnt_disp)
402 {
403         struct call_return *cr = container_of(ci->comm, struct call_return, c);
404
405         cr->i += 1;
406         return 1;
407 }
408
409 DEF_CMD(nc_close_display)
410 {
411         /* If this is only display, then refuse to close this one */
412         struct call_return cr;
413         struct display_data *dd = ci->home->data;
414
415         if (dd->noclose) {
416                 call("Message", ci->focus, 0, NULL, dd->noclose);
417                 return 1;
418         }
419
420         cr.c = cnt_disp;
421         cr.i = 0;
422         call_comm("editor:notify:all-displays", ci->focus, &cr.c);
423         if (cr.i > 1)
424                 pane_close(ci->home);
425         else
426                 call("Message", ci->focus, 0, NULL,
427                      "Cannot close only window.");
428         return 1;
429 }
430
431 DEF_CMD(nc_set_noclose)
432 {
433         struct display_data *dd = ci->home->data;
434
435         free(dd->noclose);
436         dd->noclose = NULL;
437         if (ci->str)
438                 dd->noclose = strdup(ci->str);
439         return 1;
440 }
441
442 static int nc_putc(int ch)
443 {
444         if (current_dd)
445                 fputc(ch, current_dd->scr_file);
446         return 1;
447 }
448
449 DEF_CMD(nc_external_viewer)
450 {
451         struct pane *p = ci->home;
452         struct display_data *dd = p->data;
453         char *disp = pane_attr_get(p, "DISPLAY");
454         char *remote = pane_attr_get(p, "REMOTE_SESSION");
455         char *fqdn = NULL;
456         const char *path = ci->str;
457         int pid;
458         char buf[100];
459         int n;
460         int fd;
461
462         if (!path)
463                 return Enoarg;
464         if (disp && *disp) {
465                 switch (pid = fork()) {
466                 case -1:
467                         return Efail;
468                 case 0: /* Child */
469                         setenv("DISPLAY", disp, 1);
470                         fd = open("/dev/null", O_RDWR);
471                         if (fd) {
472                                 dup2(fd, 0);
473                                 dup2(fd, 1);
474                                 dup2(fd, 2);
475                                 if (fd > 2)
476                                         close(fd);
477                         }
478                         execlp("xdg-open", "xdg-open", path, NULL);
479                         exit(1);
480                 default: /* parent */
481                         /* FIXME record pid?? */
482                         break;
483                 }
484                 return 1;
485         }
486         /* handle no-display case */
487         if (remote && strcmp(remote, "yes") == 0 &&
488             path[0] == '/' &&
489             gethostname(buf, sizeof(buf)) == 0) {
490                 struct addrinfo *res;
491                 const struct addrinfo hints = {
492                         .ai_flags = AI_CANONNAME,
493                 };
494                 if (getaddrinfo(buf, NULL, &hints, &res) == 0 &&
495                     res && res->ai_canonname)
496                         fqdn = strdup(res->ai_canonname);
497                 freeaddrinfo(res);
498         }
499         set_screen(p);
500         n = 0;
501         ioctl(fileno(dd->scr_file), FIONREAD, &n);
502         if (n)
503                 n -= read(fileno(dd->scr_file), buf,
504                           n <= (int)sizeof(buf) ? n : (int)sizeof(buf));
505         endwin();
506
507         /* Endwin doesn't seem to reset properly, at least on xfce-terminal.
508          * So do it manually
509          */
510         if (dd->rs1)
511                 tputs(dd->rs1, 1, nc_putc);
512         if (dd->rs2)
513                 tputs(dd->rs2, 1, nc_putc);
514         if (dd->rs3)
515                 tputs(dd->rs3, 1, nc_putc);
516         if (dd->clear)
517                 tputs(dd->clear, 1, nc_putc);
518         fflush(dd->scr_file);
519
520         fprintf(dd->scr_file, "# Consider copy-pasting following\n");
521         if (fqdn && path[0] == '/') {
522                 /* File will not be local for the user, so help them copy it. */
523                 const char *tmp=ci->str2;
524                 if (!tmp)
525                         tmp = "XXXXXXX";
526                 fprintf(dd->scr_file, "f=`mktemp --tmpdir %s`; scp %s:%s $f ; ",
527                         tmp, fqdn, ci->str);
528                 path = "$f";
529         }
530         fprintf(dd->scr_file, "xdg-open %s\n", path);
531         fprintf(dd->scr_file, "# Press Enter to continue\n");
532         n = read(fileno(dd->scr_file), buf, sizeof(buf));
533         free(fqdn);
534         doupdate();
535         return 1;
536 }
537
538 static void ncurses_end(struct pane *p safe)
539 {
540         struct display_data *dd = p->data;
541
542         set_screen(p);
543         close_recrep(p);
544         nl();
545         endwin();
546         if (dd->rs1)
547                 tputs(dd->rs1, 1, nc_putc);
548         if (dd->rs2)
549                 tputs(dd->rs2, 1, nc_putc);
550         if (dd->rs3)
551                 tputs(dd->rs3, 1, nc_putc);
552         fflush(dd->scr_file);
553 }
554
555 /*
556  * hash table for colours and pairs
557  * key is r,g,b (0-1000) in 10bit fields,
558  * or fg,bg in 16 bit fields with bit 31 set
559  * content is colour number of colour pair number.
560  * We never delete entries, unless we delete everything.
561  */
562
563 struct chash {
564         struct chash *next;
565         int key, content;
566 };
567 #define COL_KEY(r,g,b) ((r<<20) | (g<<10) | b)
568 #define PAIR_KEY(fg, bg) ((1<<31) | (fg<<16) | bg)
569 #define hash_key(k) ((((k) * 0x61C88647) >> 20) & 0xff)
570
571 struct col_hash {
572         int next_col, next_pair;
573         struct chash *tbl[256];
574 };
575
576 static struct col_hash *safe hash_init(struct display_data *dd safe)
577 {
578         if (!dd->col_hash) {
579                 dd->col_hash = malloc(sizeof(*dd->col_hash));
580                 memset(dd->col_hash, 0, sizeof(*dd->col_hash));
581                 dd->col_hash->next_col = 16;
582                 dd->col_hash->next_pair = 1;
583         }
584         return dd->col_hash;
585 }
586
587 static void hash_free(struct display_data *dd safe)
588 {
589         int h;
590         struct chash *c;
591         struct col_hash *ch;
592
593         ch = dd->col_hash;
594         if (!ch)
595                 return;
596         for (h = 0; h < 255; h++)
597                 while ((c = ch->tbl[h]) != NULL) {
598                         ch->tbl[h] = c->next;
599                         free(c);
600                 }
601         free(ch);
602         dd->col_hash = NULL;
603 }
604
605 static int find_col(struct display_data *dd safe, int rgb[])
606 {
607         if (0 /* dynamic colours */) {
608                 struct col_hash *ch = hash_init(dd);
609                 int k = COL_KEY(rgb[0], rgb[1], rgb[2]);
610                 int h = hash_key(k);
611                 struct chash *c;
612
613                 for (c = ch->tbl[h]; c; c = c->next)
614                         if (c->key == k)
615                                 return c->content;
616                 c = malloc(sizeof(*c));
617                 c->key = k;
618                 c->content = ch->next_col++;
619                 c->next = ch->tbl[h];
620                 ch->tbl[h] = c;
621                 init_color(c->content, rgb[0], rgb[1], rgb[2]);
622                 return c->content;
623         } else {
624                 /* If colour is grey, map to 1 of 26 for 0, 232 to 255, 15
625                  * The 24 grey shades have bit values from 8 to 238, so the
626                  * gap to white is a little bigger, but that probably doesn't
627                  * matter.
628                  * Otherwise map to 6x6x6 rgb cube from 16
629                  * Actual colours are biased bright, at 0,95,135,175,215,255
630                  * with a 95 gap at bottom and 40 elsewhere.
631                  * So we divide 5 and 2 half ranges, and merge bottom 2.
632                  */
633                 int c = 0;
634                 int h;
635
636                 //printf("want %d,%d,%d\n", rgb[0], rgb[1], rgb[2]);
637                 if (abs(rgb[0] - rgb[1]) < 10 &&
638                     abs(rgb[1] - rgb[2]) < 10) {
639                         /* grey - within 1% */
640                         int v = (rgb[0] + rgb[1] + rgb[2]) / 3;
641
642                         /* We divide the space in 24 ranges surrounding
643                          * the grey values, and 2 half-ranges near black
644                          * and white.  So add half a range - 1000/50 -
645                          * then divide by 1000/25 to get a number from 0 to 25.
646                          */
647                         v = (v + 1000/50) / (1000/25);
648                         if (v == 0)
649                                 return 0; /* black */
650                         if (v >= 25)
651                                 return 15; /* white */
652                         //printf(" grey %d\n", v + 231);
653                         /* grey shades are from 232 to 255 inclusive */
654                         return v + 231;
655                 }
656                 for (h = 0; h < 3; h++) {
657                         int v = rgb[h];
658
659                         v = (v + 1000/12) / (1000/6);
660                         /* v is from 0 to 6, we want up to 5
661                          * with 0 and 1 merged
662                          */
663                         if (v)
664                                 v -= 1;
665
666                         c = c * 6 + v;
667                 }
668                 //printf(" color %d\n", c + 16);
669                 return c + 16;
670         }
671 }
672
673 static int to_pair(struct display_data *dd safe, int fg, int bg)
674 {
675         struct col_hash *ch = hash_init(dd);
676         int k = PAIR_KEY(fg, bg);
677         int h = hash_key(k);
678         struct chash *c;
679
680         for (c = ch->tbl[h]; c; c = c->next)
681                 if (c->key == k)
682                         return c->content;
683         c = malloc(sizeof(*c));
684         c->key = k;
685         c->content = ch->next_pair++;
686         c->next = ch->tbl[h];
687         ch->tbl[h] = c;
688         init_pair(c->content, fg, bg);
689         return c->content;
690 }
691
692
693 static int cvt_attrs(struct pane *p safe, struct pane *home safe,
694                      const char *attrs)
695 {
696         struct display_data *dd = home->data;
697         int attr = 0;
698         char tmp[40];
699         const char *a;
700         PANEL *pan = NULL;
701         int fg = COLOR_BLACK;
702         int bg = COLOR_WHITE+8;
703
704         set_screen(home);
705         do {
706                 p = p->parent;
707         } while (p->parent != p &&(pan = pane_panel(p, NULL)) == NULL);
708         if (pan) {
709                 /* Get 'default colours for this pane - set at clear */
710                 int at = getbkgd(panel_window(pan));
711                 int pair = PAIR_NUMBER(at);
712                 short dfg, dbg;
713                 pair_content(pair, &dfg, &dbg);
714                 if (dfg >= 0)
715                         fg = dfg;
716                 if (dbg >= 0)
717                         bg = dbg;
718         }
719         a = attrs;
720         while (a && *a) {
721                 const char *c;
722                 if (*a == ',') {
723                         a++;
724                         continue;
725                 }
726                 c = strchr(a, ',');
727                 if (!c)
728                         c = a+strlen(a);
729                 strncpy(tmp, a, c-a);
730                 tmp[c-a] = 0;
731                 if (strcmp(tmp, "inverse")==0) attr |= A_STANDOUT;
732                 else if (strcmp(tmp, "noinverse")==0) attr &= ~A_STANDOUT;
733                 else if (strcmp(tmp, "bold")==0) attr |= A_BOLD;
734                 else if (strcmp(tmp, "nobold")==0) attr &= ~A_BOLD;
735                 else if (strcmp(tmp, "underline")==0) attr |= A_UNDERLINE;
736                 else if (strcmp(tmp, "nounderline")==0) attr &= ~A_UNDERLINE;
737                 else if (strncmp(tmp, "fg:", 3) == 0) {
738                         struct call_return cr =
739                                 call_ret(all, "colour:map", home,
740                                          0, NULL, tmp+3);
741                         int rgb[3] = {cr.i, cr.i2, cr.x};
742                         fg = find_col(dd, rgb);
743                 } else if (strncmp(tmp, "bg:", 3) == 0) {
744                         struct call_return cr =
745                                 call_ret(all, "colour:map", home,
746                                          0, NULL, tmp+3);
747                         int rgb[3] = {cr.i, cr.i2, cr.x};
748                         bg = find_col(dd, rgb);
749                 }
750                 a = c;
751         }
752         if (fg != COLOR_BLACK || bg != COLOR_WHITE+8)
753                 attr |= COLOR_PAIR(to_pair(dd, fg, bg));
754         return attr;
755 }
756
757 static int make_cursor(int attr)
758 {
759         return attr ^ A_UNDERLINE;
760 }
761
762 DEF_CMD(nc_notify_display)
763 {
764         struct display_data *dd = ci->home->data;
765         comm_call(ci->comm2, "callback:display", ci->home, dd->last_event);
766         return 1;
767 }
768
769 DEF_CMD(nc_close)
770 {
771         struct pane *p = ci->home;
772         struct display_data *dd = p->data;
773         ncurses_end(p);
774         hash_free(dd);
775         fclose(dd->scr_file);
776         return 1;
777 }
778
779 DEF_CMD(nc_pane_close)
780 {
781         PANEL *pan = NULL;
782
783         set_screen(ci->home);
784         while ((pan = panel_above(pan)) != NULL)
785                 if (panel_userptr(pan) == ci->focus)
786                         break;
787         if (pan) {
788                 WINDOW *win = panel_window(pan);
789                 del_panel(pan);
790                 delwin(win);
791                 pane_damaged(ci->home, DAMAGED_POSTORDER);
792         }
793         return 1;
794 }
795
796 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home)
797 {
798         PANEL *pan = NULL;
799
800         while ((pan = panel_above(pan)) != NULL)
801                 if (panel_userptr(pan) == p)
802                         return pan;
803
804         if (!home)
805                 return pan;
806
807         pan = new_panel(newwin(p->h, p->w, 0, 0));
808         set_panel_userptr(pan, p);
809         pane_add_notify(home, p, "Notify:Close");
810
811         return pan;
812 }
813
814 DEF_CMD(nc_clear)
815 {
816         struct pane *p = ci->home;
817         int attr = cvt_attrs(ci->focus, p, ci->str2?:ci->str);
818         PANEL *panel;
819         WINDOW *win;
820         int w, h;
821
822         set_screen(p);
823         panel = pane_panel(ci->focus, p);
824         if (!panel)
825                 return Efail;
826         win = panel_window(panel);
827         getmaxyx(win, h, w);
828         if (h != ci->focus->h || w != ci->focus->w) {
829                 wresize(win, ci->focus->h, ci->focus->w);
830                 replace_panel(panel, win);
831         }
832         wbkgdset(win, attr);
833         werase(win);
834
835         pane_damaged(p, DAMAGED_POSTORDER);
836         return 1;
837 }
838
839 DEF_CMD(nc_text_size)
840 {
841         int max_space = ci->num;
842         int max_bytes = 0;
843         int size = 0;
844         const char *str = ci->str;
845
846         if (!str)
847                 return Enoarg;
848         while (str[0] != 0) {
849                 wint_t wc = get_utf8(&str, NULL);
850                 int width;
851                 if (wc >= WERR)
852                         break;
853                 width = wcwidth(wc);
854                 if (width < 0)
855                         break;
856                 size += width;
857                 if (size <= max_space)
858                         max_bytes = str - ci->str;
859         }
860         return comm_call(ci->comm2, "callback:size", ci->focus,
861                          max_bytes, NULL, NULL,
862                          0, NULL, NULL, size, 1);
863 }
864
865 DEF_CMD(nc_draw_text)
866 {
867         struct pane *p = ci->home;
868         int attr = cvt_attrs(ci->focus, p, ci->str2);
869         int cursor_offset = ci->num;
870         short x = ci->x, y = ci->y;
871         const char *str = ci->str;
872
873         if (!str)
874                 return Enoarg;
875         set_screen(p);
876         while (str[0] != 0) {
877                 int precurs = str <= ci->str + cursor_offset;
878                 wint_t wc = get_utf8(&str, NULL);
879                 int width;
880                 if (wc == WEOF || wc == WERR)
881                         break;
882                 width = wcwidth(wc);
883                 if (width < 0)
884                         break;
885                 if (precurs && str > ci->str + cursor_offset)
886                         ncurses_text(ci->focus, p, wc, attr, x, y, 1);
887                 else
888                         ncurses_text(ci->focus, p, wc, attr, x, y, 0);
889                 x += width;
890         }
891         if (str == ci->str + cursor_offset)
892                 ncurses_text(ci->focus, p, ' ', 0, x, y, 1);
893         pane_damaged(p, DAMAGED_POSTORDER);
894         return 1;
895 }
896
897 DEF_CMD(nc_refresh_size)
898 {
899         struct pane *p = ci->home;
900
901         set_screen(p);
902         getmaxyx(stdscr, p->h, p->w);
903         clearok(curscr, 1);
904         return 0;
905 }
906
907 DEF_CMD(nc_refresh_post)
908 {
909         struct pane *p = ci->home;
910         struct pane *p1;
911         PANEL *pan, *pan2;
912
913         set_screen(p);
914
915         /* Need to ensure stacking order and panel y,x position
916          * is correct.  FIXME it would be good if we could skip this
917          * almost always.
918          */
919         pan = panel_above(NULL);
920         if (!pan)
921                 return 1;
922         p1 = (struct pane*) panel_userptr(pan);
923         for (;(pan2 = panel_above(pan)) != NULL; pan = pan2) {
924                 struct pane *p2 = (struct pane*)panel_userptr(pan2);
925                 p1 = (struct pane*)panel_userptr(pan);
926                 if (!p1 || !p2)
927                         continue;
928
929                 if (p1->abs_z < p2->abs_z)
930                         continue;
931                 if (p1->abs_z == p2->abs_z &&
932                     p1->z <= p2->z)
933                         continue;
934                 /* pan needs to be above pan2.  All we can do is move it to
935                  * the top. Anything that needs to be above it will eventually
936                  * be pushed up too.
937                  */
938                 top_panel(pan);
939                 /* Now the panel below pan might need to be over pan2 too... */
940                 pan = panel_below(pan2);
941                 if (pan)
942                         pan2 = pan;
943         }
944
945         /* As we need to crop pane against their parents, we cannot simply
946          * use update_panels().  Instead we copy each to stdscr and refresh
947          * that.
948          */
949         for (pan = NULL; (pan = panel_above(pan)) != NULL; ) {
950                 WINDOW *win;
951                 struct xy src, dest, destend;
952                 int w, h;
953
954                 p1 = (void*)panel_userptr(pan);
955                 if (!p1)
956                         continue;
957                 dest = pane_mapxy(p1, p, 0, 0, True);
958                 destend = pane_mapxy(p1, p, p1->w, p1->h, True);
959                 src = pane_mapxy(p1, p, 0, 0, False);
960                 src.x = dest.x - src.x;
961                 src.y = dest.y - src.y;
962                 win = panel_window(pan);
963                 getmaxyx(win, h, w);
964                 /* guard again accessing beyond boundary of win */
965                 if (destend.x > dest.x + (w - src.x))
966                         destend.x = dest.x + (w - src.x);
967                 if (destend.y > dest.y + (h - src.y))
968                         destend.y = dest.y - (h - src.y);
969                 copywin(win, stdscr, src.y, src.x,
970                         dest.y, dest.x, destend.y-1, destend.x-1, 0);
971         }
972         /* place the cursor */
973         p1 = pane_leaf(p);
974         pan = NULL;
975         while (p1 != p && (pan = pane_panel(p1, NULL)) == NULL)
976                 p1 = p1->parent;
977         if (pan && p1->cx >= 0) {
978                 struct xy curs = pane_mapxy(p1, p, p1->cx, p1->cy, False);
979                 wmove(stdscr, curs.y, curs.x);
980         } else if (p->cx >= 0)
981                 wmove(stdscr, p->cy, p->cx);
982         refresh();
983         record_screen(ci->home);
984         return 1;
985 }
986
987 static struct pane *ncurses_init(struct pane *ed,
988                                  const char *tty, const char *term)
989 {
990         SCREEN *scr;
991         struct pane *p;
992         struct display_data *dd;
993         int rows, cols;
994         char *area;
995         FILE *f;
996
997         set_screen(NULL);
998         if (tty)
999                 f = fopen(tty, "r+");
1000         else
1001                 f = fdopen(1, "r+");
1002         if (!f)
1003                 return NULL;
1004         scr = newterm(term, f, f);
1005         if (!scr)
1006                 return NULL;
1007
1008         alloc(dd, pane);
1009         dd->scr = scr;
1010         dd->scr_file = f;
1011         dd->is_xterm = (term && strncmp(term, "xterm", 5) == 0);
1012
1013         p = pane_register(ed, 1, &ncurses_handle.c, dd);
1014         if (!p) {
1015                 unalloc(dd, pane);
1016                 return NULL;
1017         }
1018         set_screen(p);
1019
1020         start_color();
1021         use_default_colors();
1022         raw();
1023         noecho();
1024         nonl();
1025         timeout(0);
1026         set_escdelay(100);
1027         intrflush(stdscr, FALSE);
1028         keypad(stdscr, TRUE);
1029         mousemask(BUTTON1_PRESSED | BUTTON1_RELEASED |
1030                   BUTTON2_PRESSED | BUTTON2_RELEASED |
1031                   BUTTON3_PRESSED | BUTTON3_RELEASED |
1032                   BUTTON4_PRESSED | BUTTON4_RELEASED |
1033                   BUTTON5_PRESSED | BUTTON5_RELEASED |
1034                   BUTTON_CTRL | BUTTON_SHIFT | BUTTON_ALT |
1035                   REPORT_MOUSE_POSITION, NULL);
1036         mouseinterval(0);
1037
1038         getmaxyx(stdscr, rows, cols);
1039         pane_resize(p, 0, 0, cols, rows);
1040
1041         area = dd->attr_buf;
1042         dd->rs1 = tgetstr("rs1", &area);
1043         if (!dd->rs1)
1044                 dd->rs1 = tgetstr("is1", &area);
1045         dd->rs2 = tgetstr("rs2", &area);
1046         if (!dd->rs2)
1047                 dd->rs2 = tgetstr("is2", &area);
1048         dd->rs3 = tgetstr("rs3", &area);
1049         if (!dd->rs3)
1050                 dd->rs3 = tgetstr("is3", &area);
1051         dd->clear = tgetstr("clear", &area);
1052
1053         call("editor:request:all-displays", p);
1054         if (!prepare_recrep(p)) {
1055                 call_comm("event:read", p, &input_handle, fileno(f));
1056                 if (!tty)
1057                         call_comm("event:signal", p, &handle_winch, SIGWINCH);
1058         }
1059         return p;
1060 }
1061
1062 REDEF_CMD(handle_winch)
1063 {
1064         struct pane *p = ci->home;
1065         struct display_data *dd = p->data;
1066         struct winsize size;
1067         ioctl(fileno(dd->scr_file), TIOCGWINSZ, &size);
1068         set_screen(p);
1069         resize_term(size.ws_row, size.ws_col);
1070
1071         pane_resize(p, 0, 0, size.ws_row, size.ws_col);
1072         return 1;
1073 }
1074
1075 DEF_CMD(force_redraw)
1076 {
1077         struct pane *p = ci->home;
1078
1079         set_screen(p);
1080         clearok(curscr, 1);
1081         return 1;
1082 }
1083
1084 static void ncurses_text(struct pane *p safe, struct pane *display safe,
1085                          wchar_t ch, int attr, short x, short y, short cursor)
1086 {
1087         PANEL *pan;
1088         struct pane *p2;
1089         cchar_t cc = {};
1090
1091         if (x < 0 || y < 0)
1092                 return;
1093
1094         set_screen(display);
1095         if (cursor) {
1096                 if (pane_has_focus(p->z < 0 ? p->parent : p)) {
1097                         /* Cursor is in-focus */
1098                         struct xy curs = pane_mapxy(p, display, x, y, False);
1099                         display->cx = curs.x;
1100                         display->cy = curs.y;
1101                 } else
1102                         /* Cursor here, but not focus */
1103                         attr = make_cursor(attr);
1104         }
1105         cc.attr = attr;
1106         cc.chars[0] = ch;
1107
1108         p2 = p;
1109         pan = pane_panel(p2, NULL);
1110         while (!pan && p2->parent != p2) {
1111                 p2 = p2->parent;
1112                 pan = pane_panel(p2, NULL);
1113         }
1114         if (pan) {
1115                 struct xy xy = pane_mapxy(p, p2, x, y, False);
1116                 mvwadd_wch(panel_window(pan), xy.y, xy.x, &cc);
1117         }
1118 }
1119
1120 static struct namelist {
1121         wint_t key;
1122         char *name;
1123 } key_names[] = {
1124         {KEY_DOWN, ":Down"},
1125         {KEY_UP, ":Up"},
1126         {KEY_LEFT, ":Left"},
1127         {KEY_RIGHT, ":Right"},
1128         {KEY_HOME, ":Home"},
1129         {KEY_BACKSPACE, ":Backspace"},
1130         {KEY_DL, ":DelLine"},
1131         {KEY_IL, ":InsLine"},
1132         {KEY_DC, ":Del"},
1133         {KEY_IC, ":Ins"},
1134         {KEY_ENTER, ":Enter"},
1135         {KEY_END, ":End"},
1136
1137         {KEY_NPAGE, ":Next"},
1138         {KEY_PPAGE, ":Prior"},
1139
1140         {KEY_SDC, ":S:Del"},
1141         {KEY_SDL, ":S:DelLine"},
1142         {KEY_SEND, ":S:End"},
1143         {KEY_SHOME, ":S:Home"},
1144         {KEY_SLEFT, ":S:Left"},
1145         {KEY_SRIGHT, ":S:Right"},
1146         {KEY_BTAB, ":S:Tab"},
1147
1148         {  0521, ":S:Up"},
1149         {  0520, ":S:Down"},
1150         {  0616, ":S:Prior"},
1151         {  0614, ":S:Next"},
1152         { 01041, ":S:Home"},
1153         { 01060, ":S:End"},
1154         { 01066, ":S:Prior"},
1155         { 01015, ":S:Next"},
1156
1157         { 01027, ":A:S:Home"},
1158         { 01022, ":A:S:End"},
1159         { 01046, ":A:S:Prior"},
1160         { 01047, ":A:S:Next"}, // ??
1161
1162         { 01052, ":A:Prior"},
1163         { 01045, ":A:Next"},
1164         { 01026, ":A:Home"},
1165         { 01021, ":A:End"},
1166         { 01065, ":A:Up"},
1167         { 01014, ":A:Down"},
1168         { 01040, ":A:Left"},
1169         { 01057, ":A:Right"},
1170         { 00411, ":F1"},
1171         { 00412, ":F2"},
1172         { 00413, ":F3"},
1173         { 00414, ":F4"},
1174         { 00415, ":F5"},
1175         { 00416, ":F6"},
1176         { 00417, ":F7"},
1177         { 00420, ":F8"},
1178         { 00421, ":F9"},
1179         { 00422, ":F10"},
1180         { 00423, ":F11"},
1181         { 00424, ":F12"},
1182         { 00425, ":S:F1"},
1183         { 00426, ":S:F2"},
1184         { 00427, ":S:F3"},
1185         { 00430, ":S:F4"},
1186         { 00431, ":S:F5"},
1187         { 00432, ":S:F6"},
1188         { 00433, ":S:F7"},
1189         { 00434, ":S:F8"},
1190         { 00435, ":S:F9"},
1191         { 00436, ":S:F10"},
1192         { 00437, ":S:F11"},
1193         { 00440, ":S:F12"},
1194         {0, NULL}
1195 }, char_names[] = {
1196         {'\e', ":ESC"},
1197         {'\n', ":LF"},
1198         {'\r', ":Enter"},
1199         {'\t', ":Tab"},
1200         {'\177', ":Delete"},
1201         {'\0', ":C- "},
1202         {0, NULL}
1203 };
1204
1205 static char *find_name (struct namelist *l safe, wint_t c)
1206 {
1207         int i;
1208         for (i = 0; l[i].name; i++)
1209                 if (l[i].key == c)
1210                         return l[i].name;
1211         return NULL;
1212 }
1213
1214 static void send_key(int keytype, wint_t c, int alt, struct pane *p safe)
1215 {
1216         struct display_data *dd = p->data;
1217         char *n;
1218         char buf[100];/* FIXME */
1219         char t[5];
1220         char *a = alt ? ":A" : "";
1221
1222         if (keytype == KEY_CODE_YES) {
1223                 n = find_name(key_names, c);
1224                 if (!n)
1225                         sprintf(buf, "%sNcurs-%o", a, c);
1226                 else
1227                         strcat(strcpy(buf, a), n);
1228         } else {
1229                 n = find_name(char_names, c);
1230                 if (n)
1231                         sprintf(buf, "%s%s", a, n);
1232                 else if (c < ' ' || c == 0x7f)
1233                         sprintf(buf, "%s:C-%c",
1234                                 a, c ^ 64);
1235                 else
1236                         sprintf(buf, "%s-%s", a, put_utf8(t, c));
1237         }
1238
1239         dd->last_event = time(NULL);
1240         record_key(p, buf);
1241         call("Keystroke", p, 0, NULL, buf);
1242 }
1243
1244 static void do_send_mouse(struct pane *p safe, int x, int y, char *cmd safe,
1245                           int button, char *mod, int type)
1246 {
1247         int ret;
1248         struct display_data *dd = p->data;
1249
1250         record_mouse(p, cmd, x, y);
1251         ret = call("Mouse-event", p, button, NULL, cmd, type, NULL, mod, x, y);
1252         if (type == 1 && !dd->report_position) {
1253                 if (dd->is_xterm) {
1254                         fprintf(dd->scr_file, "\033[?1002h");
1255                         fflush(dd->scr_file);
1256                 }
1257                 dd->report_position = 1;
1258         } else if (type == 3 && ret <= 0) {
1259                 if (dd->is_xterm) {
1260                         fprintf(dd->scr_file, "\033[?1002l");
1261                         fflush(dd->scr_file);
1262                 }
1263                 dd->report_position = 0;
1264         }
1265 }
1266
1267 static void send_mouse(MEVENT *mev safe, struct pane *p safe)
1268 {
1269         struct display_data *dd = p->data;
1270         int x = mev->x;
1271         int y = mev->y;
1272         int b;
1273         char buf[100];
1274
1275         /* MEVENT has lots of bits.  We want a few numbers */
1276         for (b = 1 ; b <= (NCURSES_MOUSE_VERSION <= 1 ? 3 : 5); b++) {
1277                 mmask_t s = mev->bstate;
1278                 char *action;
1279                 int modf = 0;
1280                 char *mod = "";
1281
1282                 if (s & BUTTON_SHIFT) modf |= 1;
1283                 if (s & BUTTON_CTRL)  modf |= 2;
1284                 if (s & BUTTON_ALT)   modf |= 4;
1285                 switch (modf) {
1286                 case 0: mod = ""; break;
1287                 case 1: mod = ":S"; break;
1288                 case 2: mod = ":C"; break;
1289                 case 3: mod = ":C:S"; break;
1290                 case 4: mod = ":A"; break;
1291                 case 5: mod = ":A:S"; break;
1292                 case 6: mod = ":A:C"; break;
1293                 case 7: mod = ":A:C:S"; break;
1294                 }
1295                 if (BUTTON_PRESS(s, b))
1296                         action = "%s:Press-%d";
1297                 else if (BUTTON_RELEASE(s, b)) {
1298                         action = "%s:Release-%d";
1299                         /* Modifiers only reported on button Press */
1300                         mod = "";
1301                 } else
1302                         continue;
1303                 snprintf(buf, sizeof(buf), action, mod, b);
1304                 dd->last_event = time(NULL);
1305                 do_send_mouse(p, x, y, buf, b, mod, BUTTON_PRESS(s,b) ? 1 : 2);
1306         }
1307         if ((mev->bstate & REPORT_MOUSE_POSITION) &&
1308             dd->report_position)
1309                 /* Motion doesn't update last_event */
1310                 do_send_mouse(p, x, y, ":Motion", 0, "", 3);
1311 }
1312
1313 REDEF_CMD(input_handle)
1314 {
1315         struct pane *p = ci->home;
1316
1317         wint_t c;
1318         int is_keycode;
1319         int have_escape = 0;
1320
1321         if (!(void*)p->data)
1322                 /* already closed */
1323                 return 0;
1324         set_screen(p);
1325         while ((is_keycode = get_wch(&c)) != ERR) {
1326                 if (c == KEY_MOUSE) {
1327                         MEVENT mev;
1328                         while (getmouse(&mev) != ERR)
1329                                 send_mouse(&mev, p);
1330                 } else if (have_escape) {
1331                         send_key(is_keycode, c, 1, p);
1332                         have_escape = 0;
1333                 } else if (c == '\e')
1334                         have_escape = 1;
1335                 else
1336                         send_key(is_keycode, c, 0, p);
1337                 /* Don't know what other code might have done,
1338                  * so re-set the screen
1339                  */
1340                 set_screen(p);
1341         }
1342         if (have_escape)
1343                 send_key(is_keycode, '\e', 0, p);
1344         return 1;
1345 }
1346
1347 DEF_CMD(display_ncurses)
1348 {
1349         struct pane *p;
1350         char *term;
1351
1352         term = pane_attr_get(ci->focus, "TERM");
1353         if (!term)
1354                 term = getenv("TERM");
1355         if (!term)
1356                 term = "xterm-256color";
1357
1358         p = ncurses_init(ci->focus, ci->str, term);
1359         if (p) {
1360                 struct pane *p2 = call_ret(pane, "attach-x11selection", p);
1361                 if (p2)
1362                         p = p2;
1363                 return comm_call(ci->comm2, "callback:display", p);
1364         }
1365         return Efail;
1366 }
1367
1368 void edlib_init(struct pane *ed safe)
1369 {
1370         call_comm("global-set-command", ed, &display_ncurses, 0, NULL,
1371                   "attach-display-ncurses");
1372
1373         nc_map = key_alloc();
1374         key_add(nc_map, "Display:refresh", &force_redraw);
1375         key_add(nc_map, "Display:close", &nc_close_display);
1376         key_add(nc_map, "Display:set-noclose", &nc_set_noclose);
1377         key_add(nc_map, "Display:external-viewer", &nc_external_viewer);
1378         key_add(nc_map, "Close", &nc_close);
1379         key_add(nc_map, "Free", &edlib_do_free);
1380         key_add(nc_map, "pane-clear", &nc_clear);
1381         key_add(nc_map, "text-size", &nc_text_size);
1382         key_add(nc_map, "Draw:text", &nc_draw_text);
1383         key_add(nc_map, "Refresh:size", &nc_refresh_size);
1384         key_add(nc_map, "Refresh:postorder", &nc_refresh_post);
1385         key_add(nc_map, "all-displays", &nc_notify_display);
1386         key_add(nc_map, "Sig:Winch", &handle_winch);
1387         key_add(nc_map, "Notify:Close", &nc_pane_close);
1388 }