]> git.neil.brown.name Git - edlib.git/blob - display-ncurses.c
ncurses/x11selection - clean up properly.
[edlib.git] / display-ncurses.c
1 /*
2  * Copyright Neil Brown ©2015-2019 <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 <string.h>
29 #include <locale.h>
30 #include <ctype.h>
31 #include <signal.h>
32 #include <sys/ioctl.h>
33
34 #include "core.h"
35
36 #ifdef RECORD_REPLAY
37 #include <unistd.h>
38 #include <stdio.h>
39 #include "md5.h"
40 typedef char hash_t[MD5_DIGEST_SIZE*2+1];
41 #endif
42
43 #ifdef __CHECKER__
44 #undef NCURSES_OK_ADDR
45 #define NCURSES_OK_ADDR(p) ((void*)0 != NCURSES_CAST(const void *, (p)))
46 #endif
47
48 struct col_hash;
49
50 struct display_data {
51         SCREEN                  *scr;
52         FILE                    *scr_file;
53         int                     is_xterm;
54         struct xy               cursor;
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_clear(struct pane *p safe, struct pane *display safe,
73                           int attr, short x, short y, short w, short h);
74 static void ncurses_text(struct pane *p safe, struct pane *display safe,
75                          wchar_t ch, int attr, short x, short y, short cursor);
76 DEF_CMD(input_handle);
77 DEF_CMD(handle_winch);
78 static struct map *nc_map;
79 DEF_LOOKUP_CMD(ncurses_handle, nc_map);
80
81 static void set_screen(struct pane *p)
82 {
83         struct display_data *dd;
84         extern void *_nc_globals[100];
85         int i;
86         static int index = -1, offset=0;
87
88         if (!p) {
89                 if (current_screen && index >= 0)
90                         _nc_globals[index] = NULL;
91                 current_screen = NULL;
92                 return;
93         }
94         dd = p->data;
95         if (!dd)
96                 return;
97         if (dd->scr == current_screen)
98                 return;
99
100         if (index == -1) {
101                 index = -2;
102                 for (i=0; i<100; i++)
103                         if (_nc_globals[i] < (void*)stdscr &&
104                             _nc_globals[i]+4*(sizeof(void*)) >= (void*)stdscr) {
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 int parse_event(struct pane *p safe);
122 static int 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 1;
138         }
139         return 0;
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+4];
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;
205                         int l;
206
207                         mvin_wch(r,c,&cc);
208                         getcchar(&cc, wc, &a, &color, NULL);
209                         buf[0] = htole16(color);
210                         for (l = 0; l < CCHARW_MAX && wc[l]; l++)
211                                 buf[l+2] = htole16(wc[l]);
212                         buf[1] = htole16(l);
213                         md5_update(&ctx, (uint8_t*)buf, (l+2) * 2);
214                 }
215         md5_final_txt(&ctx, out);
216         if (dd->log) {
217                 fprintf(dd->log, "Display %d,%d %s", p->w, p->h, out);
218                 strcpy(dd->last_screen, out);
219                 if (dd->cursor.x >= 0)
220                         fprintf(dd->log, " %d,%d", dd->cursor.x, dd->cursor.y);
221                 fprintf(dd->log, "\n");
222         }
223         if (dd->input && dd->next_event == DoCheck) {
224                 call_comm("event:free", p, &abort_replay);
225 //              if (strcmp(dd->last_screen, dd->next_screen) != 0)
226 //                      dd->next_event = DoClose;
227                 call_comm("editor-on-idle", p, &next_evt);
228         }
229 }
230
231 static inline int match(char *line safe, char *w safe)
232 {
233         return strncmp(line, w, strlen(w)) == 0;
234 }
235
236 static char *copy_quote(char *line safe, char *buf safe)
237 {
238         char q;
239         while (*line == ' ')
240                 line++;
241         q = *line++;
242         if (q != '"' && q != '\'' && q != '/')
243                 return NULL;
244         while (*line != q && *line)
245                 *buf++ = *line++;
246         if (!*line)
247                 return NULL;
248         *buf = '\0';
249         return line+1;
250 }
251
252 static char *get_coord(char *line safe, struct xy *co safe)
253 {
254         long v;
255         char *ep;
256
257         while (*line == ' ')
258                 line ++;
259         v = strtol(line, &ep, 10);
260         if (!ep || ep == line || *ep != ',')
261                 return NULL;
262         co->x = v;
263         line = ep+1;
264         v = strtol(line, &ep, 10);
265         if (!ep || ep == line || (*ep && *ep != ' ' && *ep != '\n'))
266                 return NULL;
267         co->y = v;
268         return ep;
269 }
270
271 static char *get_hash(char *line safe, hash_t hash safe)
272 {
273         int i;
274         while (*line == ' ')
275                 line++;
276         for (i = 0; i < MD5_DIGEST_SIZE*2 && *line; i++)
277                 hash[i] = *line++;
278         if (!*line)
279                 return NULL;
280         return line;
281 }
282
283 REDEF_CMD(abort_replay)
284 {
285         struct display_data *dd = ci->home->data;
286
287         dd->next_event = DoClose;
288         return next_evt_func(ci);
289 }
290
291 static int parse_event(struct pane *p safe)
292 {
293         struct display_data *dd = p->data;
294         char line[80];
295
296         line[79] = 0;
297         dd->next_event = DoNil;
298         if (!dd->input ||
299             fgets(line, sizeof(line)-1, dd->input) == NULL)
300                 ;
301         else if (match(line, "Key ")) {
302                 if (!copy_quote(line+4, dd->event_info))
303                         return 0;
304                 dd->next_event = DoKey;
305         } else if (match(line, "Mouse ")) {
306                 char *f = copy_quote(line+6, dd->event_info);
307                 if (!f)
308                         return 0;
309                 f = get_coord(f, &dd->event_pos);
310                 if (!f)
311                         return 0;
312                 dd->next_event = DoMouse;
313         } else if (match(line, "Display ")) {
314                 char *f = get_coord(line+8, &dd->event_pos);
315                 if (!f)
316                         return 0;
317                 f = get_hash(f, dd->next_screen);
318                 dd->next_event = DoCheck;
319         } else if (match(line, "Close")) {
320                 dd->next_event = DoClose;
321         }
322
323         if (dd->next_event != DoCheck)
324                 call_comm("editor-on-idle", p, &next_evt);
325         else
326                 call_comm("event:timer", p, &abort_replay, 10*1000);
327         return 1;
328 }
329
330 REDEF_CMD(next_evt)
331 {
332         struct pane *p = ci->home;
333         struct display_data *dd = p->data;
334         int button = 0, type = 0;
335         char *delay;
336
337         delay = getenv("EDLIB_REPLAY_DELAY");
338         if (delay && dd->next_event != DoCheck)
339                 usleep(atoi(delay)*1000);
340
341         switch(dd->next_event) {
342         case DoKey:
343                 record_key(p, dd->event_info);
344                 call("Keystroke", p, 0, NULL, dd->event_info);
345                 break;
346         case DoMouse:
347                 record_mouse(p, dd->event_info, dd->event_pos.x,
348                              dd->event_pos.y);
349                 if (strstr(dd->event_info, ":Press"))
350                         type = 1;
351                 else if (strstr(dd->event_info, ":Release"))
352                         type = 2;
353                 else if (strstr(dd->event_info, ":Motion"))
354                         type = 3;
355                 if (type == 1 || type == 2) {
356                         char *e = dd->event_info + strlen(dd->event_info) - 1;
357                         button = atoi(e);
358                 }
359                 call("Mouse-event", p, button, NULL, dd->event_info,
360                      type, NULL, NULL,
361                      dd->event_pos.x, dd->event_pos.y);
362                 break;
363         case DoCheck:
364                 /* No point checking, just do a diff against new trace log. */
365                 /* not; (strcmp(dd->next_screen, dd->last_screen) != 0) */
366                 break;
367         case DoClose:
368                 call("event:deactivate", p);
369                 pane_close(p);
370                 return 1;
371         case DoNil:
372                 call_comm("event:read", p, &input_handle, 0);
373                 call_comm("event:signal", p, &handle_winch, SIGWINCH);
374                 return 1;
375         }
376         parse_event(p);
377         return 1;
378 }
379 #else
380 static inline int  prepare_recrep(struct pane *p safe) {return 0;}
381 static inline void record_key(struct pane *p safe, char *key) {}
382 static inline void record_mouse(struct pane *p safe, char *key safe,
383                                 int x, int y) {}
384 static inline void record_screen(struct pane *p safe) {}
385 static inline void close_recrep(struct pane *p safe) {}
386 #endif
387
388 DEF_CMD(nc_refresh)
389 {
390         struct pane *p = ci->home;
391
392         call("Sig:Winch", p);
393         set_screen(p);
394         clear();
395         pane_damaged(p,  DAMAGED_SIZE);
396         return 1;
397 }
398
399 DEF_CMD(cnt_disp)
400 {
401         struct call_return *cr = container_of(ci->comm, struct call_return, c);
402
403         cr->i += 1;
404         return 1;
405 }
406
407 DEF_CMD(nc_close_display)
408 {
409         /* If this is only display, then refuse to close this one */
410         struct call_return cr;
411         struct display_data *dd = ci->home->data;
412
413         if (dd->noclose) {
414                 call("Message", ci->focus, 0, NULL, dd->noclose);
415                 return 1;
416         }
417
418         cr.c = cnt_disp;
419         cr.i = 0;
420         call_comm("editor:notify:all-displays", ci->focus, &cr.c);
421         if (cr.i > 1)
422                 pane_close(ci->home);
423         else
424                 call("Message", ci->focus, 0, NULL,
425                      "Cannot close only window.");
426         return 1;
427 }
428
429 DEF_CMD(nc_set_noclose)
430 {
431         struct display_data *dd = ci->home->data;
432
433         free(dd->noclose);
434         dd->noclose = NULL;
435         if (ci->str)
436                 dd->noclose = strdup(ci->str);
437         return 1;
438 }
439
440 static void ncurses_end(struct pane *p safe)
441 {
442         set_screen(p);
443         close_recrep(p);
444         nl();
445         endwin();
446 }
447
448 /*
449  * hash table for colours and pairs
450  * key is r,g,b (0-1000) in 10bit fields,
451  * or fg,bg in 16 bit fields with bit 31 set
452  * content is colour number of colour pair number.
453  * We never delete entries, unless we delete everything.
454  */
455
456 struct chash {
457         struct chash *next;
458         int key, content;
459 };
460 #define COL_KEY(r,g,b) ((r<<20) | (g<<10) | b)
461 #define PAIR_KEY(fg, bg) ((1<<31) | (fg<<16) | bg)
462 #define hash_key(k) ((((k) * 0x61C88647) >> 20) & 0xff)
463
464 struct col_hash {
465         int next_col, next_pair;
466         struct chash *tbl[256];
467 };
468
469 static struct col_hash *safe hash_init(struct display_data *dd safe)
470 {
471         if (!dd->col_hash) {
472                 dd->col_hash = malloc(sizeof(*dd->col_hash));
473                 memset(dd->col_hash, 0, sizeof(*dd->col_hash));
474                 dd->col_hash->next_col = 16;
475                 dd->col_hash->next_pair = 1;
476         }
477         return dd->col_hash;
478 }
479
480 static void hash_free(struct display_data *dd safe)
481 {
482         int h;
483         struct chash *c;
484         struct col_hash *ch;
485
486         ch = dd->col_hash;
487         if (!ch)
488                 return;
489         for (h = 0; h < 255; h++)
490                 while ((c = ch->tbl[h]) != NULL) {
491                         ch->tbl[h] = c->next;
492                         free(c);
493                 }
494         free(ch);
495         dd->col_hash = NULL;
496 }
497
498 static int find_col(struct display_data *dd safe, int rgb[])
499 {
500         if (0 /* dynamic colours */) {
501                 struct col_hash *ch = hash_init(dd);
502                 int k = COL_KEY(rgb[0], rgb[1], rgb[2]);
503                 int h = hash_key(k);
504                 struct chash *c;
505
506                 for (c = ch->tbl[h]; c; c = c->next)
507                         if (c->key == k)
508                                 return c->content;
509                 c = malloc(sizeof(*c));
510                 c->key = k;
511                 c->content = ch->next_col++;
512                 c->next = ch->tbl[h];
513                 ch->tbl[h] = c;
514                 init_color(c->content, rgb[0], rgb[1], rgb[2]);
515                 return c->content;
516         } else {
517                 /* If colour is grey, map to 1 of 26 for 0, 232 to 255, 15
518                  * The 24 grey shades have bit values from 8 to 238, so the
519                  * gap to white is a little bigger, but that probably doesn't
520                  * matter.
521                  * Otherwise map to 6x6x6 rgb cube from 16
522                  * Actual colours are biased bright, at 0,95,135,175,215,255
523                  * with a 95 gap at bottom and 40 elsewhere.
524                  * So we divide 5 and 2 half ranges, and merge bottom 2.
525                  */
526                 int c = 0;
527                 int h;
528
529                 //printf("want %d,%d,%d\n", rgb[0], rgb[1], rgb[2]);
530                 if (abs(rgb[0] - rgb[1]) < 10 &&
531                     abs(rgb[1] - rgb[2]) < 10) {
532                         /* grey - within 1% */
533                         int v = (rgb[0] + rgb[1] + rgb[2]) / 3;
534
535                         /* We divide the space in 24 ranges surrounding
536                          * the grey values, and 2 half-ranges near black
537                          * and white.  So add half a range - 1000/50 -
538                          * then divide by 1000/25 to get a number from 0 to 25.
539                          */
540                         v = (v + 1000/50) / (1000/25);
541                         if (v == 0)
542                                 return 0; /* black */
543                         if (v >= 25)
544                                 return 15; /* white */
545                         //printf(" grey %d\n", v + 231);
546                         /* grey shades are from 232 to 255 inclusive */
547                         return v + 231;
548                 }
549                 for (h = 0; h < 3; h++) {
550                         int v = rgb[h];
551
552                         v = (v + 1000/12) / (1000/6);
553                         /* v is from 0 to 6, we want up to 5
554                          * with 0 and 1 merged
555                          */
556                         if (v)
557                                 v -= 1;
558
559                         c = c * 6 + v;
560                 }
561                 //printf(" color %d\n", c + 16);
562                 return c + 16;
563         }
564 }
565
566 static int to_pair(struct display_data *dd safe, int fg, int bg)
567 {
568         struct col_hash *ch = hash_init(dd);
569         int k = PAIR_KEY(fg, bg);
570         int h = hash_key(k);
571         struct chash *c;
572
573         for (c = ch->tbl[h]; c; c = c->next)
574                 if (c->key == k)
575                         return c->content;
576         c = malloc(sizeof(*c));
577         c->key = k;
578         c->content = ch->next_pair++;
579         c->next = ch->tbl[h];
580         ch->tbl[h] = c;
581         init_pair(c->content, fg, bg);
582         return c->content;
583 }
584
585
586 static int cvt_attrs(struct pane *home safe, const char *attrs)
587 {
588         struct display_data *dd = home->data;
589
590         int attr = 0;
591         char tmp[40];
592         const char *a;
593         int fg = COLOR_BLACK;
594         int bg = COLOR_WHITE+8;
595
596         if (!attrs)
597                 return 0;
598         set_screen(home);
599         a = attrs;
600         while (a && *a) {
601                 const char *c;
602                 if (*a == ',') {
603                         a++;
604                         continue;
605                 }
606                 c = strchr(a, ',');
607                 if (!c)
608                         c = a+strlen(a);
609                 strncpy(tmp, a, c-a);
610                 tmp[c-a] = 0;
611                 if (strcmp(tmp, "inverse")==0) attr |= A_STANDOUT;
612                 else if (strcmp(tmp, "bold")==0) attr |= A_BOLD;
613                 else if (strcmp(tmp, "underline")==0) attr |= A_UNDERLINE;
614                 else if (strncmp(tmp, "fg:", 3) == 0) {
615                         struct call_return cr =
616                                 call_ret(all, "colour:map", home,
617                                          0, NULL, tmp+3);
618                         int rgb[3] = {cr.i, cr.i2, cr.x};
619                         fg = find_col(dd, rgb);
620                 } else if (strncmp(tmp, "bg:", 3) == 0) {
621                         struct call_return cr =
622                                 call_ret(all, "colour:map", home,
623                                          0, NULL, tmp+3);
624                         int rgb[3] = {cr.i, cr.i2, cr.x};
625                         bg = find_col(dd, rgb);
626                 }
627                 a = c;
628         }
629         if (fg != COLOR_BLACK || bg != COLOR_WHITE+8) {
630                 int p = to_pair(dd, fg, bg);
631                 attr |= COLOR_PAIR(p);
632         }
633         return attr;
634 }
635
636 static int make_cursor(int attr)
637 {
638         return attr ^ A_UNDERLINE;
639 }
640
641 DEF_CMD(nc_notify_display)
642 {
643         struct display_data *dd = ci->home->data;
644         comm_call(ci->comm2, "callback:display", ci->home, dd->last_event);
645         return 0;
646 }
647
648 DEF_CMD(nc_close)
649 {
650         struct pane *p = ci->home;
651         struct display_data *dd = p->data;
652         ncurses_end(p);
653         hash_free(dd);
654         fclose(dd->scr_file);
655         return 1;
656 }
657
658 DEF_CMD(nc_clear)
659 {
660         struct pane *p = ci->home;
661         int attr = cvt_attrs(p, ci->str2?:ci->str);
662
663         ncurses_clear(ci->focus, p, attr, 0, 0, 0, 0);
664         pane_damaged(p, DAMAGED_POSTORDER);
665         return 1;
666 }
667
668 DEF_CMD(nc_text_size)
669 {
670         int max_space = ci->num;
671         int max_bytes = 0;
672         int size = 0;
673         int offset = 0;
674         mbstate_t mbs = {};
675         const char *str = ci->str;
676         int len;
677
678         if (!str)
679                 return Enoarg;
680         len = strlen(str);
681         while (str[offset] != 0) {
682                 wchar_t wc;
683                 int skip = mbrtowc(&wc, str+offset, len-offset, &mbs);
684                 if (skip < 0)
685                         break;
686                 offset += skip;
687                 skip = wcwidth(wc);
688                 if (skip < 0)
689                         break;
690                 size += skip;
691                 if (size <= max_space)
692                         max_bytes = offset;
693         }
694         return comm_call(ci->comm2, "callback:size", ci->focus,
695                          max_bytes, NULL, NULL,
696                          0, NULL, NULL, size, 1);
697 }
698
699 DEF_CMD(nc_draw_text)
700 {
701         struct pane *p = ci->home;
702         int attr = cvt_attrs(p, ci->str2);
703         int cursor_offset = ci->num;
704         short offset = 0;
705         short x = ci->x, y = ci->y;
706         mbstate_t mbs = {};
707         const char *str = ci->str;
708         int len;
709
710         if (!str)
711                 return Enoarg;
712         set_screen(p);
713         len = strlen(str);
714         while (str[offset] != 0) {
715                 wchar_t wc;
716                 int skip = mbrtowc(&wc, str+offset, len-offset, &mbs);
717                 int width;
718                 if (skip < 0)
719                         break;
720                 width = wcwidth(wc);
721                 if (width < 0)
722                         break;
723                 if (cursor_offset >= offset &&
724                     cursor_offset < offset + skip)
725                         ncurses_text(ci->focus, p, wc, attr, x, y, 1);
726                 else
727                         ncurses_text(ci->focus, p, wc, attr, x, y, 0);
728                 offset += skip;
729                 x += width;
730         }
731         if (offset == cursor_offset)
732                 ncurses_text(ci->focus, p, ' ', 0, x, y, 1);
733         pane_damaged(p, DAMAGED_POSTORDER);
734         return 1;
735 }
736
737 DEF_CMD(nc_refresh_size)
738 {
739         struct pane *p = ci->home;
740
741         set_screen(p);
742         getmaxyx(stdscr, p->h, p->w);
743         return 0;
744 }
745
746 DEF_CMD(nc_refresh_post)
747 {
748         struct pane *p = ci->home;
749         struct display_data *dd = p->data;
750         set_screen(p);
751         if (dd->cursor.x >= 0)
752                 move(dd->cursor.y, dd->cursor.x);
753         refresh();
754         record_screen(p);
755         return 1;
756 }
757
758 static struct pane *ncurses_init(struct pane *ed,
759                                  const char *tty, const char *term)
760 {
761         SCREEN *scr;
762         struct pane *p;
763         struct display_data *dd;
764         FILE *f;
765
766         set_screen(NULL);
767         if (tty)
768                 f = fopen(tty, "r+");
769         else
770                 f = fdopen(1, "r+");
771         if (!f)
772                 return NULL;
773         scr = newterm(term, f, f);
774         if (!scr)
775                 return NULL;
776
777         alloc(dd, pane);
778         dd->scr = scr;
779         dd->scr_file = f;
780         dd->cursor.x = dd->cursor.y = -1;
781         dd->is_xterm =  (term && strncmp(term, "xterm", 5) == 0);
782
783         p = pane_register(ed, 0, &ncurses_handle.c, dd);
784         set_screen(p);
785
786         start_color();
787         use_default_colors();
788         raw();
789         noecho();
790         nonl();
791         timeout(0);
792         set_escdelay(100);
793         intrflush(stdscr, FALSE);
794         keypad(stdscr, TRUE);
795         mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL);
796         mouseinterval(0);
797
798         getmaxyx(stdscr, p->h, p->w);
799
800         call("editor:request:all-displays", p);
801         if (!prepare_recrep(p)) {
802                 call_comm("event:read", p, &input_handle, fileno(f));
803                 if (!tty)
804                         call_comm("event:signal", p, &handle_winch, SIGWINCH);
805         }
806         pane_damaged(p, DAMAGED_SIZE);
807         return p;
808 }
809
810 REDEF_CMD(handle_winch)
811 {
812         struct pane *p = ci->home;
813         struct display_data *dd = p->data;
814         struct winsize size;
815         ioctl(fileno(dd->scr_file), TIOCGWINSZ, &size);
816         set_screen(p);
817         resize_term(size.ws_row, size.ws_col);
818
819         clear();
820         pane_damaged(p, DAMAGED_SIZE);
821         return 1;
822 }
823
824 static void ncurses_clear(struct pane *p safe, struct pane *display safe,
825                           int attr, short x, short y, short w, short h)
826 {
827         short r, c;
828         short w0, h0;
829
830         if (w == 0)
831                 w = p->w - x;
832         if (h == 0)
833                 h = p->h - y;
834         pane_absxy(p, &x, &y, &w, &h);
835         w0 = w; h0 = h;
836         if (pane_masked(display, x, y, p->abs_z, &w0, &h0))
837                 w0 = h0 = 0;
838
839         set_screen(display);
840         attrset(attr);
841         for (r = y; r < y+h; r++)
842                 for (c = x; c < x+w; c++)
843                         if ((r < y+h0 && c < x+w0) ||
844                             !pane_masked(display, c, r, p->abs_z, NULL, NULL))
845                                 mvaddch(r, c, ' ');
846 }
847
848 static void ncurses_text(struct pane *p safe, struct pane *display safe,
849                          wchar_t ch, int attr, short x, short y, short cursor)
850 {
851         struct display_data *dd;
852         cchar_t cc = {};
853         short w=1, h=1;
854
855         if (x < 0 || y < 0)
856                 return;
857         if (cursor) {
858                 struct pane *p2 = p;
859                 cursor = 2;
860                 while (p2->parent != p2 && p2 != display) {
861                         if (p2->parent->focus != p2 && p2->z >= 0)
862                                 cursor = 1;
863                         p2 = p2->parent;
864                 }
865         }
866
867         pane_absxy(p, &x, &y, &w, &h);
868         if (w < 1 || h < 1)
869                 return;
870
871         if (pane_masked(display, x, y, p->abs_z, NULL, NULL))
872                 return;
873
874         dd = display->data;
875         set_screen(display);
876         if (cursor == 2) {
877                 /* Cursor is in-focus */
878                 dd->cursor.x = x;
879                 dd->cursor.y = y;
880         }
881         if (cursor == 1)
882                 /* Cursor here, but not focus */
883                 attr = make_cursor(attr);
884         cc.attr = attr;
885         cc.chars[0] = ch;
886
887         mvadd_wch(y, x, &cc);
888 }
889
890 static struct namelist {
891         wint_t key;
892         char *name;
893 } key_names[] = {
894         {KEY_DOWN, ":Down"},
895         {KEY_UP, ":Up"},
896         {KEY_LEFT, ":Left"},
897         {KEY_RIGHT, ":Right"},
898         {KEY_HOME, ":Home"},
899         {KEY_BACKSPACE, ":Backspace\037:C-H\037:C-h"},
900         {KEY_DL, ":DelLine"},
901         {KEY_IL, ":InsLine"},
902         {KEY_DC, ":Del"},
903         {KEY_IC, ":Ins"},
904         {KEY_ENTER, ":Enter\037:C-M\037:C-m"},
905         {KEY_END, ":End"},
906
907         {KEY_NPAGE, ":Next"},
908         {KEY_PPAGE, ":Prior"},
909
910         {KEY_SDC, ":S:Del"},
911         {KEY_SDL, ":S:DelLine"},
912         {KEY_SEND, ":S:End"},
913         {KEY_SHOME, ":S:Home"},
914         {KEY_SLEFT, ":S:Left"},
915         {KEY_SRIGHT, ":S:Right"},
916         {KEY_BTAB, ":S:Tab"},
917
918         { 01057, ":M:Prior"},
919         { 01051, ":M:Next"},
920         { 01072, ":M:Up"},
921         { 01061, ":M:Down"},
922         { 01042, ":M:Left"},
923         { 01064, ":M:Right"},
924         {0, NULL}
925 }, char_names[] = {
926         {'\e', ":ESC"},
927         {'\n', ":LF"},
928         {'\r', ":Enter"},
929         {'\t', ":Tab"},
930         {'\177', ":Delete"},
931         {'\0', ":C- "},
932         {0, NULL}
933 };
934
935 static char *find_name (struct namelist *l safe, wint_t c)
936 {
937         int i;
938         for (i = 0; l[i].name; i++)
939                 if (l[i].key == c)
940                         return l[i].name;
941         return NULL;
942 }
943
944 static void send_key(int keytype, wint_t c, int meta, struct pane *p safe)
945 {
946         struct display_data *dd = p->data;
947         char *n;
948         char buf[100];/* FIXME */
949         char *m = meta ? ":M" : "";
950
951         if (keytype == KEY_CODE_YES) {
952                 n = find_name(key_names, c);
953                 if (!n)
954                         sprintf(buf, "%sNcurs-%o", m, c);
955                 else
956                         strcat(strcpy(buf, m), n);
957         } else {
958                 n = find_name(char_names, c);
959                 if (n)
960                         sprintf(buf, "%s%s\037%s:C-%c\037%s:C-%c",
961                                 m, n,
962                                 m, c+64,
963                                 m, c+96);
964                 else if (c < ' ')
965                         sprintf(buf, "%s:C-%c\037%s:C-%c",
966                                 m, c+64, m, c+96);
967                 else
968                         sprintf(buf, "%s-%lc", m, c);
969         }
970
971         dd->last_event = time(NULL);
972         record_key(p, buf);
973         call("Keystroke", p, 0, NULL, buf);
974 }
975
976 static void do_send_mouse(struct pane *p safe, int x, int y, char *cmd safe,
977                           int button, char *mod, int type)
978 {
979         int ret;
980         struct display_data *dd = p->data;
981
982         record_mouse(p, cmd, x, y);
983         ret = call("Mouse-event", p, button, NULL, cmd, type, NULL, mod, x, y);
984         if (type == 1 && !dd->report_position) {
985                 if (dd->is_xterm) {
986                         fprintf(dd->scr_file, "\033[?1002h");
987                         fflush(dd->scr_file);
988                 }
989                 dd->report_position = 1;
990         } else if (type == 3 && !ret) {
991                 if (dd->is_xterm) {
992                         fprintf(dd->scr_file, "\033[?1002l");
993                         fflush(dd->scr_file);
994                 }
995                 dd->report_position = 0;
996         }
997 }
998
999 static void send_mouse(MEVENT *mev safe, struct pane *p safe)
1000 {
1001         struct display_data *dd = p->data;
1002         int x = mev->x;
1003         int y = mev->y;
1004         int b;
1005         char buf[100];
1006
1007         /* MEVENT has lots of bits.  We want a few numbers */
1008         for (b = 1 ; b <= (NCURSES_MOUSE_VERSION <= 1 ? 3 : 5); b++) {
1009                 mmask_t s = mev->bstate;
1010                 char *action;
1011                 int modf = 0;
1012                 char *mod = "";
1013
1014                 if (s & BUTTON_SHIFT) modf |= 1;
1015                 if (s & BUTTON_CTRL)  modf |= 2;
1016                 if (s & BUTTON_ALT)   modf |= 4;
1017                 switch (modf) {
1018                 case 0: mod = ""; break;
1019                 case 1: mod = ":S"; break;
1020                 case 2: mod = ":C"; break;
1021                 case 3: mod = ":C:S"; break;
1022                 case 4: mod = ":M"; break;
1023                 case 5: mod = ":M:S"; break;
1024                 case 6: mod = ":M:C"; break;
1025                 case 7: mod = ":M:C:S"; break;
1026                 }
1027                 if (BUTTON_PRESS(s, b))
1028                         action = "%s:Press-%d";
1029                 else if (BUTTON_RELEASE(s, b)) {
1030                         action = "%s:Release-%d";
1031                         /* Modifiers only reported on button Press */
1032                         mod = "";
1033                 } else
1034                         continue;
1035                 snprintf(buf, sizeof(buf), action, mod, b);
1036                 dd->last_event = time(NULL);
1037                 do_send_mouse(p, x, y, buf, b, mod, BUTTON_PRESS(s,b) ? 1 : 2);
1038         }
1039         if ((mev->bstate & REPORT_MOUSE_POSITION) &&
1040             dd->report_position)
1041                 /* Motion doesn't update last_event */
1042                 do_send_mouse(p, x, y, ":Motion", 0, "", 3);
1043 }
1044
1045 REDEF_CMD(input_handle)
1046 {
1047         struct pane *p = ci->home;
1048
1049         wint_t c;
1050         int is_keycode;
1051         int have_escape = 0;
1052
1053         if (!(void*)p->data)
1054                 /* already closed */
1055                 return 0;
1056         set_screen(p);
1057         while ((is_keycode = get_wch(&c)) != ERR) {
1058                 if (c == KEY_MOUSE) {
1059                         MEVENT mev;
1060                         while (getmouse(&mev) != ERR)
1061                                 send_mouse(&mev, p);
1062                 } else if (have_escape) {
1063                         send_key(is_keycode, c, 1, p);
1064                         have_escape = 0;
1065                 } else if (c == '\e')
1066                         have_escape = 1;
1067                 else
1068                         send_key(is_keycode, c, 0, p);
1069                 /* Don't know what other code might have done,
1070                  * so re-set the screen
1071                  */
1072                 set_screen(p);
1073         }
1074         if (have_escape)
1075                 send_key(is_keycode, '\e', 0, p);
1076         return 1;
1077 }
1078
1079 DEF_CMD(display_ncurses)
1080 {
1081         struct pane *p;
1082         char *term;
1083
1084         term = pane_attr_get(ci->focus, "TERM");
1085         if (!term)
1086                 term = getenv("TERM");
1087         if (!term)
1088                 term = "xterm-256color";
1089
1090         p = ncurses_init(ci->focus, ci->str, term);
1091         if (p) {
1092                 struct pane *p2 = call_ret(pane, "attach-x11selection", p);
1093                 if (p2)
1094                         p = p2;
1095                 return comm_call(ci->comm2, "callback:display", p);
1096         }
1097         return Efail;
1098 }
1099
1100 void edlib_init(struct pane *ed safe)
1101 {
1102         call_comm("global-set-command", ed, &display_ncurses, 0, NULL,
1103                   "attach-display-ncurses");
1104
1105         nc_map = key_alloc();
1106         key_add(nc_map, "Display:refresh", &nc_refresh);
1107         key_add(nc_map, "Display:close", &nc_close_display);
1108         key_add(nc_map, "Display:set-noclose", &nc_set_noclose);
1109         key_add(nc_map, "Close", &nc_close);
1110         key_add(nc_map, "Free", &edlib_do_free);
1111         key_add(nc_map, "pane-clear", &nc_clear);
1112         key_add(nc_map, "text-size", &nc_text_size);
1113         key_add(nc_map, "Draw:text", &nc_draw_text);
1114         key_add(nc_map, "Refresh:size", &nc_refresh_size);
1115         key_add(nc_map, "Refresh:postorder", &nc_refresh_post);
1116         key_add(nc_map, "all-displays", &nc_notify_display);
1117         key_add(nc_map, "Sig:Winch", &handle_winch);
1118 }