mirror of
https://github.com/mauiaaron/apple2.git
synced 2024-06-17 12:29:31 +00:00
- Variables needed in assembly now accessible in a struct pointer that avoids __PIC__ nastiness - Reduces code size and execution time for CPU thread
771 lines
21 KiB
C
771 lines
21 KiB
C
/*
|
|
* Apple // emulator for *ix
|
|
*
|
|
* This software package is subject to the GNU General Public License
|
|
* version 3 or later (your choice) as published by the Free Software
|
|
* Foundation.
|
|
*
|
|
* Copyright 2017 Aaron Culliney
|
|
*
|
|
*/
|
|
|
|
// ncurses renderer (for those of us who still CLI ;)
|
|
|
|
#include "common.h"
|
|
#include "video/video.h"
|
|
|
|
#include <sys/poll.h>
|
|
|
|
#if HAVE_NCURSES_H
|
|
# include <ncurses.h>
|
|
#elif HAVE_NCURSESW_NCURSES_H
|
|
# include <ncursesw/ncurses.h>
|
|
#elif HAVE_NCURSES_NCURSES_H
|
|
# include <ncurses/ncurses.h>
|
|
#elif HAVE_NCURSES_CURSES_H
|
|
# include <ncurses/curses.h>
|
|
#elif HAVE_CURSES_H
|
|
# include <curses.h>
|
|
#endif
|
|
|
|
#define THIRTYFPS NANOSECONDS_PER_SECOND / 30UL // 30FPS
|
|
|
|
#define ASCII_BACK 0x08
|
|
#define ASCII_TAB 0x09
|
|
#define ASCII_LF 0x0a
|
|
#define ASCII_CR 0x0d
|
|
#define ASCII_ESC 0x1b
|
|
|
|
//#define COLOR_BLACK 0
|
|
//#define COLOR_MAGENTA 5
|
|
#define COLOR_DARKBLUE 32
|
|
#define COLOR_PURPLE 33
|
|
#define COLOR_DARKGREEN 34
|
|
#define COLOR_DARKGREY 35
|
|
#define COLOR_MEDBLUE 36
|
|
#define COLOR_LIGHTBLUE 37
|
|
#define COLOR_BROWN 38
|
|
#define COLOR_ORANGE 39
|
|
#define COLOR_LIGHTGREY 40
|
|
#define COLOR_PINK 41
|
|
//#define COLOR_GREEN 2
|
|
//#define COLOR_YELLOW 3
|
|
#define COLOR_AQUA 42
|
|
//#define COLOR_WHITE 7
|
|
|
|
static bool ncvideo_running = true;
|
|
|
|
static WINDOW *winCurr = NULL;
|
|
static WINDOW *winMenu = NULL; // 24 x 80 text
|
|
static WINDOW *winTxt40 = NULL; // 24 x 40 text
|
|
static WINDOW *winTxt80 = NULL; // 24 x 80 text
|
|
static WINDOW *winScale = NULL; // graphics mode scaled/interpolated to terminal dimensions
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// ncurses video backend helper routines
|
|
|
|
static void _nc_convertAppleGlyphs(INOUT chtype *c, OUTPARM char **s, uint8_t *cs) {
|
|
switch (*c) {
|
|
|
|
case 0x7f: // cursor ...
|
|
*c = '_';
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
|
|
case 0x80: // closed apple ...
|
|
*c = '@';
|
|
*cs = BLACK_ON_MAGENTA;
|
|
break;
|
|
case 0x81: // open apple ...
|
|
*c = '@';
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x82: // caret ...
|
|
*c = '^';
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x83: // hourglass ...
|
|
*c = '%';
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x84: // checkmark ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "✓";
|
|
#else
|
|
*c = 'x';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x85: // reverse checkmark ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "✓";
|
|
////*cs = INVERSE_CURRENT;
|
|
#else
|
|
*c = 'X';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x86: // runner left ...
|
|
*c = ']';
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x87: // runner right ...
|
|
*c = '[';
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x88: // left arrow ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "←";
|
|
#else
|
|
*c = '<';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x89: // ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "…";
|
|
#else
|
|
*c = '.';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x8a: // down arrow ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "↓";
|
|
#else
|
|
*c = '!';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x8b: // up arrow ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "↑";
|
|
#else
|
|
*c = '^';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x8c: // top bar ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "¯";
|
|
#else
|
|
*c = '-';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x8d: // CR ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "⏎";
|
|
#else
|
|
*c = '^';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x8e: // block ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "█";
|
|
#else
|
|
*c = '#';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x8f: // filled left arrow ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "⇦";
|
|
#else
|
|
*c = '<';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x90: // filled right arrow ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "⇨";
|
|
#else
|
|
*c = '>';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x91: // filled down arrow ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "⇩";
|
|
#else
|
|
*c = '!';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x92: // filled up arrow ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "⇧";
|
|
#else
|
|
*c = '^';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x93: // mdash ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "—";
|
|
#else
|
|
*c = '-';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x94: // box bottom left ...
|
|
*c = '\\';
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x95: // right arrow ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "→";
|
|
#else
|
|
*c = '>';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
|
|
case 0x96: // hash #1 ...
|
|
*c = '#';
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x97: // hash #2 ...
|
|
*c = '#';
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x98: // folder left ...
|
|
*c = 'f';
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x99: // folder right ...
|
|
*c = 'f';
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x9a: // right full bar ...
|
|
*c = '|';
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x9b: // diamond ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "◆";
|
|
#else
|
|
*c = '*';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x9c: // top and bottom bars ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "◆";
|
|
#else
|
|
*c = '=';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x9d: // white plus ...
|
|
*c = '+';
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x9e: // box and dot ...
|
|
#if NCURSES_UTF8
|
|
*c = '\0';
|
|
*s = "▣";
|
|
#else
|
|
*c = '#';
|
|
#endif
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
case 0x9f: // left full bar ...
|
|
*c = '|';
|
|
*cs = BLACK_ON_RED;
|
|
break;
|
|
|
|
// interface menus ...
|
|
case 0xA0:
|
|
case 0xA3:
|
|
*c = '/';
|
|
break;
|
|
case 0xA1:
|
|
case 0xA2:
|
|
*c = '\\';
|
|
break;
|
|
case 0xA4:
|
|
*c = '|';
|
|
break;
|
|
case 0xA5:
|
|
*c = '-';
|
|
break;
|
|
case 0xA6:
|
|
case 0xA7:
|
|
case 0xA8:
|
|
case 0xA9:
|
|
case 0xAA:
|
|
*c = '+';
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int _nc_keyToEmulator(int ncKey, int *isCooked) {
|
|
int a2key = ncKey;
|
|
|
|
bool isASCII = false;
|
|
switch (ncKey) {
|
|
|
|
case KEY_F(1):
|
|
a2key = SCODE_F1;
|
|
break;
|
|
case KEY_F(2):
|
|
a2key = SCODE_F2;
|
|
break;
|
|
case KEY_F(3):
|
|
a2key = SCODE_F3;
|
|
break;
|
|
case KEY_F(4):
|
|
a2key = SCODE_F4;
|
|
break;
|
|
case KEY_F(5):
|
|
a2key = SCODE_F5;
|
|
break;
|
|
case KEY_F(6):
|
|
a2key = SCODE_F6;
|
|
break;
|
|
case KEY_F(7):
|
|
a2key = SCODE_F7;
|
|
break;
|
|
case KEY_F(8):
|
|
a2key = SCODE_F8;
|
|
break;
|
|
case KEY_F(9):
|
|
a2key = SCODE_F9;
|
|
break;
|
|
case KEY_F(10):
|
|
a2key = SCODE_F10;
|
|
break;
|
|
case KEY_F(11):
|
|
a2key = SCODE_F11;
|
|
break;
|
|
case KEY_F(12):
|
|
a2key = SCODE_F12;
|
|
break;
|
|
|
|
case KEY_DOWN:
|
|
a2key = SCODE_D;
|
|
break;
|
|
case KEY_BACKSPACE:
|
|
case KEY_LEFT:
|
|
case ASCII_BACK:
|
|
a2key = SCODE_L;
|
|
break;
|
|
case KEY_RIGHT:
|
|
a2key = SCODE_R;
|
|
break;
|
|
case KEY_UP:
|
|
a2key = SCODE_U;
|
|
break;
|
|
|
|
case ASCII_LF:
|
|
case ASCII_CR:
|
|
a2key = SCODE_RET;
|
|
break;
|
|
|
|
case ASCII_ESC:
|
|
a2key = SCODE_ESC;
|
|
break;
|
|
|
|
case ASCII_TAB:
|
|
default:
|
|
if (a2key >= 0 && a2key < 256) {
|
|
isASCII = true;
|
|
} else {
|
|
a2key = -1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
*isCooked = isASCII;
|
|
return a2key;
|
|
}
|
|
|
|
#define COLOR_VALUES(x) \
|
|
((NCURSES_COLOR_T)(colormap[x].red ) * 1000)/255, \
|
|
((NCURSES_COLOR_T)(colormap[x].green) * 1000)/255, \
|
|
((NCURSES_COLOR_T)(colormap[x].blue) * 1000)/255
|
|
|
|
static void _nc_initColors(void) {
|
|
if (has_colors() == FALSE) {
|
|
LOG("Your terminal does not support color, so emulated color values will not be correct...");
|
|
return;
|
|
}
|
|
|
|
if (can_change_color() == FALSE) {
|
|
LOG("Your terminal does not support changing color values, so emulated color values will not be correct...");
|
|
// drop through to start default color support ...
|
|
}
|
|
|
|
int ret = start_color();
|
|
if (ret == ERR) {
|
|
LOG("OOPS, could not initialize ncurses colors");
|
|
}
|
|
|
|
// ncurses scales colors between 0 - 1000
|
|
|
|
ret = init_color(COLOR_MAGENTA ,COLOR_VALUES(IDX_MAGENTA ));
|
|
ret = init_color(COLOR_DARKBLUE ,COLOR_VALUES(IDX_DARKBLUE ));
|
|
ret = init_color(COLOR_PURPLE ,COLOR_VALUES(IDX_PURPLE ));
|
|
ret = init_color(COLOR_DARKGREEN,COLOR_VALUES(IDX_DARKGREEN));
|
|
ret = init_color(COLOR_DARKGREY ,COLOR_VALUES(IDX_DARKGREY ));
|
|
ret = init_color(COLOR_MEDBLUE ,COLOR_VALUES(IDX_MEDBLUE ));
|
|
ret = init_color(COLOR_LIGHTBLUE,COLOR_VALUES(IDX_LIGHTBLUE));
|
|
ret = init_color(COLOR_BROWN ,COLOR_VALUES(IDX_BROWN ));
|
|
ret = init_color(COLOR_ORANGE ,COLOR_VALUES(IDX_ORANGE ));
|
|
ret = init_color(COLOR_LIGHTGREY,COLOR_VALUES(IDX_LIGHTGREY));
|
|
ret = init_color(COLOR_PINK ,COLOR_VALUES(IDX_PINK ));
|
|
ret = init_color(COLOR_GREEN ,COLOR_VALUES(IDX_GREEN ));
|
|
ret = init_color(COLOR_YELLOW ,COLOR_VALUES(IDX_YELLOW ));
|
|
ret = init_color(COLOR_AQUA ,COLOR_VALUES(IDX_AQUA ));
|
|
|
|
// interface and mousetext colors
|
|
init_pair(1+GREEN_ON_BLACK, COLOR_GREEN, COLOR_BLACK );
|
|
init_pair(1+GREEN_ON_BLUE, COLOR_GREEN, COLOR_BLUE );
|
|
init_pair(1+RED_ON_BLACK, COLOR_RED, COLOR_BLACK );
|
|
init_pair(1+BLUE_ON_BLACK, COLOR_BLUE, COLOR_BLACK );
|
|
init_pair(1+WHITE_ON_BLACK, COLOR_WHITE, COLOR_BLACK );
|
|
|
|
init_pair(1+BLACK_ON_RED, COLOR_BLACK, COLOR_RED );
|
|
|
|
// 16 COLORS:
|
|
init_pair(1+BLACK_ON_BLACK, COLOR_BLACK, COLOR_BLACK );
|
|
init_pair(1+BLACK_ON_MAGENTA, COLOR_BLACK, COLOR_MAGENTA );
|
|
init_pair(1+BLACK_ON_DARKBLUE, COLOR_BLACK, COLOR_DARKBLUE );
|
|
init_pair(1+BLACK_ON_PURPLE, COLOR_BLACK, COLOR_PURPLE );
|
|
init_pair(1+BLACK_ON_DARKGREEN, COLOR_BLACK, COLOR_DARKGREEN);
|
|
init_pair(1+BLACK_ON_DARKGREY, COLOR_BLACK, COLOR_DARKGREY );
|
|
init_pair(1+BLACK_ON_MEDBLUE, COLOR_BLACK, COLOR_MEDBLUE );
|
|
init_pair(1+BLACK_ON_LIGHTBLUE, COLOR_BLACK, COLOR_LIGHTBLUE);
|
|
init_pair(1+BLACK_ON_BROWN, COLOR_BLACK, COLOR_BROWN );
|
|
init_pair(1+BLACK_ON_ORANGE, COLOR_BLACK, COLOR_ORANGE );
|
|
init_pair(1+BLACK_ON_LIGHTGREY, COLOR_BLACK, COLOR_LIGHTGREY);
|
|
init_pair(1+BLACK_ON_PINK, COLOR_BLACK, COLOR_PINK );
|
|
init_pair(1+BLACK_ON_GREEN, COLOR_BLACK, COLOR_GREEN );
|
|
init_pair(1+BLACK_ON_YELLOW, COLOR_BLACK, COLOR_YELLOW );
|
|
init_pair(1+BLACK_ON_AQUA, COLOR_BLACK, COLOR_AQUA );
|
|
init_pair(1+BLACK_ON_WHITE, COLOR_BLACK, COLOR_WHITE );
|
|
}
|
|
|
|
static WINDOW *_nc_newwin(unsigned int height, int width, int starty, int startx) {
|
|
WINDOW *win = newwin(height+2, width+2, starty-1, startx-1);
|
|
//box(win, 0 , 0);
|
|
//meta(win, TRUE);
|
|
return win;
|
|
}
|
|
|
|
static void _nc_delwin(WINDOW **winRef) {
|
|
wborder(*winRef, ' ', ' ', ' ',' ',' ',' ',' ',' ');
|
|
wrefresh(*winRef);
|
|
delwin(*winRef);
|
|
*winRef = NULL;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// ncurses video backend main routines
|
|
|
|
static const char *ncvideo_name(void) {
|
|
return "ncurses";
|
|
}
|
|
|
|
static void ncvideo_init(void *context) {
|
|
// ...
|
|
}
|
|
|
|
static void ncvideo_main_loop(void) {
|
|
|
|
// start curses mode and check colors ...
|
|
initscr();
|
|
_nc_initColors();
|
|
|
|
LOG("ncurses video main loop beginning, silencing STDERR logging ...");
|
|
do_std_logging = false;
|
|
|
|
noecho(); // Do not echo output ...
|
|
raw(); // Line buffering disabled ...
|
|
keypad(stdscr, TRUE); // Special and F keys ...
|
|
nodelay(stdscr, TRUE); // getch() is non-blocking ...
|
|
|
|
int starty24 = (LINES - TEXT_ROWS) / 2;
|
|
int startx40 = (COLS - 40) / 2;
|
|
int startx80 = (COLS - 80) / 2;
|
|
|
|
winMenu = _nc_newwin(TEXT_ROWS, 80, starty24, startx80);
|
|
winTxt40 = _nc_newwin(TEXT_ROWS, 40, starty24, startx40);
|
|
winTxt80 = _nc_newwin(TEXT_ROWS, 80, starty24, startx80);
|
|
winScale = _nc_newwin(LINES, COLS, 0, 0);
|
|
|
|
////wbkgd(winTxt40, COLOR_PAIR(1)); // GREEN ON BLACK (for now)
|
|
////wbkgd(winTxt80, COLOR_PAIR(1)); // GREEN ON BLACK (for now)
|
|
|
|
static uint8_t fb[SCANWIDTH*SCANHEIGHT*sizeof(uint8_t)];
|
|
|
|
#if INTERFACE_CLASSIC
|
|
interface_setStagingFramebuffer(fb);
|
|
#endif
|
|
|
|
while (ncvideo_running) {
|
|
|
|
unsigned long wasDirty = 0UL;
|
|
if (!cpu_isPaused()) {
|
|
// check if a2 video memory is dirty
|
|
wasDirty = video_clearDirty(A2_DIRTY_FLAG);
|
|
if (wasDirty) {
|
|
display_renderStagingFramebuffer(fb);
|
|
}
|
|
}
|
|
|
|
if (interface_isShowing()) {
|
|
winCurr = winMenu;
|
|
WINDOW *winPrev = winCurr;
|
|
if (winPrev != winCurr) {
|
|
wclear(winPrev);
|
|
}
|
|
}
|
|
|
|
wasDirty = video_clearDirty(FB_DIRTY_FLAG);
|
|
if (wasDirty) {
|
|
wrefresh(winCurr);
|
|
}
|
|
|
|
// handle keyboard input
|
|
int c = getch();
|
|
int is_ascii = 0;
|
|
if (c == ERR) {
|
|
c_keys_handle_input(-1, /*pressed:*/0, /*is_ascii:*/0);
|
|
} else {
|
|
c = _nc_keyToEmulator(c, &is_ascii);
|
|
|
|
if (is_ascii) {
|
|
c_keys_handle_input(c, /*pressed:*/1, /*is_ascii:*/1);
|
|
} else {
|
|
c_keys_handle_input(c, /*pressed:*/1, /*is_ascii:*/0);
|
|
c_keys_handle_input(c, /*pressed:*/0, /*is_ascii:*/0);
|
|
}
|
|
}
|
|
|
|
// FIXME TODO ... does not account for execution time drift
|
|
struct timespec thirtyfps = { .tv_sec = 0, .tv_nsec = THIRTYFPS };
|
|
nanosleep(&thirtyfps, NULL);
|
|
}
|
|
|
|
_nc_delwin(&winMenu);
|
|
_nc_delwin(&winTxt40);
|
|
_nc_delwin(&winTxt80);
|
|
_nc_delwin(&winScale);
|
|
|
|
endwin();
|
|
}
|
|
|
|
static void ncvideo_render(void) {
|
|
// no-op ...
|
|
}
|
|
|
|
static void ncvideo_shutdown(void) {
|
|
ncvideo_running = false;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// plotting callbacks ...
|
|
|
|
static void _nc_graphicsUpdate(pixel_delta_t pixel) {
|
|
// TODO FIXME ... actually need to scale/plot graphics here
|
|
|
|
uint8_t cs = pixel.cs;
|
|
uint8_t row = pixel.row;
|
|
uint8_t col = pixel.col;
|
|
|
|
assert(cs == COLOR16);
|
|
|
|
WINDOW *win = winCurr;
|
|
chtype c = 0;
|
|
char *s = NULL;
|
|
|
|
uint8_t color = (pixel.b & 0x0F);
|
|
c = ' ';
|
|
switch (color) {
|
|
case 0x0: // Black
|
|
cs = BLACK_ON_BLACK;
|
|
break;
|
|
case 0x1: // Magenta
|
|
cs = BLACK_ON_MAGENTA;
|
|
break;
|
|
case 0x2: // Dark Blue
|
|
cs = BLACK_ON_DARKBLUE;
|
|
break;
|
|
case 0x3: // Purple
|
|
cs = BLACK_ON_PURPLE;
|
|
break;
|
|
case 0x4: // Dark Green
|
|
cs = BLACK_ON_DARKGREEN;
|
|
break;
|
|
case 0x5: // Dark Grey
|
|
cs = BLACK_ON_DARKGREY;
|
|
break;
|
|
case 0x6: // Medium Blue
|
|
cs = BLACK_ON_MEDBLUE;
|
|
break;
|
|
case 0x7: // Light Blue
|
|
cs = BLACK_ON_LIGHTBLUE;
|
|
break;
|
|
case 0x8: // Brown
|
|
cs = BLACK_ON_BROWN;
|
|
break;
|
|
case 0x9: // Orange
|
|
cs = BLACK_ON_ORANGE;
|
|
break;
|
|
case 0xa: // Light Grey
|
|
cs = BLACK_ON_LIGHTGREY;
|
|
break;
|
|
case 0xb: // Pink
|
|
cs = BLACK_ON_PINK;
|
|
break;
|
|
case 0xc: // Green
|
|
cs = BLACK_ON_GREEN;
|
|
break;
|
|
case 0xd: // Yellow
|
|
cs = BLACK_ON_YELLOW;
|
|
break;
|
|
case 0xe: // Aqua
|
|
cs = BLACK_ON_AQUA;
|
|
break;
|
|
case 0xf: // WHITE
|
|
cs = BLACK_ON_WHITE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void _nc_textUpdate(pixel_delta_t pixel) {
|
|
uint8_t cs = pixel.cs;
|
|
uint8_t row = pixel.row;
|
|
uint8_t col = pixel.col;
|
|
|
|
WINDOW *win = winCurr;
|
|
chtype c = 0;
|
|
char *s = NULL;
|
|
font_mode_t fontMode = FONT_MODE_NORMAL;
|
|
do {
|
|
if (cs == COLOR16) {
|
|
// LORES/DLORES ...
|
|
_nc_graphicsUpdate(pixel);
|
|
return;
|
|
} else if (cs == INVALID_COLORSCHEME) {
|
|
// TEXT
|
|
c = keys_apple2ASCII(pixel.b, &fontMode);
|
|
_nc_convertAppleGlyphs(&c, &s, &cs);
|
|
} else {
|
|
// interface menu
|
|
c = pixel.b;
|
|
_nc_convertAppleGlyphs(&c, &s, &cs);
|
|
win = winMenu;
|
|
}
|
|
|
|
attr_t attrs = 0;
|
|
{
|
|
short ignoredPair = 0;
|
|
if (wattr_get(win, &attrs, &ignoredPair, /*opts:*/NULL) == ERR) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fontMode == FONT_MODE_INVERSE) {
|
|
wattron(win, A_REVERSE);
|
|
} else if (fontMode == FONT_MODE_FLASH) {
|
|
wattron(win, A_BOLD); // TODO FIXME ... A_BLINK not reliable, but maybe switch between normal and A_REVERSE at the flash rate?
|
|
}
|
|
|
|
if (cs != INVALID_COLORSCHEME) {
|
|
wattr_set(win, attrs, cs+1, /*opts:*/NULL);
|
|
}
|
|
#if NCURSES_UTF8
|
|
if (s) {
|
|
mvwaddstr(win, row+1, col+1, s);
|
|
} else
|
|
#endif
|
|
mvwaddch(win, row+1, col+1, c);
|
|
if (cs != INVALID_COLORSCHEME) {
|
|
wattr_set(win, attrs, 0, /*opts:*/NULL);
|
|
}
|
|
|
|
|
|
if (fontMode == FONT_MODE_INVERSE) {
|
|
wattroff(win, A_REVERSE);
|
|
} else if (fontMode == FONT_MODE_FLASH) {
|
|
wattroff(win, A_BOLD);
|
|
}
|
|
} while (0);
|
|
}
|
|
|
|
static void _nc_modeChange(pixel_delta_t pixel) {
|
|
#warning FIXME TODO, this copies display.c/vm.c routines somewhat, likely need to consolidate if/when other video backends implement video update callbacks
|
|
if (interface_isShowing()) {
|
|
return;
|
|
}
|
|
|
|
WINDOW *winPrev = winCurr;
|
|
uint32_t currswitches = run_args.softswitches;
|
|
if ((currswitches & SS_TEXT) && !(currswitches & SS_MIXED)) {
|
|
winCurr = (currswitches & SS_80COL) ? winTxt80 : winTxt40;
|
|
} else {
|
|
winCurr = winScale;
|
|
}
|
|
if (winPrev != winCurr) {
|
|
wclear(winPrev);
|
|
wrefresh(winPrev);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
static void _init_ncvideo(void) {
|
|
LOG("Initializing ncurses renderer");
|
|
|
|
static video_backend_s ncvideo_backend = { 0 };
|
|
ncvideo_backend.name = &ncvideo_name;
|
|
ncvideo_backend.init = &ncvideo_init;
|
|
ncvideo_backend.main_loop = &ncvideo_main_loop;
|
|
ncvideo_backend.render = &ncvideo_render;
|
|
ncvideo_backend.shutdown = &ncvideo_shutdown;
|
|
|
|
static video_animation_s ncvideo_anim = {
|
|
#if 1
|
|
0
|
|
// FIXME TODO ... likely we need to follow the 'glnode' model with 'ncnode.c' and render the ncalert.c stuff over the underlying winCurr here ...
|
|
#else
|
|
.animation_showMessage = &_ncanim_showMessage;
|
|
.animation_showPaused = &_ncanim_showPaused;
|
|
.animation_showCPUSpeed = &_ncanim_showCPUSpeed;
|
|
.animation_showDiskChosen = &_ncanim_showDiskChosen;
|
|
.animation_showTrackSector = &_ncanim_showTrackSector;
|
|
#endif
|
|
};
|
|
ncvideo_backend.anim = &ncvideo_anim;
|
|
|
|
video_registerBackend(&ncvideo_backend, VID_PRIO_TERMINAL);
|
|
display_setUpdateCallback(DRAWPAGE_TEXT, &_nc_textUpdate);
|
|
display_setUpdateCallback(DRAWPAGE_MODE_CHANGE, &_nc_modeChange);
|
|
}
|
|
|
|
static __attribute__((constructor)) void __init_ncvideo(void) {
|
|
emulator_registerStartupCallback(CTOR_PRIORITY_EARLY, &_init_ncvideo);
|
|
}
|
|
|