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