]> git.neil.brown.name Git - edlib.git/blob - display-ncurses.c
Update some copyright date
[edlib.git] / display-ncurses.c
1 /*
2  * Copyright Neil Brown ©2015-2021 <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 <time.h>
27 #include <curses.h>
28 #include <panel.h>
29 #include <string.h>
30 #include <locale.h>
31 #include <ctype.h>
32 #include <signal.h>
33 #include <sys/ioctl.h>
34
35 #include "core.h"
36
37 #ifdef RECORD_REPLAY
38 #include <unistd.h>
39 #include <stdio.h>
40 #include "md5.h"
41 typedef char hash_t[MD5_DIGEST_SIZE*2+1];
42 #endif
43
44 #ifdef __CHECKER__
45 #undef NCURSES_OK_ADDR
46 #define NCURSES_OK_ADDR(p) ((void*)0 != NCURSES_CAST(const void *, (p)))
47 #endif
48
49 struct col_hash;
50
51 struct display_data {
52         SCREEN                  *scr;
53         FILE                    *scr_file;
54         int                     is_xterm;
55         char                    *noclose;
56         struct col_hash         *col_hash;
57         int                     report_position;
58         long                    last_event;
59         #ifdef RECORD_REPLAY
60         FILE                    *log;
61         FILE                    *input;
62         char                    last_screen[MD5_DIGEST_SIZE*2+1];
63         char                    next_screen[MD5_DIGEST_SIZE*2+1];
64         /* The next event to generate when idle */
65         enum { DoNil, DoMouse, DoKey, DoCheck, DoClose} next_event;
66         char                    event_info[30];
67         struct xy               event_pos;
68         #endif
69 };
70
71 static SCREEN *current_screen;
72 static void ncurses_text(struct pane *p safe, struct pane *display safe,
73                          wchar_t ch, int attr, short x, short y, short cursor);
74 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home);
75 DEF_CMD(input_handle);
76 DEF_CMD(handle_winch);
77 static struct map *nc_map;
78 DEF_LOOKUP_CMD(ncurses_handle, nc_map);
79
80 static void set_screen(struct pane *p)
81 {
82         struct display_data *dd;
83         extern void *_nc_globals[100];
84         int i;
85         static int index = -1, offset=0;
86
87         if (!p) {
88                 if (current_screen && index >= 0)
89                         _nc_globals[index] = NULL;
90                 current_screen = NULL;
91                 return;
92         }
93         dd = p->data;
94         if (!dd)
95                 return;
96         if (dd->scr == current_screen)
97                 return;
98
99         if (index == -1) {
100                 index = -2;
101                 for (i=0; i<100; i++)
102                         if (_nc_globals[i] < (void*)stdscr &&
103                             _nc_globals[i]+4*(sizeof(void*)) >= (void*)stdscr) {
104                                 /* This is _nc_windowlist */
105                                 index = i;
106                                 offset = ((void*)stdscr) - _nc_globals[i];
107                         }
108         }
109
110         set_term(dd->scr);
111         current_screen = dd->scr;
112         if (index >= 0) {
113                 _nc_globals[index] = ((void*)stdscr) - offset;
114         }
115 }
116
117 #ifdef RECORD_REPLAY
118 DEF_CMD(next_evt);
119 DEF_CMD(abort_replay);
120
121 static bool parse_event(struct pane *p safe);
122 static bool prepare_recrep(struct pane *p safe)
123 {
124         struct display_data *dd = p->data;
125         char *name;
126
127         name = getenv("EDLIB_RECORD");
128         if (name)
129                 dd->log = fopen(name, "w");
130         name = getenv("EDLIB_REPLAY");
131         if (name)
132                 dd->input = fopen(name, "r");
133         if (getenv("EDLIB_PAUSE"))
134                 sleep(atoi(getenv("EDLIB_PAUSE")));
135         if (dd->input) {
136                 parse_event(p);
137                 return True;
138         }
139         return False;
140 }
141
142 static void close_recrep(struct pane *p safe)
143 {
144         struct display_data *dd = p->data;
145
146         if (dd->log) {
147                 fprintf(dd->log, "Close\n");
148                 fclose(dd->log);
149         }
150 }
151
152 static void record_key(struct pane *p safe, char *key safe)
153 {
154         struct display_data *dd = p->data;
155         char q;
156
157         if (!dd->log)
158                 return;
159         if (!strchr(key, '"'))
160                 q = '"';
161         else if (!strchr(key, '\''))
162                 q = '\'';
163         else if (!strchr(key, '/'))
164                 q = '/';
165         else
166                 return;
167         fprintf(dd->log, "Key %c%s%c\n", q,key,q);
168 }
169
170 static void record_mouse(struct pane *p safe, char *key safe, int x, int y)
171 {
172         struct display_data *dd = p->data;
173         char q;
174         if (!dd->log)
175                 return;
176         if (!strchr(key, '"'))
177                 q = '"';
178         else if (!strchr(key, '\''))
179                 q = '\'';
180         else if (!strchr(key, '/'))
181                 q = '/';
182         else
183                 return;
184         fprintf(dd->log, "Mouse %c%s%c %d,%d\n", q,key,q, x, y);
185 }
186
187 static void record_screen(struct pane *p safe)
188 {
189         struct display_data *dd = p->data;
190         struct md5_state ctx;
191         uint16_t buf[CCHARW_MAX+5];
192         char out[MD5_DIGEST_SIZE*2+1];
193         int r,c;
194
195         if (!dd->log && !(dd->input && dd->next_event == DoCheck))
196                 return;
197         set_screen(p);
198         md5_init(&ctx);
199         for (r = 0; r < p->h; r++)
200                 for (c = 0; c < p->w; c++) {
201                         cchar_t cc;
202                         wchar_t wc[CCHARW_MAX+2];
203                         attr_t a;
204                         short color, fg, bg;
205                         int l;
206
207                         mvwin_wch(stdscr, r, c, &cc);
208                         getcchar(&cc, wc, &a, &color, NULL);
209                         pair_content(color, &fg, &bg);
210                         buf[0] = htole16(fg);
211                         buf[1] = htole16(bg);
212                         for (l = 0; l < CCHARW_MAX && wc[l]; l++)
213                                 buf[l+3] = htole16(wc[l]);
214                         buf[2] = htole16(l);
215                         LOG("%d,%d %d:%d:%d:%d %lc", c,r,fg,bg,l,wc[0], wc[0] > ' ' ? wc[0] : '?');
216                         md5_update(&ctx, (uint8_t*)buf,
217                                    (l+3) * sizeof(uint16_t));
218                 }
219         md5_final_txt(&ctx, out);
220         if (dd->log) {
221                 fprintf(dd->log, "Display %d,%d %s", p->w, p->h, out);
222                 strcpy(dd->last_screen, out);
223                 if (p->cx >= 0)
224                         fprintf(dd->log, " %d,%d", p->cx, p->cy);
225                 fprintf(dd->log, "\n");
226         }
227         if (dd->input && dd->next_event == DoCheck) {
228                 call_comm("event:free", p, &abort_replay);
229 //              if (strcmp(dd->last_screen, dd->next_screen) != 0)
230 //                      dd->next_event = DoClose;
231                 call_comm("editor-on-idle", p, &next_evt);
232         }
233 }
234
235 static inline int match(char *line safe, char *w safe)
236 {
237         return strncmp(line, w, strlen(w)) == 0;
238 }
239
240 static char *copy_quote(char *line safe, char *buf safe)
241 {
242         char q;
243         while (*line == ' ')
244                 line++;
245         q = *line++;
246         if (q != '"' && q != '\'' && q != '/')
247                 return NULL;
248         while (*line != q && *line)
249                 *buf++ = *line++;
250         if (!*line)
251                 return NULL;
252         *buf = '\0';
253         return line+1;
254 }
255
256 static char *get_coord(char *line safe, struct xy *co safe)
257 {
258         long v;
259         char *ep;
260
261         while (*line == ' ')
262                 line ++;
263         v = strtol(line, &ep, 10);
264         if (!ep || ep == line || *ep != ',')
265                 return NULL;
266         co->x = v;
267         line = ep+1;
268         v = strtol(line, &ep, 10);
269         if (!ep || ep == line || (*ep && *ep != ' ' && *ep != '\n'))
270                 return NULL;
271         co->y = v;
272         return ep;
273 }
274
275 static char *get_hash(char *line safe, hash_t hash safe)
276 {
277         int i;
278         while (*line == ' ')
279                 line++;
280         for (i = 0; i < MD5_DIGEST_SIZE*2 && *line; i++)
281                 hash[i] = *line++;
282         if (!*line)
283                 return NULL;
284         return line;
285 }
286
287 REDEF_CMD(abort_replay)
288 {
289         struct display_data *dd = ci->home->data;
290
291         dd->next_event = DoClose;
292         return next_evt_func(ci);
293 }
294
295 static bool parse_event(struct pane *p safe)
296 {
297         struct display_data *dd = p->data;
298         char line[80];
299
300         line[79] = 0;
301         dd->next_event = DoNil;
302         if (!dd->input ||
303             fgets(line, sizeof(line)-1, dd->input) == NULL)
304                 line[0]=0;
305         else if (match(line, "Key ")) {
306                 if (!copy_quote(line+4, dd->event_info))
307                         return False;
308                 dd->next_event = DoKey;
309         } else if (match(line, "Mouse ")) {
310                 char *f = copy_quote(line+6, dd->event_info);
311                 if (!f)
312                         return False;
313                 f = get_coord(f, &dd->event_pos);
314                 if (!f)
315                         return False;
316                 dd->next_event = DoMouse;
317         } else if (match(line, "Display ")) {
318                 char *f = get_coord(line+8, &dd->event_pos);
319                 if (!f)
320                         return False;
321                 f = get_hash(f, dd->next_screen);
322                 dd->next_event = DoCheck;
323         } else if (match(line, "Close")) {
324                 dd->next_event = DoClose;
325         }
326         LOG("parse %s", line);
327
328         if (dd->next_event != DoCheck)
329                 call_comm("editor-on-idle", p, &next_evt);
330         else
331                 call_comm("event:timer", p, &abort_replay, 10*1000);
332         return True;
333 }
334
335 REDEF_CMD(next_evt)
336 {
337         struct pane *p = ci->home;
338         struct display_data *dd = p->data;
339         int button = 0, type = 0;
340         char *delay;
341
342         delay = getenv("EDLIB_REPLAY_DELAY");
343         if (delay && dd->next_event != DoCheck)
344                 usleep(atoi(delay)*1000);
345
346         switch(dd->next_event) {
347         case DoKey:
348                 record_key(p, dd->event_info);
349                 call("Keystroke", p, 0, NULL, dd->event_info);
350                 break;
351         case DoMouse:
352                 record_mouse(p, dd->event_info, dd->event_pos.x,
353                              dd->event_pos.y);
354                 if (strstr(dd->event_info, ":Press"))
355                         type = 1;
356                 else if (strstr(dd->event_info, ":Release"))
357                         type = 2;
358                 else if (strstr(dd->event_info, ":Motion"))
359                         type = 3;
360                 if (type == 1 || type == 2) {
361                         char *e = dd->event_info + strlen(dd->event_info) - 1;
362                         button = atoi(e);
363                 }
364                 call("Mouse-event", p, button, NULL, dd->event_info,
365                      type, NULL, NULL,
366                      dd->event_pos.x, dd->event_pos.y);
367                 break;
368         case DoCheck:
369                 /* No point checking, just do a diff against new trace log. */
370                 /* not; (strcmp(dd->next_screen, dd->last_screen) != 0) */
371                 break;
372         case DoClose:
373                 call("event:deactivate", p);
374                 pane_close(p);
375                 return 1;
376         case DoNil:
377                 call_comm("event:read", p, &input_handle, 0);
378                 call_comm("event:signal", p, &handle_winch, SIGWINCH);
379                 return 1;
380         }
381         parse_event(p);
382         return 1;
383 }
384 #else
385 static inline bool  prepare_recrep(struct pane *p safe) {return False;}
386 static inline void record_key(struct pane *p safe, char *key) {}
387 static inline void record_mouse(struct pane *p safe, char *key safe,
388                                 int x, int y) {}
389 static inline void record_screen(struct pane *p safe) {}
390 static inline void close_recrep(struct pane *p safe) {}
391 #endif
392
393 DEF_CB(cnt_disp)
394 {
395         struct call_return *cr = container_of(ci->comm, struct call_return, c);
396
397         cr->i += 1;
398         return 1;
399 }
400
401 DEF_CMD(nc_close_display)
402 {
403         /* If this is only display, then refuse to close this one */
404         struct call_return cr;
405         struct display_data *dd = ci->home->data;
406
407         if (dd->noclose) {
408                 call("Message", ci->focus, 0, NULL, dd->noclose);
409                 return 1;
410         }
411
412         cr.c = cnt_disp;
413         cr.i = 0;
414         call_comm("editor:notify:all-displays", ci->focus, &cr.c);
415         if (cr.i > 1)
416                 pane_close(ci->home);
417         else
418                 call("Message", ci->focus, 0, NULL,
419                      "Cannot close only window.");
420         return 1;
421 }
422
423 DEF_CMD(nc_set_noclose)
424 {
425         struct display_data *dd = ci->home->data;
426
427         free(dd->noclose);
428         dd->noclose = NULL;
429         if (ci->str)
430                 dd->noclose = strdup(ci->str);
431         return 1;
432 }
433
434 static void ncurses_end(struct pane *p safe)
435 {
436         set_screen(p);
437         close_recrep(p);
438         nl();
439         endwin();
440 }
441
442 /*
443  * hash table for colours and pairs
444  * key is r,g,b (0-1000) in 10bit fields,
445  * or fg,bg in 16 bit fields with bit 31 set
446  * content is colour number of colour pair number.
447  * We never delete entries, unless we delete everything.
448  */
449
450 struct chash {
451         struct chash *next;
452         int key, content;
453 };
454 #define COL_KEY(r,g,b) ((r<<20) | (g<<10) | b)
455 #define PAIR_KEY(fg, bg) ((1<<31) | (fg<<16) | bg)
456 #define hash_key(k) ((((k) * 0x61C88647) >> 20) & 0xff)
457
458 struct col_hash {
459         int next_col, next_pair;
460         struct chash *tbl[256];
461 };
462
463 static struct col_hash *safe hash_init(struct display_data *dd safe)
464 {
465         if (!dd->col_hash) {
466                 dd->col_hash = malloc(sizeof(*dd->col_hash));
467                 memset(dd->col_hash, 0, sizeof(*dd->col_hash));
468                 dd->col_hash->next_col = 16;
469                 dd->col_hash->next_pair = 1;
470         }
471         return dd->col_hash;
472 }
473
474 static void hash_free(struct display_data *dd safe)
475 {
476         int h;
477         struct chash *c;
478         struct col_hash *ch;
479
480         ch = dd->col_hash;
481         if (!ch)
482                 return;
483         for (h = 0; h < 255; h++)
484                 while ((c = ch->tbl[h]) != NULL) {
485                         ch->tbl[h] = c->next;
486                         free(c);
487                 }
488         free(ch);
489         dd->col_hash = NULL;
490 }
491
492 static int find_col(struct display_data *dd safe, int rgb[])
493 {
494         if (0 /* dynamic colours */) {
495                 struct col_hash *ch = hash_init(dd);
496                 int k = COL_KEY(rgb[0], rgb[1], rgb[2]);
497                 int h = hash_key(k);
498                 struct chash *c;
499
500                 for (c = ch->tbl[h]; c; c = c->next)
501                         if (c->key == k)
502                                 return c->content;
503                 c = malloc(sizeof(*c));
504                 c->key = k;
505                 c->content = ch->next_col++;
506                 c->next = ch->tbl[h];
507                 ch->tbl[h] = c;
508                 init_color(c->content, rgb[0], rgb[1], rgb[2]);
509                 return c->content;
510         } else {
511                 /* If colour is grey, map to 1 of 26 for 0, 232 to 255, 15
512                  * The 24 grey shades have bit values from 8 to 238, so the
513                  * gap to white is a little bigger, but that probably doesn't
514                  * matter.
515                  * Otherwise map to 6x6x6 rgb cube from 16
516                  * Actual colours are biased bright, at 0,95,135,175,215,255
517                  * with a 95 gap at bottom and 40 elsewhere.
518                  * So we divide 5 and 2 half ranges, and merge bottom 2.
519                  */
520                 int c = 0;
521                 int h;
522
523                 //printf("want %d,%d,%d\n", rgb[0], rgb[1], rgb[2]);
524                 if (abs(rgb[0] - rgb[1]) < 10 &&
525                     abs(rgb[1] - rgb[2]) < 10) {
526                         /* grey - within 1% */
527                         int v = (rgb[0] + rgb[1] + rgb[2]) / 3;
528
529                         /* We divide the space in 24 ranges surrounding
530                          * the grey values, and 2 half-ranges near black
531                          * and white.  So add half a range - 1000/50 -
532                          * then divide by 1000/25 to get a number from 0 to 25.
533                          */
534                         v = (v + 1000/50) / (1000/25);
535                         if (v == 0)
536                                 return 0; /* black */
537                         if (v >= 25)
538                                 return 15; /* white */
539                         //printf(" grey %d\n", v + 231);
540                         /* grey shades are from 232 to 255 inclusive */
541                         return v + 231;
542                 }
543                 for (h = 0; h < 3; h++) {
544                         int v = rgb[h];
545
546                         v = (v + 1000/12) / (1000/6);
547                         /* v is from 0 to 6, we want up to 5
548                          * with 0 and 1 merged
549                          */
550                         if (v)
551                                 v -= 1;
552
553                         c = c * 6 + v;
554                 }
555                 //printf(" color %d\n", c + 16);
556                 return c + 16;
557         }
558 }
559
560 static int to_pair(struct display_data *dd safe, int fg, int bg)
561 {
562         struct col_hash *ch = hash_init(dd);
563         int k = PAIR_KEY(fg, bg);
564         int h = hash_key(k);
565         struct chash *c;
566
567         for (c = ch->tbl[h]; c; c = c->next)
568                 if (c->key == k)
569                         return c->content;
570         c = malloc(sizeof(*c));
571         c->key = k;
572         c->content = ch->next_pair++;
573         c->next = ch->tbl[h];
574         ch->tbl[h] = c;
575         init_pair(c->content, fg, bg);
576         return c->content;
577 }
578
579
580 static int cvt_attrs(struct pane *p safe, struct pane *home safe,
581                      const char *attrs)
582 {
583         struct display_data *dd = home->data;
584         int attr = 0;
585         char tmp[40];
586         const char *a;
587         PANEL *pan = NULL;
588         int fg = COLOR_BLACK;
589         int bg = COLOR_WHITE+8;
590
591         set_screen(home);
592         do {
593                 p = p->parent;
594         } while (p->parent != p &&(pan = pane_panel(p, NULL)) == NULL);
595         if (pan) {
596                 /* Get 'default colours for this pane - set at clear */
597                 int at = getbkgd(panel_window(pan));
598                 int pair = PAIR_NUMBER(at);
599                 short dfg, dbg;
600                 pair_content(pair, &dfg, &dbg);
601                 if (dfg >= 0)
602                         fg = dfg;
603                 if (dbg >= 0)
604                         bg = dbg;
605         }
606         a = attrs;
607         while (a && *a) {
608                 const char *c;
609                 if (*a == ',') {
610                         a++;
611                         continue;
612                 }
613                 c = strchr(a, ',');
614                 if (!c)
615                         c = a+strlen(a);
616                 strncpy(tmp, a, c-a);
617                 tmp[c-a] = 0;
618                 if (strcmp(tmp, "inverse")==0) attr |= A_STANDOUT;
619                 else if (strcmp(tmp, "noinverse")==0) attr &= ~A_STANDOUT;
620                 else if (strcmp(tmp, "bold")==0) attr |= A_BOLD;
621                 else if (strcmp(tmp, "nobold")==0) attr &= ~A_BOLD;
622                 else if (strcmp(tmp, "underline")==0) attr |= A_UNDERLINE;
623                 else if (strcmp(tmp, "nounderline")==0) attr &= ~A_UNDERLINE;
624                 else if (strncmp(tmp, "fg:", 3) == 0) {
625                         struct call_return cr =
626                                 call_ret(all, "colour:map", home,
627                                          0, NULL, tmp+3);
628                         int rgb[3] = {cr.i, cr.i2, cr.x};
629                         fg = find_col(dd, rgb);
630                 } else if (strncmp(tmp, "bg:", 3) == 0) {
631                         struct call_return cr =
632                                 call_ret(all, "colour:map", home,
633                                          0, NULL, tmp+3);
634                         int rgb[3] = {cr.i, cr.i2, cr.x};
635                         bg = find_col(dd, rgb);
636                 }
637                 a = c;
638         }
639         if (fg != COLOR_BLACK || bg != COLOR_WHITE+8)
640                 attr |= COLOR_PAIR(to_pair(dd, fg, bg));
641         return attr;
642 }
643
644 static int make_cursor(int attr)
645 {
646         return attr ^ A_UNDERLINE;
647 }
648
649 DEF_CMD(nc_notify_display)
650 {
651         struct display_data *dd = ci->home->data;
652         comm_call(ci->comm2, "callback:display", ci->home, dd->last_event);
653         return 1;
654 }
655
656 DEF_CMD(nc_close)
657 {
658         struct pane *p = ci->home;
659         struct display_data *dd = p->data;
660         ncurses_end(p);
661         hash_free(dd);
662         fclose(dd->scr_file);
663         return 1;
664 }
665
666 DEF_CMD(nc_pane_close)
667 {
668         PANEL *pan = NULL;
669
670         set_screen(ci->home);
671         while ((pan = panel_above(pan)) != NULL)
672                 if (panel_userptr(pan) == ci->focus)
673                         break;
674         if (pan) {
675                 WINDOW *win = panel_window(pan);
676                 del_panel(pan);
677                 delwin(win);
678                 pane_damaged(ci->home, DAMAGED_POSTORDER);
679         }
680         return 1;
681 }
682
683 static PANEL * safe pane_panel(struct pane *p safe, struct pane *home)
684 {
685         PANEL *pan = NULL;
686
687         while ((pan = panel_above(pan)) != NULL)
688                 if (panel_userptr(pan) == p)
689                         return pan;
690
691         if (!home)
692                 return pan;
693
694         pan = new_panel(newwin(p->h, p->w, 0, 0));
695         set_panel_userptr(pan, p);
696         pane_add_notify(home, p, "Notify:Close");
697
698         return pan;
699 }
700
701 DEF_CMD(nc_clear)
702 {
703         struct pane *p = ci->home;
704         int attr = cvt_attrs(ci->focus, p, ci->str2?:ci->str);
705         PANEL *panel;
706         WINDOW *win;
707         int w, h;
708
709         set_screen(p);
710         panel = pane_panel(ci->focus, p);
711         if (!panel)
712                 return Efail;
713         win = panel_window(panel);
714         getmaxyx(win, h, w);
715         if (h != ci->focus->h || w != ci->focus->w) {
716                 wresize(win, ci->focus->h, ci->focus->w);
717                 replace_panel(panel, win);
718         }
719         wbkgdset(win, attr);
720         werase(win);
721
722         pane_damaged(p, DAMAGED_POSTORDER);
723         return 1;
724 }
725
726 DEF_CMD(nc_text_size)
727 {
728         int max_space = ci->num;
729         int max_bytes = 0;
730         int size = 0;
731         const char *str = ci->str;
732
733         if (!str)
734                 return Enoarg;
735         while (str[0] != 0) {
736                 wint_t wc = get_utf8(&str, NULL);
737                 int width;
738                 if (wc == WEOF || wc == WERR)
739                         break;
740                 width = wcwidth(wc);
741                 if (width < 0)
742                         break;
743                 size += width;
744                 if (size <= max_space)
745                         max_bytes = str - ci->str;
746         }
747         return comm_call(ci->comm2, "callback:size", ci->focus,
748                          max_bytes, NULL, NULL,
749                          0, NULL, NULL, size, 1);
750 }
751
752 DEF_CMD(nc_draw_text)
753 {
754         struct pane *p = ci->home;
755         int attr = cvt_attrs(ci->focus, p, ci->str2);
756         int cursor_offset = ci->num;
757         short x = ci->x, y = ci->y;
758         const char *str = ci->str;
759
760         if (!str)
761                 return Enoarg;
762         set_screen(p);
763         while (str[0] != 0) {
764                 int precurs = str <= ci->str + cursor_offset;
765                 wint_t wc = get_utf8(&str, NULL);
766                 int width;
767                 if (wc == WEOF || wc == WERR)
768                         break;
769                 width = wcwidth(wc);
770                 if (width < 0)
771                         break;
772                 if (precurs && str > ci->str + cursor_offset)
773                         ncurses_text(ci->focus, p, wc, attr, x, y, 1);
774                 else
775                         ncurses_text(ci->focus, p, wc, attr, x, y, 0);
776                 x += width;
777         }
778         if (str == ci->str + cursor_offset)
779                 ncurses_text(ci->focus, p, ' ', 0, x, y, 1);
780         pane_damaged(p, DAMAGED_POSTORDER);
781         return 1;
782 }
783
784 DEF_CMD(nc_refresh_size)
785 {
786         struct pane *p = ci->home;
787
788         set_screen(p);
789         getmaxyx(stdscr, p->h, p->w);
790         clearok(curscr, 1);
791         return 0;
792 }
793
794 DEF_CMD(nc_refresh_post)
795 {
796         struct pane *p = ci->home;
797         struct pane *p1;
798         PANEL *pan, *pan2;
799
800         set_screen(p);
801
802         /* Need to ensure stacking order and panel y,x position
803          * is correct.  FIXME it would be good if we could skip this
804          * almost always.
805          */
806         pan = panel_above(NULL);
807         if (!pan)
808                 return 1;
809         p1 = (struct pane*) panel_userptr(pan);
810         for (;(pan2 = panel_above(pan)) != NULL; pan = pan2) {
811                 struct pane *p2 = (struct pane*)panel_userptr(pan2);
812                 p1 = (struct pane*)panel_userptr(pan);
813                 if (!p1 || !p2)
814                         continue;
815
816                 if (p1->abs_z < p2->abs_z)
817                         continue;
818                 if (p1->abs_z == p2->abs_z &&
819                     p1->z <= p2->z)
820                         continue;
821                 /* pan needs to be above pan2.  All we can do is move it to
822                  * the top. Anything that needs to be above it will eventually
823                  * be pushed up too.
824                  */
825                 top_panel(pan);
826                 /* Now the panel below pan might need to be over pan2 too... */
827                 pan = panel_below(pan2);
828                 if (pan)
829                         pan2 = pan;
830         }
831
832         /* As we need to crop pane against their parents, we cannot simply
833          * use update_panels().  Instead we copy each to stdscr and refresh
834          * that.
835          */
836         for (pan = NULL; (pan = panel_above(pan)) != NULL; ) {
837                 WINDOW *win;
838                 struct xy src, dest, destend;
839                 int w, h;
840
841                 p1 = (void*)panel_userptr(pan);
842                 if (!p1)
843                         continue;
844                 dest = pane_mapxy(p1, p, 0, 0, True);
845                 destend = pane_mapxy(p1, p, p1->w, p1->h, True);
846                 src = pane_mapxy(p1, p, 0, 0, False);
847                 src.x = dest.x - src.x;
848                 src.y = dest.y - src.y;
849                 win = panel_window(pan);
850                 getmaxyx(win, h, w);
851                 /* guard again accessing beyond boundary of win */
852                 if (destend.x > dest.x + (w - src.x))
853                         destend.x = dest.x + (w - src.x);
854                 if (destend.y > dest.y + (h - src.y))
855                         destend.y = dest.y - (h - src.y);
856                 copywin(win, stdscr, src.y, src.x,
857                         dest.y, dest.x, destend.y-1, destend.x-1, 0);
858         }
859         /* place the cursor */
860         p1 = pane_leaf(p);
861         pan = NULL;
862         while (p1 != p && (pan = pane_panel(p1, NULL)) == NULL)
863                 p1 = p1->parent;
864         if (pan && p1->cx >= 0) {
865                 struct xy curs = pane_mapxy(p1, p, p1->cx, p1->cy, False);
866                 wmove(stdscr, curs.y, curs.x);
867         } else if (p->cx >= 0)
868                 wmove(stdscr, p->cy, p->cx);
869         refresh();
870         record_screen(ci->home);
871         return 1;
872 }
873
874 static struct pane *ncurses_init(struct pane *ed,
875                                  const char *tty, const char *term)
876 {
877         SCREEN *scr;
878         struct pane *p;
879         struct display_data *dd;
880         int rows, cols;
881         FILE *f;
882
883         set_screen(NULL);
884         if (tty)
885                 f = fopen(tty, "r+");
886         else
887                 f = fdopen(1, "r+");
888         if (!f)
889                 return NULL;
890         scr = newterm(term, f, f);
891         if (!scr)
892                 return NULL;
893
894         alloc(dd, pane);
895         dd->scr = scr;
896         dd->scr_file = f;
897         dd->is_xterm = (term && strncmp(term, "xterm", 5) == 0);
898
899         p = pane_register(ed, 1, &ncurses_handle.c, dd);
900         if (!p) {
901                 unalloc(dd, pane);
902                 return NULL;
903         }
904         set_screen(p);
905
906         start_color();
907         use_default_colors();
908         raw();
909         noecho();
910         nonl();
911         timeout(0);
912         set_escdelay(100);
913         intrflush(stdscr, FALSE);
914         keypad(stdscr, TRUE);
915         mousemask(BUTTON1_PRESSED | BUTTON1_RELEASED |
916                   BUTTON2_PRESSED | BUTTON2_RELEASED |
917                   BUTTON3_PRESSED | BUTTON3_RELEASED |
918                   BUTTON4_PRESSED | BUTTON4_RELEASED |
919                   BUTTON5_PRESSED | BUTTON5_RELEASED |
920                   BUTTON_CTRL | BUTTON_SHIFT | BUTTON_ALT |
921                   REPORT_MOUSE_POSITION, NULL);
922         mouseinterval(10);
923
924         getmaxyx(stdscr, rows, cols);
925         pane_resize(p, 0, 0, cols, rows);
926
927         call("editor:request:all-displays", p);
928         if (!prepare_recrep(p)) {
929                 call_comm("event:read", p, &input_handle, fileno(f));
930                 if (!tty)
931                         call_comm("event:signal", p, &handle_winch, SIGWINCH);
932         }
933         return p;
934 }
935
936 REDEF_CMD(handle_winch)
937 {
938         struct pane *p = ci->home;
939         struct display_data *dd = p->data;
940         struct winsize size;
941         ioctl(fileno(dd->scr_file), TIOCGWINSZ, &size);
942         set_screen(p);
943         resize_term(size.ws_row, size.ws_col);
944
945         pane_resize(p, 0, 0, size.ws_row, size.ws_col);
946         return 1;
947 }
948
949 DEF_CMD(force_redraw)
950 {
951         struct pane *p = ci->home;
952
953         set_screen(p);
954         clearok(curscr, 1);
955         return 1;
956 }
957
958 static void ncurses_text(struct pane *p safe, struct pane *display safe,
959                          wchar_t ch, int attr, short x, short y, short cursor)
960 {
961         PANEL *pan;
962         struct pane *p2;
963         cchar_t cc = {};
964
965         if (x < 0 || y < 0)
966                 return;
967
968         set_screen(display);
969         if (cursor) {
970                 if (pane_has_focus(p->z < 0 ? p->parent : p)) {
971                         /* Cursor is in-focus */
972                         struct xy curs = pane_mapxy(p, display, x, y, False);
973                         display->cx = curs.x;
974                         display->cy = curs.y;
975                 } else
976                         /* Cursor here, but not focus */
977                         attr = make_cursor(attr);
978         }
979         cc.attr = attr;
980         cc.chars[0] = ch;
981
982         p2 = p;
983         pan = pane_panel(p2, NULL);
984         while (!pan && p2->parent != p2) {
985                 p2 = p2->parent;
986                 pan = pane_panel(p2, NULL);
987         }
988         if (pan) {
989                 struct xy xy = pane_mapxy(p, p2, x, y, False);
990                 mvwadd_wch(panel_window(pan), xy.y, xy.x, &cc);
991         }
992 }
993
994 static struct namelist {
995         wint_t key;
996         char *name;
997 } key_names[] = {
998         {KEY_DOWN, ":Down"},
999         {KEY_UP, ":Up"},
1000         {KEY_LEFT, ":Left"},
1001         {KEY_RIGHT, ":Right"},
1002         {KEY_HOME, ":Home"},
1003         {KEY_BACKSPACE, ":Backspace"},
1004         {KEY_DL, ":DelLine"},
1005         {KEY_IL, ":InsLine"},
1006         {KEY_DC, ":Del"},
1007         {KEY_IC, ":Ins"},
1008         {KEY_ENTER, ":Enter"},
1009         {KEY_END, ":End"},
1010
1011         {KEY_NPAGE, ":Next"},
1012         {KEY_PPAGE, ":Prior"},
1013
1014         {KEY_SDC, ":S:Del"},
1015         {KEY_SDL, ":S:DelLine"},
1016         {KEY_SEND, ":S:End"},
1017         {KEY_SHOME, ":S:Home"},
1018         {KEY_SLEFT, ":S:Left"},
1019         {KEY_SRIGHT, ":S:Right"},
1020         {KEY_BTAB, ":S:Tab"},
1021
1022         {  0521, ":S:Up"},
1023         {  0520, ":S:Down"},
1024         {  0616, ":S:Prior"},
1025         {  0614, ":S:Next"},
1026         { 01041, ":S:Home"},
1027         { 01060, ":S:End"},
1028         { 01066, ":S:Prior"},
1029         { 01015, ":S:Next"},
1030
1031         { 01027, ":A:S:Home"},
1032         { 01022, ":A:S:End"},
1033         { 01046, ":A:S:Prior"},
1034         { 01047, ":A:S:Next"}, // ??
1035
1036         { 01052, ":A:Prior"},
1037         { 01045, ":A:Next"},
1038         { 01026, ":A:Home"},
1039         { 01021, ":A:End"},
1040         { 01065, ":A:Up"},
1041         { 01014, ":A:Down"},
1042         { 01040, ":A:Left"},
1043         { 01057, ":A:Right"},
1044         { 00411, ":F1"},
1045         { 00412, ":F2"},
1046         { 00413, ":F3"},
1047         { 00414, ":F4"},
1048         { 00415, ":F5"},
1049         { 00416, ":F6"},
1050         { 00417, ":F7"},
1051         { 00420, ":F8"},
1052         { 00421, ":F9"},
1053         { 00422, ":F10"},
1054         { 00423, ":F11"},
1055         { 00424, ":F12"},
1056         { 00425, ":S:F1"},
1057         { 00426, ":S:F2"},
1058         { 00427, ":S:F3"},
1059         { 00430, ":S:F4"},
1060         { 00431, ":S:F5"},
1061         { 00432, ":S:F6"},
1062         { 00433, ":S:F7"},
1063         { 00434, ":S:F8"},
1064         { 00435, ":S:F9"},
1065         { 00436, ":S:F10"},
1066         { 00437, ":S:F11"},
1067         { 00440, ":S:F12"},
1068         {0, NULL}
1069 }, char_names[] = {
1070         {'\e', ":ESC"},
1071         {'\n', ":LF"},
1072         {'\r', ":Enter"},
1073         {'\t', ":Tab"},
1074         {'\177', ":Delete"},
1075         {'\0', ":C- "},
1076         {0, NULL}
1077 };
1078
1079 static char *find_name (struct namelist *l safe, wint_t c)
1080 {
1081         int i;
1082         for (i = 0; l[i].name; i++)
1083                 if (l[i].key == c)
1084                         return l[i].name;
1085         return NULL;
1086 }
1087
1088 static void send_key(int keytype, wint_t c, int alt, struct pane *p safe)
1089 {
1090         struct display_data *dd = p->data;
1091         char *n;
1092         char buf[100];/* FIXME */
1093         char t[5];
1094         char *a = alt ? ":A" : "";
1095
1096         if (keytype == KEY_CODE_YES) {
1097                 n = find_name(key_names, c);
1098                 if (!n)
1099                         sprintf(buf, "%sNcurs-%o", a, c);
1100                 else
1101                         strcat(strcpy(buf, a), n);
1102         } else {
1103                 n = find_name(char_names, c);
1104                 if (n)
1105                         sprintf(buf, "%s%s", a, n);
1106                 else if (c < ' ' || c == 0x7f)
1107                         sprintf(buf, "%s:C-%c",
1108                                 a, c ^ 64);
1109                 else
1110                         sprintf(buf, "%s-%s", a, put_utf8(t, c));
1111         }
1112
1113         dd->last_event = time(NULL);
1114         record_key(p, buf);
1115         call("Keystroke", p, 0, NULL, buf);
1116 }
1117
1118 static void do_send_mouse(struct pane *p safe, int x, int y, char *cmd safe,
1119                           int button, char *mod, int type)
1120 {
1121         int ret;
1122         struct display_data *dd = p->data;
1123
1124         record_mouse(p, cmd, x, y);
1125         ret = call("Mouse-event", p, button, NULL, cmd, type, NULL, mod, x, y);
1126         if (type == 1 && !dd->report_position) {
1127                 if (dd->is_xterm) {
1128                         fprintf(dd->scr_file, "\033[?1002h");
1129                         fflush(dd->scr_file);
1130                 }
1131                 dd->report_position = 1;
1132         } else if (type == 3 && !ret) {
1133                 if (dd->is_xterm) {
1134                         fprintf(dd->scr_file, "\033[?1002l");
1135                         fflush(dd->scr_file);
1136                 }
1137                 dd->report_position = 0;
1138         }
1139 }
1140
1141 static void send_mouse(MEVENT *mev safe, struct pane *p safe)
1142 {
1143         struct display_data *dd = p->data;
1144         int x = mev->x;
1145         int y = mev->y;
1146         int b;
1147         char buf[100];
1148
1149         /* MEVENT has lots of bits.  We want a few numbers */
1150         for (b = 1 ; b <= (NCURSES_MOUSE_VERSION <= 1 ? 3 : 5); b++) {
1151                 mmask_t s = mev->bstate;
1152                 char *action;
1153                 int modf = 0;
1154                 char *mod = "";
1155
1156                 if (s & BUTTON_SHIFT) modf |= 1;
1157                 if (s & BUTTON_CTRL)  modf |= 2;
1158                 if (s & BUTTON_ALT)   modf |= 4;
1159                 switch (modf) {
1160                 case 0: mod = ""; break;
1161                 case 1: mod = ":S"; break;
1162                 case 2: mod = ":C"; break;
1163                 case 3: mod = ":C:S"; break;
1164                 case 4: mod = ":A"; break;
1165                 case 5: mod = ":A:S"; break;
1166                 case 6: mod = ":A:C"; break;
1167                 case 7: mod = ":A:C:S"; break;
1168                 }
1169                 if (BUTTON_PRESS(s, b))
1170                         action = "%s:Press-%d";
1171                 else if (BUTTON_RELEASE(s, b)) {
1172                         action = "%s:Release-%d";
1173                         /* Modifiers only reported on button Press */
1174                         mod = "";
1175                 } else
1176                         continue;
1177                 snprintf(buf, sizeof(buf), action, mod, b);
1178                 dd->last_event = time(NULL);
1179                 do_send_mouse(p, x, y, buf, b, mod, BUTTON_PRESS(s,b) ? 1 : 2);
1180         }
1181         if ((mev->bstate & REPORT_MOUSE_POSITION) &&
1182             dd->report_position)
1183                 /* Motion doesn't update last_event */
1184                 do_send_mouse(p, x, y, ":Motion", 0, "", 3);
1185 }
1186
1187 REDEF_CMD(input_handle)
1188 {
1189         struct pane *p = ci->home;
1190
1191         wint_t c;
1192         int is_keycode;
1193         int have_escape = 0;
1194
1195         if (!(void*)p->data)
1196                 /* already closed */
1197                 return 0;
1198         set_screen(p);
1199         while ((is_keycode = get_wch(&c)) != ERR) {
1200                 if (c == KEY_MOUSE) {
1201                         MEVENT mev;
1202                         while (getmouse(&mev) != ERR)
1203                                 send_mouse(&mev, p);
1204                 } else if (have_escape) {
1205                         send_key(is_keycode, c, 1, p);
1206                         have_escape = 0;
1207                 } else if (c == '\e')
1208                         have_escape = 1;
1209                 else
1210                         send_key(is_keycode, c, 0, p);
1211                 /* Don't know what other code might have done,
1212                  * so re-set the screen
1213                  */
1214                 set_screen(p);
1215         }
1216         if (have_escape)
1217                 send_key(is_keycode, '\e', 0, p);
1218         return 1;
1219 }
1220
1221 DEF_CMD(display_ncurses)
1222 {
1223         struct pane *p;
1224         char *term;
1225
1226         term = pane_attr_get(ci->focus, "TERM");
1227         if (!term)
1228                 term = getenv("TERM");
1229         if (!term)
1230                 term = "xterm-256color";
1231
1232         p = ncurses_init(ci->focus, ci->str, term);
1233         if (p) {
1234                 struct pane *p2 = call_ret(pane, "attach-x11selection", p);
1235                 if (p2)
1236                         p = p2;
1237                 return comm_call(ci->comm2, "callback:display", p);
1238         }
1239         return Efail;
1240 }
1241
1242 void edlib_init(struct pane *ed safe)
1243 {
1244         call_comm("global-set-command", ed, &display_ncurses, 0, NULL,
1245                   "attach-display-ncurses");
1246
1247         nc_map = key_alloc();
1248         key_add(nc_map, "Display:refresh", &force_redraw);
1249         key_add(nc_map, "Display:close", &nc_close_display);
1250         key_add(nc_map, "Display:set-noclose", &nc_set_noclose);
1251         key_add(nc_map, "Close", &nc_close);
1252         key_add(nc_map, "Free", &edlib_do_free);
1253         key_add(nc_map, "pane-clear", &nc_clear);
1254         key_add(nc_map, "text-size", &nc_text_size);
1255         key_add(nc_map, "Draw:text", &nc_draw_text);
1256         key_add(nc_map, "Refresh:size", &nc_refresh_size);
1257         key_add(nc_map, "Refresh:postorder", &nc_refresh_post);
1258         key_add(nc_map, "all-displays", &nc_notify_display);
1259         key_add(nc_map, "Sig:Winch", &handle_winch);
1260         key_add(nc_map, "Notify:Close", &nc_pane_close);
1261 }