1 /*
2 * UAE - The Un*x Amiga Emulator
3 *
4 * [n]curses output.
5 *
6 * There are 17 color modes:
7 * -H0/-H1 are black/white output
8 * -H2 through -H16 give you different color allocation strategies. On my
9 * system, -H14 seems to give nice results.
10 *
11 * Copyright 1997 Samuel Devulder, Bernd Schmidt
12 */
13
14 /****************************************************************************/
15
16 #include "sysconfig.h"
17 #include "sysdeps.h"
18
19 #include <ctype.h>
20 #include <signal.h>
21
22 /****************************************************************************/
23
24 #include "options.h"
25 #include "threaddep/thread.h"
26 #include "uae.h"
27 #include "memory.h"
28 #include "custom.h"
29 #include "newcpu.h"
30 #include "xwin.h"
31 #include "keyboard.h"
32 #include "keybuf.h"
33 #include "disk.h"
34 #include "debug.h"
35 #include "gui.h"
36
37 #ifdef HAVE_NCURSES_H
38 #include <ncurses.h>
39 #else
40 #include <curses.h>
41 #endif
42
43 /****************************************************************************/
44
45 #define MAXGRAYCHAR 128
46
47 enum {
48 MYCOLOR_BLACK, MYCOLOR_RED, MYCOLOR_GREEN, MYCOLOR_BLUE,
49 MYCOLOR_YELLOW, MYCOLOR_CYAN, MYCOLOR_MAGENTA, MYCOLOR_WHITE
50 };
51
52 static int mycolor2curses_map [] = {
53 COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE,
54 COLOR_YELLOW, COLOR_CYAN, COLOR_MAGENTA, COLOR_WHITE
55 };
56
57 static int mycolor2pair_map[] = { 1,2,3,4,5,6,7,8 };
58
59 static chtype graychar[MAXGRAYCHAR];
60 static int maxc,max_graychar;
61 static int curses_on;
62
63 static int *x2graymap;
64
65 /* Keyboard and mouse */
66
67 static int keystate[256];
68 static int keydelay = 20;
69
70 static void curses_exit(void);
71
72 /****************************************************************************/
73
sigbrkhandler(int foo)74 static RETSIGTYPE sigbrkhandler(int foo)
75 {
76 curses_exit();
77 activate_debugger();
78 }
79
setup_brkhandler(void)80 void setup_brkhandler(void)
81 {
82 struct sigaction sa;
83 sa.sa_handler = sigbrkhandler;
84 sa.sa_flags = 0;
85 sa.sa_flags = SA_RESTART;
86 sigemptyset(&sa.sa_mask);
87 sigaction(SIGINT, &sa, NULL);
88 }
89
90 /***************************************************************************/
91
curses_insert_disk(void)92 static void curses_insert_disk(void)
93 {
94 curses_exit();
95 gui_changesettings();
96 flush_screen(0,0);
97 }
98
99 /****************************************************************************/
100
101 /*
102 * old: fmt = " .,:=(Io^vM^vb*X^#M^vX*boI(=:. ^b^vobX^#M" doesn't work: "^vXb*oI(=:. ";
103 * good: fmt = " .':;=(IoJpgFPEB#^vgpJoI(=;:'. ^v^b=(IoJpgFPEB";
104 *
105 * fmt = " .,:=(Io*b^vM^vX^#M^vXb*oI(=:. ";
106 */
107
init_graychar(void)108 static void init_graychar(void)
109 {
110 chtype *p = graychar;
111 chtype attrs;
112 int i,j;
113 char *fmt;
114
115 attrs = termattrs();
116 if ((currprefs.color_mode & 1) == 0 && (attrs & (A_REVERSE | A_BOLD)))
117 fmt = " .':;=(IoJpgFPEB#^vgpJoI(=;:'. ^v^boJpgFPEB";
118 else if ((currprefs.color_mode & 1) == 0 && (attrs & A_REVERSE))
119 fmt = " .':;=(IoJpgFPEB#^vgpJoI(=;:'. ";
120 else
121 /* One could find a better pattern.. */
122 fmt = " .`'^^\",:;i!1Il+=tfjxznuvyZYXHUOQ0MWB";
123 attrs = A_NORMAL | COLOR_PAIR (0);
124 while(*fmt) {
125 if(*fmt == '^') {
126 ++fmt;
127 switch(*fmt) {
128 case 's': case 'S': attrs ^= A_STANDOUT; break;
129 case 'v': case 'V': attrs ^= A_REVERSE; break;
130 case 'b': case 'B': attrs ^= A_BOLD; break;
131 case 'd': case 'D': attrs ^= A_DIM; break;
132 case 'u': case 'U': attrs ^= A_UNDERLINE; break;
133 case 'p': case 'P': attrs = A_NORMAL; break;
134 case '#': if(ACS_CKBOARD == ':')
135 *p++ = (attrs | '#');
136 else *p++ = (attrs | ACS_CKBOARD); break;
137 default: *p++ = (attrs | *fmt); break;
138 }
139 ++fmt;
140 } else *p++ = (attrs | *fmt++);
141 if(p >= graychar + MAXGRAYCHAR) break;
142 }
143 max_graychar = (p - graychar) - 1;
144
145 for (i = 0; i <= maxc; i++)
146 x2graymap[i] = i * max_graychar / maxc;
147 #if 0
148 for(j=0;j<LINES;++j) {
149 move(j,0);
150 for(i=0;i<COLS;++i) addch(graychar[i % (max_graychar+1)]);
151 }
152 refresh();
153 sleep(3);
154 #endif
155 }
156
157 static int x_map[900], y_map[700], y_rev_map [700];
158
159
160 /****************************************************************************/
161
init_colors(void)162 static void init_colors(void)
163 {
164 int i;
165
166 maxc = 0;
167
168 for(i = 0; i < 4096; ++i) {
169 int r,g,b,r1,g1,b1;
170 int m, comp;
171 int ctype;
172
173 r = i >> 8;
174 g = (i >> 4) & 15;
175 b = i & 15;
176
177 xcolors[i] = (77 * r + 151 * g + 28 * b)/16;
178 if(xcolors[i] > maxc)
179 maxc = xcolors[i];
180 m = r;
181 if (g > m)
182 m = g;
183 if (b > m)
184 m = b;
185 if (m == 0) {
186 xcolors[i] |= MYCOLOR_WHITE << 8; /* to get gray instead of black in dark areas */
187 continue;
188 }
189
190 if ((currprefs.color_mode & ~1) != 0) {
191 r1 = r*15 / m;
192 g1 = g*15 / m;
193 b1 = b*15 / m;
194
195 comp = 8;
196 for (;;) {
197 if (b1 < comp) {
198 if (r1 < comp)
199 ctype = MYCOLOR_GREEN;
200 else if (g1 < comp)
201 ctype = MYCOLOR_RED;
202 else
203 ctype = MYCOLOR_YELLOW;
204 } else {
205 if (r1 < comp) {
206 if (g1 < comp)
207 ctype = MYCOLOR_BLUE;
208 else
209 ctype = MYCOLOR_CYAN;
210 } else if (g1 < comp)
211 ctype = MYCOLOR_MAGENTA;
212 else {
213 comp += 4;
214 if (comp == 12 && (currprefs.color_mode & 2) != 0)
215 continue;
216 ctype = MYCOLOR_WHITE;
217 }
218 }
219 break;
220 }
221 if (currprefs.color_mode & 8) {
222 if (ctype == MYCOLOR_BLUE && xcolors[i] > /*27*/50)
223 ctype = r1 > (g1+2) ? MYCOLOR_MAGENTA : MYCOLOR_CYAN;
224 if (ctype == MYCOLOR_RED && xcolors[i] > /*75*/ 90)
225 ctype = b1 > (g1+6) ? MYCOLOR_MAGENTA : MYCOLOR_YELLOW;
226 }
227 xcolors[i] |= ctype << 8;
228 }
229 }
230 if (currprefs.color_mode & 4) {
231 int j;
232 for (j = MYCOLOR_RED; j < MYCOLOR_WHITE; j++) {
233 int best = 0, maxv = 0;
234 int multi, divi;
235
236 for (i = 0; i < 4096; i++)
237 if ((xcolors[i] & 255) > maxv && (xcolors[i] >> 8) == j) {
238 best = i;
239 maxv = (xcolors[best] & 255);
240 }
241 /* Now maxv is the highest intensity a color of type J is supposed to have.
242 * In reality, it will most likely only have intensity maxv*multi/divi.
243 * We try to correct this. */
244 maxv = maxv * 256 / maxc;
245
246 divi = 256;
247 switch (j) {
248 case MYCOLOR_RED: multi = 77; break;
249 case MYCOLOR_GREEN: multi = 151; break;
250 case MYCOLOR_BLUE: multi = 28; break;
251 case MYCOLOR_YELLOW: multi = 228; break;
252 case MYCOLOR_CYAN: multi = 179; break;
253 case MYCOLOR_MAGENTA: multi = 105; break;
254 default: abort();
255 }
256 #if 1 /* This makes the correction less extreme */
257 if (! (currprefs.color_mode & 8))
258 multi = (multi + maxv) / 2;
259 #endif
260 for (i = 0; i < 4096; i++) {
261 int v = xcolors[i];
262 if ((v >> 8) != j)
263 continue;
264 v &= 255;
265 /* I don't think either of these is completely correct, but
266 * the first one produces rather good results. */
267 #if 1
268 v = v * divi / multi;
269 if (v > maxc)
270 v = maxc;
271 #else
272 v = v * 256 / maxv);
273 if (v > maxc)
274 /*maxc = v*/abort();
275 #endif
276 xcolors[i] = v | (j << 8);
277 }
278 }
279 }
280 x2graymap = (int *)malloc(sizeof(int) * (maxc+1));
281 }
282
curses_init(void)283 static void curses_init(void)
284 {
285 initscr ();
286
287 start_color ();
288 if (! has_colors () || COLOR_PAIRS < 20 /* whatever */)
289 currprefs.color_mode &= 1;
290 else {
291 init_pair (1, COLOR_BLACK, COLOR_BLACK);
292 init_pair (2, COLOR_RED, COLOR_BLACK);
293 init_pair (3, COLOR_GREEN, COLOR_BLACK);
294 init_pair (4, COLOR_BLUE, COLOR_BLACK);
295 init_pair (5, COLOR_YELLOW, COLOR_BLACK);
296 init_pair (6, COLOR_CYAN, COLOR_BLACK);
297 init_pair (7, COLOR_MAGENTA, COLOR_BLACK);
298 init_pair (8, COLOR_WHITE, COLOR_BLACK);
299 }
300 printf ("curses_init: %d pairs available\n", COLOR_PAIRS);
301
302 cbreak(); noecho();
303 nonl (); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
304 nodelay(stdscr, TRUE);
305 leaveok(stdscr, TRUE);
306
307 attron (A_NORMAL | COLOR_PAIR (0));
308 bkgd(' '|COLOR_PAIR(0));
309
310 #ifdef NCURSES_MOUSE_VERSION
311 mousemask(BUTTON1_PRESSED | BUTTON1_RELEASED |
312 BUTTON2_PRESSED | BUTTON2_RELEASED |
313 BUTTON3_PRESSED | BUTTON3_RELEASED |
314 REPORT_MOUSE_POSITION, NULL);
315 #endif
316
317 init_graychar();
318 curses_on = 1;
319 }
320
curses_exit(void)321 static void curses_exit(void)
322 {
323 #ifdef NCURSES_MOUSE_VERSION
324 mousemask(0, NULL);
325 #endif
326
327 nocbreak(); echo(); nl(); intrflush(stdscr, TRUE);
328 keypad(stdscr, FALSE); nodelay(stdscr, FALSE); leaveok(stdscr, FALSE);
329 endwin();
330 curses_on = 0;
331 }
332
333 /****************************************************************************/
334
getgraycol(int x,int y)335 static int getgraycol(int x, int y)
336 {
337 uae_u8 *bufpt;
338 int xs, xl, ys, yl, c, cm;
339
340 xl = x_map[x+1] - (xs = x_map[x]);
341 yl = y_map[y+1] - (ys = y_map[y]);
342
343 bufpt = ((uae_u8 *)gfxvidinfo.bufmem) + ys*currprefs.gfx_width + xs;
344
345 cm = c = 0;
346 for(y = 0; y < yl; y++, bufpt += currprefs.gfx_width)
347 for(x = 0; x < xl; x++) {
348 c += bufpt[x];
349 ++cm;
350 }
351 if (cm)
352 c /= cm;
353 if (! currprefs.curses_reverse_video)
354 c = maxc - c;
355 return graychar[x2graymap[c]];
356 }
357
getcol(int x,int y)358 static int getcol(int x, int y)
359 {
360 uae_u16 *bufpt;
361 int xs, xl, ys, yl, c, cm;
362 int bestcol = MYCOLOR_BLACK, bestccnt = 0;
363 unsigned char colcnt [8];
364
365 memset (colcnt, 0 , sizeof colcnt);
366
367 xl = x_map[x+1] - (xs = x_map[x]);
368 yl = y_map[y+1] - (ys = y_map[y]);
369
370 bufpt = ((uae_u16 *)gfxvidinfo.bufmem) + ys*currprefs.gfx_width + xs;
371
372 cm = c = 0;
373 for(y = 0; y < yl; y++, bufpt += currprefs.gfx_width)
374 for(x = 0; x < xl; x++) {
375 int v = bufpt[x];
376 int cnt;
377
378 c += v & 0xFF;
379 cnt = ++colcnt[v >> 8];
380 if (cnt > bestccnt) {
381 bestccnt = cnt;
382 bestcol = v >> 8;
383 }
384 ++cm;
385 }
386 if (cm)
387 c /= cm;
388 if (! currprefs.curses_reverse_video)
389 c = maxc - c;
390 return (graychar[x2graymap[c]] & ~A_COLOR) | COLOR_PAIR (mycolor2pair_map[bestcol]);
391 }
392
flush_line_txt(int y)393 static void flush_line_txt(int y)
394 {
395 int x;
396 move (y,0);
397 if (currprefs.color_mode < 2)
398 for (x = 0; x < COLS; ++x) {
399 int c;
400
401 c = getgraycol(x,y);
402 addch(c);
403 }
404 else
405 for (x = 0; x < COLS; ++x) {
406 int c;
407
408 c = getcol(x,y);
409 addch(c);
410 }
411 }
412
flush_line(int y)413 __inline__ void flush_line(int y)
414 {
415 if(y < 0 || y >= currprefs.gfx_height) {
416 /* printf("flush_line out of window: %d\n", y); */
417 return;
418 }
419 if(!curses_on)
420 return;
421 flush_line_txt(y_rev_map[y]);
422 }
423
flush_block(int ystart,int ystop)424 void flush_block (int ystart, int ystop)
425 {
426 int y;
427 if(!curses_on)
428 return;
429 ystart = y_rev_map[ystart];
430 ystop = y_rev_map[ystop];
431 for(y = ystart; y <= ystop; ++y)
432 flush_line_txt(y);
433 }
434
flush_screen(int ystart,int ystop)435 void flush_screen (int ystart, int ystop)
436 {
437 if(!debugging && !curses_on) {
438 curses_init();
439 flush_block(0, currprefs.gfx_height - 1);
440 }
441 refresh();
442 }
443
444 /****************************************************************************/
445
446 struct bstring *video_mode_menu = NULL;
447
vidmode_menu_selected(int a)448 void vidmode_menu_selected(int a)
449 {
450 }
451
graphics_setup(void)452 int graphics_setup(void)
453 {
454 return 1;
455 }
456
graphics_init(void)457 int graphics_init(void)
458 {
459 int i;
460
461 if (currprefs.color_mode > 16)
462 write_log ("Bad color mode selected. Using default.\n"), currprefs.color_mode = 0;
463
464 init_colors();
465
466 curses_init();
467 write_log ("Using %s.\n",longname());
468
469 if (debugging)
470 curses_exit ();
471
472 /* we have a 320x256x8 pseudo screen */
473
474 currprefs.gfx_width = 320;
475 currprefs.gfx_height = 256;
476 currprefs.gfx_lores = 1;
477
478 gfxvidinfo.width = currprefs.gfx_width;
479 gfxvidinfo.height = currprefs.gfx_height;
480 gfxvidinfo.maxblocklines = 1000;
481 gfxvidinfo.pixbytes = currprefs.color_mode < 2 ? 1 : 2;
482 gfxvidinfo.rowbytes = gfxvidinfo.pixbytes * currprefs.gfx_width;
483 gfxvidinfo.bufmem = (char *)calloc(gfxvidinfo.rowbytes, currprefs.gfx_height+1);
484 gfxvidinfo.linemem = 0;
485 gfxvidinfo.emergmem = 0;
486 gfxvidinfo.can_double = 0;
487 switch (gfxvidinfo.pixbytes) {
488 case 1:
489 for (i = 0; i < 4096; i++)
490 xcolors[i] = xcolors[i] * 0x01010101;
491 gfxvidinfo.can_double = 1;
492 break;
493 case 2:
494 for (i = 0; i < 4096; i++)
495 xcolors[i] = xcolors[i] * 0x00010001;
496 gfxvidinfo.can_double = 1;
497 break;
498 }
499 if(!gfxvidinfo.bufmem) {
500 write_log ("Not enough memory.\n");
501 return 0;
502 }
503
504 for (i = 0; i < sizeof x_map / sizeof *x_map; i++)
505 x_map[i] = (i * currprefs.gfx_width) / COLS;
506 for (i = 0; i < sizeof y_map / sizeof *y_map; i++)
507 y_map[i] = (i * currprefs.gfx_height) / LINES;
508 for (i = 0; i < sizeof y_map / sizeof *y_map - 1; i++) {
509 int l1 = y_map[i];
510 int l2 = y_map[i+1];
511 int j;
512 if (l2 >= sizeof y_rev_map / sizeof *y_rev_map)
513 break;
514 for (j = l1; j < l2; j++)
515 y_rev_map[j] = i;
516 }
517
518 buttonstate[0] = buttonstate[1] = buttonstate[2] = 0;
519 for(i=0; i<256; i++)
520 keystate[i] = 0;
521
522 lastmx = lastmy = 0;
523 newmousecounters = 0;
524
525 return 1;
526 }
527
528 /****************************************************************************/
529
graphics_leave(void)530 void graphics_leave(void)
531 {
532 curses_exit();
533 }
534
535 /****************************************************************************/
536
keycode2amiga(int ch)537 static int keycode2amiga(int ch)
538 {
539 switch(ch) {
540 case KEY_A1: return AK_NP7;
541 case KEY_UP: return AK_NP8;
542 case KEY_A3: return AK_NP9;
543 case KEY_LEFT: return AK_NP4;
544 case KEY_B2: return AK_NP5;
545 case KEY_RIGHT: return AK_NP6;
546 case KEY_C1: return AK_NP1;
547 case KEY_DOWN: return AK_NP2;
548 case KEY_C3: return AK_NP3;
549 case KEY_ENTER: return AK_ENT;
550 case 13: return AK_RET;
551 case ' ': return AK_SPC;
552 case 27: return AK_ESC;
553 default: return -1;
554 }
555 }
556
557 /***************************************************************************/
558
handle_events(void)559 void handle_events(void)
560 {
561 int ch;
562 int kc;
563
564 /* Hack to simulate key release */
565 for(kc = 0; kc < 256; ++kc) {
566 if(keystate[kc]) if(!--keystate[kc]) record_key((kc << 1) | 1);
567 }
568 if(buttonstate[0]) --buttonstate[0];
569 if(buttonstate[1]) --buttonstate[1];
570 if(buttonstate[2]) --buttonstate[2];
571
572 newmousecounters = 0;
573 if(!curses_on) return;
574
575 while((ch = getch())!=ERR) {
576 if(ch == 12) {clearok(stdscr,TRUE);refresh();}
577 #ifdef NCURSES_MOUSE_VERSION
578 if(ch == KEY_MOUSE) {
579 MEVENT ev;
580 if(getmouse(&ev) == OK) {
581 lastmx = (ev.x*currprefs.gfx_width)/COLS;
582 lastmy = (ev.y*currprefs.gfx_height)/LINES;
583 if(ev.bstate & BUTTON1_PRESSED) buttonstate[0] = keydelay;
584 if(ev.bstate & BUTTON1_RELEASED) buttonstate[0] = 0;
585 if(ev.bstate & BUTTON2_PRESSED) buttonstate[1] = keydelay;
586 if(ev.bstate & BUTTON2_RELEASED) buttonstate[1] = 0;
587 if(ev.bstate & BUTTON3_PRESSED) buttonstate[2] = keydelay;
588 if(ev.bstate & BUTTON3_RELEASED) buttonstate[2] = 0;
589 }
590 }
591 #endif
592 if (ch == 6) ++lastmx; /* ^F */
593 if (ch == 2) --lastmx; /* ^B */
594 if (ch == 14) ++lastmy; /* ^N */
595 if (ch == 16) --lastmy; /* ^P */
596 if (ch == 11) {buttonstate[0] = keydelay;ch = 0;} /* ^K */
597 if (ch == 25) {buttonstate[2] = keydelay;ch = 0;} /* ^Y */
598 if (ch == 15) uae_reset (); /* ^O */
599 if (ch == 17) uae_quit (); /* ^Q */
600 if (ch == KEY_F(1)) {
601 curses_insert_disk();
602 ch = 0;
603 }
604
605 if(isupper(ch)) {
606 keystate[AK_LSH] =
607 keystate[AK_RSH] = keydelay;
608 record_key(AK_LSH << 1);
609 record_key(AK_RSH << 1);
610 kc = keycode2amiga(tolower(ch));
611 keystate[kc] = keydelay;
612 record_key(kc << 1);
613 } else if((kc = keycode2amiga(ch)) >= 0) {
614 keystate[kc] = keydelay;
615 record_key(kc << 1);
616 }
617 }
618 gui_handle_events();
619 }
620
621 /***************************************************************************/
622
target_specific_usage(void)623 void target_specific_usage(void)
624 {
625 printf("----------------------------------------------------------------------------\n");
626 printf("[n]curses specific usage:\n");
627 printf(" -x : Display reverse video.\n");
628 printf("By default uae will assume a black on white display. If yours\n");
629 printf("is light on dark, use -x. In case of graphics garbage, ^L will\n");
630 printf("redisplay the screen. ^K simulate left mouse button, ^Y RMB.\n");
631 printf("If you are using a xterm UAE can use the mouse. Else use ^F ^B\n");
632 printf("^P ^N to emulate mouse mouvements.\n");
633 printf("----------------------------------------------------------------------------\n");
634 }
635
636 /***************************************************************************/
637
check_prefs_changed_gfx(void)638 int check_prefs_changed_gfx (void)
639 {
640 return 0;
641 }
642
debuggable(void)643 int debuggable(void)
644 {
645 return 1;
646 }
647
needmousehack(void)648 int needmousehack(void)
649 {
650 return 1;
651 }
652
LED(int on)653 void LED(int on)
654 {
655 }
656
write_log(const char * buf,...)657 void write_log (const char *buf, ...)
658 {
659
660 }
661
lockscr(void)662 int lockscr (void)
663 {
664 return 1;
665 }
666
unlockscr(void)667 void unlockscr (void)
668 {
669 }
670
target_save_options(FILE * f,struct uae_prefs * p)671 void target_save_options (FILE *f, struct uae_prefs *p)
672 {
673 fprintf (f, "curses.reverse_video=%s\n", p->curses_reverse_video ? "true" : "false");
674 }
675
target_parse_option(struct uae_prefs * p,char * option,char * value)676 int target_parse_option (struct uae_prefs *p, char *option, char *value)
677 {
678 return (cfgfile_yesno (option, value, "reverse_video", &p->curses_reverse_video));
679 }
680