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