First cut at CLI ncurses video renderer

- Currently supports 40/80col TEXT modes
    - TODO : graphics scaling
This commit is contained in:
Aaron Culliney 2017-09-09 17:14:51 -10:00
parent dd02333eae
commit b5b79faf1d
6 changed files with 853 additions and 3 deletions

View File

@ -36,6 +36,7 @@ VIDEO_SRC = \
src/video/glnode.c \
src/video/glhudmodel.c \
src/video/glalert.c \
src/video/ncvideo.c \
src/video/video.c \
src/video/xvideo.c \
src/video_util/matrixUtil.c \

View File

@ -146,6 +146,7 @@ dnl Video ...
AC_PATH_XTRA
video_output="VIDEO RENDERERS:"
VIDEO_O="src/video/video.o"
testcpu_VIDEO_O="src/video/video.o"
testdisk_VIDEO_O="src/video/video.o"
@ -163,7 +164,7 @@ AC_CHECK_HEADER(GL/glew.h, [
AC_SEARCH_LIBS(glewInit, [GLEW glew], [
AS_IF([test "x$enable_opengl" != "xno"], [
video_output="$video_output OpenGL"
AC_DEFINE(VIDEO_OPENGL, 1, [Building with OpenGL support])
AC_DEFINE(USE_GLUT, 1, [Use GLUT library])
VIDEO_O="$VIDEO_O src/video/glvideo.o src/video/glnode.o src/video/glalert.o src/video/glhudmodel.o src/video/glutinput.o src/video_util/matrixUtil.o src/video_util/modelUtil.o src/video_util/sourceUtil.o src/video_util/vectorUtil.o"
@ -175,7 +176,7 @@ AC_CHECK_HEADER(GL/glew.h, [
testtrace_VIDEO_O="$testtrace_VIDEO_O src/video/testtrace-glvideo.o src/video/testtrace-glnode.o src/video/testtrace-glalert.o src/video/testtrace-glhudmodel.o src/video/testtrace-glutinput.o src/video_util/testtrace-matrixUtil.o src/video_util/testtrace-modelUtil.o src/video_util/testtrace-sourceUtil.o src/video_util/testtrace-vectorUtil.o"
testui_VIDEO_O="$testui_VIDEO_O src/video/testui-glvideo.o src/video/testui-glnode.o src/video/testui-glalert.o src/video/testui-glhudmodel.o src/video/testui-glutinput.o src/video_util/testui-matrixUtil.o src/video_util/testui-modelUtil.o src/video_util/testui-sourceUtil.o src/video_util/testui-vectorUtil.o"
testvm_VIDEO_O="$testvm_VIDEO_O src/video/testvm-glvideo.o src/video/testvm-glnode.o src/video/testvm-glalert.o src/video/testvm-glhudmodel.o src/video/testvm-glutinput.o src/video_util/testvm-matrixUtil.o src/video_util/testvm-modelUtil.o src/video_util/testvm-sourceUtil.o src/video_util/testvm-vectorUtil.o"
AC_MSG_RESULT([Building emulator with OpenGL support, w00t!])
AC_MSG_RESULT([configure: NOTE: Building emulator with OpenGL support, w00t!])
])
], [
AS_IF([test "x$enable_opengl" != "xno"], [
@ -213,6 +214,7 @@ AC_CHECK_HEADER(X11/XKBlib.h, [
], [
AC_MSG_WARN([Building emulator without support of X11 MITSHM extension...])
], [-lX11])
video_output="$video_output X11"
VIDEO_O="$VIDEO_O src/video/xvideo.o"
dnl HACK there's gotta be a better way ... without this verbosity, CFLAGS are not correct (lacking -DTESTING=1 , etc) if we don't specify specific obj files for test binaries
testcpu_VIDEO_O="$testcpu_VIDEO_O src/video/testcpu-xvideo.o"
@ -222,7 +224,7 @@ AC_CHECK_HEADER(X11/XKBlib.h, [
testtrace_VIDEO_O="$testtrace_VIDEO_O src/video/testtrace-xvideo.o"
testui_VIDEO_O="$testui_VIDEO_O src/video/testui-xvideo.o"
testvm_VIDEO_O="$testvm_VIDEO_O src/video/testvm-xvideo.o"
AC_MSG_RESULT([Building emulator with X11 support])
AC_MSG_RESULT([configure: NOTE: Building emulator with X11 support])
])
], [
AS_IF([test "x$enable_x11" != "xno"], [
@ -236,6 +238,46 @@ AC_CHECK_HEADER(X11/XKBlib.h, [
])
AC_ARG_ENABLE([ncurses], AS_HELP_STRING([--enable-ncurses], [Enable ncurses graphics output (autodetected)]))
AS_IF([test "x$enable_ncurses" != "xno"], [
AC_CHECK_HEADERS(ncurses.h ncursesw/ncurses.h ncurses/ncurses.h ncurses/curses.h curses.h, [
AC_SEARCH_LIBS(initscr, [ncursesw], [
found_ncurses="1"
video_output="$video_output terminal"
VIDEO_O="$VIDEO_O src/video/ncvideo.o"
dnl HACK there's gotta be a better way ... without this verbosity, CFLAGS are not correct (lacking -DTESTING=1 , etc) if we don't specify specific obj files for test binaries
testcpu_VIDEO_O="$testcpu_VIDEO_O src/video/testcpu-ncvideo.o"
testdisk_VIDEO_O="$testdisk_VIDEO_O src/video/testdisk-ncvideo.o"
testdisplay_VIDEO_O="$testdisplay_VIDEO_O src/video/testdisplay-ncvideo.o"
testprefs_VIDEO_O="$testprefs_VIDEO_O src/video/testprefs-ncvideo.o"
testtrace_VIDEO_O="$testtrace_VIDEO_O src/video/testtrace-ncvideo.o"
testui_VIDEO_O="$testui_VIDEO_O src/video/testui-ncvideo.o"
testvm_VIDEO_O="$testvm_VIDEO_O src/video/testvm-ncvideo.o"
AC_DEFINE(NCURSES_UTF8, 1, [ncurses supports UTF-8])
AC_MSG_RESULT([configure: NOTE: Building emulator with ncurses (UTF-8) support])
], [
AC_SEARCH_LIBS(initscr, [ncurses], [
found_ncurses="1"
video_output="$video_output terminal"
VIDEO_O="$VIDEO_O src/video/ncvideo.o"
testcpu_VIDEO_O="$testcpu_VIDEO_O src/video/testcpu-ncvideo.o"
testdisk_VIDEO_O="$testdisk_VIDEO_O src/video/testdisk-ncvideo.o"
testdisplay_VIDEO_O="$testdisplay_VIDEO_O src/video/testdisplay-ncvideo.o"
testprefs_VIDEO_O="$testprefs_VIDEO_O src/video/testprefs-ncvideo.o"
testtrace_VIDEO_O="$testtrace_VIDEO_O src/video/testtrace-ncvideo.o"
testui_VIDEO_O="$testui_VIDEO_O src/video/testui-ncvideo.o"
testvm_VIDEO_O="$testvm_VIDEO_O src/video/testvm-ncvideo.o"
AC_MSG_RESULT([configure: NOTE: Building emulator with ncurses support])
])
])
break
])
])
AS_IF([test "x$found_ncurses" != "x1"], [
AC_MSG_WARN([Did not find ncurses headers/libraries ... ncurses support is disabled ...])
])
AC_SUBST(VIDEO_O)
AC_SUBST(testcpu_VIDEO_O)
AC_SUBST(testdisk_VIDEO_O)
@ -261,12 +303,14 @@ testvm_AUDIO_O="src/audio/testvm-soundcore.o src/audio/testvm-speaker.o src/audi
AC_ARG_ENABLE([openal], AS_HELP_STRING([--enable-openal], [Enable OpenAL audio output (autodetected)]))
audio_output="AUDIO RENDERERS:"
AC_CHECK_HEADER(AL/al.h, [
AC_CHECK_HEADER(AL/alc.h, [
AC_CHECK_HEADER(AL/alext.h, [
AC_SEARCH_LIBS(alcOpenDevice, openal, [
AS_IF([test "x$enable_openal" != "xno"], [
dnl found OpenAL ...
audio_output="$audio_output OpenAL"
AUDIO_O="$AUDIO_O src/audio/soundcore-openal.o src/audio/playqueue.o src/audio/alhelpers.o"
dnl HACK there's gotta be a better way ... without this verbosity, CFLAGS are not correct (lacking -DTESTING=1 , etc) if we don't specify specific obj files for test binaries
testcpu_AUDIO_O="$testcpu_AUDIO_O src/audio/testcpu-soundcore-openal.o src/audio/testcpu-playqueue.o src/audio/testcpu-alhelpers.o"
@ -276,6 +320,7 @@ AC_CHECK_HEADER(AL/al.h, [
testtrace_AUDIO_O="$testtrace_AUDIO_O src/audio/testtrace-soundcore-openal.o src/audio/testtrace-playqueue.o src/audio/testtrace-alhelpers.o"
testui_AUDIO_O="$testui_AUDIO_O src/audio/testui-soundcore-openal.o src/audio/testui-playqueue.o src/audio/testui-alhelpers.o"
testvm_AUDIO_O="$testvm_AUDIO_O src/audio/testvm-soundcore-openal.o src/audio/testvm-playqueue.o src/audio/testvm-alhelpers.o"
AC_MSG_RESULT([configure: NOTE: Building emulator with OpenAL sound output, w00t!])
])
], [
AS_IF([test "x$enable_openal" != "xno"], [
@ -326,3 +371,9 @@ dnl ---------------------------------------------------------------------------
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
AC_MSG_RESULT([])
AC_MSG_RESULT([Apple //ix emulator A/V configuration:])
AC_MSG_RESULT([ $video_output])
AC_MSG_RESULT([ $audio_output])
AC_MSG_RESULT([])

View File

@ -60,8 +60,10 @@ static const int kVSyncLines = 4; // lines per VSync duration
static uint8_t video__odd_colors[2] = { COLOR_LIGHT_PURPLE, COLOR_LIGHT_BLUE };
static uint8_t video__even_colors[2] = { COLOR_LIGHT_GREEN, COLOR_LIGHT_RED };
// TODO FIXME : support linked list of callbacks here ...
static display_update_fn textCallbackFn = NULL;
static display_update_fn hiresCallbackFn = NULL;
static display_update_fn modeCallbackFn = NULL;
// 40col/80col/lores/hires/dhires line offsets
static uint16_t video__line_offset[TEXT_ROWS] = {
@ -1264,6 +1266,8 @@ void display_setUpdateCallback(drawpage_mode_t mode, display_update_fn updateFn)
textCallbackFn = updateFn;
} else if (mode == DRAWPAGE_HIRES) {
hiresCallbackFn = updateFn;
} else if (mode == DRAWPAGE_MODE_CHANGE) {
modeCallbackFn = updateFn;
} else {
assert(false);
}
@ -1372,6 +1376,9 @@ bool video_isDirty(unsigned long flags) {
}
unsigned long video_setDirty(unsigned long flags) {
if (modeCallbackFn) {
modeCallbackFn((pixel_delta_t){ 0 });
}
return __sync_fetch_and_or(&_vid_dirty, flags);
}

View File

@ -70,6 +70,7 @@ typedef enum font_mode_t {
typedef enum drawpage_mode_t {
DRAWPAGE_TEXT = 1,
DRAWPAGE_HIRES,
DRAWPAGE_MODE_CHANGE,
NUM_DRAWPAGE_MODES,
} drawpage_mode_t;

View File

@ -22,6 +22,29 @@ typedef enum interface_colorscheme_t {
GREEN_ON_BLACK = 0,
GREEN_ON_BLUE,
RED_ON_BLACK,
BLACK_ON_RED,
// 16 COLORS
BLACK_ON_BLACK,
BLACK_ON_MAGENTA,
BLACK_ON_DARKBLUE,
BLACK_ON_PURPLE,
BLACK_ON_DARKGREEN,
BLACK_ON_DARKGREY,
BLACK_ON_MEDBLUE,
BLACK_ON_LIGHTBLUE,
BLACK_ON_BROWN,
BLACK_ON_ORANGE,
BLACK_ON_LIGHTGREY,
BLACK_ON_PINK,
BLACK_ON_GREEN,
BLACK_ON_YELLOW,
BLACK_ON_AQUA,
BLACK_ON_WHITE,
NUM_INTERFACE_COLORSCHEMES,
COLOR16 = 0x80,
INVALID_COLORSCHEME = 0xFF,
} interface_colorscheme_t;
#if INTERFACE_CLASSIC

767
src/video/ncvideo.c Normal file
View File

@ -0,0 +1,767 @@
/*
* 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+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 = 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);
}