]> git.neil.brown.name Git - edlib.git/blob - display-ncurses.c
display-ncurses: Improve attr parsing
[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         char                    *noclose;
68         struct col_hash         *col_hash;
69         int                     report_position;
70         long                    last_event;
71
72         bool                    did_close;
73         bool                    suspended;
74
75         struct buf              paste_buf;
76         time_t                  paste_start;
77         char                    *paste_latest;
78         int                     paste_pending;
79
80         struct pids {
81                 pid_t           pid;
82                 struct pids     *next;
83         }                       *pids;
84
85         char                    *rs1, *rs2, *rs3, *clear;
86         char                    attr_buf[1024];
87         #ifdef RECORD_REPLAY
88         FILE                    *log;
89         FILE                    *input;
90         int                     input_sleeping;
91         /* Sometimes I get duplicate Display lines, but not consistently.
92          * To avoid these, record last, filter repeats.
93          */
94         int                     last_cx, last_cy;
95         char                    last_screen[MD5_DIGEST_SIZE*2+1];
96         char                    next_screen[MD5_DIGEST_SIZE*2+1];
97         /* The next event to generate when idle */
98         enum { DoNil, DoMouse, DoKey, DoCheck, DoClose} next_event;
99         char                    event_info[30];
100         struct xy               event_pos;
101
102         int                     clears; /* counts of Draw:clear events */
103         #endif
104 };
105 #include "core-pane.h"
106
107 static SCREEN *current_screen;
108 static void ncurses_text(struct pane *p safe, struct pane *display safe,
109                          wchar_t ch, int attr, int pair,
110                          short x, short y, short cursor);
111 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home);
112 DEF_CMD(input_handle);
113 DEF_CMD(handle_winch);
114 static struct map *nc_map;
115 DEF_LOOKUP_CMD(ncurses_handle, nc_map);
116
117 static struct display_data *current_dd;
118 static void set_screen(struct pane *p)
119 {
120         struct display_data *dd;
121         extern void *_nc_globals[100];
122         int i;
123         static int index = -1, offset=0;
124
125         if (!p) {
126                 if (current_screen && index >= 0)
127                         _nc_globals[index] = NULL;
128                 current_screen = NULL;
129                 return;
130         }
131         dd = &p->data;
132         current_dd = dd;
133         if (!dd)
134                 return;
135         if (dd->scr == current_screen)
136                 return;
137
138         if (index == -1) {
139                 index = -2;
140                 for (i=0; i<100; i++)
141                         if (_nc_globals[i] < (void*)stdscr &&
142                             _nc_globals[i]+4*(sizeof(void*)) >= (void*)stdscr) {
143                                 /* This is _nc_windowlist */
144                                 index = i;
145                                 offset = ((void*)stdscr) - _nc_globals[i];
146                         }
147         }
148
149         set_term(dd->scr);
150         current_screen = dd->scr;
151         if (index >= 0) {
152                 _nc_globals[index] = ((void*)stdscr) - offset;
153         }
154 }
155
156 #ifdef RECORD_REPLAY
157 DEF_CMD(next_evt);
158
159 static bool parse_event(struct pane *p safe);
160 static bool prepare_recrep(struct pane *p safe)
161 {
162         struct display_data *dd = &p->data;
163         char *name;
164
165         name = getenv("EDLIB_RECORD");
166         if (name)
167                 dd->log = fopen(name, "w");
168         name = getenv("EDLIB_REPLAY");
169         if (name)
170                 dd->input = fopen(name, "r");
171         if (getenv("EDLIB_PAUSE"))
172                 sleep(atoi(getenv("EDLIB_PAUSE")));
173         if (dd->input) {
174                 parse_event(p);
175                 return True;
176         }
177         return False;
178 }
179
180 static void close_recrep(struct pane *p safe)
181 {
182         struct display_data *dd = &p->data;
183
184         if (dd->log) {
185                 fprintf(dd->log, "Close %d\n", dd->clears);
186                 fclose(dd->log);
187         }
188 }
189
190 static void record_key(struct pane *p safe, char *key safe)
191 {
192         struct display_data *dd = &p->data;
193         char q;
194
195         if (!dd->log)
196                 return;
197         if (!strchr(key, '"'))
198                 q = '"';
199         else if (!strchr(key, '\''))
200                 q = '\'';
201         else if (!strchr(key, '/'))
202                 q = '/';
203         else
204                 return;
205         fprintf(dd->log, "Key %c%s%c\n", q,key,q);
206         dd->last_cx = -2; /* Force next Display to be shown */
207         fflush(dd->log);
208 }
209
210 static void record_mouse(struct pane *p safe, char *key safe, int x, int y)
211 {
212         struct display_data *dd = &p->data;
213         char q;
214         if (!dd->log)
215                 return;
216         if (!strchr(key, '"'))
217                 q = '"';
218         else if (!strchr(key, '\''))
219                 q = '\'';
220         else if (!strchr(key, '/'))
221                 q = '/';
222         else
223                 return;
224         fprintf(dd->log, "Mouse %c%s%c %d,%d\n", q,key,q, x, y);
225         dd->last_cx = -2; /* Force next Display to be shown */
226         fflush(dd->log);
227 }
228
229 static void record_screen(struct pane *p safe)
230 {
231         struct display_data *dd = &p->data;
232         struct md5_state ctx;
233         uint16_t buf[CCHARW_MAX+5];
234         char out[MD5_DIGEST_SIZE*2+1];
235         int r,c;
236
237         if (!dd->log && !(dd->input && dd->next_event == DoCheck))
238                 return;
239         set_screen(p);
240         md5_init(&ctx);
241         for (r = 0; r < p->h; r++)
242                 for (c = 0; c < p->w; c++) {
243                         cchar_t cc;
244                         wchar_t wc[CCHARW_MAX+2];
245                         attr_t a;
246                         short color, fg, bg;
247                         int l;
248
249                         mvwin_wch(stdscr, r, c, &cc);
250                         getcchar(&cc, wc, &a, &color, NULL);
251                         pair_content(color, &fg, &bg);
252                         buf[0] = htole16(fg);
253                         buf[1] = htole16(bg);
254                         for (l = 0; l < CCHARW_MAX && wc[l]; l++)
255                                 buf[l+3] = htole16(wc[l]);
256                         buf[2] = htole16(l);
257                         LOG("%d,%d %d:%d:%d:%d %lc", c,r,fg,bg,l,wc[0], wc[0] > ' ' ? wc[0] : '?');
258                         md5_update(&ctx, (uint8_t*)buf,
259                                    (l+3) * sizeof(uint16_t));
260                 }
261         md5_final_txt(&ctx, out);
262         if (strcmp(out, dd->last_screen) == 0 &&
263              p->cx == dd->last_cx && p->cy == dd->last_cy) {
264                 /* No  change - filter it */
265                 dd->clears -= 1;
266         } else if (dd->log) {
267                 fprintf(dd->log, "Display %d,%d %s", p->w, p->h, out);
268                 if (p->cx >= 0)
269                         fprintf(dd->log, " %d,%d", p->cx, p->cy);
270                 fprintf(dd->log, "\n");
271                 fflush(dd->log);
272                 strcpy(dd->last_screen, out);
273                 dd->last_cx = p->cx; dd->last_cy = p->cy;
274         }
275         if (dd->input && dd->input_sleeping) {
276                 char *delay = getenv("EDLIB_REPLAY_DELAY");
277                 call_comm("event:free", p, &next_evt);
278                 if (delay)
279                         call_comm("event:timer", p, &next_evt, atoi(delay));
280                 else
281                         call_comm("event:on-idle", p, &next_evt);
282         }
283 }
284
285 static char *copy_quote(char *line safe, char *buf safe)
286 {
287         char q;
288         while (*line == ' ')
289                 line++;
290         q = *line++;
291         if (q != '"' && q != '\'' && q != '/')
292                 return NULL;
293         while (*line != q && *line)
294                 *buf++ = *line++;
295         if (!*line)
296                 return NULL;
297         *buf = '\0';
298         return line+1;
299 }
300
301 static char *get_coord(char *line safe, struct xy *co safe)
302 {
303         long v;
304         char *ep;
305
306         while (*line == ' ')
307                 line ++;
308         v = strtol(line, &ep, 10);
309         if (!ep || ep == line || *ep != ',')
310                 return NULL;
311         co->x = v;
312         line = ep+1;
313         v = strtol(line, &ep, 10);
314         if (!ep || ep == line || (*ep && *ep != ' ' && *ep != '\n'))
315                 return NULL;
316         co->y = v;
317         return ep;
318 }
319
320 static char *get_hash(char *line safe, hash_t hash safe)
321 {
322         int i;
323         while (*line == ' ')
324                 line++;
325         for (i = 0; i < MD5_DIGEST_SIZE*2 && *line; i++)
326                 hash[i] = *line++;
327         if (!*line)
328                 return NULL;
329         return line;
330 }
331
332 static bool parse_event(struct pane *p safe)
333 {
334         struct display_data *dd = &p->data;
335         char line[80];
336
337         line[79] = 0;
338         dd->next_event = DoNil;
339         if (!dd->input ||
340             fgets(line, sizeof(line)-1, dd->input) == NULL)
341                 line[0]=0;
342         else if (strstarts(line, "Key ")) {
343                 if (!copy_quote(line+4, dd->event_info))
344                         return False;
345                 dd->next_event = DoKey;
346         } else if (strstarts(line, "Mouse ")) {
347                 char *f = copy_quote(line+6, dd->event_info);
348                 if (!f)
349                         return False;
350                 f = get_coord(f, &dd->event_pos);
351                 if (!f)
352                         return False;
353                 dd->next_event = DoMouse;
354         } else if (strstarts(line, "Display ")) {
355                 char *f = get_coord(line+8, &dd->event_pos);
356                 if (!f)
357                         return False;
358                 f = get_hash(f, dd->next_screen);
359                 dd->next_event = DoCheck;
360         } else if (strstarts(line, "Close")) {
361                 dd->next_event = DoClose;
362         }
363         LOG("parse %s", line);
364
365         dd->input_sleeping = 1;
366         if (dd->next_event != DoCheck) {
367                 char *delay = getenv("EDLIB_REPLAY_DELAY");
368                 if (delay)
369                         call_comm("event:timer", p, &next_evt, atoi(delay));
370                 else
371                         call_comm("event:on-idle", p, &next_evt);
372         } else
373                 call_comm("event:timer", p, &next_evt, 10*1000);
374         return True;
375 }
376
377 REDEF_CMD(next_evt)
378 {
379         struct pane *p = ci->home;
380         struct display_data *dd = &p->data;
381         int button = 0, type = 0;
382
383         dd->input_sleeping = 0;
384         switch(dd->next_event) {
385         case DoKey:
386                 record_key(p, dd->event_info);
387                 call("Keystroke", p, 0, NULL, dd->event_info);
388                 break;
389         case DoMouse:
390                 record_mouse(p, dd->event_info, dd->event_pos.x,
391                              dd->event_pos.y);
392                 if (strstr(dd->event_info, ":Press"))
393                         type = 1;
394                 else if (strstr(dd->event_info, ":Release"))
395                         type = 2;
396                 else if (strstr(dd->event_info, ":Motion"))
397                         type = 3;
398                 if (type == 1 || type == 2) {
399                         char *e = dd->event_info + strlen(dd->event_info) - 1;
400                         button = atoi(e);
401                 }
402                 call("Mouse-event", p, button, NULL, dd->event_info,
403                      type, NULL, NULL,
404                      dd->event_pos.x, dd->event_pos.y);
405                 break;
406         case DoCheck:
407                 /* No point checking, just do a diff against new trace log. */
408                 /* not; (strcmp(dd->next_screen, dd->last_screen) != 0) */
409                 break;
410         case DoClose:
411                 call("event:deactivate", p);
412                 pane_close(p);
413                 return 1;
414         case DoNil:
415                 call_comm("event:read", p, &input_handle, 0);
416                 call_comm("event:signal", p, &handle_winch, SIGWINCH);
417                 return 1;
418         }
419         parse_event(p);
420         return 1;
421 }
422 #else
423 static inline bool  prepare_recrep(struct pane *p safe) {return False;}
424 static inline void record_key(struct pane *p safe, char *key) {}
425 static inline void record_mouse(struct pane *p safe, char *key safe,
426                                 int x, int y) {}
427 static inline void record_screen(struct pane *p safe) {}
428 static inline void close_recrep(struct pane *p safe) {}
429 #endif
430
431 DEF_CB(cnt_disp)
432 {
433         struct call_return *cr = container_of(ci->comm, struct call_return, c);
434
435         cr->i += 1;
436         return 1;
437 }
438
439 static void ncurses_end(struct pane *p safe);
440
441 DEF_CMD(nc_close_display)
442 {
443         /* If this is only display, then refuse to close this one */
444         struct call_return cr;
445         struct display_data *dd = &ci->home->data;
446
447         if (dd->noclose) {
448                 call("Message", ci->focus, 0, NULL, dd->noclose);
449                 return 1;
450         }
451
452         cr.c = cnt_disp;
453         cr.i = 0;
454         call_comm("editor:notify:all-displays", ci->focus, &cr.c);
455         if (cr.i > 1) {
456                 /* Need to call ncurses_end() before we send a Notify:Close
457                  * notification, else server exits too early
458                  */
459                 ncurses_end(ci->home);
460                 return Efallthrough;
461         } else
462                 call("Message", ci->focus, 0, NULL,
463                      "Cannot close only window.");
464         return 1;
465 }
466
467 DEF_CMD(nc_set_noclose)
468 {
469         struct display_data *dd = &ci->home->data;
470
471         free(dd->noclose);
472         dd->noclose = NULL;
473         if (ci->str)
474                 dd->noclose = strdup(ci->str);
475         return 1;
476 }
477
478 static int nc_putc(int ch)
479 {
480         if (current_dd)
481                 fputc(ch, current_dd->scr_file);
482         return 1;
483 }
484
485 static char *fnormalize(struct pane *p safe, const char *str) safe
486 {
487         char *ret = strsave(p, str);
488         char *cp;
489
490         for (cp = ret ; cp && *cp ; cp++)
491                 if (!isalnum(*cp) &&
492                     !strchr("/_-+=.,@#", *cp))
493                         /* Don't like this char */
494                         *cp = '_';
495         return ret ?: "_";
496 }
497
498 static void wait_for(struct display_data *dd safe)
499 {
500         struct pids **pp = &dd->pids;
501
502         while (*pp) {
503                 struct pids *p = *pp;
504                 if (waitpid(p->pid, NULL, WNOHANG) > 0) {
505                         *pp = p->next;
506                         free(p);
507                 } else
508                         pp = &p->next;
509         }
510 }
511
512 DEF_CB(ns_resume)
513 {
514         struct display_data *dd = &ci->home->data;
515
516         if (dd->suspended) {
517                 dd->suspended = False;
518                 set_screen(ci->home);
519                 doupdate();
520         }
521         return 1;
522 }
523
524 DEF_CMD(nc_external_viewer)
525 {
526         struct pane *p = ci->home;
527         struct display_data *dd = &p->data;
528         char *disp = pane_attr_get(p, "DISPLAY");
529         char *disp_auth = pane_attr_get(p, "XAUTHORITY");
530         char *remote = pane_attr_get(p, "REMOTE_SESSION");
531         char *fqdn = NULL;
532         const char *path = ci->str;
533         int pid;
534         char buf[100];
535         int n;
536         int fd;
537
538         if (!path)
539                 return Enoarg;
540         if (disp && *disp) {
541                 struct pids *pds;
542                 switch (pid = fork()) {
543                 case -1:
544                         return Efail;
545                 case 0: /* Child */
546                         setenv("DISPLAY", disp, 1);
547                         if (disp_auth)
548                                 setenv("XAUTHORITY", disp_auth, 1);
549                         fd = open("/dev/null", O_RDWR);
550                         if (fd) {
551                                 dup2(fd, 0);
552                                 dup2(fd, 1);
553                                 dup2(fd, 2);
554                                 if (fd > 2)
555                                         close(fd);
556                         }
557                         execlp("xdg-open", "xdg-open", path, NULL);
558                         exit(1);
559                 default: /* parent */
560                         pds = malloc(sizeof(*pds));
561                         pds->pid = pid;
562                         pds->next = dd->pids;
563                         dd->pids = pds;
564                         break;
565                 }
566                 wait_for(dd);
567                 return 1;
568         }
569         /* handle no-display case */
570         if (remote && strcmp(remote, "yes") == 0 &&
571             path[0] == '/' &&
572             gethostname(buf, sizeof(buf)) == 0) {
573                 struct addrinfo *res;
574                 const struct addrinfo hints = {
575                         .ai_flags = AI_CANONNAME,
576                 };
577                 if (getaddrinfo(buf, NULL, &hints, &res) == 0 &&
578                     res && res->ai_canonname)
579                         fqdn = strdup(res->ai_canonname);
580                 freeaddrinfo(res);
581         }
582         set_screen(p);
583         n = 0;
584         ioctl(fileno(dd->scr_file), FIONREAD, &n);
585         if (n)
586                 n -= read(fileno(dd->scr_file), buf,
587                           n <= (int)sizeof(buf) ? n : (int)sizeof(buf));
588         endwin();
589         /* stay in raw mode */
590         raw();
591         noecho();
592
593         /* Endwin doesn't seem to reset properly, at least on xfce-terminal.
594          * So do it manually
595          */
596         if (dd->rs1)
597                 tputs(dd->rs1, 1, nc_putc);
598         if (dd->rs2)
599                 tputs(dd->rs2, 1, nc_putc);
600         if (dd->rs3)
601                 tputs(dd->rs3, 1, nc_putc);
602         if (dd->clear)
603                 tputs(dd->clear, 1, nc_putc);
604         fflush(dd->scr_file);
605
606         fprintf(dd->scr_file, "# Consider copy-pasting following\r\n");
607         if (fqdn && path[0] == '/') {
608                 /* File will not be local for the user, so help them copy it. */
609                 const char *tmp = fnormalize(p, ci->str2 ?: "XXXXXX");
610                 const char *fname = fnormalize(p, ci->str);
611
612                 if (strcmp(fname, ci->str) != 0)
613                         /* file name had unusuable chars, need to create safe name */
614                         link(ci->str, fname);
615                 fprintf(dd->scr_file, "f=`mktemp --tmpdir %s`; scp %s:%s $f ; ",
616                         tmp, fqdn, fname);
617                 path = "$f";
618         }
619         free(fqdn);
620         fprintf(dd->scr_file, "xdg-open %s\r\n", path);
621         fprintf(dd->scr_file, "# Press Enter to continue\r\n");
622         dd->suspended = True;
623         call_comm("event:timer", p, &ns_resume, 30*1000);
624         return 1;
625 }
626
627 static void ncurses_stop(struct pane *p safe)
628 {
629         struct display_data *dd = &p->data;
630
631         if (dd->is_xterm) {
632                 /* disable bracketed-paste */
633                 fprintf(dd->scr_file, "\033[?2004l");
634                 fflush(dd->scr_file);
635         }
636         if (dd->paste_start)
637                 free(buf_final(&dd->paste_buf));
638         dd->paste_start = 0;
639         free(dd->paste_latest);
640         dd->paste_latest = NULL;
641         nl();
642         endwin();
643         if (dd->rs1)
644                 tputs(dd->rs1, 1, nc_putc);
645         if (dd->rs2)
646                 tputs(dd->rs2, 1, nc_putc);
647         if (dd->rs3)
648                 tputs(dd->rs3, 1, nc_putc);
649         fflush(dd->scr_file);
650 }
651
652 static void ncurses_end(struct pane *p safe)
653 {
654         struct display_data *dd = &p->data;
655
656         if (dd->did_close)
657                 return;
658         dd->did_close = True;
659         set_screen(p);
660         close_recrep(p);
661
662         ncurses_stop(p);
663 }
664
665 /*
666  * hash table for colours and pairs
667  * key is r,g,b (0-1000) in 10bit fields,
668  * or fg,bg in 16 bit fields with bit 31 set
669  * content is colour number of colour pair number.
670  * We never delete entries, unless we delete everything.
671  */
672
673 struct chash {
674         struct chash *next;
675         int key, content;
676 };
677 #define COL_KEY(r,g,b) ((r<<20) | (g<<10) | b)
678 #define PAIR_KEY(fg, bg) ((1<<31) | (fg<<16) | bg)
679 #define hash_key(k) ((((k) * 0x61C88647) >> 20) & 0xff)
680
681 struct col_hash {
682         int next_col, next_pair;
683         struct chash *tbl[256];
684 };
685
686 static struct col_hash *safe hash_init(struct display_data *dd safe)
687 {
688         if (!dd->col_hash) {
689                 dd->col_hash = malloc(sizeof(*dd->col_hash));
690                 memset(dd->col_hash, 0, sizeof(*dd->col_hash));
691                 dd->col_hash->next_col = 16;
692                 dd->col_hash->next_pair = 1;
693         }
694         return dd->col_hash;
695 }
696
697 static void hash_free(struct display_data *dd safe)
698 {
699         int h;
700         struct chash *c;
701         struct col_hash *ch;
702
703         ch = dd->col_hash;
704         if (!ch)
705                 return;
706         for (h = 0; h < 255; h++)
707                 while ((c = ch->tbl[h]) != NULL) {
708                         ch->tbl[h] = c->next;
709                         free(c);
710                 }
711         free(ch);
712         dd->col_hash = NULL;
713 }
714
715 static int find_col(struct display_data *dd safe, int rgb[])
716 {
717         if (0 /* dynamic colours */) {
718                 struct col_hash *ch = hash_init(dd);
719                 int k = COL_KEY(rgb[0], rgb[1], rgb[2]);
720                 int h = hash_key(k);
721                 struct chash *c;
722
723                 for (c = ch->tbl[h]; c; c = c->next)
724                         if (c->key == k)
725                                 return c->content;
726                 c = malloc(sizeof(*c));
727                 c->key = k;
728                 c->content = ch->next_col++;
729                 c->next = ch->tbl[h];
730                 ch->tbl[h] = c;
731                 init_color(c->content, rgb[0], rgb[1], rgb[2]);
732                 return c->content;
733         } else {
734                 /* If colour is grey, map to 1 of 26 for 0, 232 to 255, 15
735                  * The 24 grey shades have bit values from 8 to 238, so the
736                  * gap to white is a little bigger, but that probably doesn't
737                  * matter.
738                  * Otherwise map to 6x6x6 rgb cube from 16
739                  * Actual colours are biased bright, at 0,95,135,175,215,255
740                  * with a 95 gap at bottom and 40 elsewhere.
741                  * So we divide 5 and 2 half ranges, and merge bottom 2.
742                  */
743                 int c = 0;
744                 int h;
745
746                 //printf("want %d,%d,%d\n", rgb[0], rgb[1], rgb[2]);
747                 if (abs(rgb[0] - rgb[1]) < 10 &&
748                     abs(rgb[1] - rgb[2]) < 10) {
749                         /* grey - within 1% */
750                         int v = (rgb[0] + rgb[1] + rgb[2]) / 3;
751
752                         /* We divide the space in 24 ranges surrounding
753                          * the grey values, and 2 half-ranges near black
754                          * and white.  So add half a range - 1000/50 -
755                          * then divide by 1000/25 to get a number from 0 to 25.
756                          */
757                         v = (v + 1000/50) / (1000/25);
758                         if (v == 0)
759                                 return 0; /* black */
760                         if (v >= 25)
761                                 return 15; /* white */
762                         //printf(" grey %d\n", v + 231);
763                         /* grey shades are from 232 to 255 inclusive */
764                         return v + 231;
765                 }
766                 for (h = 0; h < 3; h++) {
767                         int v = rgb[h];
768
769                         v = (v + 1000/12) / (1000/6);
770                         /* v is from 0 to 6, we want up to 5
771                          * with 0 and 1 merged
772                          */
773                         if (v)
774                                 v -= 1;
775
776                         c = c * 6 + v;
777                 }
778                 //printf(" color %d\n", c + 16);
779                 return c + 16;
780         }
781 }
782
783 static int to_pair(struct display_data *dd safe, int fg, int bg)
784 {
785         struct col_hash *ch = hash_init(dd);
786         int k = PAIR_KEY(fg, bg);
787         int h = hash_key(k);
788         struct chash *c;
789
790         for (c = ch->tbl[h]; c; c = c->next)
791                 if (c->key == k)
792                         return c->content;
793         c = malloc(sizeof(*c));
794         c->key = k;
795         c->content = ch->next_pair++;
796         c->next = ch->tbl[h];
797         ch->tbl[h] = c;
798         init_pair(c->content, fg, bg);
799         return c->content;
800 }
801
802 static int cvt_attrs(struct pane *p safe, struct pane *home safe,
803                      const char *attrs, int *pairp safe, bool use_parent)
804 {
805         struct display_data *dd = &home->data;
806         int attr = 0;
807         const char *a, *v;
808         char *col = NULL;
809         PANEL *pan = NULL;
810         int fg = COLOR_BLACK;
811         int bg = COLOR_WHITE+8;
812
813         set_screen(home);
814         do {
815                 p = p->parent;
816         } while (p->parent != p &&(pan = pane_panel(p, NULL)) == NULL);
817         if (pan && use_parent) {
818                 /* Get 'default colours for this pane - set at clear */
819                 int at = getbkgd(panel_window(pan));
820                 int pair = PAIR_NUMBER(at);
821                 short dfg, dbg;
822                 pair_content(pair, &dfg, &dbg);
823                 if (dfg >= 0)
824                         fg = dfg;
825                 if (dbg >= 0)
826                         bg = dbg;
827         }
828
829         foreach_attr(a, v, attrs, NULL) {
830                 if (amatch(a, "inverse"))
831                         attr |= A_STANDOUT;
832                 else if (amatch(a, "noinverse"))
833                         attr &= ~A_STANDOUT;
834                 else if (amatch(a, "bold"))
835                         attr |= A_BOLD;
836                 else if (amatch(a, "nobold"))
837                         attr &= ~A_BOLD;
838                 else if (amatch(a, "underline"))
839                         attr |= A_UNDERLINE;
840                 else if (amatch(a, "nounderline"))
841                         attr &= ~A_UNDERLINE;
842                 else if (amatch(a, "fg") && v) {
843                         struct call_return cr =
844                                 call_ret(all, "colour:map", home,
845                                          0, NULL, aupdate(&col, v));
846                         int rgb[3] = {cr.i, cr.i2, cr.x};
847                         fg = find_col(dd, rgb);
848                 } else if (amatch(a, "bg") && v) {
849                         struct call_return cr =
850                                 call_ret(all, "colour:map", home,
851                                          0, NULL, aupdate(&col, v));
852                         int rgb[3] = {cr.i, cr.i2, cr.x};
853                         bg = find_col(dd, rgb);
854                 }
855         }
856         free(col);
857         if (fg != COLOR_BLACK || bg != COLOR_WHITE+8)
858                 *pairp = to_pair(dd, fg, bg);
859         return attr;
860 }
861
862 static int make_cursor(int attr)
863 {
864         return attr ^ A_UNDERLINE;
865 }
866
867 DEF_CMD(nc_notify_display)
868 {
869         struct display_data *dd = &ci->home->data;
870         comm_call(ci->comm2, "callback:display", ci->home, dd->last_event);
871         return 1;
872 }
873
874 DEF_CMD(nc_close)
875 {
876         struct pane *p = ci->home;
877         struct display_data *dd = &p->data;
878         ncurses_end(p);
879         hash_free(dd);
880         fclose(dd->scr_file);
881         return 1;
882 }
883
884 DEF_CMD(nc_pane_close)
885 {
886         PANEL *pan = NULL;
887
888         set_screen(ci->home);
889         while ((pan = panel_above(pan)) != NULL)
890                 if (panel_userptr(pan) == ci->focus)
891                         break;
892         if (pan) {
893                 WINDOW *win = panel_window(pan);
894                 del_panel(pan);
895                 delwin(win);
896                 pane_damaged(ci->home, DAMAGED_POSTORDER);
897         }
898         return 1;
899 }
900
901 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home)
902 {
903         PANEL *pan = NULL;
904
905         while ((pan = panel_above(pan)) != NULL)
906                 if (panel_userptr(pan) == p)
907                         return pan;
908
909         if (!home)
910                 return pan;
911
912         pan = new_panel(newwin(p->h, p->w, 0, 0));
913         set_panel_userptr(pan, p);
914         pane_add_notify(home, p, "Notify:Close");
915
916         return pan;
917 }
918
919 DEF_CMD(nc_clear)
920 {
921         struct pane *p = ci->home;
922         struct display_data *dd = &p->data;
923         cchar_t cc = {};
924         int pair = 0;
925         int attr = cvt_attrs(ci->focus, p, ci->str, &pair, ci->str == NULL);
926         PANEL *panel;
927         WINDOW *win;
928         int w, h;
929
930         set_screen(p);
931         panel = pane_panel(ci->focus, p);
932         if (!panel)
933                 return Efail;
934         win = panel_window(panel);
935         getmaxyx(win, h, w);
936         if (h != ci->focus->h || w != ci->focus->w) {
937                 wresize(win, ci->focus->h, ci->focus->w);
938                 replace_panel(panel, win);
939         }
940         cc.attr = attr;
941         cc.ext_color = pair;
942         cc.chars[0] = ' ';
943         wbkgrndset(win, &cc);
944         werase(win);
945         dd->clears += 1;
946
947         pane_damaged(p, DAMAGED_POSTORDER);
948         return 1;
949 }
950
951 DEF_CMD(nc_text_size)
952 {
953         int max_space = ci->num;
954         int max_bytes = 0;
955         int size = 0;
956         const char *str = ci->str;
957
958         if (!str)
959                 return Enoarg;
960         while (str[0] != 0) {
961                 wint_t wc = get_utf8(&str, NULL);
962                 int width;
963                 if (wc >= WERR)
964                         break;
965                 width = wcwidth(wc);
966                 if (width < 0)
967                         break;
968                 size += width;
969                 if (size <= max_space)
970                         max_bytes = str - ci->str;
971         }
972         return comm_call(ci->comm2, "callback:size", ci->focus,
973                          max_bytes, NULL, NULL,
974                          0, NULL, NULL, size, 1);
975 }
976
977 DEF_CMD(nc_draw_text)
978 {
979         struct pane *p = ci->home;
980         int pair = 0;
981         int attr = cvt_attrs(ci->focus, p, ci->str2, &pair, True);
982         int cursor_offset = ci->num;
983         short x = ci->x, y = ci->y;
984         const char *str = ci->str;
985
986         if (!str)
987                 return Enoarg;
988         set_screen(p);
989         while (str[0] != 0) {
990                 int precurs = str <= ci->str + cursor_offset;
991                 wint_t wc = get_utf8(&str, NULL);
992                 int width;
993                 if (wc == WEOF || wc == WERR)
994                         break;
995                 width = wcwidth(wc);
996                 if (width < 0)
997                         break;
998                 if (precurs && str > ci->str + cursor_offset)
999                         ncurses_text(ci->focus, p, wc, attr, pair, x, y, 1);
1000                 else
1001                         ncurses_text(ci->focus, p, wc, attr, pair, x, y, 0);
1002                 x += width;
1003         }
1004         if (str == ci->str + cursor_offset)
1005                 ncurses_text(ci->focus, p, ' ', 0, 0, x, y, 1);
1006         pane_damaged(p, DAMAGED_POSTORDER);
1007         return 1;
1008 }
1009
1010 DEF_CMD(nc_draw_image)
1011 {
1012         /* 'str' identifies the image. Options are:
1013          *     file:filename  - load file from fs
1014          *     comm:command   - run command collecting bytes
1015          * 'num' is '16' if image should be stretched to fill pane
1016          * Otherwise it is the 'or' of
1017          *   0,1,2 for left/middle/right in x direction
1018          *   0,4,8 for top/middle/bottom in y direction
1019          * only one of these can be used as image will fill pane
1020          * in other direction.
1021          * If 'x' and 'y' are both positive, draw cursor box at
1022          * p->cx, p->cy of a size so that 'x' will fit across and
1023          * 'y' will fit down.
1024          */
1025         struct pane *p = ci->home;
1026         struct display_data *dd = &p->data;
1027         int x = 0, y = 0;
1028         bool stretch = ci->num & 16;
1029         int pos = ci->num;
1030         int w = ci->focus->w, h = ci->focus->h * 2;
1031         int cx = -1, cy = -1;
1032         MagickBooleanType status;
1033         MagickWand *wd;
1034         unsigned char *buf;
1035         int i, j;
1036
1037         if (!ci->str)
1038                 return Enoarg;
1039         if (strstarts(ci->str, "file:")) {
1040                 wd = NewMagickWand();
1041                 status = MagickReadImage(wd, ci->str + 5);
1042                 if (status == MagickFalse) {
1043                         DestroyMagickWand(wd);
1044                         return Efail;
1045                 }
1046         } else if (strstarts(ci->str, "comm:")) {
1047                 struct call_return cr;
1048                 wd = NewMagickWand();
1049                 cr = call_ret(bytes, ci->str+5, ci->focus, 0, NULL, ci->str2);
1050                 if (!cr.s) {
1051                         DestroyMagickWand(wd);
1052                         return Efail;
1053                 }
1054                 status = MagickReadImageBlob(wd, cr.s, cr.i);
1055                 free(cr.s);
1056                 if (status == MagickFalse) {
1057                         DestroyMagickWand(wd);
1058                         return Efail;
1059                 }
1060         } else
1061                 return Einval;
1062
1063         MagickAutoOrientImage(wd);
1064         if (!stretch) {
1065                 int ih = MagickGetImageHeight(wd);
1066                 int iw = MagickGetImageWidth(wd);
1067
1068                 if (iw <= 0 || iw <= 0) {
1069                         DestroyMagickWand(wd);
1070                         return Efail;
1071                 }
1072                 if (iw * h > ih * w) {
1073                         /* Image is wider than space, use less height */
1074                         ih = ih * w / iw;
1075                         switch(pos & (8+4)) {
1076                         case 4: /* center */
1077                                 y = (h - ih) / 2; break;
1078                         case 8: /* bottom */
1079                                 y = h - ih; break;
1080                         }
1081                         /* Keep 'h' even! */
1082                         h = ((ih+1)/2) * 2;
1083                 } else {
1084                         /* image is too tall, use less width */
1085                         iw = iw * h / ih;
1086                         switch (pos & (1+2)) {
1087                         case 1: /* center */
1088                                 x = (w - iw) / 2; break;
1089                         case 2: /* right */
1090                                 x = w - iw ; break;
1091                         }
1092                         w = iw;
1093                 }
1094         }
1095         MagickAdaptiveResizeImage(wd, w, h);
1096         buf = malloc(h * w * 4);
1097         MagickExportImagePixels(wd, 0, 0, w, h, "RGBA", CharPixel, buf);
1098
1099         if (ci->x > 0 && ci->y > 0 && ci->focus->cx >= 0) {
1100                 /* We want a cursor */
1101                 cx = x + ci->focus->cx;
1102                 cy = y + ci->focus->cy;
1103         }
1104         for (i = 0; i < h; i+= 2) {
1105                 static const wint_t hilo = 0x2580; /* L'▀' */
1106                 for (j = 0; j < w ; j+= 1) {
1107                         unsigned char *p1 = buf + i*w*4 + j*4;
1108                         unsigned char *p2 = buf + (i+1)*w*4 + j*4;
1109                         int rgb1[3] = { p1[0]*1000/255, p1[1]*1000/255, p1[2]*1000/255 };
1110                         int rgb2[3] = { p2[0]*1000/255, p2[1]*1000/255, p2[2]*1000/255 };
1111                         int fg = find_col(dd, rgb1);
1112                         int bg = find_col(dd, rgb2);
1113
1114                         if (p1[3] < 128 || p2[3] < 128) {
1115                                 /* transparent */
1116                                 cchar_t cc;
1117                                 short f,b;
1118                                 struct pane *pn2 = ci->focus;
1119                                 PANEL *pan = pane_panel(pn2, NULL);
1120
1121                                 while (!pan && pn2->parent != pn2) {
1122                                         pn2 = pn2->parent;
1123                                         pan = pane_panel(pn2, NULL);
1124                                 }
1125                                 if (pan) {
1126                                         wgetbkgrnd(panel_window(pan), &cc);
1127                                         if (cc.ext_color == 0)
1128                                                 /* default.  This is light
1129                                                  * gray rather then white,
1130                                                  * but I think it is a good
1131                                                  * result.
1132                                                  */
1133                                                 b = COLOR_WHITE;
1134                                         else
1135                                                 pair_content(cc.ext_color, &f, &b);
1136                                         if (p1[3] < 128)
1137                                                 fg = b;
1138                                         if (p2[3] < 128)
1139                                                 bg = b;
1140                                 }
1141                         }
1142                         /* FIXME this doesn't work because
1143                          * render-line knows too much and gets it wrong.
1144                          */
1145                         if (cx == x+j && cy == y + (i/2))
1146                                 ncurses_text(ci->focus, p, 'X', 0,
1147                                              to_pair(dd, 0, 0),
1148                                              x+j, y+(i/2), 1);
1149                         else
1150                                 ncurses_text(ci->focus, p, hilo, 0,
1151                                              to_pair(dd, fg, bg),
1152                                              x+j, y+(i/2), 0);
1153
1154                 }
1155         }
1156         free(buf);
1157
1158         DestroyMagickWand(wd);
1159
1160         pane_damaged(ci->home, DAMAGED_POSTORDER);
1161
1162         return 1;
1163 }
1164
1165 DEF_CMD(nc_image_size)
1166 {
1167         MagickBooleanType status;
1168         MagickWand *wd;
1169         int ih, iw;
1170
1171         if (!ci->str)
1172                 return Enoarg;
1173         if (strstarts(ci->str, "file:")) {
1174                 wd = NewMagickWand();
1175                 status = MagickReadImage(wd, ci->str + 5);
1176                 if (status == MagickFalse) {
1177                         DestroyMagickWand(wd);
1178                         return Efail;
1179                 }
1180         } else if (strstarts(ci->str, "comm:")) {
1181                 struct call_return cr;
1182                 wd = NewMagickWand();
1183                 cr = call_ret(bytes, ci->str+5, ci->focus, 0, NULL, ci->str2);
1184                 if (!cr.s) {
1185                         DestroyMagickWand(wd);
1186                         return Efail;
1187                 }
1188                 status = MagickReadImageBlob(wd, cr.s, cr.i);
1189                 free(cr.s);
1190                 if (status == MagickFalse) {
1191                         DestroyMagickWand(wd);
1192                         return Efail;
1193                 }
1194         } else
1195                 return Einval;
1196
1197         MagickAutoOrientImage(wd);
1198         ih = MagickGetImageHeight(wd);
1199         iw = MagickGetImageWidth(wd);
1200
1201         DestroyMagickWand(wd);
1202         comm_call(ci->comm2, "callback:size", ci->focus,
1203                   0, NULL, NULL, 0, NULL, NULL,
1204                   iw, ih);
1205         return 1;
1206 }
1207
1208 DEF_CMD(nc_refresh_size)
1209 {
1210         struct pane *p = ci->home;
1211
1212         set_screen(p);
1213         getmaxyx(stdscr, p->h, p->w);
1214         clearok(curscr, 1);
1215         return 0;
1216 }
1217
1218 DEF_CMD(nc_refresh_post)
1219 {
1220         struct pane *p = ci->home;
1221         struct display_data *dd = &p->data;
1222         struct pane *p1;
1223         PANEL *pan, *pan2;
1224
1225         if (dd->suspended)
1226                 return 1;
1227
1228         set_screen(p);
1229
1230         /* Need to ensure stacking order and panel y,x position
1231          * is correct.  FIXME it would be good if we could skip this
1232          * almost always.
1233          */
1234         pan = panel_above(NULL);
1235         if (!pan)
1236                 return 1;
1237         p1 = (struct pane*) panel_userptr(pan);
1238         for (;(pan2 = panel_above(pan)) != NULL; pan = pan2) {
1239                 struct pane *p2 = (struct pane*)panel_userptr(pan2);
1240                 p1 = (struct pane*)panel_userptr(pan);
1241                 if (!p1 || !p2)
1242                         continue;
1243
1244                 if (p1->abs_z < p2->abs_z)
1245                         continue;
1246                 if (p1->abs_z == p2->abs_z &&
1247                     p1->z <= p2->z)
1248                         continue;
1249                 /* pan needs to be above pan2.  All we can do is move it to
1250                  * the top. Anything that needs to be above it will eventually
1251                  * be pushed up too.
1252                  */
1253                 top_panel(pan);
1254                 /* Now the panel below pan might need to be over pan2 too... */
1255                 pan = panel_below(pan2);
1256                 if (pan)
1257                         pan2 = pan;
1258         }
1259
1260         /* As we need to crop pane against their parents, we cannot simply
1261          * use update_panels().  Instead we copy each to stdscr and refresh
1262          * that.
1263          */
1264         for (pan = NULL; (pan = panel_above(pan)) != NULL; ) {
1265                 WINDOW *win;
1266                 struct xy src, dest, destend;
1267                 int w, h;
1268
1269                 p1 = (void*)panel_userptr(pan);
1270                 if (!p1)
1271                         continue;
1272                 dest = pane_mapxy(p1, p, 0, 0, True);
1273                 destend = pane_mapxy(p1, p, p1->w, p1->h, True);
1274                 src = pane_mapxy(p1, p, 0, 0, False);
1275                 src.x = dest.x - src.x;
1276                 src.y = dest.y - src.y;
1277                 win = panel_window(pan);
1278                 getmaxyx(win, h, w);
1279                 /* guard again accessing beyond boundary of win */
1280                 if (destend.x > dest.x + (w - src.x))
1281                         destend.x = dest.x + (w - src.x);
1282                 if (destend.y > dest.y + (h - src.y))
1283                         destend.y = dest.y - (h - src.y);
1284                 copywin(win, stdscr, src.y, src.x,
1285                         dest.y, dest.x, destend.y-1, destend.x-1, 0);
1286         }
1287         /* place the cursor */
1288         p1 = pane_leaf(p);
1289         pan = NULL;
1290         while (p1 != p && (pan = pane_panel(p1, NULL)) == NULL)
1291                 p1 = p1->parent;
1292         if (pan && p1->cx >= 0) {
1293                 struct xy curs = pane_mapxy(p1, p, p1->cx, p1->cy, False);
1294                 wmove(stdscr, curs.y, curs.x);
1295         } else if (p->cx >= 0)
1296                 wmove(stdscr, p->cy, p->cx);
1297         refresh();
1298         record_screen(ci->home);
1299         return 1;
1300 }
1301
1302 static void ncurses_start(struct pane *p safe)
1303 {
1304         struct display_data *dd = &p->data;
1305         int rows, cols;
1306
1307         start_color();
1308         use_default_colors();
1309         raw();
1310         noecho();
1311         nonl();
1312         timeout(0);
1313         set_escdelay(100);
1314         intrflush(stdscr, FALSE);
1315         keypad(stdscr, TRUE);
1316         mousemask(BUTTON1_PRESSED | BUTTON1_RELEASED |
1317                   BUTTON2_PRESSED | BUTTON2_RELEASED |
1318                   BUTTON3_PRESSED | BUTTON3_RELEASED |
1319                   BUTTON4_PRESSED | BUTTON4_RELEASED |
1320                   BUTTON5_PRESSED | BUTTON5_RELEASED |
1321                   BUTTON_CTRL | BUTTON_SHIFT | BUTTON_ALT |
1322                   REPORT_MOUSE_POSITION, NULL);
1323         mouseinterval(0);
1324         if (dd->is_xterm) {
1325                 /* Enable bracketed-paste */
1326                 fprintf(dd->scr_file, "\033[?2004h");
1327                 fflush(dd->scr_file);
1328         }
1329
1330         getmaxyx(stdscr, rows, cols);
1331         pane_resize(p, 0, 0, cols, rows);
1332 }
1333
1334 static struct pane *ncurses_init(struct pane *ed safe,
1335                                  const char *tty, const char *term)
1336 {
1337         SCREEN *scr;
1338         struct pane *p;
1339         struct display_data *dd;
1340         char *area;
1341         FILE *f;
1342
1343         set_screen(NULL);
1344         if (tty && strcmp(tty, "-") != 0)
1345                 f = fopen(tty, "r+");
1346         else
1347                 f = fdopen(1, "r+");
1348         if (!f)
1349                 return NULL;
1350         scr = newterm(term, f, f);
1351         if (!scr)
1352                 return NULL;
1353
1354         p = pane_register(ed, 1, &ncurses_handle.c);
1355         if (!p)
1356                 return NULL;
1357         dd = &p->data;
1358         dd->scr = scr;
1359         dd->scr_file = f;
1360         dd->is_xterm = (term && strstarts(term, "xterm"));
1361
1362         set_screen(p);
1363
1364         ncurses_start(p);
1365
1366         area = dd->attr_buf;
1367         dd->rs1 = tgetstr("rs1", &area);
1368         if (!dd->rs1)
1369                 dd->rs1 = tgetstr("is1", &area);
1370         dd->rs2 = tgetstr("rs2", &area);
1371         if (!dd->rs2)
1372                 dd->rs2 = tgetstr("is2", &area);
1373         dd->rs3 = tgetstr("rs3", &area);
1374         if (!dd->rs3)
1375                 dd->rs3 = tgetstr("is3", &area);
1376         dd->clear = tgetstr("clear", &area);
1377
1378         call("editor:request:all-displays", p);
1379         if (!prepare_recrep(p)) {
1380                 call_comm("event:read", p, &input_handle, fileno(f));
1381                 if (!tty || strcmp(tty, "-") == 0)
1382                         call_comm("event:signal", p, &handle_winch, SIGWINCH);
1383         }
1384         return p;
1385 }
1386
1387 REDEF_CMD(handle_winch)
1388 {
1389         struct pane *p = ci->home;
1390         struct display_data *dd = &p->data;
1391         struct winsize size;
1392         ioctl(fileno(dd->scr_file), TIOCGWINSZ, &size);
1393         set_screen(p);
1394         resize_term(size.ws_row, size.ws_col);
1395
1396         pane_resize(p, 0, 0, size.ws_row, size.ws_col);
1397         return 1;
1398 }
1399
1400 DEF_CMD(force_redraw)
1401 {
1402         struct pane *p = ci->home;
1403
1404         set_screen(p);
1405
1406         /* full reset, as mosh sometimes gets confused */
1407         ncurses_stop(p);
1408         ncurses_start(p);
1409
1410         clearok(curscr, 1);
1411         return 1;
1412 }
1413
1414 static void ncurses_text(struct pane *p safe, struct pane *display safe,
1415                          wchar_t ch, int attr, int pair,
1416                          short x, short y, short cursor)
1417 {
1418         PANEL *pan;
1419         struct pane *p2;
1420         cchar_t cc = {};
1421
1422         if (x < 0 || y < 0)
1423                 return;
1424
1425         set_screen(display);
1426         if (cursor) {
1427                 if (pane_has_focus(p->z < 0 ? p->parent : p)) {
1428                         /* Cursor is in-focus */
1429                         struct xy curs = pane_mapxy(p, display, x, y, False);
1430                         display->cx = curs.x;
1431                         display->cy = curs.y;
1432                 } else
1433                         /* Cursor here, but not focus */
1434                         attr = make_cursor(attr);
1435         }
1436         cc.attr = attr;
1437         cc.ext_color = pair;
1438         cc.chars[0] = ch;
1439
1440         p2 = p;
1441         pan = pane_panel(p2, NULL);
1442         while (!pan && p2->parent != p2) {
1443                 p2 = p2->parent;
1444                 pan = pane_panel(p2, NULL);
1445         }
1446         if (pan) {
1447                 struct xy xy = pane_mapxy(p, p2, x, y, False);
1448                 mvwadd_wch(panel_window(pan), xy.y, xy.x, &cc);
1449         }
1450 }
1451
1452 static struct namelist {
1453         wint_t key;
1454         char *name;
1455 } key_names[] = {
1456         {KEY_DOWN, ":Down"},
1457         {KEY_UP, ":Up"},
1458         {KEY_LEFT, ":Left"},
1459         {KEY_RIGHT, ":Right"},
1460         {KEY_HOME, ":Home"},
1461         {KEY_BACKSPACE, ":Backspace"},
1462         {KEY_DL, ":DelLine"},
1463         {KEY_IL, ":InsLine"},
1464         {KEY_DC, ":Del"},
1465         {KEY_IC, ":Ins"},
1466         {KEY_ENTER, ":Enter"},
1467         {KEY_END, ":End"},
1468
1469         {KEY_NPAGE, ":Next"},
1470         {KEY_PPAGE, ":Prior"},
1471
1472         {KEY_SDC, ":S:Del"},
1473         {KEY_SDL, ":S:DelLine"},
1474         {KEY_SEND, ":S:End"},
1475         {KEY_SHOME, ":S:Home"},
1476         {KEY_SLEFT, ":S:Left"},
1477         {KEY_SRIGHT, ":S:Right"},
1478         {KEY_BTAB, ":S:Tab"},
1479
1480         {  0521, ":S:Up"},
1481         {  0520, ":S:Down"},
1482         {  0616, ":S:Prior"},
1483         {  0614, ":S:Next"},
1484         { 01041, ":S:Home"},
1485         { 01060, ":S:End"},
1486         { 01066, ":S:Prior"},
1487         { 01015, ":S:Next"},
1488
1489         { 01027, ":A:S:Home"},
1490         { 01022, ":A:S:End"},
1491         { 01046, ":A:S:Prior"},
1492         { 01047, ":A:S:Next"}, // ??
1493
1494         { 01052, ":A:Prior"},
1495         { 01045, ":A:Next"},
1496         { 01026, ":A:Home"},
1497         { 01021, ":A:End"},
1498         { 01065, ":A:Up"},
1499         { 01014, ":A:Down"},
1500         { 01040, ":A:Left"},
1501         { 01057, ":A:Right"},
1502         { 00411, ":F1"},
1503         { 00412, ":F2"},
1504         { 00413, ":F3"},
1505         { 00414, ":F4"},
1506         { 00415, ":F5"},
1507         { 00416, ":F6"},
1508         { 00417, ":F7"},
1509         { 00420, ":F8"},
1510         { 00421, ":F9"},
1511         { 00422, ":F10"},
1512         { 00423, ":F11"},
1513         { 00424, ":F12"},
1514         { 00425, ":S:F1"},
1515         { 00426, ":S:F2"},
1516         { 00427, ":S:F3"},
1517         { 00430, ":S:F4"},
1518         { 00431, ":S:F5"},
1519         { 00432, ":S:F6"},
1520         { 00433, ":S:F7"},
1521         { 00434, ":S:F8"},
1522         { 00435, ":S:F9"},
1523         { 00436, ":S:F10"},
1524         { 00437, ":S:F11"},
1525         { 00440, ":S:F12"},
1526         { 01114, ":Focus-in"},
1527         { 01115, ":Focus-out"},
1528         {0, NULL}
1529 }, char_names[] = {
1530         {'\e', ":ESC"},
1531         {'\n', ":LF"},
1532         {'\r', ":Enter"},
1533         {'\t', ":Tab"},
1534         {'\177', ":Delete"},
1535         {'\0', ":C- "},
1536         {0, NULL}
1537 };
1538
1539 static char *find_name (struct namelist *l safe, wint_t c)
1540 {
1541         int i;
1542         for (i = 0; l[i].name; i++)
1543                 if (l[i].key == c)
1544                         return l[i].name;
1545         return NULL;
1546 }
1547
1548 static void send_key(int keytype, wint_t c, int alt, struct pane *p safe)
1549 {
1550         struct display_data *dd = &p->data;
1551         char *n;
1552         char buf[100];/* FIXME */
1553         char t[5];
1554         char *a = alt ? ":A" : "";
1555
1556         if (keytype == KEY_CODE_YES) {
1557                 n = find_name(key_names, c);
1558                 if (!n) {
1559                         LOG("Unknown ncurses key 0o%o", c);
1560                         sprintf(buf, "%sNcurs-%o", a, c);
1561                 } else if (strstarts(n, ":Focus-"))
1562                         /* Ignore focus changes for now */;
1563                 else
1564                         strcat(strcpy(buf, a), n);
1565         } else {
1566                 n = find_name(char_names, c);
1567                 if (n)
1568                         sprintf(buf, "%s%s", a, n);
1569                 else if (c < ' ' || c == 0x7f)
1570                         sprintf(buf, "%s:C-%c",
1571                                 a, c ^ 64);
1572                 else
1573                         sprintf(buf, "%s-%s", a, put_utf8(t, c));
1574         }
1575
1576         dd->last_event = time(NULL);
1577         record_key(p, buf);
1578         call("Keystroke", p, 0, NULL, buf);
1579 }
1580
1581 static void do_send_mouse(struct pane *p safe, int x, int y, char *cmd safe,
1582                           int button, char *mod, int type)
1583 {
1584         int ret;
1585         struct display_data *dd = &p->data;
1586
1587         record_mouse(p, cmd, x, y);
1588         ret = call("Mouse-event", p, button, NULL, cmd, type, NULL, mod, x, y);
1589         if (type == 1 && !dd->report_position) {
1590                 if (dd->is_xterm) {
1591                         fprintf(dd->scr_file, "\033[?1002h");
1592                         fflush(dd->scr_file);
1593                 }
1594                 dd->report_position = 1;
1595         } else if (type == 3 && ret <= 0) {
1596                 if (dd->is_xterm) {
1597                         fprintf(dd->scr_file, "\033[?1002l");
1598                         fflush(dd->scr_file);
1599                 }
1600                 dd->report_position = 0;
1601         }
1602 }
1603
1604 static void send_mouse(MEVENT *mev safe, struct pane *p safe)
1605 {
1606         struct display_data *dd = &p->data;
1607         int x = mev->x;
1608         int y = mev->y;
1609         int b;
1610         char buf[100];
1611
1612         /* MEVENT has lots of bits.  We want a few numbers */
1613         for (b = 1 ; b <= (NCURSES_MOUSE_VERSION <= 1 ? 3 : 5); b++) {
1614                 mmask_t s = mev->bstate;
1615                 char *action;
1616                 int modf = 0;
1617                 char *mod = "";
1618
1619                 if (s & BUTTON_SHIFT) modf |= 1;
1620                 if (s & BUTTON_CTRL)  modf |= 2;
1621                 if (s & BUTTON_ALT)   modf |= 4;
1622                 switch (modf) {
1623                 case 0: mod = ""; break;
1624                 case 1: mod = ":S"; break;
1625                 case 2: mod = ":C"; break;
1626                 case 3: mod = ":C:S"; break;
1627                 case 4: mod = ":A"; break;
1628                 case 5: mod = ":A:S"; break;
1629                 case 6: mod = ":A:C"; break;
1630                 case 7: mod = ":A:C:S"; break;
1631                 }
1632                 if (BUTTON_PRESS(s, b))
1633                         action = "%s:Press-%d";
1634                 else if (BUTTON_RELEASE(s, b)) {
1635                         action = "%s:Release-%d";
1636                         /* Modifiers only reported on button Press */
1637                         mod = "";
1638                 } else
1639                         continue;
1640                 snprintf(buf, sizeof(buf), action, mod, b);
1641                 dd->last_event = time(NULL);
1642                 do_send_mouse(p, x, y, buf, b, mod, BUTTON_PRESS(s,b) ? 1 : 2);
1643         }
1644         if ((mev->bstate & REPORT_MOUSE_POSITION) &&
1645             dd->report_position)
1646                 /* Motion doesn't update last_event */
1647                 do_send_mouse(p, x, y, ":Motion", 0, "", 3);
1648 }
1649
1650 static void paste_start(struct pane *home safe)
1651 {
1652         struct display_data *dd = &home->data;
1653
1654         dd->paste_start = time(NULL);
1655         buf_init(&dd->paste_buf);
1656 }
1657
1658 static void paste_flush(struct pane *home safe)
1659 {
1660         struct display_data *dd = &home->data;
1661
1662         if (!dd->paste_start)
1663                 return;
1664         free(dd->paste_latest);
1665         dd->paste_latest = buf_final(&dd->paste_buf);
1666         if (dd->paste_buf.len > 0)
1667                 dd->paste_pending = 1;
1668         dd->paste_start = 0;
1669 }
1670
1671 static bool paste_recv(struct pane *home safe, int is_keycode, wint_t ch)
1672 {
1673         struct display_data *dd = &home->data;
1674         time_t now;
1675         if (dd->paste_start == 0)
1676                 return False;
1677         now = time(NULL);
1678         if (dd->paste_start < now || dd->paste_start > now + 2 ||
1679             is_keycode != OK || ch == KEY_MOUSE) {
1680                 /* time to close */
1681                 paste_flush(home);
1682                 return False;
1683         }
1684         if (ch == '\r')
1685                 /* I really don't want carriage-returns... */
1686                 ch = '\n';
1687         buf_append(&dd->paste_buf, ch);
1688         if (ch == '~' && dd->paste_buf.len >= 6 &&
1689             strcmp("\e[201~",
1690                    buf_final(&dd->paste_buf) + dd->paste_buf.len - 6) == 0) {
1691                 dd->paste_buf.len -= 6;
1692                 paste_flush(home);
1693         }
1694         return True;
1695 }
1696
1697 DEF_CMD(nc_get_paste)
1698 {
1699         struct display_data *dd = &ci->home->data;
1700
1701         comm_call(ci->comm2, "cb", ci->focus,
1702                   dd->paste_start, NULL, dd->paste_latest);
1703         return 1;
1704 }
1705
1706 REDEF_CMD(input_handle)
1707 {
1708         struct pane *p = ci->home;
1709         struct display_data *dd = &p->data;
1710         static const char paste_seq[] = "\e[200~";
1711         wint_t c;
1712         int is_keycode;
1713         int have_escape = 0;
1714         int i;
1715
1716         wait_for(dd);
1717         set_screen(p);
1718         while ((is_keycode = get_wch(&c)) != ERR) {
1719                 if (dd->suspended && c != KEY_MOUSE) {
1720                         dd->suspended = False;
1721                         doupdate();
1722                         call_comm("event:free", p, &ns_resume);
1723                         /* swallow the key */
1724                         continue;
1725                 }
1726                 if (paste_recv(p, is_keycode, c))
1727                         continue;
1728                 if (c == KEY_MOUSE) {
1729                         MEVENT mev;
1730                         paste_flush(p);
1731                         while (getmouse(&mev) != ERR) {
1732                                 if (dd->paste_pending &&
1733                                     mev.bstate == REPORT_MOUSE_POSITION) {
1734                                         /* xcfe-terminal is a bit weird.
1735                                          * It captures middle-press to
1736                                          * sanitise the paste, but lets
1737                                          * middle-release though. It comes
1738                                          * here as REPORT_MOUSE_POSTION
1739                                          * and we can use that to find the
1740                                          * position of the paste.
1741                                          * '6' is an unused button, and
1742                                          * ensures lib-input doesn't expect
1743                                          * matching press/release
1744                                          */
1745                                         call("Mouse-event", ci->home,
1746                                              1, NULL, ":Paste",
1747                                              6, NULL, NULL, mev.x, mev.y);
1748                                         dd->paste_pending = 0;
1749                                 }
1750                                 send_mouse(&mev, p);
1751                         }
1752                 } else if (c == (wint_t)paste_seq[have_escape]) {
1753                         have_escape += 1;
1754                         if (!paste_seq[have_escape]) {
1755                                 paste_start(p);
1756                                 have_escape = 0;
1757                         }
1758                 } else if (have_escape == 1) {
1759                         send_key(is_keycode, c, 1, p);
1760                         have_escape = 0;
1761                 } else if (have_escape) {
1762                         send_key(OK, paste_seq[1], 1, p);
1763                         for (i = 2; i < have_escape; i++)
1764                                 send_key(OK, paste_seq[i], 0, p);
1765                         send_key(is_keycode, c, 0, p);
1766                         have_escape = 0;
1767                 } else {
1768                         send_key(is_keycode, c, 0, p);
1769                 }
1770                 /* Don't know what other code might have done,
1771                  * so re-set the screen
1772                  */
1773                 set_screen(p);
1774         }
1775         if (have_escape == 1)
1776                 send_key(is_keycode, '\e', 0, p);
1777         else if (have_escape > 1) {
1778                 send_key(OK, paste_seq[1], 1, p);
1779                 for (i = 2; i < have_escape; i++)
1780                         send_key(OK, paste_seq[i], 0, p);
1781         }
1782         if (dd->paste_pending == 2) {
1783                 /* no mouse event to give postion, so treat as keyboard */
1784                 call("Keystroke", ci->home, 0, NULL, ":Paste");
1785                 dd->paste_pending = 0;
1786         } else if (dd->paste_pending == 1) {
1787                 /* Wait for possible mouse-position update. */
1788                 dd->paste_pending = 2;
1789                 call_comm("event:timer", p, &input_handle, 200);
1790         }
1791         return 1;
1792 }
1793
1794 DEF_CMD(display_ncurses)
1795 {
1796         struct pane *p;
1797         struct pane *ed = pane_root(ci->focus);
1798         const char *tty = ci->str;
1799         const char *term = ci->str2;
1800
1801         if (!term)
1802                 term = "xterm-256color";
1803
1804         p = ncurses_init(ed, tty, term);
1805         if (p)
1806                 p = call_ret(pane, "editor:activate-display", p);
1807         if (p && ci->focus != ed)
1808                 /* Assume ci->focus is a document */
1809                 p = home_call_ret(pane, ci->focus, "doc:attach-view", p, 1);
1810         if (p)
1811                 return comm_call(ci->comm2, "callback:display", p);
1812
1813         return Efail;
1814 }
1815
1816 void edlib_init(struct pane *ed safe)
1817 {
1818         call_comm("global-set-command", ed, &display_ncurses, 0, NULL,
1819                   "attach-display-ncurses");
1820
1821         nc_map = key_alloc();
1822         key_add(nc_map, "Display:refresh", &force_redraw);
1823         key_add(nc_map, "Display:close", &nc_close_display);
1824         key_add(nc_map, "Display:set-noclose", &nc_set_noclose);
1825         key_add(nc_map, "Display:external-viewer", &nc_external_viewer);
1826         key_add(nc_map, "Close", &nc_close);
1827         key_add(nc_map, "Draw:clear", &nc_clear);
1828         key_add(nc_map, "Draw:text-size", &nc_text_size);
1829         key_add(nc_map, "Draw:text", &nc_draw_text);
1830
1831         key_add(nc_map, "Draw:image", &nc_draw_image);
1832         key_add(nc_map, "Draw:image-size", &nc_image_size);
1833
1834         key_add(nc_map, "Refresh:size", &nc_refresh_size);
1835         key_add(nc_map, "Refresh:postorder", &nc_refresh_post);
1836         key_add(nc_map, "Paste:get", &nc_get_paste);
1837         key_add(nc_map, "all-displays", &nc_notify_display);
1838         key_add(nc_map, "Sig:Winch", &handle_winch);
1839         key_add(nc_map, "Notify:Close", &nc_pane_close);
1840 }