1 #include <ncurses.h>
2 #include "term.h"
3 #include <math.h>
4 #include <stdlib.h>
5 #include <string.h>
6 
7 
8 // As a rule, everything in term.c is the result of gradual evolutionary
9 // change.  It's messy.
10 
11 #define COLORING(fg,bg) (((fg) & 0x0f) | (((bg) & 0x07) << 4))
12 #define COLOR_FG(color,fg) (((fg) & 0x0f) + ((color) & 0x70))
13 #define COLOR_BG(color,bg) (((color) & 0x0f) + (((bg) & 0x07) << 4))
14 #define COLOR_INDEX(color) (1 + ((color)&0x07) + (((color) >> 1) & 0x38))
15 #define COLOR_ATTR(color) (COLOR_PAIR(COLOR_INDEX(color)) | (((color)&0x08) ? A_BOLD : 0))
16 
17 
18 static struct { int curses, color; } videomode = { 0, 0 };
19 
20 static struct { int width, height; } minsize = { 80, 24 };
21 
22 static void init_coersion();
23 
24 
25 // 256 color mode stuff
26 static void initialize_prs();
27 
28 typedef struct {
29     int r, g, b, idx;
30 } intcolor;
31 
32 struct {
33     intcolor fore, back;
34     int count, next;
35 } prs[256];
36 
37 
38 typedef struct {
39     int ch, pair, shuffle;
40     intcolor fore, back;
41 } pairmode_cell;
42 
43 pairmode_cell *cell_buffer;
44 
45 enum {
46     coerce_16,
47     coerce_256,
48     truecolor
49 } colormode;
50 
51 int is_xterm;
52 
53 
54 //
55 
preparecolor()56 static void preparecolor ( ) {
57     // sixteen color mode colors (we use these in 256-color mode, too)
58     static int pairParts[8] = {
59         COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW,
60         COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE
61     };
62 
63     int fg, bg;
64     for (bg=0; bg<8; bg++) {
65         for (fg=0; fg<8; fg++) {
66             init_pair(
67                 COLOR_INDEX(COLORING(fg, bg)),
68                 pairParts[fg], pairParts[bg]
69             );
70         }
71     }
72 
73     char *env = getenv("COLORTERM");
74     if (env && ((strncmp(env, "truecolor", 9) == 0) || (strncmp(env, "24bit", 5) == 0))) {
75         colormode = truecolor;
76     } else if (COLORS >= 256) {
77         colormode = coerce_256;
78     } else {
79         colormode = coerce_16;
80     }
81 }
82 
term_title(const char * title)83 static void term_title(const char *title) {
84     if (is_xterm) {
85         printf ("\033]2;%s\007", title); // ESC ]0; title BEL
86     }
87 }
88 
term_title_pop()89 static void term_title_pop() {
90     if (is_xterm) {
91         term_title("Terminal");
92         printf ("\033[22;2t");
93     }
94 }
term_title_push()95 static void term_title_push() {
96     if (is_xterm) {
97         printf ("\033[23;2t");
98     }
99 }
100 
term_set_size(int h,int w)101 static void term_set_size(int h, int w) {
102     // works in gnome-terminal, but not xterm; causes trouble for maximized windows
103     if (is_xterm) {
104         // first, try resizing the height, in case only that is supported
105         printf ("\033[%dt", (h > 24 ? h : 24));
106 
107         // then try resizing both, in case we can
108         printf ("\033[8;%d;%dt", h, w);
109 
110         // then refresh so ncurses knows about it
111         refresh( );
112     }
113 }
114 
term_show_scrollbar(int show)115 static void term_show_scrollbar(int show) {
116     // works in xterm, but not gnome-terminal
117     if (is_xterm) {
118         if (show) {
119             printf ("\033[?30h");
120         } else {
121             printf ("\033[?30l");
122         }
123     }
124 }
125 
curses_init()126 static int curses_init( ) {
127     if (videomode.curses) return 0;
128 
129     // isterm?
130     initscr( );
131     if (!has_colors( )) {
132         endwin( );
133         fprintf (stderr, "Your terminal has no color support.\n");
134         return 1;
135     }
136 
137     start_color( );
138     clear( );
139     curs_set( 0 );
140     refresh( );
141     leaveok(stdscr, TRUE);
142     preparecolor( );
143     cbreak( );
144     noecho( );
145 
146     nodelay(stdscr, TRUE);
147     meta(stdscr, TRUE);
148     keypad(stdscr, TRUE);
149 
150     mousemask(BUTTON1_PRESSED | BUTTON1_RELEASED | REPORT_MOUSE_POSITION | BUTTON_SHIFT | BUTTON_CTRL, NULL);
151     mouseinterval(0); //do no click processing, thank you
152 
153     videomode.curses = 1;
154 
155     getmaxyx(stdscr, Term.height, Term.width);
156 
157     return 1;
158 }
159 
160 
term_start()161 static int term_start() {
162     char *term = getenv("TERM");
163     is_xterm = (strncmp(term, "xterm", 5) == 0) || (strncmp(term, "gnome", 5) == 0) || (strncmp(term, "st", 2) == 0);
164 
165     term_title_push();
166     term_show_scrollbar(0);
167 
168     int ok = curses_init();
169     init_coersion();
170 
171     return ok;
172 }
173 
term_end()174 static void term_end() {
175     term_title_pop();
176     clear();
177     refresh();
178     endwin();
179 }
180 
181 typedef struct CIE {
182     float X, Y, Z;
183     float x, y, z;
184 } CIE;
185 
186 typedef struct Lab {
187     float L, a, b;
188 } Lab;
189 
190 #define DARK 0.0
191 #define DIM 0.1
192 #define MID 0.3
193 #define HALFBRIGHT 0.5
194 #define BRIGHT 0.9
195 
196 fcolor palette[16] = {
197     {DARK, DARK, DARK},
198     {MID, DARK, DARK},
199     {DARK, MID, DARK},
200     {MID, .8 * MID, DIM},
201     {DARK, DARK, MID},
202     {MID + DIM, DARK, MID},
203     {DARK, MID, MID},
204     {HALFBRIGHT, HALFBRIGHT, HALFBRIGHT},
205 
206     {MID, MID, MID},
207     {BRIGHT, DARK, DARK},
208     {DARK, BRIGHT, DARK},
209     {BRIGHT, BRIGHT, DARK},
210     {HALFBRIGHT, MID, BRIGHT},
211     {BRIGHT, HALFBRIGHT, BRIGHT},
212     {DARK, BRIGHT, BRIGHT},
213     {BRIGHT, BRIGHT, BRIGHT}
214 };
215 
216 CIE ciePalette[16];
217 Lab labPalette[16];
218 CIE adamsPalette[16];
219 
220 static CIE white;
221 
toCIE(fcolor c)222 static CIE toCIE(fcolor c) {
223     double a = 0.055;
224 
225     // http://en.wikipedia.org/wiki/SRGB_color_space#The_reverse_transformation
226 
227     c.r = c.r <= 0.04045 ? c.r / 12.92 : pow((c.r + a) / (1 + a), 2.4);
228     c.g = c.g <= 0.04045 ? c.g / 12.92 : pow((c.g + a) / (1 + a), 2.4);
229     c.b = c.b <= 0.04045 ? c.b / 12.92 : pow((c.b + a) / (1 + a), 2.4);
230 
231     CIE cie;
232     cie.X = 0.4124 * c.r + 0.3576 * c.g + 0.1805 * c.b;
233     cie.Y = 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b;
234     cie.Z = 0.0193 * c.r + 0.1192 * c.g + 0.9505 * c.b;
235 
236     float sum = cie.X + cie.Y + cie.Z;
237     if (sum == 0.0) sum = 1.0;
238     cie.x = cie.X / sum;
239     cie.y = cie.Y / sum;
240     cie.z = 1.0 - cie.x - cie.y;
241 
242     return cie;
243 }
244 
Labf(float t)245 static float Labf(float t) {
246     return t > ((6.0/29.0) * (6.0/29.0) * (6.0/29.0)) ? pow(t, 1.0/3.0) : ((1.0/3.0) * (29.0 / 6.0) * (29.0 / 6.0)) * t + (4.0 / 29.0);
247 }
248 
toLab(CIE * c)249 static Lab toLab(CIE *c) {
250     CIE n = (CIE) {Labf(c->X / white.X), Labf(c->Y / white.Y), Labf(c->Z / white.Z)};
251     Lab l;
252 
253     // http://en.wikipedia.org/wiki/L*a*b*#RGB_and_CMYK_conversions
254     l.L = 116.0 * n.Y - 16;
255     l.a = 500.0 * (n.X - n.Y);
256     l.b = 200.0 * (n.Y - n.Z);
257 
258     return l;
259 }
260 
munsellSloanGodlove(float t)261 static float munsellSloanGodlove(float t) {
262     return sqrt(1.4742 * t - 0.004743 * t * t);
263 }
264 
adams(CIE * v)265 static CIE adams(CIE *v) {
266     CIE c;
267     c.Y = munsellSloanGodlove(v->Y);
268     c.X = munsellSloanGodlove((white.Y / white.X) * v->X) - c.Y;
269     c.Z = munsellSloanGodlove((white.Z / white.X) * v->Z) - c.Y;
270 
271     return c;
272 }
273 
274 #define SQUARE(x) ((x) * (x))
275 
CIE76(Lab * L1,Lab * L2)276 static float CIE76(Lab *L1, Lab *L2) {
277     // http://en.wikipedia.org/wiki/Color_difference#CIE76
278     float lbias = 1.0;
279     return sqrt(lbias * SQUARE(L2->L - L1->L) + SQUARE(L2->a - L1->a) + SQUARE(L2->b - L1->b));
280 }
281 
init_coersion()282 static void init_coersion() {
283     fcolor sRGB_white = (fcolor) {1, 1, 1};
284     white = toCIE(sRGB_white);
285 
286     int i;
287     for (i = 0; i < 16; i++) {
288         ciePalette[i] = toCIE(palette[i]);
289         labPalette[i] = toLab(&ciePalette[i]);
290         adamsPalette[i] = adams(&ciePalette[i]);
291     }
292 
293     if (colormode == coerce_256) {
294         initialize_prs();
295     }
296 
297     cell_buffer = 0;
298 }
299 
best(fcolor * fg,fcolor * bg)300 static int best (fcolor *fg, fcolor *bg) {
301     // analyze fg & bg for their contrast
302     CIE cieFg = toCIE(*fg);
303     CIE cieBg = toCIE(*bg);
304     Lab labFg = toLab(&cieFg);
305     Lab labBg = toLab(&cieBg);
306     // CIE adamsFg = adams(&cieFg);
307     // CIE adamsBg = adams(&cieBg);
308 
309     float JND = 2.3; // just-noticeable-difference
310     int areTheSame = CIE76(&labFg, &labBg) <= 2.0 * JND; // a little extra fudge
311 
312     float big = 100000000;
313     int fg1 = 0, fg2 = 0, bg1 = 0, bg2 = 0;
314     float fg1_score = big, fg2_score = big;
315     float bg1_score = big, bg2_score = big;
316 
317     int i;
318 
319     for (i = 0; i < 8; i++) {
320         float s = CIE76(labPalette + i, &labBg);
321 
322         if (s < bg2_score) {
323             if (s < bg1_score) {
324                 bg2 = bg1; bg1 = i;
325                 bg2_score = bg1_score; bg1_score = s;
326             } else {
327                 bg2 = i; bg2_score = s;
328             }
329         }
330     }
331 
332     if (areTheSame) {
333         return COLORING(bg1, bg1);
334     }
335 
336     for (i = 0; i < 16; i++) {
337         float s = CIE76(labPalette + i, &labFg);
338 
339         if (s < fg2_score) {
340             if (s < fg1_score) {
341                 fg2 = fg1; fg1 = i;
342                 fg2_score = fg1_score; fg1_score = s;
343             } else {
344                 fg2 = i; fg2_score = s;
345             }
346         }
347     }
348 
349     if (fg1 != bg1) {
350         return COLORING (fg1, bg1);
351     } else {
352         if (fg1_score + bg2_score < fg2_score + bg1_score) {
353             return COLORING(fg1, bg2);
354         } else {
355             return COLORING(fg2, bg1);
356         }
357     }
358 }
359 
360 
361 
362 
initialize_prs()363 static void initialize_prs() {
364     int i;
365     for (i = 16; i < 255; i++) {
366         prs[i].next = i + 1;
367     }
368     prs[0].next = 16;
369     prs[1].next = 0;
370     prs[255].next = 0;
371 }
372 
coerce_colorcube(fcolor * f,intcolor * c)373 static void coerce_colorcube (fcolor *f, intcolor *c) {
374     // 0-15 are the standard ANSI colors
375     // 16-231 are a 6x6x6 RGB color cube given by ((36 * r) + (6 * g) + b + 16) with r,g,b in [0..5]
376     // 232-255 are a greyscale ramp without black and white.
377 
378     float sat = 0.2, bright = 0.6, contrast = 6.3;
379 
380     float rf = bright + f->r * contrast,
381         gf = bright + f->g * contrast,
382         bf = bright + f->b * contrast;
383 
384     if (rf < gf && rf < bf) rf -= sat * ((gf < bf ? bf : gf) - rf);
385     else if (gf < bf && gf < rf) gf -= sat * ((rf < bf ? bf : rf) - gf);
386     else if (bf < gf && bf < rf) bf -= sat * ((gf < rf ? rf : gf) - bf);
387 
388     int r = rf, g = gf, b = bf;
389     r = r < 0 ? 0 : r > 5 ? 5 : r;
390     g = g < 0 ? 0 : g > 5 ? 5 : g;
391     b = b < 0 ? 0 : b > 5 ? 5 : b;
392 
393     c->r = r;
394     c->g = g;
395     c->b = b;
396     c->idx = ((36 * r) + (6 * g) + b + 16);
397 }
398 
intcolor_distance(intcolor * a,intcolor * b)399 static int intcolor_distance (intcolor *a, intcolor *b) {
400     return
401         (a->r - b->r) * (a->r - b->r)
402         + (a->g - b->g) * (a->g - b->g)
403         + (a->b - b->b) * (a->b - b->b);
404 }
405 
coerce_prs(intcolor * fg,intcolor * bg)406 static int coerce_prs (intcolor *fg, intcolor *bg) {
407     // search for an exact match in the list
408     int pair;
409     pair = prs[1].next;
410     while (pair) {
411         if (prs[pair].fore.idx == fg->idx && prs[pair].back.idx == bg->idx) {
412             // perfect.
413             prs[pair].count++;
414             return pair;
415         }
416         pair = prs[pair].next;
417     }
418 
419     // no exact match? try to insert it as a new one
420     pair = prs[0].next;
421     if (pair) {
422         // there's room!
423 
424         // remove
425         prs[0].next = prs[pair].next;
426 
427         // insert at the front
428         prs[pair].next = prs[1].next;
429         prs[1].next = pair;
430 
431         // initialize it
432         prs[pair].fore = *fg;
433         prs[pair].back = *bg;
434         prs[pair].count = 1;
435 
436         init_pair(pair, fg->idx, bg->idx);
437 
438         return pair;
439     }
440 
441     // search for an approximate match in the list
442     int bestpair = 0, bestscore = 2 * 3 * 6 * 6; // naive distance metric for now
443     pair = prs[1].next;
444     while (pair) {
445         int delta = intcolor_distance(&prs[pair].fore, fg) + intcolor_distance(&prs[pair].back, bg);
446         if (delta < bestscore) {
447             bestscore = delta;
448             bestpair = pair;
449             if (delta == 1) break; // as good as it gets without being exact!
450         }
451         pair = prs[pair].next;
452     }
453 
454     prs[bestpair].count++;
455     return bestpair;
456 }
457 
buffer_plot(int ch,int x,int y,fcolor * fg,fcolor * bg)458 static void buffer_plot(int ch, int x, int y, fcolor *fg, fcolor *bg) {
459     // int pair = 256 + x + y * minsize.width;
460     // intcolor cube_fg, cube_bg;
461     // coerce_colorcube(fg, &cube_fg),
462     // coerce_colorcube(bg, &cube_bg);
463 
464     // pair = cube_bg.idx;
465     // cube_fg = cube_bg;
466 
467 
468     // init_pair(pair, cube_fg.idx, cube_bg.idx);
469 
470     // return pair;
471 
472     intcolor cube_fg, cube_bg;
473 
474     if (colormode == coerce_256) {
475         coerce_colorcube(fg, &cube_fg);
476         coerce_colorcube(bg, &cube_bg);
477         if (cube_fg.idx == cube_bg.idx) {
478             // verify that the colors are really the same; otherwise, we'd better force the output apart
479             int naive_distance =
480                 (fg->r - bg->r) * (fg->r - bg->r)
481                 + (fg->g - bg->g) * (fg->g - bg->g)
482                 + (fg->b - bg->b) * (fg->b - bg->b);
483             if (naive_distance > 3) {
484                 // very arbitrary cutoff, and an arbitrary fix, very lazy
485                 if (cube_bg.r > 0) {cube_bg.r -= 1; cube_bg.idx -= 1; }
486                 if (cube_bg.g > 0) {cube_bg.g -= 1; cube_bg.idx -= 6; }
487                 if (cube_bg.b > 0) {cube_bg.b -= 1; cube_bg.idx -= 36; }
488             }
489         }
490     } else {
491         cube_fg = (intcolor){
492             .r = round(fg->r * 255),
493             .g = round(fg->g * 255),
494             .b = round(fg->b * 255)
495         };
496         cube_bg = (intcolor){
497             .r = round(bg->r * 255),
498             .g = round(bg->g * 255),
499             .b = round(bg->b * 255)
500         };
501     }
502 
503     int cell = x + y * minsize.width;
504     cell_buffer[cell].ch = ch;
505     cell_buffer[cell].pair = -1;
506     cell_buffer[cell].fore = cube_fg;
507     cell_buffer[cell].back = cube_bg;
508 }
509 
buffer_render_256()510 static void buffer_render_256() {
511     // build a new palette
512     initialize_prs();
513 
514     int length = minsize.width * minsize.height;
515     int i, idx, x, y;
516 
517     for (i = 0; i < length; i++) {
518         cell_buffer[i].shuffle = i;
519     }
520     for (i = length - 1; i >= 0; i--) {
521         // int roll = i == 0 ? 0 : rand() % i;
522         // idx = cell_buffer[roll].shuffle;
523 
524         // cell_buffer[roll].shuffle = cell_buffer[i].shuffle;
525 
526         idx = i;
527 
528         int pair = coerce_prs(&cell_buffer[idx].fore, &cell_buffer[idx].back);
529         cell_buffer[idx].pair = pair;
530     }
531 
532     // render it all!
533     i = 0;
534     for (y = 0; y < minsize.height; y++) {
535         move(y, 0);
536         for (x = 0; x < minsize.width; x++) {
537             color_set(cell_buffer[i].pair, NULL);
538             addch(cell_buffer[i].ch);
539             i++;
540         }
541     }
542     refresh();
543 }
544 
545 static int fullRefresh = 1; // screen needs a full refresh
546 
buffer_render_24bit()547 static void buffer_render_24bit() {
548     int cx, cy;      // cursor coordinates
549     intcolor fg, bg; // current colors
550 
551     cx = cy = fg.r = fg.g = fg.b = bg.r = bg.g = bg.b = -1;
552 
553     for (int y = 0; y < minsize.height; y++) {
554         for (int x = 0; x < minsize.width; x++) {
555             pairmode_cell *c = &cell_buffer[x + y * minsize.width];
556 
557             // `pair` is set to -1 when a tile changes, which signals we need to print it
558             if (!c->pair && !fullRefresh) continue;
559             c->pair = 0;
560 
561             // change background color
562             if (c->back.r != bg.r || c->back.g != bg.g || c->back.b != bg.b) {
563                 bg = c->back;
564                 printf("\033[48;2;%d;%d;%dm", bg.r, bg.g, bg.b);
565             }
566 
567             // change foreground color (doesn't matter for whitespace)
568             if (c->ch != ' ' && (fg.r != c->fore.r || fg.g != c->fore.g || fg.b != c->fore.b)) {
569                 fg = c->fore;
570                 printf("\033[38;2;%d;%d;%dm", fg.r, fg.g, fg.b);
571             }
572 
573             // move cursor if necessary
574             if (cx != x || cy != y) {
575                 cx = x, cy = y;
576                 printf("\033[%d;%df", cy+1, cx+1);
577             }
578 
579             // print the character
580             printf("%c", c->ch);
581             cx++;
582         }
583     }
584 
585     fflush(stdout);
586     fullRefresh = 0;
587 }
588 
term_mvaddch(int x,int y,int ch,fcolor * fg,fcolor * bg)589 static void term_mvaddch(int x, int y, int ch, fcolor *fg, fcolor *bg) {
590     if (x < 0 || y < 0 || x >= minsize.width || y >= minsize.height) return;
591 
592     if (colormode == coerce_16) {
593         int c = best(fg, bg);
594         attrset(COLOR_ATTR(c));
595         mvaddch(y, x, ch);
596     } else {
597         buffer_plot(ch, x, y, fg, bg);
598     }
599 }
600 
term_refresh()601 static void term_refresh() {
602     // to set up a 256-color terminal, see:
603     // http://push.cx/2008/256-color-xterms-in-ubuntu
604     if (0 && can_change_color()) {
605         int i;
606         for (i = 0; i < 16; i++) {
607             short r = palette[i].r * 1000;
608             short g = palette[i].g * 1000;
609             short b = palette[i].b * 1000;
610             if (r < 0) r = 0;
611             if (g < 0) g = 0;
612             if (b < 0) b = 0;
613             init_color(i + 1, r, g, b);
614         }
615     }
616     if (0) {
617         int i;
618         short r, g, b;
619         for (i = 0; i < 8; i++) {
620             color_content(i, &r, &g, &b);
621             palette[i].r = r * .001;
622             palette[i].g = g * .001;
623             palette[i].b = b * .001;
624         }
625     }
626 
627     if (colormode == truecolor) {
628         buffer_render_24bit();
629     } else if (colormode == coerce_256) {
630         buffer_render_256();
631     } else if (colormode == coerce_16) {
632         refresh();
633     }
634 }
635 
636 static void ensure_size( );
637 
term_getkey()638 static int term_getkey( ) {
639     Term.mouse.justPressed = 0;
640     Term.mouse.justReleased = 0;
641     Term.mouse.justMoved = 0;
642 
643     while (1) {
644         int got = getch();
645         if (got == KEY_RESIZE) {
646             ensure_size( );
647             fullRefresh = 1;
648         } else if (got == KEY_MOUSE) {
649             MEVENT mevent;
650             getmouse (&mevent);
651             Term.mouse.x = mevent.x;
652             Term.mouse.y = mevent.y;
653             Term.mouse.shift = (mevent.bstate & BUTTON_SHIFT) != 0;
654             Term.mouse.control = (mevent.bstate & BUTTON_CTRL) != 0;
655             if (mevent.bstate & BUTTON1_PRESSED) {
656                 Term.mouse.justPressed = 1;
657                 Term.mouse.isPressed = 1;
658             } else if (mevent.bstate & BUTTON1_RELEASED) {
659                 if (Term.mouse.isPressed) {
660                     Term.mouse.justReleased = 1;
661                     Term.mouse.isPressed = 0;
662                 }
663             } else {
664                 Term.mouse.justMoved = 1;
665             }
666             return TERM_MOUSE;
667         } else {
668             if (got == KEY_ENTER) got = 13; // KEY_ENTER -> ^M for systems with odd values for KEY_ENTER
669             if (got == ERR) return TERM_NONE;
670             else return got;
671         }
672     }
673 }
674 
term_has_key()675 static int term_has_key() {
676     int ch = getch();
677     if (ch != ERR) {
678         ungetch(ch);
679         return 1;
680     } else {
681         return 0;
682     }
683 }
684 
ensure_size()685 static void ensure_size( ) {
686     int w = minsize.width, h = minsize.height;
687 
688     getmaxyx(stdscr, Term.height, Term.width);
689     if (Term.height < h || Term.width < w) {
690         getmaxyx(stdscr, Term.height, Term.width);
691         nodelay(stdscr, FALSE);
692         while (Term.height < h || Term.width < w) {
693             erase();
694             attrset(COLOR_ATTR(7));
695 
696             mvprintw(1,0,"Brogue needs a terminal window that is at least [%d x %d]", w, h);
697 
698             attrset(COLOR_ATTR(15));
699             mvprintw(2,0,"If your terminal can be resized, resize it now.\n");
700 
701             attrset(COLOR_ATTR(7));
702             mvprintw(3,0,"Press ctrl-c at any time to quit.\n");
703 
704             printw("Width:  %d/%d\n", Term.width, w);
705             printw("Height: %d/%d\n", Term.height, h);
706 
707             mvprintw(10, 0, "Colors (pairs): %d (%d)\n", COLORS, COLOR_PAIRS);
708 
709             getch();
710             getmaxyx(stdscr, Term.height, Term.width);
711         }
712         nodelay(stdscr, TRUE);
713         erase();
714         refresh();
715     }
716 }
717 
term_resize(int w,int h)718 static void term_resize(int w, int h) {
719     minsize.width = w;
720     minsize.height = h;
721 
722     // try to set the terminal size if the terminal will let us:
723     term_set_size(h, w);
724     // (this works in gnome-terminal, but causes trouble for curses on maximized windows.)
725 
726     // now make sure it worked, and ask the user to resize the terminal if it didn't
727     ensure_size();
728 
729 
730     // make a new cell buffer
731 
732     if (cell_buffer) free(cell_buffer);
733     cell_buffer = malloc(sizeof(pairmode_cell) * w * h);
734     // add error checking
735     int i;
736 
737     for (i = 0; i < w * h; i++) {
738         // I guess we could just zero it all, hmm
739         cell_buffer[i].ch = 0;
740         cell_buffer[i].pair = 0;
741         cell_buffer[i].fore.idx = 0;
742         cell_buffer[i].back.idx = 0;
743     }
744 }
745 
term_wait(int ms)746 static void term_wait(int ms) {
747     napms(ms);
748 }
749 
750 
751 struct {
752     char *name;
753     int ch;
754 } curses_keys[] = {
755     {"NONE", TERM_NONE},
756 
757     {"TAB", '\t'},
758     {"ENTER", '\n'},
759     {"RETURN", '\n'},
760     {"SPACE", ' '},
761 
762     {"ESC", 27},
763     {"ESCAPE", 27},
764 
765     {"BREAK", KEY_BREAK},
766     {"SRESET", KEY_SRESET},
767     {"RESET", KEY_RESET},
768     {"DOWN", KEY_DOWN},
769     {"UP", KEY_UP   },
770     {"LEFT", KEY_LEFT},
771     {"RIGHT", KEY_RIGHT},
772     {"HOME", KEY_HOME},
773     {"BACKSPACE", KEY_BACKSPACE},
774     {"F1", KEY_F(1)},
775     {"F2", KEY_F(2)},
776     {"F3", KEY_F(3)},
777     {"F4", KEY_F(4)},
778     {"F5", KEY_F(5)},
779     {"F6", KEY_F(6)},
780     {"F7", KEY_F(7)},
781     {"F8", KEY_F(8)},
782     {"F9", KEY_F(9)},
783     {"F10", KEY_F(10)},
784     {"F11", KEY_F(11)},
785     {"F12", KEY_F(12)},
786     {"DL", KEY_DL},
787     {"IL", KEY_IL},
788     {"DC", KEY_DC},
789     {"DEL", KEY_DC},
790     {"DELETE", KEY_DC},
791     {"IC", KEY_IC},
792     {"EIC", KEY_EIC},
793     {"CLEAR", KEY_CLEAR},
794     {"EOS", KEY_EOS},
795     {"EOL", KEY_EOL},
796     {"SF", KEY_SF},
797     {"SR", KEY_SR},
798 
799     {"PGUP", KEY_NPAGE},
800     {"PGDN", KEY_PPAGE},
801     {"PAGEDOWN", KEY_NPAGE},
802     {"PAGEUP", KEY_PPAGE},
803     {"NPAGE", KEY_NPAGE},
804     {"PPAGE", KEY_PPAGE},
805 
806     {"STAB", KEY_STAB},
807     {"CTAB", KEY_CTAB},
808     {"CATAB", KEY_CATAB},
809 
810     {"PRINT", KEY_PRINT},
811     {"LL", KEY_LL},
812     {"A1", KEY_A1},
813     {"A3", KEY_A3},
814     {"B2", KEY_B2},
815     {"C1", KEY_C1},
816     {"C3", KEY_C3},
817     {"BTAB", KEY_BTAB},
818     {"BEG", KEY_BEG },
819     {"CANCEL", KEY_CANCEL},
820     {"CLOSE", KEY_CLOSE},
821     {"COMMAND", KEY_COMMAND},
822     {"COPY", KEY_COPY},
823     {"CREATE", KEY_CREATE},
824     {"END", KEY_END },
825     {"EXIT", KEY_EXIT},
826     {"FIND", KEY_FIND},
827     {"HELP", KEY_HELP},
828     {"MARK", KEY_MARK},
829     {"MESSAGE", KEY_MESSAGE},
830     {"MOVE", KEY_MOVE},
831     {"NEXT", KEY_NEXT},
832     {"OPEN", KEY_OPEN},
833     {"OPTIONS", KEY_OPTIONS},
834     {"PREVIOUS", KEY_PREVIOUS},
835     {"REDO", KEY_REDO},
836     {"REFERENCE", KEY_REFERENCE},
837     {"REFRESH", KEY_REFRESH},
838     {"REPLACE", KEY_REPLACE},
839     {"RESTART", KEY_RESTART},
840     {"RESUME", KEY_RESUME},
841     {"SAVE", KEY_SAVE},
842     {"SBEG", KEY_SBEG},
843     {"SCANCEL", KEY_SCANCEL},
844     {"SCOMMAND", KEY_SCOMMAND},
845     {"SCOPY", KEY_SCOPY},
846     {"SCREATE", KEY_SCREATE},
847     {"SDC", KEY_SDC },
848     {"SDL", KEY_SDL },
849     {"SELECT", KEY_SELECT},
850     {"SEND", KEY_SEND},
851     {"SEOL", KEY_SEOL},
852     {"SEXIT", KEY_SEXIT},
853     {"SFIND", KEY_SFIND},
854     {"SHELP", KEY_SHELP},
855     {"SHOME", KEY_SHOME},
856     {"SIC", KEY_SIC },
857     {"SLEFT", KEY_SLEFT},
858     {"SMESSAGE", KEY_SMESSAGE},
859     {"SMOVE", KEY_SMOVE},
860     {"SNEXT", KEY_SNEXT},
861     {"SOPTIONS", KEY_SOPTIONS},
862     {"SPREVIOUS", KEY_SPREVIOUS},
863     {"SPRINT", KEY_SPRINT},
864     {"SREDO", KEY_SREDO},
865     {"SREPLACE", KEY_SREPLACE},
866     {"SRIGHT", KEY_SRIGHT},
867     {"SRSUME", KEY_SRSUME},
868     {"SSAVE", KEY_SSAVE},
869     {"SSUSPEND", KEY_SSUSPEND},
870     {"SUNDO", KEY_SUNDO},
871     {"SUSPEND", KEY_SUSPEND},
872     {"UNDO", KEY_UNDO},
873     {"MOUSE", KEY_MOUSE},
874     {"RESIZE", KEY_RESIZE},
875     {NULL, 0},
876 };
877 
term_keycodeByName(const char * name)878 int term_keycodeByName(const char *name) {
879     int i = 0;
880     while (curses_keys[i].name != NULL) {
881         if (strcmp(name, curses_keys[i].name) == 0) {
882             return curses_keys[i].ch;
883         }
884         i++;
885     }
886 
887     return name[0];
888 }
889 
890 
891 struct term_t Term = {
892     term_start,
893     term_end,
894     term_mvaddch,
895     term_refresh,
896     term_getkey,
897     term_wait,
898     term_has_key,
899     term_title,
900     term_resize,
901     term_keycodeByName,
902     {KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_BACKSPACE, KEY_DC, KEY_F(12)}
903 };
904