mirror of
https://github.com/mauiaaron/apple2.git
synced 2024-06-01 13:41:28 +00:00
First cut at CLI ncurses video renderer
- Currently supports 40/80col TEXT modes - TODO : graphics scaling
This commit is contained in:
parent
dd02333eae
commit
b5b79faf1d
|
@ -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 \
|
||||
|
|
57
configure.ac
57
configure.ac
|
@ -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([])
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
767
src/video/ncvideo.c
Normal 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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user