]> git.neil.brown.name Git - edlib.git/blob - display-ncurses.c
ee8ce70db64af4366808912674d4b51bab452d78
[edlib.git] / display-ncurses.c
1 /*
2  * Copyright Neil Brown ©2015-2023 <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 <sys/wait.h>
36 #include <netdb.h>
37
38 #include <wand/MagickWand.h>
39 #ifdef __CHECKER__
40 // enums confuse sparse...
41 #define MagickBooleanType int
42 #endif
43
44 #include <term.h>
45
46 #define PANE_DATA_TYPE struct display_data
47 #include "core.h"
48
49 #ifdef RECORD_REPLAY
50 #include <unistd.h>
51 #include <stdio.h>
52 #include "md5.h"
53 typedef char hash_t[MD5_DIGEST_SIZE*2+1];
54 #endif
55
56 #ifdef __CHECKER__
57 #undef NCURSES_OK_ADDR
58 #define NCURSES_OK_ADDR(p) ((void*)0 != NCURSES_CAST(const void *, (p)))
59 #endif
60
61 struct col_hash;
62
63 struct display_data {
64         SCREEN                  *scr;
65         FILE                    *scr_file;
66         int                     is_xterm;
67         struct col_hash         *col_hash;
68         int                     report_position;
69         long                    last_event;
70
71         bool                    did_close;
72         bool                    suspended;
73
74         struct buf              paste_buf;
75         time_t                  paste_start;
76         char                    *paste_latest;
77         int                     paste_pending;
78
79         struct pids {
80                 pid_t           pid;
81                 struct pids     *next;
82         }                       *pids;
83
84         char                    *rs1, *rs2, *rs3, *clear;
85         char                    attr_buf[1024];
86         #ifdef RECORD_REPLAY
87         FILE                    *log;
88         FILE                    *input;
89         int                     input_sleeping;
90         /* Sometimes I get duplicate Display lines, but not consistently.
91          * To avoid these, record last, filter repeats.
92          */
93         int                     last_cx, last_cy;
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;
98         char                    event_info[30];
99         struct xy               event_pos;
100
101         int                     clears; /* counts of Draw:clear events */
102         #endif
103 };
104 #include "core-pane.h"
105
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);
115
116 static struct display_data *current_dd;
117 static void set_screen(struct pane *p)
118 {
119         struct display_data *dd;
120         extern void *_nc_globals[100];
121         int i;
122         static int index = -1, offset=0;
123
124         if (!p) {
125                 if (current_screen && index >= 0)
126                         _nc_globals[index] = NULL;
127                 current_screen = NULL;
128                 return;
129         }
130         dd = p->data;
131         current_dd = dd;
132         if (!dd)
133                 return;
134         if (dd->scr == current_screen)
135                 return;
136
137         if (index == -1) {
138                 index = -2;
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 */
143                                 index = i;
144                                 offset = ((void*)stdscr) - _nc_globals[i];
145                         }
146         }
147
148         set_term(dd->scr);
149         current_screen = dd->scr;
150         if (index >= 0) {
151                 _nc_globals[index] = ((void*)stdscr) - offset;
152         }
153 }
154
155 #ifdef RECORD_REPLAY
156 DEF_CMD(next_evt);
157
158 static bool parse_event(struct pane *p safe);
159 static bool prepare_recrep(struct pane *p safe)
160 {
161         struct display_data *dd = p->data;
162         char *name;
163
164         name = getenv("EDLIB_RECORD");
165         if (name)
166                 dd->log = fopen(name, "w");
167         name = getenv("EDLIB_REPLAY");
168         if (name)
169                 dd->input = fopen(name, "r");
170         if (getenv("EDLIB_PAUSE"))
171                 sleep(atoi(getenv("EDLIB_PAUSE")));
172         if (dd->input) {
173                 parse_event(p);
174                 return True;
175         }
176         return False;
177 }
178
179 static void close_recrep(struct pane *p safe)
180 {
181         struct display_data *dd = p->data;
182
183         if (dd->log) {
184                 fprintf(dd->log, "Close %d\n", dd->clears);
185                 fclose(dd->log);
186         }
187 }
188
189 static void record_key(struct pane *p safe, char *key safe)
190 {
191         struct display_data *dd = p->data;
192         char q;
193
194         if (!dd->log)
195                 return;
196         if (!strchr(key, '"'))
197                 q = '"';
198         else if (!strchr(key, '\''))
199                 q = '\'';
200         else if (!strchr(key, '/'))
201                 q = '/';
202         else
203                 return;
204         fprintf(dd->log, "Key %c%s%c\n", q,key,q);
205         dd->last_cx = -2; /* Force next Display to be shown */
206         fflush(dd->log);
207 }
208
209 static void record_mouse(struct pane *p safe, char *key safe, int x, int y)
210 {
211         struct display_data *dd = p->data;
212         char q;
213         if (!dd->log)
214                 return;
215         if (!strchr(key, '"'))
216                 q = '"';
217         else if (!strchr(key, '\''))
218                 q = '\'';
219         else if (!strchr(key, '/'))
220                 q = '/';
221         else
222                 return;
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 */
225         fflush(dd->log);
226 }
227
228 static void record_screen(struct pane *p safe)
229 {
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];
234         int r,c;
235
236         if (!dd->log && !(dd->input && dd->next_event == DoCheck))
237                 return;
238         set_screen(p);
239         md5_init(&ctx);
240         for (r = 0; r < p->h; r++)
241                 for (c = 0; c < p->w; c++) {
242                         cchar_t cc;
243                         wchar_t wc[CCHARW_MAX+2];
244                         attr_t a;
245                         short color, fg, bg;
246                         int l;
247
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]);
255                         buf[2] = htole16(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));
259                 }
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 */
264                 dd->clears -= 1;
265         } else if (dd->log) {
266                 fprintf(dd->log, "Display %d,%d %s", p->w, p->h, out);
267                 if (p->cx >= 0)
268                         fprintf(dd->log, " %d,%d", p->cx, p->cy);
269                 fprintf(dd->log, "\n");
270                 fflush(dd->log);
271                 strcpy(dd->last_screen, out);
272                 dd->last_cx = p->cx; dd->last_cy = p->cy;
273         }
274         if (dd->input && dd->input_sleeping) {
275                 char *delay = getenv("EDLIB_REPLAY_DELAY");
276                 call_comm("event:free", p, &next_evt);
277                 if (delay)
278                         call_comm("event:timer", p, &next_evt, atoi(delay));
279                 else
280                         call_comm("event:on-idle", p, &next_evt);
281         }
282 }
283
284 static char *copy_quote(char *line safe, char *buf safe)
285 {
286         char q;
287         while (*line == ' ')
288                 line++;
289         q = *line++;
290         if (q != '"' && q != '\'' && q != '/')
291                 return NULL;
292         while (*line != q && *line)
293                 *buf++ = *line++;
294         if (!*line)
295                 return NULL;
296         *buf = '\0';
297         return line+1;
298 }
299
300 static char *get_coord(char *line safe, struct xy *co safe)
301 {
302         long v;
303         char *ep;
304
305         while (*line == ' ')
306                 line ++;
307         v = strtol(line, &ep, 10);
308         if (!ep || ep == line || *ep != ',')
309                 return NULL;
310         co->x = v;
311         line = ep+1;
312         v = strtol(line, &ep, 10);
313         if (!ep || ep == line || (*ep && *ep != ' ' && *ep != '\n'))
314                 return NULL;
315         co->y = v;
316         return ep;
317 }
318
319 static char *get_hash(char *line safe, hash_t hash safe)
320 {
321         int i;
322         while (*line == ' ')
323                 line++;
324         for (i = 0; i < MD5_DIGEST_SIZE*2 && *line; i++)
325                 hash[i] = *line++;
326         if (!*line)
327                 return NULL;
328         return line;
329 }
330
331 static bool parse_event(struct pane *p safe)
332 {
333         struct display_data *dd = p->data;
334         char line[80];
335
336         line[79] = 0;
337         dd->next_event = DoNil;
338         if (!dd->input ||
339             fgets(line, sizeof(line)-1, dd->input) == NULL)
340                 line[0]=0;
341         else if (strstarts(line, "Key ")) {
342                 if (!copy_quote(line+4, dd->event_info))
343                         return False;
344                 dd->next_event = DoKey;
345         } else if (strstarts(line, "Mouse ")) {
346                 char *f = copy_quote(line+6, dd->event_info);
347                 if (!f)
348                         return False;
349                 f = get_coord(f, &dd->event_pos);
350                 if (!f)
351                         return False;
352                 dd->next_event = DoMouse;
353         } else if (strstarts(line, "Display ")) {
354                 char *f = get_coord(line+8, &dd->event_pos);
355                 if (!f)
356                         return False;
357                 f = get_hash(f, dd->next_screen);
358                 dd->next_event = DoCheck;
359         } else if (strstarts(line, "Close")) {
360                 dd->next_event = DoClose;
361         }
362         LOG("parse %s", line);
363
364         dd->input_sleeping = 1;
365         if (dd->next_event != DoCheck) {
366                 char *delay = getenv("EDLIB_REPLAY_DELAY");
367                 if (delay)
368                         call_comm("event:timer", p, &next_evt, atoi(delay));
369                 else
370                         call_comm("event:on-idle", p, &next_evt);
371         } else
372                 call_comm("event:timer", p, &next_evt, 10*1000);
373         return True;
374 }
375
376 REDEF_CMD(next_evt)
377 {
378         struct pane *p = ci->home;
379         struct display_data *dd = p->data;
380         int button = 0, type = 0;
381
382         dd->input_sleeping = 0;
383         switch(dd->next_event) {
384         case DoKey:
385                 record_key(p, dd->event_info);
386                 call("Keystroke", p, 0, NULL, dd->event_info);
387                 break;
388         case DoMouse:
389                 record_mouse(p, dd->event_info, dd->event_pos.x,
390                              dd->event_pos.y);
391                 if (strstr(dd->event_info, ":Press"))
392                         type = 1;
393                 else if (strstr(dd->event_info, ":Release"))
394                         type = 2;
395                 else if (strstr(dd->event_info, ":Motion"))
396                         type = 3;
397                 if (type == 1 || type == 2) {
398                         char *e = dd->event_info + strlen(dd->event_info) - 1;
399                         button = atoi(e);
400                 }
401                 call("Mouse-event", p, button, NULL, dd->event_info,
402                      type, NULL, NULL,
403                      dd->event_pos.x, dd->event_pos.y);
404                 break;
405         case DoCheck:
406                 /* No point checking, just do a diff against new trace log. */
407                 /* not; (strcmp(dd->next_screen, dd->last_screen) != 0) */
408                 break;
409         case DoClose:
410                 call("event:deactivate", p);
411                 pane_close(p);
412                 return 1;
413         case DoNil:
414                 call_comm("event:read", p, &input_handle, 0);
415                 call_comm("event:signal", p, &handle_winch, SIGWINCH);
416                 return 1;
417         }
418         parse_event(p);
419         return 1;
420 }
421 #else
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,
425                                 int x, int y) {}
426 static inline void record_screen(struct pane *p safe) {}
427 static inline void close_recrep(struct pane *p safe) {}
428 #endif
429
430 DEF_CB(cnt_disp)
431 {
432         struct call_return *cr = container_of(ci->comm, struct call_return, c);
433
434         cr->i += 1;
435         return 1;
436 }
437
438 static void ncurses_end(struct pane *p safe);
439
440 DEF_CMD(nc_close_display)
441 {
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");
445
446         if (nc) {
447                 call("Message", ci->focus, 0, NULL, nc);
448                 return 1;
449         }
450
451         cr.c = cnt_disp;
452         cr.i = 0;
453         call_comm("editor:notify:all-displays", ci->focus, &cr.c);
454         if (cr.i > 1) {
455                 /* Need to call ncurses_end() before we send a Notify:Close
456                  * notification, else server exits too early
457                  */
458                 ncurses_end(ci->home);
459                 return Efallthrough;
460         } else
461                 call("Message", ci->focus, 0, NULL,
462                      "Cannot close only window.");
463         return 1;
464 }
465
466 static int nc_putc(int ch)
467 {
468         if (current_dd)
469                 fputc(ch, current_dd->scr_file);
470         return 1;
471 }
472
473 static char *fnormalize(struct pane *p safe, const char *str) safe
474 {
475         char *ret = strsave(p, str);
476         char *cp;
477
478         for (cp = ret ; cp && *cp ; cp++)
479                 if (!isalnum(*cp) &&
480                     !strchr("/_-+=.,@#", *cp))
481                         /* Don't like this char */
482                         *cp = '_';
483         return ret ?: "_";
484 }
485
486 static void wait_for(struct display_data *dd safe)
487 {
488         struct pids **pp = &dd->pids;
489
490         while (*pp) {
491                 struct pids *p = *pp;
492                 if (waitpid(p->pid, NULL, WNOHANG) > 0) {
493                         *pp = p->next;
494                         free(p);
495                 } else
496                         pp = &p->next;
497         }
498 }
499
500 DEF_CB(ns_resume)
501 {
502         struct display_data *dd = ci->home->data;
503
504         if (dd->suspended) {
505                 dd->suspended = False;
506                 set_screen(ci->home);
507                 doupdate();
508         }
509         return 1;
510 }
511
512 DEF_CMD(nc_external_viewer)
513 {
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");
519         char *fqdn = NULL;
520         const char *path = ci->str;
521         int pid;
522         char buf[100];
523         int n;
524         int fd;
525
526         if (!path)
527                 return Enoarg;
528         if (disp && *disp) {
529                 struct pids *pds;
530                 switch (pid = fork()) {
531                 case -1:
532                         return Efail;
533                 case 0: /* Child */
534                         setenv("DISPLAY", disp, 1);
535                         if (disp_auth)
536                                 setenv("XAUTHORITY", disp_auth, 1);
537                         fd = open("/dev/null", O_RDWR);
538                         if (fd) {
539                                 dup2(fd, 0);
540                                 dup2(fd, 1);
541                                 dup2(fd, 2);
542                                 if (fd > 2)
543                                         close(fd);
544                         }
545                         execlp("xdg-open", "xdg-open", path, NULL);
546                         exit(1);
547                 default: /* parent */
548                         pds = malloc(sizeof(*pds));
549                         pds->pid = pid;
550                         pds->next = dd->pids;
551                         dd->pids = pds;
552                         break;
553                 }
554                 wait_for(dd);
555                 return 1;
556         }
557         /* handle no-display case */
558         if (remote && strcmp(remote, "yes") == 0 &&
559             path[0] == '/' &&
560             gethostname(buf, sizeof(buf)) == 0) {
561                 struct addrinfo *res;
562                 const struct addrinfo hints = {
563                         .ai_flags = AI_CANONNAME,
564                 };
565                 if (getaddrinfo(buf, NULL, &hints, &res) == 0 &&
566                     res && res->ai_canonname)
567                         fqdn = strdup(res->ai_canonname);
568                 freeaddrinfo(res);
569         }
570         set_screen(p);
571         n = 0;
572         ioctl(fileno(dd->scr_file), FIONREAD, &n);
573         if (n)
574                 n -= read(fileno(dd->scr_file), buf,
575                           n <= (int)sizeof(buf) ? n : (int)sizeof(buf));
576         endwin();
577         /* stay in raw mode */
578         raw();
579         noecho();
580
581         /* Endwin doesn't seem to reset properly, at least on xfce-terminal.
582          * So do it manually
583          */
584         if (dd->rs1)
585                 tputs(dd->rs1, 1, nc_putc);
586         if (dd->rs2)
587                 tputs(dd->rs2, 1, nc_putc);
588         if (dd->rs3)
589                 tputs(dd->rs3, 1, nc_putc);
590         if (dd->clear)
591                 tputs(dd->clear, 1, nc_putc);
592         fflush(dd->scr_file);
593
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);
599
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 ; ",
604                         tmp, fqdn, fname);
605                 path = "$f";
606         }
607         free(fqdn);
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);
612         return 1;
613 }
614
615 static void ncurses_stop(struct pane *p safe)
616 {
617         struct display_data *dd = p->data;
618
619         if (dd->is_xterm) {
620                 /* disable bracketed-paste */
621                 fprintf(dd->scr_file, "\033[?2004l");
622                 fflush(dd->scr_file);
623         }
624         if (dd->paste_start)
625                 free(buf_final(&dd->paste_buf));
626         dd->paste_start = 0;
627         free(dd->paste_latest);
628         dd->paste_latest = NULL;
629         nl();
630         endwin();
631         if (dd->rs1)
632                 tputs(dd->rs1, 1, nc_putc);
633         if (dd->rs2)
634                 tputs(dd->rs2, 1, nc_putc);
635         if (dd->rs3)
636                 tputs(dd->rs3, 1, nc_putc);
637         fflush(dd->scr_file);
638 }
639
640 static void ncurses_end(struct pane *p safe)
641 {
642         struct display_data *dd = p->data;
643
644         if (dd->did_close)
645                 return;
646         dd->did_close = True;
647         set_screen(p);
648         close_recrep(p);
649
650         ncurses_stop(p);
651 }
652
653 /*
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.
659  */
660
661 struct chash {
662         struct chash *next;
663         int key, content;
664 };
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)
668
669 struct col_hash {
670         int next_col, next_pair;
671         struct chash *tbl[256];
672 };
673
674 static struct col_hash *safe hash_init(struct display_data *dd safe)
675 {
676         if (!dd->col_hash) {
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;
681         }
682         return dd->col_hash;
683 }
684
685 static void hash_free(struct display_data *dd safe)
686 {
687         int h;
688         struct chash *c;
689         struct col_hash *ch;
690
691         ch = dd->col_hash;
692         if (!ch)
693                 return;
694         for (h = 0; h < 255; h++)
695                 while ((c = ch->tbl[h]) != NULL) {
696                         ch->tbl[h] = c->next;
697                         free(c);
698                 }
699         free(ch);
700         dd->col_hash = NULL;
701 }
702
703 static int find_col(struct display_data *dd safe, int rgb[])
704 {
705         if (0 /* dynamic colours */) {
706                 struct col_hash *ch = hash_init(dd);
707                 int k = COL_KEY(rgb[0], rgb[1], rgb[2]);
708                 int h = hash_key(k);
709                 struct chash *c;
710
711                 for (c = ch->tbl[h]; c; c = c->next)
712                         if (c->key == k)
713                                 return c->content;
714                 c = malloc(sizeof(*c));
715                 c->key = k;
716                 c->content = ch->next_col++;
717                 c->next = ch->tbl[h];
718                 ch->tbl[h] = c;
719                 init_color(c->content, rgb[0], rgb[1], rgb[2]);
720                 return c->content;
721         } else {
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
725                  * matter.
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.
730                  */
731                 int c = 0;
732                 int h;
733
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;
739
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.
744                          */
745                         v = (v + 1000/50) / (1000/25);
746                         if (v == 0)
747                                 return 0; /* black */
748                         if (v >= 25)
749                                 return 15; /* white */
750                         //printf(" grey %d\n", v + 231);
751                         /* grey shades are from 232 to 255 inclusive */
752                         return v + 231;
753                 }
754                 for (h = 0; h < 3; h++) {
755                         int v = rgb[h];
756
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
760                          */
761                         if (v)
762                                 v -= 1;
763
764                         c = c * 6 + v;
765                 }
766                 //printf(" color %d\n", c + 16);
767                 return c + 16;
768         }
769 }
770
771 static int to_pair(struct display_data *dd safe, int fg, int bg)
772 {
773         struct col_hash *ch = hash_init(dd);
774         int k = PAIR_KEY(fg, bg);
775         int h = hash_key(k);
776         struct chash *c;
777
778         for (c = ch->tbl[h]; c; c = c->next)
779                 if (c->key == k)
780                         return c->content;
781         c = malloc(sizeof(*c));
782         c->key = k;
783         c->content = ch->next_pair++;
784         c->next = ch->tbl[h];
785         ch->tbl[h] = c;
786         init_pair(c->content, fg, bg);
787         return c->content;
788 }
789
790 static int cvt_attrs(struct pane *p safe, struct pane *home safe,
791                      const char *attrs, int *pairp safe)
792 {
793         struct display_data *dd = home->data;
794         int attr = 0;
795         const char *a, *v;
796         char *col = NULL;
797         PANEL *pan = NULL;
798         int fg = COLOR_BLACK;
799         int bg = COLOR_WHITE+8;
800
801         set_screen(home);
802         while (p->parent != p &&(pan = pane_panel(p, NULL)) == NULL)
803                 p = p->parent;
804         if (pan) {
805                 /* Get 'default colours for this pane - set at clear */
806                 int at = getbkgd(panel_window(pan));
807                 int pair = PAIR_NUMBER(at);
808                 short dfg, dbg;
809                 pair_content(pair, &dfg, &dbg);
810                 if (dfg >= 0)
811                         fg = dfg;
812                 if (dbg >= 0)
813                         bg = dbg;
814         }
815
816         foreach_attr(a, v, attrs, NULL) {
817                 if (amatch(a, "inverse"))
818                         attr |= A_STANDOUT;
819                 else if (amatch(a, "noinverse"))
820                         attr &= ~A_STANDOUT;
821                 else if (amatch(a, "bold"))
822                         attr |= A_BOLD;
823                 else if (amatch(a, "nobold"))
824                         attr &= ~A_BOLD;
825                 else if (amatch(a, "underline"))
826                         attr |= 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);
841                 }
842         }
843         free(col);
844         if (fg != COLOR_BLACK || bg != COLOR_WHITE+8)
845                 *pairp = to_pair(dd, fg, bg);
846         return attr;
847 }
848
849 static int make_cursor(int attr)
850 {
851         return attr ^ A_UNDERLINE;
852 }
853
854 DEF_CMD(nc_notify_display)
855 {
856         struct display_data *dd = ci->home->data;
857         comm_call(ci->comm2, "callback:display", ci->home, dd->last_event);
858         return 1;
859 }
860
861 DEF_CMD_CLOSED(nc_close)
862 {
863         struct pane *p = ci->home;
864         struct display_data *dd = p->data;
865         ncurses_end(p);
866         hash_free(dd);
867         fclose(dd->scr_file);
868         return 1;
869 }
870
871 DEF_CMD(nc_pane_close)
872 {
873         PANEL *pan = NULL;
874
875         set_screen(ci->home);
876         while ((pan = panel_above(pan)) != NULL)
877                 if (panel_userptr(pan) == ci->focus)
878                         break;
879         if (pan) {
880                 WINDOW *win = panel_window(pan);
881                 del_panel(pan);
882                 delwin(win);
883                 pane_damaged(ci->home, DAMAGED_POSTORDER);
884         }
885         return 1;
886 }
887
888 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home)
889 {
890         PANEL *pan = NULL;
891
892         while ((pan = panel_above(pan)) != NULL)
893                 if (panel_userptr(pan) == p)
894                         return pan;
895
896         if (!home)
897                 return pan;
898
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");
902
903         return pan;
904 }
905
906 DEF_CMD(nc_clear)
907 {
908         struct pane *p = ci->home;
909         struct display_data *dd = p->data;
910         cchar_t cc = {};
911         int pair = 0;
912         /* default come from parent when clearing pane */
913         int attr = cvt_attrs(ci->focus->parent, p, ci->str, &pair);
914         PANEL *panel;
915         WINDOW *win;
916         int w, h;
917
918         set_screen(p);
919         panel = pane_panel(ci->focus, p);
920         if (!panel)
921                 return Efail;
922         win = panel_window(panel);
923         getmaxyx(win, h, w);
924         if (h != ci->focus->h || w != ci->focus->w) {
925                 wresize(win, ci->focus->h, ci->focus->w);
926                 replace_panel(panel, win);
927         }
928         cc.attr = attr;
929         cc.ext_color = pair;
930         cc.chars[0] = ' ';
931         wbkgrndset(win, &cc);
932         werase(win);
933         dd->clears += 1;
934
935         pane_damaged(p, DAMAGED_POSTORDER);
936         return 1;
937 }
938
939 DEF_CMD(nc_text_size)
940 {
941         int max_space = ci->num;
942         int max_bytes = 0;
943         int size = 0;
944         const char *str = ci->str;
945
946         if (!str)
947                 return Enoarg;
948         while (str[0] != 0) {
949                 wint_t wc = get_utf8(&str, NULL);
950                 int width;
951                 if (wc >= WERR)
952                         break;
953                 width = wcwidth(wc);
954                 if (width < 0)
955                         break;
956                 size += width;
957                 if (size <= max_space)
958                         max_bytes = str - ci->str;
959         }
960         return comm_call(ci->comm2, "callback:size", ci->focus,
961                          max_bytes, NULL, NULL,
962                          0, NULL, NULL, size, 1);
963 }
964
965 DEF_CMD(nc_draw_text)
966 {
967         struct pane *p = ci->home;
968         int pair = 0;
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;
973
974         if (!str)
975                 return Enoarg;
976         set_screen(p);
977         while (str[0] != 0) {
978                 int precurs = str <= ci->str + cursor_offset;
979                 wint_t wc = get_utf8(&str, NULL);
980                 int width;
981                 if (wc == WEOF || wc == WERR)
982                         break;
983                 width = wcwidth(wc);
984                 if (width < 0)
985                         break;
986                 if (precurs && str > ci->str + cursor_offset)
987                         ncurses_text(ci->focus, p, wc, attr, pair, x, y, 1);
988                 else
989                         ncurses_text(ci->focus, p, wc, attr, pair, x, y, 0);
990                 x += width;
991         }
992         if (str == ci->str + cursor_offset)
993                 ncurses_text(ci->focus, p, ' ', 0, 0, x, y, 1);
994         pane_damaged(p, DAMAGED_POSTORDER);
995         return 1;
996 }
997
998 struct di_info {
999         struct command c;
1000         MagickWand *wd safe;
1001         int x,y,w,h;
1002         int xo, yo;
1003         struct pane *p safe;
1004 };
1005
1006 DEF_CB(nc_draw_image_cb)
1007 {
1008         struct di_info *dii = container_of(ci->comm, struct di_info, c);
1009         struct display_data *dd = dii->p->data;
1010         int i, j;
1011         unsigned char *buf;
1012
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);
1020                 return 1;
1021         case 'c': /* crop or cursor */
1022                 if (ci->key[1] != 'u') {
1023                         /* crop */
1024                         dii->x = ci->x;
1025                         dii->y = ci->y;
1026                         dii->w = ci->num;
1027                         dii->h = ci->num2;
1028                         return 1;
1029                 } else {
1030                         /* cursor */
1031                         /* FIXME this doesn't work because
1032                          * render-line knows too much and gets it wrong.
1033                          */
1034                         ncurses_text(ci->focus, dii->p, 'X', 0,
1035                                      to_pair(dd, 0, 0),
1036                                      ci->x + dii->xo,
1037                                      (ci->y + dii->xo)/2, 1);
1038                         return 1;
1039                 }
1040         case 'd': /* draw */
1041                 if (dii->w <= 0 || dii->h <= 0)
1042                         return Efail;
1043                 buf = malloc(dii->h * dii->w * 4);
1044
1045                 MagickExportImagePixels(dii->wd, dii->x, dii->y,
1046                                         dii->w, dii->h,
1047                                         "RGBA", CharPixel, buf);
1048
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);
1058
1059                                 if (p1[3] < 128 || p2[3] < 128) {
1060                                         /* transparent */
1061                                         cchar_t cc;
1062                                         short f,b;
1063                                         struct pane *pn2 = ci->focus;
1064                                         PANEL *pan = pane_panel(pn2, NULL);
1065
1066                                         while (!pan && pn2->parent != pn2) {
1067                                                 pn2 = pn2->parent;
1068                                                 pan = pane_panel(pn2, NULL);
1069                                         }
1070                                         if (pan) {
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
1076                                                          * result.
1077                                                          */
1078                                                         b = COLOR_WHITE;
1079                                                 else
1080                                                         pair_content(cc.ext_color, &f, &b);
1081                                                 if (p1[3] < 128)
1082                                                         fg = b;
1083                                                 if (p2[3] < 128)
1084                                                         bg = b;
1085                                         }
1086                                 }
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,
1091                                              0);
1092                         }
1093                 }
1094                 free(buf);
1095                 return 1;
1096         default:
1097                 return Efail;
1098         }
1099 }
1100
1101 DEF_CMD(nc_draw_image)
1102 {
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.
1107          */
1108         struct pane *p = ci->home;
1109         MagickBooleanType status;
1110         MagickWand *wd = NULL;
1111         struct di_info dii;
1112
1113         if (!ci->str)
1114                 return Enoarg;
1115         if (strstarts(ci->str, "file:")) {
1116                 wd = NewMagickWand();
1117                 status = MagickReadImage(wd, ci->str + 5);
1118                 if (status == MagickFalse) {
1119                         DestroyMagickWand(wd);
1120                         return Efail;
1121                 }
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);
1126                 if (!cr.s) {
1127                         DestroyMagickWand(wd);
1128                         return Efail;
1129                 }
1130                 status = MagickReadImageBlob(wd, cr.s, cr.i);
1131                 free(cr.s);
1132                 if (status == MagickFalse) {
1133                         DestroyMagickWand(wd);
1134                         return Efail;
1135                 }
1136         }
1137
1138         if (!wd)
1139                 return Einval;
1140
1141         MagickAutoOrientImage(wd);
1142         dii.c = nc_draw_image_cb;
1143         dii.wd = wd;
1144         dii.p = p;
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);
1149
1150         DestroyMagickWand(wd);
1151
1152         pane_damaged(ci->home, DAMAGED_POSTORDER);
1153
1154         return 1;
1155 }
1156
1157 DEF_CMD(nc_image_size)
1158 {
1159         MagickBooleanType status;
1160         MagickWand *wd;
1161         int ih, iw;
1162
1163         if (!ci->str)
1164                 return Enoarg;
1165         if (strstarts(ci->str, "file:")) {
1166                 wd = NewMagickWand();
1167                 status = MagickReadImage(wd, ci->str + 5);
1168                 if (status == MagickFalse) {
1169                         DestroyMagickWand(wd);
1170                         return Efail;
1171                 }
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);
1176                 if (!cr.s) {
1177                         DestroyMagickWand(wd);
1178                         return Efail;
1179                 }
1180                 status = MagickReadImageBlob(wd, cr.s, cr.i);
1181                 free(cr.s);
1182                 if (status == MagickFalse) {
1183                         DestroyMagickWand(wd);
1184                         return Efail;
1185                 }
1186         } else
1187                 return Einval;
1188
1189         MagickAutoOrientImage(wd);
1190         ih = MagickGetImageHeight(wd);
1191         iw = MagickGetImageWidth(wd);
1192
1193         DestroyMagickWand(wd);
1194         comm_call(ci->comm2, "callback:size", ci->focus,
1195                   0, NULL, NULL, 0, NULL, NULL,
1196                   iw, ih);
1197         return 1;
1198 }
1199
1200 DEF_CMD(nc_refresh_size)
1201 {
1202         struct pane *p = ci->home;
1203
1204         set_screen(p);
1205         getmaxyx(stdscr, p->h, p->w);
1206         clearok(curscr, 1);
1207         return 0;
1208 }
1209
1210 DEF_CMD(nc_refresh_post)
1211 {
1212         struct pane *p = ci->home;
1213         struct display_data *dd = p->data;
1214         struct pane *p1;
1215         PANEL *pan, *pan2;
1216
1217         if (dd->suspended)
1218                 return 1;
1219
1220         set_screen(p);
1221
1222         /* Need to ensure stacking order and panel y,x position
1223          * is correct.  FIXME it would be good if we could skip this
1224          * almost always.
1225          */
1226         pan = panel_above(NULL);
1227         if (!pan)
1228                 return 1;
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);
1233                 if (!p1 || !p2)
1234                         continue;
1235
1236                 if (p1->abs_z < p2->abs_z)
1237                         continue;
1238                 if (p1->abs_z == p2->abs_z &&
1239                     p1->z <= p2->z)
1240                         continue;
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
1243                  * be pushed up too.
1244                  */
1245                 top_panel(pan);
1246                 /* Now the panel below pan might need to be over pan2 too... */
1247                 pan = panel_below(pan2);
1248                 if (pan)
1249                         pan2 = pan;
1250         }
1251
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
1254          * that.
1255          */
1256         for (pan = NULL; (pan = panel_above(pan)) != NULL; ) {
1257                 WINDOW *win;
1258                 struct xy src, dest, destend;
1259                 int w, h;
1260
1261                 p1 = (void*)panel_userptr(pan);
1262                 if (!p1)
1263                         continue;
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);
1278         }
1279         /* place the cursor */
1280         p1 = pane_focus(p);
1281         pan = NULL;
1282         while (p1 != p && (pan = pane_panel(p1, NULL)) == NULL)
1283                 p1 = p1->parent;
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);
1289         refresh();
1290         record_screen(ci->home);
1291         return 1;
1292 }
1293
1294 static void ncurses_start(struct pane *p safe)
1295 {
1296         struct display_data *dd = p->data;
1297         int rows, cols;
1298
1299         start_color();
1300         use_default_colors();
1301         raw();
1302         noecho();
1303         nonl();
1304         timeout(0);
1305         set_escdelay(100);
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);
1315         mouseinterval(0);
1316         if (dd->is_xterm) {
1317                 /* Enable bracketed-paste */
1318                 fprintf(dd->scr_file, "\033[?2004h");
1319                 fflush(dd->scr_file);
1320         }
1321
1322         getmaxyx(stdscr, rows, cols);
1323         pane_resize(p, 0, 0, cols, rows);
1324 }
1325
1326 static struct pane *ncurses_init(struct pane *ed safe,
1327                                  const char *tty, const char *term)
1328 {
1329         SCREEN *scr;
1330         struct pane *p;
1331         struct display_data *dd;
1332         char *area;
1333         FILE *f;
1334
1335         set_screen(NULL);
1336         if (tty && strcmp(tty, "-") != 0)
1337                 f = fopen(tty, "r+");
1338         else
1339                 f = fdopen(1, "r+");
1340         if (!f)
1341                 return NULL;
1342         scr = newterm(term, f, f);
1343         if (!scr)
1344                 return NULL;
1345
1346         p = pane_register(ed, 1, &ncurses_handle.c);
1347         if (!p)
1348                 return NULL;
1349         dd = p->data;
1350         dd->scr = scr;
1351         dd->scr_file = f;
1352         dd->is_xterm = (term && strstarts(term, "xterm"));
1353
1354         attr_set_str(&p->attrs, "Display:pixels", "1x2");
1355
1356         set_screen(p);
1357
1358         ncurses_start(p);
1359
1360         area = dd->attr_buf;
1361         dd->rs1 = tgetstr("rs1", &area);
1362         if (!dd->rs1)
1363                 dd->rs1 = tgetstr("is1", &area);
1364         dd->rs2 = tgetstr("rs2", &area);
1365         if (!dd->rs2)
1366                 dd->rs2 = tgetstr("is2", &area);
1367         dd->rs3 = tgetstr("rs3", &area);
1368         if (!dd->rs3)
1369                 dd->rs3 = tgetstr("is3", &area);
1370         dd->clear = tgetstr("clear", &area);
1371
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);
1377         }
1378         return p;
1379 }
1380
1381 REDEF_CMD(handle_winch)
1382 {
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);
1387         set_screen(p);
1388         resize_term(size.ws_row, size.ws_col);
1389
1390         pane_resize(p, 0, 0, size.ws_row, size.ws_col);
1391         return 1;
1392 }
1393
1394 DEF_CMD(force_redraw)
1395 {
1396         struct pane *p = ci->home;
1397
1398         set_screen(p);
1399
1400         /* full reset, as mosh sometimes gets confused */
1401         ncurses_stop(p);
1402         ncurses_start(p);
1403
1404         clearok(curscr, 1);
1405         return 1;
1406 }
1407
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)
1411 {
1412         PANEL *pan;
1413         struct pane *p2;
1414         cchar_t cc = {};
1415
1416         if (x < 0 || y < 0)
1417                 return;
1418
1419         set_screen(display);
1420         if (cursor) {
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;
1426                 } else
1427                         /* Cursor here, but not focus */
1428                         attr = make_cursor(attr);
1429         }
1430         cc.attr = attr;
1431         cc.ext_color = pair;
1432         cc.chars[0] = ch;
1433
1434         p2 = p;
1435         pan = pane_panel(p2, NULL);
1436         while (!pan && p2->parent != p2) {
1437                 p2 = p2->parent;
1438                 pan = pane_panel(p2, NULL);
1439         }
1440         if (pan) {
1441                 struct xy xy = pane_mapxy(p, p2, x, y, False);
1442                 mvwadd_wch(panel_window(pan), xy.y, xy.x, &cc);
1443         }
1444 }
1445
1446 static struct namelist {
1447         wint_t key;
1448         char *name;
1449 } key_names[] = {
1450         {KEY_DOWN, ":Down"},
1451         {KEY_UP, ":Up"},
1452         {KEY_LEFT, ":Left"},
1453         {KEY_RIGHT, ":Right"},
1454         {KEY_HOME, ":Home"},
1455         {KEY_BACKSPACE, ":Backspace"},
1456         {KEY_DL, ":DelLine"},
1457         {KEY_IL, ":InsLine"},
1458         {KEY_DC, ":Del"},
1459         {KEY_IC, ":Ins"},
1460         {KEY_ENTER, ":Enter"},
1461         {KEY_END, ":End"},
1462
1463         {KEY_NPAGE, ":Next"},
1464         {KEY_PPAGE, ":Prior"},
1465
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"},
1473
1474         {  0521, ":S:Up"},
1475         {  0520, ":S:Down"},
1476         {  0616, ":S:Prior"},
1477         {  0614, ":S:Next"},
1478         { 01041, ":S:Home"},
1479         { 01060, ":S:End"},
1480         { 01066, ":S:Prior"},
1481         { 01015, ":S:Next"},
1482
1483         { 01027, ":A:S:Home"},
1484         { 01022, ":A:S:End"},
1485         { 01046, ":A:S:Prior"},
1486         { 01047, ":A:S:Next"}, // ??
1487
1488         { 01052, ":A:Prior"},
1489         { 01045, ":A:Next"},
1490         { 01026, ":A:Home"},
1491         { 01021, ":A:End"},
1492         { 01065, ":A:Up"},
1493         { 01014, ":A:Down"},
1494         { 01040, ":A:Left"},
1495         { 01057, ":A:Right"},
1496         { 00411, ":F1"},
1497         { 00412, ":F2"},
1498         { 00413, ":F3"},
1499         { 00414, ":F4"},
1500         { 00415, ":F5"},
1501         { 00416, ":F6"},
1502         { 00417, ":F7"},
1503         { 00420, ":F8"},
1504         { 00421, ":F9"},
1505         { 00422, ":F10"},
1506         { 00423, ":F11"},
1507         { 00424, ":F12"},
1508         { 00425, ":S:F1"},
1509         { 00426, ":S:F2"},
1510         { 00427, ":S:F3"},
1511         { 00430, ":S:F4"},
1512         { 00431, ":S:F5"},
1513         { 00432, ":S:F6"},
1514         { 00433, ":S:F7"},
1515         { 00434, ":S:F8"},
1516         { 00435, ":S:F9"},
1517         { 00436, ":S:F10"},
1518         { 00437, ":S:F11"},
1519         { 00440, ":S:F12"},
1520         { 01114, ":Focus-in"},
1521         { 01115, ":Focus-out"},
1522         {0, NULL}
1523 }, char_names[] = {
1524         {'\e', ":ESC"},
1525         {'\n', ":LF"},
1526         {'\r', ":Enter"},
1527         {'\t', ":Tab"},
1528         {'\177', ":Delete"},
1529         {'\0', ":C- "},
1530         {0, NULL}
1531 };
1532
1533 static char *find_name (struct namelist *l safe, wint_t c)
1534 {
1535         int i;
1536         for (i = 0; l[i].name; i++)
1537                 if (l[i].key == c)
1538                         return l[i].name;
1539         return NULL;
1540 }
1541
1542 static void send_key(int keytype, wint_t c, int alt, struct pane *p safe)
1543 {
1544         struct display_data *dd = p->data;
1545         char *n;
1546         char buf[100];/* FIXME */
1547         char t[5];
1548         char *a = alt ? ":A" : "";
1549
1550         if (keytype == KEY_CODE_YES) {
1551                 n = find_name(key_names, c);
1552                 if (!n) {
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 */
1557                         buf[0] = 0;
1558                 else
1559                         strcat(strcpy(buf, a), n);
1560         } else {
1561                 n = find_name(char_names, c);
1562                 if (n)
1563                         sprintf(buf, "%s%s", a, n);
1564                 else if (c < ' ' || c == 0x7f)
1565                         sprintf(buf, "%s:C-%c",
1566                                 a, c ^ 64);
1567                 else
1568                         sprintf(buf, "%s-%s", a, put_utf8(t, c));
1569         }
1570
1571         dd->last_event = time(NULL);
1572         if (buf[0]) {
1573                 record_key(p, buf);
1574                 call("Keystroke", p, 0, NULL, buf);
1575         }
1576 }
1577
1578 static void do_send_mouse(struct pane *p safe, int x, int y, char *cmd safe,
1579                           int button, char *mod, int type)
1580 {
1581         int ret;
1582         struct display_data *dd = p->data;
1583
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) {
1587                 if (dd->is_xterm) {
1588                         fprintf(dd->scr_file, "\033[?1002h");
1589                         fflush(dd->scr_file);
1590                 }
1591                 dd->report_position = 1;
1592         } else if (type == 3 && ret <= 0) {
1593                 if (dd->is_xterm) {
1594                         fprintf(dd->scr_file, "\033[?1002l");
1595                         fflush(dd->scr_file);
1596                 }
1597                 dd->report_position = 0;
1598         }
1599 }
1600
1601 static void send_mouse(MEVENT *mev safe, struct pane *p safe)
1602 {
1603         struct display_data *dd = p->data;
1604         int x = mev->x;
1605         int y = mev->y;
1606         int b;
1607         char buf[100];
1608
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;
1612                 char *action;
1613                 int modf = 0;
1614                 char *mod = "";
1615
1616                 if (s & BUTTON_SHIFT) modf |= 1;
1617                 if (s & BUTTON_CTRL)  modf |= 2;
1618                 if (s & BUTTON_ALT)   modf |= 4;
1619                 switch (modf) {
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;
1628                 }
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 */
1634                         mod = "";
1635                 } else
1636                         continue;
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);
1640         }
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);
1645 }
1646
1647 static void paste_start(struct pane *home safe)
1648 {
1649         struct display_data *dd = home->data;
1650
1651         dd->paste_start = time(NULL);
1652         buf_init(&dd->paste_buf);
1653 }
1654
1655 static void paste_flush(struct pane *home safe)
1656 {
1657         struct display_data *dd = home->data;
1658
1659         if (!dd->paste_start)
1660                 return;
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;
1666 }
1667
1668 static bool paste_recv(struct pane *home safe, int is_keycode, wint_t ch)
1669 {
1670         struct display_data *dd = home->data;
1671         time_t now;
1672         if (dd->paste_start == 0)
1673                 return False;
1674         now = time(NULL);
1675         if (dd->paste_start < now || dd->paste_start > now + 2 ||
1676             is_keycode != OK || ch == KEY_MOUSE) {
1677                 /* time to close */
1678                 paste_flush(home);
1679                 return False;
1680         }
1681         if (ch == '\r')
1682                 /* I really don't want carriage-returns... */
1683                 ch = '\n';
1684         buf_append(&dd->paste_buf, ch);
1685         if (ch == '~' && dd->paste_buf.len >= 6 &&
1686             strcmp("\e[201~",
1687                    buf_final(&dd->paste_buf) + dd->paste_buf.len - 6) == 0) {
1688                 dd->paste_buf.len -= 6;
1689                 paste_flush(home);
1690         }
1691         return True;
1692 }
1693
1694 DEF_CMD(nc_get_paste)
1695 {
1696         struct display_data *dd = ci->home->data;
1697
1698         comm_call(ci->comm2, "cb", ci->focus,
1699                   dd->paste_start, NULL, dd->paste_latest);
1700         return 1;
1701 }
1702
1703 REDEF_CMD(input_handle)
1704 {
1705         struct pane *p = ci->home;
1706         struct display_data *dd = p->data;
1707         static const char paste_seq[] = "\e[200~";
1708         wint_t c;
1709         int is_keycode;
1710         int have_escape = 0;
1711         int i;
1712
1713         wait_for(dd);
1714         set_screen(p);
1715         while ((is_keycode = get_wch(&c)) != ERR) {
1716                 if (dd->suspended && c != KEY_MOUSE) {
1717                         dd->suspended = False;
1718                         doupdate();
1719                         call_comm("event:free", p, &ns_resume);
1720                         /* swallow the key */
1721                         continue;
1722                 }
1723                 if (paste_recv(p, is_keycode, c))
1724                         continue;
1725                 if (c == KEY_MOUSE) {
1726                         MEVENT mev;
1727                         paste_flush(p);
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
1741                                          */
1742                                         call("Mouse-event", ci->home,
1743                                              1, NULL, ":Paste",
1744                                              6, NULL, NULL, mev.x, mev.y);
1745                                         dd->paste_pending = 0;
1746                                 }
1747                                 send_mouse(&mev, p);
1748                         }
1749                 } else if (c == (wint_t)paste_seq[have_escape]) {
1750                         have_escape += 1;
1751                         if (!paste_seq[have_escape]) {
1752                                 paste_start(p);
1753                                 have_escape = 0;
1754                         }
1755                 } else if (have_escape == 1) {
1756                         send_key(is_keycode, c, 1, p);
1757                         have_escape = 0;
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);
1763                         have_escape = 0;
1764                 } else {
1765                         send_key(is_keycode, c, 0, p);
1766                 }
1767                 /* Don't know what other code might have done,
1768                  * so re-set the screen
1769                  */
1770                 set_screen(p);
1771         }
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);
1778         }
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);
1787         }
1788         return 1;
1789 }
1790
1791 DEF_CMD(display_ncurses)
1792 {
1793         struct pane *p;
1794         struct pane *ed = pane_root(ci->focus);
1795         const char *tty = ci->str;
1796         const char *term = ci->str2;
1797
1798         if (!term)
1799                 term = "xterm-256color";
1800
1801         p = ncurses_init(ed, tty, term);
1802         if (p)
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);
1807         if (p)
1808                 return comm_call(ci->comm2, "callback:display", p);
1809
1810         return Efail;
1811 }
1812
1813 void edlib_init(struct pane *ed safe)
1814 {
1815         call_comm("global-set-command", ed, &display_ncurses, 0, NULL,
1816                   "attach-display-ncurses");
1817
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);
1826
1827         key_add(nc_map, "Draw:image", &nc_draw_image);
1828         key_add(nc_map, "Draw:image-size", &nc_image_size);
1829
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);
1836 }