]> git.neil.brown.name Git - edlib.git/blob - display-ncurses.c
core-log: reduce number of statics
[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(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 DEF_CMD(nc_draw_image)
999 {
1000         /* 'str' identifies the image. Options are:
1001          *     file:filename  - load file from fs
1002          *     comm:command   - run command collecting bytes
1003          * 'num' is '16' if image should be stretched to fill pane
1004          * Otherwise it is the 'or' of
1005          *   0,1,2 for left/middle/right in x direction
1006          *   0,4,8 for top/middle/bottom in y direction
1007          * only one of these can be used as image will fill pane
1008          * in other direction.
1009          * If 'x' and 'y' are both positive, draw cursor box at
1010          * p->cx, p->cy of a size so that 'x' will fit across and
1011          * 'y' will fit down.
1012          */
1013         struct pane *p = ci->home;
1014         struct display_data *dd = p->data;
1015         int x = 0, y = 0;
1016         bool stretch = ci->num & 16;
1017         int pos = ci->num;
1018         int w = ci->focus->w, h = ci->focus->h * 2;
1019         int cx = -1, cy = -1;
1020         MagickBooleanType status;
1021         MagickWand *wd;
1022         unsigned char *buf;
1023         int i, j;
1024
1025         if (!ci->str)
1026                 return Enoarg;
1027         if (strstarts(ci->str, "file:")) {
1028                 wd = NewMagickWand();
1029                 status = MagickReadImage(wd, ci->str + 5);
1030                 if (status == MagickFalse) {
1031                         DestroyMagickWand(wd);
1032                         return Efail;
1033                 }
1034         } else if (strstarts(ci->str, "comm:")) {
1035                 struct call_return cr;
1036                 wd = NewMagickWand();
1037                 cr = call_ret(bytes, ci->str+5, ci->focus, 0, NULL, ci->str2);
1038                 if (!cr.s) {
1039                         DestroyMagickWand(wd);
1040                         return Efail;
1041                 }
1042                 status = MagickReadImageBlob(wd, cr.s, cr.i);
1043                 free(cr.s);
1044                 if (status == MagickFalse) {
1045                         DestroyMagickWand(wd);
1046                         return Efail;
1047                 }
1048         } else
1049                 return Einval;
1050
1051         MagickAutoOrientImage(wd);
1052         if (!stretch) {
1053                 int ih = MagickGetImageHeight(wd);
1054                 int iw = MagickGetImageWidth(wd);
1055
1056                 if (iw <= 0 || iw <= 0) {
1057                         DestroyMagickWand(wd);
1058                         return Efail;
1059                 }
1060                 if (iw * h > ih * w) {
1061                         /* Image is wider than space, use less height */
1062                         ih = ih * w / iw;
1063                         switch(pos & (8+4)) {
1064                         case 4: /* center */
1065                                 y = (h - ih) / 2; break;
1066                         case 8: /* bottom */
1067                                 y = h - ih; break;
1068                         }
1069                         /* Keep 'h' even! */
1070                         h = ((ih+1)/2) * 2;
1071                 } else {
1072                         /* image is too tall, use less width */
1073                         iw = iw * h / ih;
1074                         switch (pos & (1+2)) {
1075                         case 1: /* center */
1076                                 x = (w - iw) / 2; break;
1077                         case 2: /* right */
1078                                 x = w - iw ; break;
1079                         }
1080                         w = iw;
1081                 }
1082         }
1083         MagickAdaptiveResizeImage(wd, w, h);
1084         buf = malloc(h * w * 4);
1085         MagickExportImagePixels(wd, 0, 0, w, h, "RGBA", CharPixel, buf);
1086
1087         if (ci->x > 0 && ci->y > 0 && ci->focus->cx >= 0) {
1088                 /* We want a cursor */
1089                 cx = x + ci->focus->cx;
1090                 cy = y + ci->focus->cy;
1091         }
1092         for (i = 0; i < h; i+= 2) {
1093                 static const wint_t hilo = 0x2580; /* L'▀' */
1094                 for (j = 0; j < w ; j+= 1) {
1095                         unsigned char *p1 = buf + i*w*4 + j*4;
1096                         unsigned char *p2 = buf + (i+1)*w*4 + j*4;
1097                         int rgb1[3] = { p1[0]*1000/255, p1[1]*1000/255, p1[2]*1000/255 };
1098                         int rgb2[3] = { p2[0]*1000/255, p2[1]*1000/255, p2[2]*1000/255 };
1099                         int fg = find_col(dd, rgb1);
1100                         int bg = find_col(dd, rgb2);
1101
1102                         if (p1[3] < 128 || p2[3] < 128) {
1103                                 /* transparent */
1104                                 cchar_t cc;
1105                                 short f,b;
1106                                 struct pane *pn2 = ci->focus;
1107                                 PANEL *pan = pane_panel(pn2, NULL);
1108
1109                                 while (!pan && pn2->parent != pn2) {
1110                                         pn2 = pn2->parent;
1111                                         pan = pane_panel(pn2, NULL);
1112                                 }
1113                                 if (pan) {
1114                                         wgetbkgrnd(panel_window(pan), &cc);
1115                                         if (cc.ext_color == 0)
1116                                                 /* default.  This is light
1117                                                  * gray rather then white,
1118                                                  * but I think it is a good
1119                                                  * result.
1120                                                  */
1121                                                 b = COLOR_WHITE;
1122                                         else
1123                                                 pair_content(cc.ext_color, &f, &b);
1124                                         if (p1[3] < 128)
1125                                                 fg = b;
1126                                         if (p2[3] < 128)
1127                                                 bg = b;
1128                                 }
1129                         }
1130                         /* FIXME this doesn't work because
1131                          * render-line knows too much and gets it wrong.
1132                          */
1133                         if (cx == x+j && cy == y + (i/2))
1134                                 ncurses_text(ci->focus, p, 'X', 0,
1135                                              to_pair(dd, 0, 0),
1136                                              x+j, y+(i/2), 1);
1137                         else
1138                                 ncurses_text(ci->focus, p, hilo, 0,
1139                                              to_pair(dd, fg, bg),
1140                                              x+j, y+(i/2), 0);
1141
1142                 }
1143         }
1144         free(buf);
1145
1146         DestroyMagickWand(wd);
1147
1148         pane_damaged(ci->home, DAMAGED_POSTORDER);
1149
1150         return 1;
1151 }
1152
1153 DEF_CMD(nc_image_size)
1154 {
1155         MagickBooleanType status;
1156         MagickWand *wd;
1157         int ih, iw;
1158
1159         if (!ci->str)
1160                 return Enoarg;
1161         if (strstarts(ci->str, "file:")) {
1162                 wd = NewMagickWand();
1163                 status = MagickReadImage(wd, ci->str + 5);
1164                 if (status == MagickFalse) {
1165                         DestroyMagickWand(wd);
1166                         return Efail;
1167                 }
1168         } else if (strstarts(ci->str, "comm:")) {
1169                 struct call_return cr;
1170                 wd = NewMagickWand();
1171                 cr = call_ret(bytes, ci->str+5, ci->focus, 0, NULL, ci->str2);
1172                 if (!cr.s) {
1173                         DestroyMagickWand(wd);
1174                         return Efail;
1175                 }
1176                 status = MagickReadImageBlob(wd, cr.s, cr.i);
1177                 free(cr.s);
1178                 if (status == MagickFalse) {
1179                         DestroyMagickWand(wd);
1180                         return Efail;
1181                 }
1182         } else
1183                 return Einval;
1184
1185         MagickAutoOrientImage(wd);
1186         ih = MagickGetImageHeight(wd);
1187         iw = MagickGetImageWidth(wd);
1188
1189         DestroyMagickWand(wd);
1190         comm_call(ci->comm2, "callback:size", ci->focus,
1191                   0, NULL, NULL, 0, NULL, NULL,
1192                   iw, ih);
1193         return 1;
1194 }
1195
1196 DEF_CMD(nc_refresh_size)
1197 {
1198         struct pane *p = ci->home;
1199
1200         set_screen(p);
1201         getmaxyx(stdscr, p->h, p->w);
1202         clearok(curscr, 1);
1203         return 0;
1204 }
1205
1206 DEF_CMD(nc_refresh_post)
1207 {
1208         struct pane *p = ci->home;
1209         struct display_data *dd = p->data;
1210         struct pane *p1;
1211         PANEL *pan, *pan2;
1212
1213         if (dd->suspended)
1214                 return 1;
1215
1216         set_screen(p);
1217
1218         /* Need to ensure stacking order and panel y,x position
1219          * is correct.  FIXME it would be good if we could skip this
1220          * almost always.
1221          */
1222         pan = panel_above(NULL);
1223         if (!pan)
1224                 return 1;
1225         p1 = (struct pane*) panel_userptr(pan);
1226         for (;(pan2 = panel_above(pan)) != NULL; pan = pan2) {
1227                 struct pane *p2 = (struct pane*)panel_userptr(pan2);
1228                 p1 = (struct pane*)panel_userptr(pan);
1229                 if (!p1 || !p2)
1230                         continue;
1231
1232                 if (p1->abs_z < p2->abs_z)
1233                         continue;
1234                 if (p1->abs_z == p2->abs_z &&
1235                     p1->z <= p2->z)
1236                         continue;
1237                 /* pan needs to be above pan2.  All we can do is move it to
1238                  * the top. Anything that needs to be above it will eventually
1239                  * be pushed up too.
1240                  */
1241                 top_panel(pan);
1242                 /* Now the panel below pan might need to be over pan2 too... */
1243                 pan = panel_below(pan2);
1244                 if (pan)
1245                         pan2 = pan;
1246         }
1247
1248         /* As we need to crop pane against their parents, we cannot simply
1249          * use update_panels().  Instead we copy each to stdscr and refresh
1250          * that.
1251          */
1252         for (pan = NULL; (pan = panel_above(pan)) != NULL; ) {
1253                 WINDOW *win;
1254                 struct xy src, dest, destend;
1255                 int w, h;
1256
1257                 p1 = (void*)panel_userptr(pan);
1258                 if (!p1)
1259                         continue;
1260                 dest = pane_mapxy(p1, p, 0, 0, True);
1261                 destend = pane_mapxy(p1, p, p1->w, p1->h, True);
1262                 src = pane_mapxy(p1, p, 0, 0, False);
1263                 src.x = dest.x - src.x;
1264                 src.y = dest.y - src.y;
1265                 win = panel_window(pan);
1266                 getmaxyx(win, h, w);
1267                 /* guard again accessing beyond boundary of win */
1268                 if (destend.x > dest.x + (w - src.x))
1269                         destend.x = dest.x + (w - src.x);
1270                 if (destend.y > dest.y + (h - src.y))
1271                         destend.y = dest.y - (h - src.y);
1272                 copywin(win, stdscr, src.y, src.x,
1273                         dest.y, dest.x, destend.y-1, destend.x-1, 0);
1274         }
1275         /* place the cursor */
1276         p1 = pane_leaf(p);
1277         pan = NULL;
1278         while (p1 != p && (pan = pane_panel(p1, NULL)) == NULL)
1279                 p1 = p1->parent;
1280         if (pan && p1->cx >= 0) {
1281                 struct xy curs = pane_mapxy(p1, p, p1->cx, p1->cy, False);
1282                 wmove(stdscr, curs.y, curs.x);
1283         } else if (p->cx >= 0)
1284                 wmove(stdscr, p->cy, p->cx);
1285         refresh();
1286         record_screen(ci->home);
1287         return 1;
1288 }
1289
1290 static void ncurses_start(struct pane *p safe)
1291 {
1292         struct display_data *dd = p->data;
1293         int rows, cols;
1294
1295         start_color();
1296         use_default_colors();
1297         raw();
1298         noecho();
1299         nonl();
1300         timeout(0);
1301         set_escdelay(100);
1302         intrflush(stdscr, FALSE);
1303         keypad(stdscr, TRUE);
1304         mousemask(BUTTON1_PRESSED | BUTTON1_RELEASED |
1305                   BUTTON2_PRESSED | BUTTON2_RELEASED |
1306                   BUTTON3_PRESSED | BUTTON3_RELEASED |
1307                   BUTTON4_PRESSED | BUTTON4_RELEASED |
1308                   BUTTON5_PRESSED | BUTTON5_RELEASED |
1309                   BUTTON_CTRL | BUTTON_SHIFT | BUTTON_ALT |
1310                   REPORT_MOUSE_POSITION, NULL);
1311         mouseinterval(0);
1312         if (dd->is_xterm) {
1313                 /* Enable bracketed-paste */
1314                 fprintf(dd->scr_file, "\033[?2004h");
1315                 fflush(dd->scr_file);
1316         }
1317
1318         getmaxyx(stdscr, rows, cols);
1319         pane_resize(p, 0, 0, cols, rows);
1320 }
1321
1322 static struct pane *ncurses_init(struct pane *ed safe,
1323                                  const char *tty, const char *term)
1324 {
1325         SCREEN *scr;
1326         struct pane *p;
1327         struct display_data *dd;
1328         char *area;
1329         FILE *f;
1330
1331         set_screen(NULL);
1332         if (tty && strcmp(tty, "-") != 0)
1333                 f = fopen(tty, "r+");
1334         else
1335                 f = fdopen(1, "r+");
1336         if (!f)
1337                 return NULL;
1338         scr = newterm(term, f, f);
1339         if (!scr)
1340                 return NULL;
1341
1342         p = pane_register(ed, 1, &ncurses_handle.c);
1343         if (!p)
1344                 return NULL;
1345         dd = p->data;
1346         dd->scr = scr;
1347         dd->scr_file = f;
1348         dd->is_xterm = (term && strstarts(term, "xterm"));
1349
1350         set_screen(p);
1351
1352         ncurses_start(p);
1353
1354         area = dd->attr_buf;
1355         dd->rs1 = tgetstr("rs1", &area);
1356         if (!dd->rs1)
1357                 dd->rs1 = tgetstr("is1", &area);
1358         dd->rs2 = tgetstr("rs2", &area);
1359         if (!dd->rs2)
1360                 dd->rs2 = tgetstr("is2", &area);
1361         dd->rs3 = tgetstr("rs3", &area);
1362         if (!dd->rs3)
1363                 dd->rs3 = tgetstr("is3", &area);
1364         dd->clear = tgetstr("clear", &area);
1365
1366         call("editor:request:all-displays", p);
1367         if (!prepare_recrep(p)) {
1368                 call_comm("event:read", p, &input_handle, fileno(f));
1369                 if (!tty || strcmp(tty, "-") == 0)
1370                         call_comm("event:signal", p, &handle_winch, SIGWINCH);
1371         }
1372         return p;
1373 }
1374
1375 REDEF_CMD(handle_winch)
1376 {
1377         struct pane *p = ci->home;
1378         struct display_data *dd = p->data;
1379         struct winsize size;
1380         ioctl(fileno(dd->scr_file), TIOCGWINSZ, &size);
1381         set_screen(p);
1382         resize_term(size.ws_row, size.ws_col);
1383
1384         pane_resize(p, 0, 0, size.ws_row, size.ws_col);
1385         return 1;
1386 }
1387
1388 DEF_CMD(force_redraw)
1389 {
1390         struct pane *p = ci->home;
1391
1392         set_screen(p);
1393
1394         /* full reset, as mosh sometimes gets confused */
1395         ncurses_stop(p);
1396         ncurses_start(p);
1397
1398         clearok(curscr, 1);
1399         return 1;
1400 }
1401
1402 static void ncurses_text(struct pane *p safe, struct pane *display safe,
1403                          wchar_t ch, int attr, int pair,
1404                          short x, short y, short cursor)
1405 {
1406         PANEL *pan;
1407         struct pane *p2;
1408         cchar_t cc = {};
1409
1410         if (x < 0 || y < 0)
1411                 return;
1412
1413         set_screen(display);
1414         if (cursor) {
1415                 if (pane_has_focus(p->z < 0 ? p->parent : p)) {
1416                         /* Cursor is in-focus */
1417                         struct xy curs = pane_mapxy(p, display, x, y, False);
1418                         display->cx = curs.x;
1419                         display->cy = curs.y;
1420                 } else
1421                         /* Cursor here, but not focus */
1422                         attr = make_cursor(attr);
1423         }
1424         cc.attr = attr;
1425         cc.ext_color = pair;
1426         cc.chars[0] = ch;
1427
1428         p2 = p;
1429         pan = pane_panel(p2, NULL);
1430         while (!pan && p2->parent != p2) {
1431                 p2 = p2->parent;
1432                 pan = pane_panel(p2, NULL);
1433         }
1434         if (pan) {
1435                 struct xy xy = pane_mapxy(p, p2, x, y, False);
1436                 mvwadd_wch(panel_window(pan), xy.y, xy.x, &cc);
1437         }
1438 }
1439
1440 static struct namelist {
1441         wint_t key;
1442         char *name;
1443 } key_names[] = {
1444         {KEY_DOWN, ":Down"},
1445         {KEY_UP, ":Up"},
1446         {KEY_LEFT, ":Left"},
1447         {KEY_RIGHT, ":Right"},
1448         {KEY_HOME, ":Home"},
1449         {KEY_BACKSPACE, ":Backspace"},
1450         {KEY_DL, ":DelLine"},
1451         {KEY_IL, ":InsLine"},
1452         {KEY_DC, ":Del"},
1453         {KEY_IC, ":Ins"},
1454         {KEY_ENTER, ":Enter"},
1455         {KEY_END, ":End"},
1456
1457         {KEY_NPAGE, ":Next"},
1458         {KEY_PPAGE, ":Prior"},
1459
1460         {KEY_SDC, ":S:Del"},
1461         {KEY_SDL, ":S:DelLine"},
1462         {KEY_SEND, ":S:End"},
1463         {KEY_SHOME, ":S:Home"},
1464         {KEY_SLEFT, ":S:Left"},
1465         {KEY_SRIGHT, ":S:Right"},
1466         {KEY_BTAB, ":S:Tab"},
1467
1468         {  0521, ":S:Up"},
1469         {  0520, ":S:Down"},
1470         {  0616, ":S:Prior"},
1471         {  0614, ":S:Next"},
1472         { 01041, ":S:Home"},
1473         { 01060, ":S:End"},
1474         { 01066, ":S:Prior"},
1475         { 01015, ":S:Next"},
1476
1477         { 01027, ":A:S:Home"},
1478         { 01022, ":A:S:End"},
1479         { 01046, ":A:S:Prior"},
1480         { 01047, ":A:S:Next"}, // ??
1481
1482         { 01052, ":A:Prior"},
1483         { 01045, ":A:Next"},
1484         { 01026, ":A:Home"},
1485         { 01021, ":A:End"},
1486         { 01065, ":A:Up"},
1487         { 01014, ":A:Down"},
1488         { 01040, ":A:Left"},
1489         { 01057, ":A:Right"},
1490         { 00411, ":F1"},
1491         { 00412, ":F2"},
1492         { 00413, ":F3"},
1493         { 00414, ":F4"},
1494         { 00415, ":F5"},
1495         { 00416, ":F6"},
1496         { 00417, ":F7"},
1497         { 00420, ":F8"},
1498         { 00421, ":F9"},
1499         { 00422, ":F10"},
1500         { 00423, ":F11"},
1501         { 00424, ":F12"},
1502         { 00425, ":S:F1"},
1503         { 00426, ":S:F2"},
1504         { 00427, ":S:F3"},
1505         { 00430, ":S:F4"},
1506         { 00431, ":S:F5"},
1507         { 00432, ":S:F6"},
1508         { 00433, ":S:F7"},
1509         { 00434, ":S:F8"},
1510         { 00435, ":S:F9"},
1511         { 00436, ":S:F10"},
1512         { 00437, ":S:F11"},
1513         { 00440, ":S:F12"},
1514         { 01114, ":Focus-in"},
1515         { 01115, ":Focus-out"},
1516         {0, NULL}
1517 }, char_names[] = {
1518         {'\e', ":ESC"},
1519         {'\n', ":LF"},
1520         {'\r', ":Enter"},
1521         {'\t', ":Tab"},
1522         {'\177', ":Delete"},
1523         {'\0', ":C- "},
1524         {0, NULL}
1525 };
1526
1527 static char *find_name (struct namelist *l safe, wint_t c)
1528 {
1529         int i;
1530         for (i = 0; l[i].name; i++)
1531                 if (l[i].key == c)
1532                         return l[i].name;
1533         return NULL;
1534 }
1535
1536 static void send_key(int keytype, wint_t c, int alt, struct pane *p safe)
1537 {
1538         struct display_data *dd = p->data;
1539         char *n;
1540         char buf[100];/* FIXME */
1541         char t[5];
1542         char *a = alt ? ":A" : "";
1543
1544         if (keytype == KEY_CODE_YES) {
1545                 n = find_name(key_names, c);
1546                 if (!n) {
1547                         LOG("Unknown ncurses key 0o%o", c);
1548                         sprintf(buf, "%sNcurs-%o", a, c);
1549                 } else if (strstarts(n, ":Focus-"))
1550                         /* Ignore focus changes for now */
1551                         buf[0] = 0;
1552                 else
1553                         strcat(strcpy(buf, a), n);
1554         } else {
1555                 n = find_name(char_names, c);
1556                 if (n)
1557                         sprintf(buf, "%s%s", a, n);
1558                 else if (c < ' ' || c == 0x7f)
1559                         sprintf(buf, "%s:C-%c",
1560                                 a, c ^ 64);
1561                 else
1562                         sprintf(buf, "%s-%s", a, put_utf8(t, c));
1563         }
1564
1565         dd->last_event = time(NULL);
1566         if (buf[0]) {
1567                 record_key(p, buf);
1568                 call("Keystroke", p, 0, NULL, buf);
1569         }
1570 }
1571
1572 static void do_send_mouse(struct pane *p safe, int x, int y, char *cmd safe,
1573                           int button, char *mod, int type)
1574 {
1575         int ret;
1576         struct display_data *dd = p->data;
1577
1578         record_mouse(p, cmd, x, y);
1579         ret = call("Mouse-event", p, button, NULL, cmd, type, NULL, mod, x, y);
1580         if (type == 1 && !dd->report_position) {
1581                 if (dd->is_xterm) {
1582                         fprintf(dd->scr_file, "\033[?1002h");
1583                         fflush(dd->scr_file);
1584                 }
1585                 dd->report_position = 1;
1586         } else if (type == 3 && ret <= 0) {
1587                 if (dd->is_xterm) {
1588                         fprintf(dd->scr_file, "\033[?1002l");
1589                         fflush(dd->scr_file);
1590                 }
1591                 dd->report_position = 0;
1592         }
1593 }
1594
1595 static void send_mouse(MEVENT *mev safe, struct pane *p safe)
1596 {
1597         struct display_data *dd = p->data;
1598         int x = mev->x;
1599         int y = mev->y;
1600         int b;
1601         char buf[100];
1602
1603         /* MEVENT has lots of bits.  We want a few numbers */
1604         for (b = 1 ; b <= (NCURSES_MOUSE_VERSION <= 1 ? 3 : 5); b++) {
1605                 mmask_t s = mev->bstate;
1606                 char *action;
1607                 int modf = 0;
1608                 char *mod = "";
1609
1610                 if (s & BUTTON_SHIFT) modf |= 1;
1611                 if (s & BUTTON_CTRL)  modf |= 2;
1612                 if (s & BUTTON_ALT)   modf |= 4;
1613                 switch (modf) {
1614                 case 0: mod = ""; break;
1615                 case 1: mod = ":S"; break;
1616                 case 2: mod = ":C"; break;
1617                 case 3: mod = ":C:S"; break;
1618                 case 4: mod = ":A"; break;
1619                 case 5: mod = ":A:S"; break;
1620                 case 6: mod = ":A:C"; break;
1621                 case 7: mod = ":A:C:S"; break;
1622                 }
1623                 if (BUTTON_PRESS(s, b))
1624                         action = "%s:Press-%d";
1625                 else if (BUTTON_RELEASE(s, b)) {
1626                         action = "%s:Release-%d";
1627                         /* Modifiers only reported on button Press */
1628                         mod = "";
1629                 } else
1630                         continue;
1631                 snprintf(buf, sizeof(buf), action, mod, b);
1632                 dd->last_event = time(NULL);
1633                 do_send_mouse(p, x, y, buf, b, mod, BUTTON_PRESS(s,b) ? 1 : 2);
1634         }
1635         if ((mev->bstate & REPORT_MOUSE_POSITION) &&
1636             dd->report_position)
1637                 /* Motion doesn't update last_event */
1638                 do_send_mouse(p, x, y, ":Motion", 0, "", 3);
1639 }
1640
1641 static void paste_start(struct pane *home safe)
1642 {
1643         struct display_data *dd = home->data;
1644
1645         dd->paste_start = time(NULL);
1646         buf_init(&dd->paste_buf);
1647 }
1648
1649 static void paste_flush(struct pane *home safe)
1650 {
1651         struct display_data *dd = home->data;
1652
1653         if (!dd->paste_start)
1654                 return;
1655         free(dd->paste_latest);
1656         dd->paste_latest = buf_final(&dd->paste_buf);
1657         if (dd->paste_buf.len > 0)
1658                 dd->paste_pending = 1;
1659         dd->paste_start = 0;
1660 }
1661
1662 static bool paste_recv(struct pane *home safe, int is_keycode, wint_t ch)
1663 {
1664         struct display_data *dd = home->data;
1665         time_t now;
1666         if (dd->paste_start == 0)
1667                 return False;
1668         now = time(NULL);
1669         if (dd->paste_start < now || dd->paste_start > now + 2 ||
1670             is_keycode != OK || ch == KEY_MOUSE) {
1671                 /* time to close */
1672                 paste_flush(home);
1673                 return False;
1674         }
1675         if (ch == '\r')
1676                 /* I really don't want carriage-returns... */
1677                 ch = '\n';
1678         buf_append(&dd->paste_buf, ch);
1679         if (ch == '~' && dd->paste_buf.len >= 6 &&
1680             strcmp("\e[201~",
1681                    buf_final(&dd->paste_buf) + dd->paste_buf.len - 6) == 0) {
1682                 dd->paste_buf.len -= 6;
1683                 paste_flush(home);
1684         }
1685         return True;
1686 }
1687
1688 DEF_CMD(nc_get_paste)
1689 {
1690         struct display_data *dd = ci->home->data;
1691
1692         comm_call(ci->comm2, "cb", ci->focus,
1693                   dd->paste_start, NULL, dd->paste_latest);
1694         return 1;
1695 }
1696
1697 REDEF_CMD(input_handle)
1698 {
1699         struct pane *p = ci->home;
1700         struct display_data *dd = p->data;
1701         static const char paste_seq[] = "\e[200~";
1702         wint_t c;
1703         int is_keycode;
1704         int have_escape = 0;
1705         int i;
1706
1707         wait_for(dd);
1708         set_screen(p);
1709         while ((is_keycode = get_wch(&c)) != ERR) {
1710                 if (dd->suspended && c != KEY_MOUSE) {
1711                         dd->suspended = False;
1712                         doupdate();
1713                         call_comm("event:free", p, &ns_resume);
1714                         /* swallow the key */
1715                         continue;
1716                 }
1717                 if (paste_recv(p, is_keycode, c))
1718                         continue;
1719                 if (c == KEY_MOUSE) {
1720                         MEVENT mev;
1721                         paste_flush(p);
1722                         while (getmouse(&mev) != ERR) {
1723                                 if (dd->paste_pending &&
1724                                     mev.bstate == REPORT_MOUSE_POSITION) {
1725                                         /* xcfe-terminal is a bit weird.
1726                                          * It captures middle-press to
1727                                          * sanitise the paste, but lets
1728                                          * middle-release though. It comes
1729                                          * here as REPORT_MOUSE_POSTION
1730                                          * and we can use that to find the
1731                                          * position of the paste.
1732                                          * '6' is an unused button, and
1733                                          * ensures lib-input doesn't expect
1734                                          * matching press/release
1735                                          */
1736                                         call("Mouse-event", ci->home,
1737                                              1, NULL, ":Paste",
1738                                              6, NULL, NULL, mev.x, mev.y);
1739                                         dd->paste_pending = 0;
1740                                 }
1741                                 send_mouse(&mev, p);
1742                         }
1743                 } else if (c == (wint_t)paste_seq[have_escape]) {
1744                         have_escape += 1;
1745                         if (!paste_seq[have_escape]) {
1746                                 paste_start(p);
1747                                 have_escape = 0;
1748                         }
1749                 } else if (have_escape == 1) {
1750                         send_key(is_keycode, c, 1, p);
1751                         have_escape = 0;
1752                 } else if (have_escape) {
1753                         send_key(OK, paste_seq[1], 1, p);
1754                         for (i = 2; i < have_escape; i++)
1755                                 send_key(OK, paste_seq[i], 0, p);
1756                         send_key(is_keycode, c, 0, p);
1757                         have_escape = 0;
1758                 } else {
1759                         send_key(is_keycode, c, 0, p);
1760                 }
1761                 /* Don't know what other code might have done,
1762                  * so re-set the screen
1763                  */
1764                 set_screen(p);
1765         }
1766         if (have_escape == 1)
1767                 send_key(is_keycode, '\e', 0, p);
1768         else if (have_escape > 1) {
1769                 send_key(OK, paste_seq[1], 1, p);
1770                 for (i = 2; i < have_escape; i++)
1771                         send_key(OK, paste_seq[i], 0, p);
1772         }
1773         if (dd->paste_pending == 2) {
1774                 /* no mouse event to give postion, so treat as keyboard */
1775                 call("Keystroke", ci->home, 0, NULL, ":Paste");
1776                 dd->paste_pending = 0;
1777         } else if (dd->paste_pending == 1) {
1778                 /* Wait for possible mouse-position update. */
1779                 dd->paste_pending = 2;
1780                 call_comm("event:timer", p, &input_handle, 200);
1781         }
1782         return 1;
1783 }
1784
1785 DEF_CMD(display_ncurses)
1786 {
1787         struct pane *p;
1788         struct pane *ed = pane_root(ci->focus);
1789         const char *tty = ci->str;
1790         const char *term = ci->str2;
1791
1792         if (!term)
1793                 term = "xterm-256color";
1794
1795         p = ncurses_init(ed, tty, term);
1796         if (p)
1797                 p = call_ret(pane, "editor:activate-display", p);
1798         if (p && ci->focus != ed)
1799                 /* Assume ci->focus is a document */
1800                 p = home_call_ret(pane, ci->focus, "doc:attach-view", p, 1);
1801         if (p)
1802                 return comm_call(ci->comm2, "callback:display", p);
1803
1804         return Efail;
1805 }
1806
1807 void edlib_init(struct pane *ed safe)
1808 {
1809         call_comm("global-set-command", ed, &display_ncurses, 0, NULL,
1810                   "attach-display-ncurses");
1811
1812         nc_map = key_alloc();
1813         key_add(nc_map, "window:refresh", &force_redraw);
1814         key_add(nc_map, "window:close", &nc_close_display);
1815         key_add(nc_map, "window:external-viewer", &nc_external_viewer);
1816         key_add(nc_map, "Close", &nc_close);
1817         key_add(nc_map, "Draw:clear", &nc_clear);
1818         key_add(nc_map, "Draw:text-size", &nc_text_size);
1819         key_add(nc_map, "Draw:text", &nc_draw_text);
1820
1821         key_add(nc_map, "Draw:image", &nc_draw_image);
1822         key_add(nc_map, "Draw:image-size", &nc_image_size);
1823
1824         key_add(nc_map, "Refresh:size", &nc_refresh_size);
1825         key_add(nc_map, "Refresh:postorder", &nc_refresh_post);
1826         key_add(nc_map, "Paste:get", &nc_get_paste);
1827         key_add(nc_map, "all-displays", &nc_notify_display);
1828         key_add(nc_map, "Sig:Winch", &handle_winch);
1829         key_add(nc_map, "Notify:Close", &nc_pane_close);
1830 }