#include "StdAfx.h" #include #include #include #include "Common.h" #include "Disk.h" #include "Video.h" #include "DiskImageHelper.h" #include "Memory.h" #include "linux/interface.h" #include "frontends/ncurses/frame.h" #include "frontends/ncurses/colors.h" namespace { std::shared_ptr frame; std::shared_ptr grColors; int g_nTrackDrive1 = -1; int g_nTrackDrive2 = -1; int g_nSectorDrive1 = -1; int g_nSectorDrive2 = -1; TCHAR g_sTrackDrive1 [8] = TEXT("??"); TCHAR g_sTrackDrive2 [8] = TEXT("??"); TCHAR g_sSectorDrive1[8] = TEXT("??"); TCHAR g_sSectorDrive2[8] = TEXT("??"); Disk_Status_e g_eStatusDrive1 = DISK_STATUS_OFF; Disk_Status_e g_eStatusDrive2 = DISK_STATUS_OFF; LPBYTE g_pTextBank1; // Aux LPBYTE g_pTextBank0; // Main LPBYTE g_pHiresBank1; LPBYTE g_pHiresBank0; #define SW_80COL (g_uVideoMode & VF_80COL) #define SW_DHIRES (g_uVideoMode & VF_DHIRES) #define SW_HIRES (g_uVideoMode & VF_HIRES) #define SW_80STORE (g_uVideoMode & VF_80STORE) #define SW_MIXED (g_uVideoMode & VF_MIXED) #define SW_PAGE2 (g_uVideoMode & VF_PAGE2) #define SW_TEXT (g_uVideoMode & VF_TEXT) BYTE nextKey = 0; bool keyReady = false; bool openApple = false; bool solidApple = false; bool g_bTextFlashState = false; void sig_handler(int signo) { endwin(); exit(1); } chtype mapCharacter(BYTE ch) { const char low = ch & 0x7f; const char high = ch & 0x80; chtype result = low; const int code = low >> 5; switch (code) { case 0: // 00 - 1F result += 0x40; // UPPERCASE break; case 1: // 20 - 3F // SPECIAL CHARACTER break; case 2: // 40 - 5F // UPPERCASE break; case 3: // 60 - 7F // LOWERCASE if (high == 0 && g_nAltCharSetOffset == 0) { result -= 0x40; } break; } if (result == 0x7f) { result = ACS_CKBOARD; } result != A_BLINK; if (!high) { if ((g_nAltCharSetOffset == 0) && (low >= 0x40)) { // result |= A_BLINK; // does not work on my terminal if (g_bTextFlashState) { result |= A_REVERSE; } } else { result |= A_REVERSE; } } return result; } void output(const char *fmt, ...) { va_list args; va_start(args, fmt); WINDOW * win = frame->getBuffer(); vwprintw(win, fmt, args); wrefresh(win); } void VideoUpdateFlash() { static UINT nTextFlashCnt = 0; ++nTextFlashCnt; if (nTextFlashCnt == 16) // Flash rate = 0.5 * 60 / 16 Hz (as we need 2 changes for a period) { nTextFlashCnt = 0; g_bTextFlashState = !g_bTextFlashState; } } } bool Update40ColCell (int x, int y, int xpixel, int ypixel, int offset) { frame->init(40); BYTE ch = *(g_pTextBank0+offset); WINDOW * win = frame->getWindow(); const chtype ch2 = mapCharacter(ch); mvwaddch(win, 1 + y, 1 + x, ch2); return true; } bool Update80ColCell (int x, int y, int xpixel, int ypixel, int offset) { frame->init(80); BYTE ch1 = *(g_pTextBank1+offset); BYTE ch2 = *(g_pTextBank0+offset); WINDOW * win = frame->getWindow(); const chtype ch12 = mapCharacter(ch1); mvwaddch(win, 1 + y, 1 + 2 * x, ch12); const chtype ch22 = mapCharacter(ch2); mvwaddch(win, 1 + y, 1 + 2 * x + 1, ch22); return true; } bool UpdateLoResCell (int x, int y, int xpixel, int ypixel, int offset) { BYTE val = *(g_pTextBank0+offset); const int pair = grColors->getPair(val); WINDOW * win = frame->getWindow(); wattron(win, COLOR_PAIR(pair)); if (frame->getColumns() == 40) { mvwaddstr(win, 1 + y, 1 + x, "\u2580"); } else { mvwaddstr(win, 1 + y, 1 + 2 * x, "\u2580\u2580"); } wattroff(win, COLOR_PAIR(pair)); return true; } bool UpdateDLoResCell (int x, int y, int xpixel, int ypixel, int offset) { return true; } bool UpdateHiResCell (int x, int y, int xpixel, int ypixel, int offset) { return true; } bool UpdateDHiResCell (int x, int y, int xpixel, int ypixel, int offset) { return true; } void FrameRefresh() { // std::cout << "Status Drive 1: " << g_eStatusDrive1 << ", Track: " << g_sTrackDrive1 << ", Sector: " << g_sSectorDrive1 << " "; // std::cout << "Status Drive 2: " << g_eStatusDrive2 << ", Track: " << g_sTrackDrive2 << ", Sector: " << g_sSectorDrive2 << " "; // std::cout << "\r" << std::flush; } void FrameDrawDiskLEDS(HDC x) { DiskGetLightStatus(&g_eStatusDrive1, &g_eStatusDrive1); FrameRefresh(); } void FrameDrawDiskStatus(HDC x) { if (mem == NULL) return; // We use the actual drive since probing from memory doesn't tell us anything we don't already know. // DOS3.3 ProDOS // Drive $B7EA $BE3D // Track $B7EC LC1 $D356 // Sector $B7ED LC1 $D357 // RWTS LC1 $D300 int nActiveFloppy = DiskGetCurrentDrive(); int nDisk1Track = DiskGetTrack(0); int nDisk2Track = DiskGetTrack(1); // Probe known OS's for Track/Sector int isProDOS = mem[ 0xBF00 ] == 0x4C; bool isValid = true; // Try DOS3.3 Sector if ( !isProDOS ) { int nDOS33track = mem[ 0xB7EC ]; int nDOS33sector = mem[ 0xB7ED ]; if ((nDOS33track >= 0 && nDOS33track < 40) && (nDOS33sector >= 0 && nDOS33sector < 16)) { /**/ if (nActiveFloppy == 0) g_nSectorDrive1 = nDOS33sector; else if (nActiveFloppy == 1) g_nSectorDrive2 = nDOS33sector; } else isValid = false; } else // isProDOS { // we can't just read from mem[ 0xD357 ] since it might be bank-switched from ROM // and we need the Language Card RAM // memrom[ 0xD350 ] = " ERROR\x07\x00" Applesoft error message // T S int nProDOStrack = *MemGetMainPtr( 0xC356 ); // LC1 $D356 int nProDOSsector = *MemGetMainPtr( 0xC357 ); // LC1 $D357 if ((nProDOStrack >= 0 && nProDOStrack < 40) && (nProDOSsector >= 0 && nProDOSsector < 16)) { /**/ if (nActiveFloppy == 0) g_nSectorDrive1 = nProDOSsector; else if (nActiveFloppy == 1) g_nSectorDrive2 = nProDOSsector; } else isValid = false; } g_nTrackDrive1 = nDisk1Track; g_nTrackDrive2 = nDisk2Track; if( !isValid ) { if (nActiveFloppy == 0) g_nSectorDrive1 = -1; else g_nSectorDrive2 = -1; } snprintf( g_sTrackDrive1 , sizeof(g_sTrackDrive1 ), "%2d", g_nTrackDrive1 ); if (g_nSectorDrive1 < 0) snprintf( g_sSectorDrive1, sizeof(g_sSectorDrive1), "??" ); else snprintf( g_sSectorDrive1, sizeof(g_sSectorDrive1), "%2d", g_nSectorDrive1 ); snprintf( g_sTrackDrive2 , sizeof(g_sTrackDrive2), "%2d", g_nTrackDrive2 ); if (g_nSectorDrive2 < 0) snprintf( g_sSectorDrive2, sizeof(g_sSectorDrive2), "??" ); else snprintf( g_sSectorDrive2, sizeof(g_sSectorDrive2), "%2d", g_nSectorDrive2 ); FrameRefresh(); } void FrameRefreshStatus(int x, bool) { // std::cerr << "Status: " << x << std::endl; } void VideoInitialize() { setlocale(LC_ALL, ""); initscr(); grColors.reset(new GRColors(20, 20)); curs_set(0); noecho(); cbreak(); set_escdelay(0); frame.reset(new Frame()); signal(SIGINT, sig_handler); } void VideoRedrawScreen() { VideoUpdateFlash(); const int displaypage2 = (SW_PAGE2) == 0 ? 0 : 1; g_pHiresBank1 = MemGetAuxPtr (0x2000 << displaypage2); g_pHiresBank0 = MemGetMainPtr(0x2000 << displaypage2); g_pTextBank1 = MemGetAuxPtr (0x400 << displaypage2); g_pTextBank0 = MemGetMainPtr(0x400 << displaypage2); VideoUpdateFuncPtr_t update = SW_TEXT ? SW_80COL ? Update80ColCell : Update40ColCell : SW_HIRES ? (SW_DHIRES && SW_80COL) ? UpdateDHiResCell : UpdateHiResCell : (SW_DHIRES && SW_80COL) ? UpdateDLoResCell : UpdateLoResCell; int y = 0; int ypixel = 0; while (y < 20) { int offset = ((y & 7) << 7) + ((y >> 3) * 40); int x = 0; int xpixel = 0; while (x < 40) { update(x, y, xpixel, ypixel, offset + x); ++x; xpixel += 14; } ++y; ypixel += 16; } if (SW_MIXED) update = SW_80COL ? Update80ColCell : Update40ColCell; while (y < 24) { int offset = ((y & 7) << 7) + ((y >> 3) * 40); int x = 0; int xpixel = 0; while (x < 40) { update(x, y, xpixel, ypixel, offset + x); ++x; xpixel += 14; } ++y; ypixel += 16; } wrefresh(frame->getWindow()); } void ProcessKeyboard() { const int inch = wgetch(frame->getWindow()); int ch = ERR; switch (inch) { case ERR: break; case KEY_PPAGE: // Page Up openApple = true; break; case KEY_NPAGE: // Page Down solidApple = true; break; case '\n': ch = 0x0d; // ENTER break; case KEY_BACKSPACE: case KEY_LEFT: ch = 0x08; break; case KEY_RIGHT: ch = 0x15; break; case KEY_UP: ch = 0x0b; break; case KEY_DOWN: ch = 0x0a; break; case 0x14a: // DEL ch = 0x7f; break; default: if (inch < 0x80) { ch = inch; // Standard for Apple II is Upper case if (ch >= 'A' && ch <= 'Z') { ch += 'a' - 'A'; } else if (ch >= 'a' && ch <= 'z') { ch -= 'a' - 'A'; } } } if (ch != ERR) { nextKey = ch | 0x80; keyReady = true; } } BYTE KeybGetKeycode () { return 0; } BYTE __stdcall KeybReadData (WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft) { return nextKey; } BYTE __stdcall KeybReadFlag (WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft) { BYTE result = keyReady ? nextKey : 0; nextKey = 0; return result; } BYTE __stdcall JoyReadButton(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft) { addr &= 0xFF; BOOL pressed = 0; /* In ncurses it is not possible to detect key UP or DOWN, just characters. So when do we stop advertising button pressed? We read it once and then reset. We should probably have a timer, like x ms after a press. */ switch (addr) { case 0x61: pressed = openApple; openApple = false; break; case 0x62: pressed = solidApple; solidApple = false; break; case 0x63: break; } return MemReadFloatingBus(pressed, nCyclesLeft); } BYTE __stdcall JoyReadPosition(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft) { int nJoyNum = (addr & 2) ? 1 : 0; // $C064..$C067 BOOL nPdlCntrActive = 0; return MemReadFloatingBus(nPdlCntrActive, nCyclesLeft); } BYTE __stdcall JoyResetPosition(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft) { return MemReadFloatingBus(nCyclesLeft); }