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