2012-04-21 00:04:34 +00:00
|
|
|
/*
|
|
|
|
epple2
|
2015-07-29 01:14:58 +00:00
|
|
|
Copyright (C) 2008 by Christopher A. Mosher <cmosher01@gmail.com>
|
2012-04-21 00:04:34 +00:00
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2013-10-11 02:32:39 +00:00
|
|
|
*/
|
2012-04-21 00:04:34 +00:00
|
|
|
#include "emulator.h"
|
2022-12-12 05:52:19 +00:00
|
|
|
#include "E2wxApp.h"
|
2022-12-12 01:13:39 +00:00
|
|
|
#include "e2config.h"
|
2022-12-12 03:07:50 +00:00
|
|
|
#include "e2command.h"
|
2012-04-21 00:04:34 +00:00
|
|
|
#include "e2const.h"
|
2022-12-19 00:40:54 +00:00
|
|
|
#include "KeyRepeatHandler.h"
|
2022-12-11 22:17:25 +00:00
|
|
|
|
|
|
|
#include <wx/msgdlg.h>
|
2022-12-15 02:05:04 +00:00
|
|
|
#include <wx/string.h>
|
2012-04-21 00:04:34 +00:00
|
|
|
|
2022-11-02 19:07:31 +00:00
|
|
|
#include <SDL.h>
|
2012-04-21 00:04:34 +00:00
|
|
|
|
2022-12-14 01:32:50 +00:00
|
|
|
#include <boost/log/trivial.hpp>
|
|
|
|
|
2012-04-21 00:04:34 +00:00
|
|
|
#include <ctime>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-12-13 02:47:10 +00:00
|
|
|
|
|
|
|
static bool isKeyDown(const SDL_Keycode sym, const SDL_Keymod mod) {
|
|
|
|
return (
|
|
|
|
(sym < 0x7F || sym == SDLK_LEFT || sym == SDLK_RIGHT) &&
|
|
|
|
!(sym == SDLK_TAB || sym == SDLK_BACKQUOTE || sym == '[' || sym == '\\' || sym == SDLK_DELETE) &&
|
|
|
|
!(sym == ']' && mod & KMOD_SHIFT)
|
|
|
|
);
|
2012-04-21 00:04:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-12-13 02:47:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Emulator::Emulator() :
|
|
|
|
display(screenImage),
|
|
|
|
videoStatic(display),
|
2022-12-13 04:30:24 +00:00
|
|
|
apple2(keypresses, paddleButtonStates, display, buffered, screenImage),
|
2022-12-13 02:47:10 +00:00
|
|
|
timable(nullptr), // No ticked object (NULL pointer)
|
2022-12-19 00:40:54 +00:00
|
|
|
keyrepeater(keypresses),
|
2022-12-13 02:47:10 +00:00
|
|
|
keysDown(0),
|
2022-12-13 03:34:13 +00:00
|
|
|
prev_ms(SDL_GetTicks()) {
|
2012-04-21 00:04:34 +00:00
|
|
|
}
|
|
|
|
|
2022-12-13 02:47:10 +00:00
|
|
|
Emulator::~Emulator() {
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-12-12 01:13:39 +00:00
|
|
|
void Emulator::config(E2Config& cfg) {
|
2022-11-03 22:26:07 +00:00
|
|
|
cfg.parse(this->apple2.ram, this->apple2.rom, this->apple2.slts, this->apple2.revision, this->screenImage, this->apple2.cassetteIn, this->apple2.cassetteOut, &this->apple2);
|
2019-02-07 05:45:54 +00:00
|
|
|
this->apple2.ram.dump_config();
|
2013-10-11 02:32:39 +00:00
|
|
|
powerOffComputer();
|
|
|
|
this->display.powerOn(true);
|
2012-04-21 00:04:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-12-06 03:19:24 +00:00
|
|
|
|
|
|
|
void Emulator::handleAnyPendingEvents() {
|
|
|
|
SDL_Event event;
|
|
|
|
while (SDL_PollEvent(&event)) {
|
|
|
|
switch (event.type) {
|
|
|
|
case SDL_QUIT:
|
|
|
|
// If SDL is going away...
|
2022-12-12 05:52:19 +00:00
|
|
|
// could be due to user closing the SDL window, pressing cmd-Q on Mac,
|
|
|
|
// ctrl-C from the command line on Linux, process being killed, etc.
|
2022-12-13 02:47:10 +00:00
|
|
|
wxGetApp().CloseMainFrame();
|
2022-12-06 03:19:24 +00:00
|
|
|
break;
|
|
|
|
case SDL_KEYDOWN:
|
2022-12-13 03:34:13 +00:00
|
|
|
// we're collecting keypresses for the keyboard
|
|
|
|
// emulation (and thus the Apple ][ emulation itself)
|
|
|
|
dispatchKeyDown(event.key);
|
|
|
|
// People who have too many press-releases should be referred to as "keyboards"
|
2022-12-06 03:19:24 +00:00
|
|
|
break;
|
|
|
|
case SDL_KEYUP:
|
2022-12-13 03:34:13 +00:00
|
|
|
dispatchKeyUp(event.key);
|
2022-12-06 03:19:24 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-12-12 05:52:19 +00:00
|
|
|
// How many emulation ticks between asking SDL if there is any new input
|
|
|
|
// from the user or other GUI events.
|
|
|
|
// This is also how often we shall update the estimate of the emulator's
|
|
|
|
// actual speed performance
|
|
|
|
// When the CPU is the object being ticked (each tick is a CPU cycle), then
|
|
|
|
// this is 20.04378892 Hz in emulated seconds time
|
|
|
|
#define CHECK_EVERY_CYCLE 51024
|
2022-12-06 03:19:24 +00:00
|
|
|
|
2022-12-12 05:52:19 +00:00
|
|
|
// The core of this Apple
|
2022-12-06 18:30:35 +00:00
|
|
|
void Emulator::tick50ms() {
|
|
|
|
if (this->timable) {
|
|
|
|
for (int i = 0; i < CHECK_EVERY_CYCLE; ++i) {
|
|
|
|
this->timable->tick(); // this runs the emulator!
|
2022-12-19 00:40:54 +00:00
|
|
|
this->keyrepeater.tick(); // TODO move into Apple2
|
2022-12-06 18:30:35 +00:00
|
|
|
}
|
|
|
|
}
|
2022-12-19 00:40:54 +00:00
|
|
|
|
2022-12-06 18:30:35 +00:00
|
|
|
handleAnyPendingEvents();
|
2022-12-19 00:40:54 +00:00
|
|
|
|
2022-12-12 05:52:19 +00:00
|
|
|
this->screenImage.displayHz((1000*CHECK_EVERY_CYCLE)/(SDL_GetTicks() - this->prev_ms));
|
2022-12-06 18:30:35 +00:00
|
|
|
this->prev_ms = SDL_GetTicks();
|
|
|
|
}
|
2022-12-06 03:19:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2013-10-11 02:32:39 +00:00
|
|
|
static bool translateKeysToAppleModernized(SDL_Keycode keycode, SDL_Keymod modifiers, unsigned char* key) {
|
2013-10-16 12:26:41 +00:00
|
|
|
if (keycode == SDLK_LEFT) {
|
|
|
|
*key = 8;
|
|
|
|
return true;
|
|
|
|
} else if (keycode == SDLK_RIGHT) {
|
|
|
|
*key = 21;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-10-11 02:32:39 +00:00
|
|
|
if (keycode >= 0x100) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
*key = (unsigned char) (keycode & 0x7F);
|
|
|
|
|
|
|
|
// The Apple ][ hardware keyboard only generates upper-case
|
|
|
|
if ('a' <= *key && *key <= 'z') {
|
|
|
|
*key -= 32;
|
|
|
|
}
|
|
|
|
|
|
|
|
// from SDL 1.2 to 2.0, we can't use UNICODE so we need to
|
|
|
|
// apply shift and control modifiers ourselves
|
|
|
|
if (modifiers & KMOD_SHIFT) {
|
|
|
|
if (keycode == SDLK_BACKQUOTE) *key = '~';
|
|
|
|
else if (keycode == SDLK_1) *key = '!';
|
|
|
|
else if (keycode == SDLK_2) *key = '@';
|
|
|
|
else if (keycode == SDLK_3) *key = '#';
|
|
|
|
else if (keycode == SDLK_4) *key = '$';
|
|
|
|
else if (keycode == SDLK_5) *key = '%';
|
|
|
|
else if (keycode == SDLK_6) *key = '^';
|
|
|
|
else if (keycode == SDLK_7) *key = '&';
|
|
|
|
else if (keycode == SDLK_8) *key = '*';
|
|
|
|
else if (keycode == SDLK_9) *key = '(';
|
|
|
|
else if (keycode == SDLK_0) *key = ')';
|
|
|
|
else if (keycode == SDLK_MINUS) *key = '_';
|
|
|
|
else if (keycode == SDLK_EQUALS) *key = '+';
|
|
|
|
|
|
|
|
else if (keycode == SDLK_SEMICOLON) *key = ':';
|
|
|
|
else if (keycode == SDLK_QUOTE) *key = '\"';
|
|
|
|
|
|
|
|
else if (keycode == SDLK_COMMA) *key = '<';
|
|
|
|
else if (keycode == SDLK_PERIOD) *key = '>';
|
|
|
|
else if (keycode == SDLK_SLASH) *key = '?';
|
2013-10-16 12:26:41 +00:00
|
|
|
|
|
|
|
else if (keycode == SDLK_m) *key = ']';
|
|
|
|
else if (keycode == SDLK_n) *key = '^';
|
|
|
|
else if (keycode == SDLK_p) *key = '@';
|
2013-10-11 02:32:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (modifiers & KMOD_CTRL) {
|
2013-10-16 12:26:41 +00:00
|
|
|
if (('A' <= *key && *key <= 'Z') || (*key == ']') || (*key == '^') || (*key == '@')) {
|
2013-10-11 02:32:39 +00:00
|
|
|
*key -= 64;
|
2013-10-08 14:29:28 +00:00
|
|
|
}
|
2013-10-11 02:32:39 +00:00
|
|
|
}
|
|
|
|
|
2013-10-16 12:26:41 +00:00
|
|
|
if ((modifiers & KMOD_SHIFT) && (modifiers & KMOD_CTRL) && keycode == ' ') {
|
2013-10-11 02:32:39 +00:00
|
|
|
// Ctrl-Shift-Space is the same as Space
|
|
|
|
*key = ' ';
|
|
|
|
} else if ((modifiers & KMOD_CTRL) && !(modifiers & KMOD_SHIFT) && (('0' <= keycode && keycode <= '9') || keycode == '/' || keycode == ' ')) {
|
2013-10-16 12:26:41 +00:00
|
|
|
// Control-only upon 0-9, / and space leaves them unchanged, the same as unmodified
|
2013-10-11 02:32:39 +00:00
|
|
|
*key = keycode;
|
|
|
|
} else if (keycode == ']') {
|
|
|
|
if (modifiers & KMOD_SHIFT) {
|
|
|
|
// ignore '}' (shift ']')
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (modifiers & KMOD_CTRL) {
|
|
|
|
// Ctrl-] == ASCII: $1D
|
|
|
|
*key = 29;
|
|
|
|
}
|
2013-10-16 12:26:41 +00:00
|
|
|
} // else if this is one of the *keys that can't be typed on an Apple ][ keyboard
|
2013-10-11 02:32:39 +00:00
|
|
|
else if (*key == 0 || keycode == SDLK_TAB || keycode == SDLK_BACKQUOTE || keycode == '[' || keycode == '\\' || keycode == SDLK_DELETE) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-12-13 02:47:10 +00:00
|
|
|
// Take real-world keystrokes from SDL and filter them to emulate the Apple ][ keyboard
|
|
|
|
void Emulator::dispatchKeyDown(const SDL_KeyboardEvent& keyEvent) {
|
2013-10-11 02:32:39 +00:00
|
|
|
if (keyEvent.repeat) {
|
2022-12-19 00:40:54 +00:00
|
|
|
// To repeat on the real Apple ][, you need to use the REPT key (emulated by F10)
|
2013-10-11 02:32:39 +00:00
|
|
|
return;
|
|
|
|
}
|
2013-10-08 14:29:28 +00:00
|
|
|
|
2022-12-13 02:47:10 +00:00
|
|
|
const SDL_Keycode sym = keyEvent.keysym.sym;
|
|
|
|
const SDL_Keymod mod = (SDL_Keymod) keyEvent.keysym.mod;
|
2013-10-11 02:32:39 +00:00
|
|
|
|
2013-10-17 01:47:14 +00:00
|
|
|
//printf("keydown: mod: %04X sym: %08X scan:%04X name:%s\n", mod, sym, scan, SDL_GetKeyName(sym));
|
2013-10-11 02:32:39 +00:00
|
|
|
|
2022-12-13 02:47:10 +00:00
|
|
|
if (isKeyDown(sym, mod)) {
|
2013-10-11 02:32:39 +00:00
|
|
|
++this->keysDown;
|
|
|
|
}
|
|
|
|
|
2022-12-13 02:47:10 +00:00
|
|
|
if (sym == SDLK_F10) {
|
2022-12-19 00:40:54 +00:00
|
|
|
this->keyrepeater.press();
|
|
|
|
// } else if (SDLK_F1 <= sym && sym <= SDLK_F12) {
|
|
|
|
// wxGetApp().OnFnKeyPressed(sym);
|
2022-12-14 01:32:50 +00:00
|
|
|
} else {
|
|
|
|
unsigned char key;
|
|
|
|
const bool sendKey = translateKeysToAppleModernized(sym, mod, &key);
|
|
|
|
if (sendKey) {
|
|
|
|
//printf(" sending to apple as ASCII ------------------------------> %02X (%02X) (%d)\n", key, key | 0x80, key | 0x80);
|
|
|
|
this->keypresses.push(key);
|
2022-12-19 00:40:54 +00:00
|
|
|
this->keyrepeater.setKey(key);
|
2022-12-14 01:32:50 +00:00
|
|
|
}
|
2013-10-11 02:32:39 +00:00
|
|
|
}
|
2012-04-21 00:04:34 +00:00
|
|
|
}
|
|
|
|
|
2022-12-13 02:47:10 +00:00
|
|
|
void Emulator::dispatchKeyUp(const SDL_KeyboardEvent& keyEvent) {
|
|
|
|
const SDL_Keycode sym = keyEvent.keysym.sym;
|
|
|
|
const SDL_Keymod mod = (SDL_Keymod) keyEvent.keysym.mod;
|
|
|
|
|
|
|
|
if (isKeyDown(sym, mod)) {
|
|
|
|
--this->keysDown;
|
2022-12-19 00:40:54 +00:00
|
|
|
if (this->keysDown <= 0) {
|
|
|
|
this->keyrepeater.clearKey();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sym == SDLK_F10) {
|
|
|
|
this->keyrepeater.release();
|
2022-12-13 02:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-10-11 02:32:39 +00:00
|
|
|
|
|
|
|
|
2022-12-13 02:47:10 +00:00
|
|
|
|
2022-12-13 03:34:13 +00:00
|
|
|
void Emulator::cmd(const wxString& c) {
|
|
|
|
if (c.empty()) {
|
2013-10-11 02:32:39 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-12-13 03:34:13 +00:00
|
|
|
E2Command{}.parseLine(
|
|
|
|
c.c_str().AsChar(),
|
|
|
|
this->apple2.ram, this->apple2.rom, this->apple2.slts, this->apple2.revision, this->screenImage, this->apple2.cassetteIn, this->apple2.cassetteOut, nullptr);
|
2012-04-21 00:04:34 +00:00
|
|
|
}
|
2018-12-29 05:24:16 +00:00
|
|
|
|
2022-12-13 02:47:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-12-11 22:17:25 +00:00
|
|
|
static int askSave() {
|
|
|
|
wxMessageDialog *dlg = new wxMessageDialog{
|
|
|
|
nullptr,
|
|
|
|
"You have unsaved changes to your floppy disk images.\nDo you want to SAVE them?",
|
|
|
|
"Save changes",
|
2022-12-12 00:41:04 +00:00
|
|
|
(long)(wxYES_NO|wxCANCEL|wxCANCEL_DEFAULT)};
|
2022-12-11 22:17:25 +00:00
|
|
|
|
|
|
|
return dlg->ShowModal();
|
|
|
|
}
|
|
|
|
|
2018-12-29 05:24:16 +00:00
|
|
|
bool Emulator::isSafeToQuit() {
|
2022-12-14 01:32:50 +00:00
|
|
|
BOOST_LOG_TRIVIAL(info) << "Checking for any unsaved changes...";
|
|
|
|
|
2022-12-12 05:52:19 +00:00
|
|
|
this->screenImage.exitFullScreen();
|
|
|
|
|
2019-01-21 23:27:26 +00:00
|
|
|
if (!this->apple2.cassetteOut.eject()) {
|
2022-12-12 05:52:19 +00:00
|
|
|
// TODO does this handle the case where we fail to save?
|
2019-01-21 23:27:26 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-12-29 05:24:16 +00:00
|
|
|
if (!this->apple2.slts.isDirty()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-12-11 22:17:25 +00:00
|
|
|
const int resp = askSave();
|
2018-12-29 05:24:16 +00:00
|
|
|
|
2022-12-11 22:17:25 +00:00
|
|
|
if (resp == wxID_CANCEL) {
|
2018-12-29 05:24:16 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-12-11 22:17:25 +00:00
|
|
|
if (resp == wxID_YES) {
|
2018-12-29 05:24:16 +00:00
|
|
|
this->apple2.slts.save(0);
|
|
|
|
this->apple2.slts.save(1);
|
2022-12-12 05:52:19 +00:00
|
|
|
// TODO handle case where we fail to save,
|
|
|
|
// in which case we should alert the user,
|
|
|
|
// and return false
|
2018-12-29 05:24:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2022-12-04 04:18:52 +00:00
|
|
|
|
2022-12-13 02:47:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Emulator::toggleComputerPower() {
|
2022-12-19 00:40:54 +00:00
|
|
|
if (this->timable == &this->videoStatic) {
|
2022-12-13 02:47:10 +00:00
|
|
|
powerOnComputer();
|
2022-12-19 00:40:54 +00:00
|
|
|
} else {
|
2022-12-13 02:47:10 +00:00
|
|
|
powerOffComputer();
|
2022-12-19 00:40:54 +00:00
|
|
|
}
|
2022-12-13 02:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Emulator::powerOnComputer() {
|
|
|
|
this->apple2.powerOn();
|
|
|
|
this->screenImage.drawPower(true);
|
|
|
|
this->display.setNoise(false);
|
|
|
|
|
|
|
|
// The apple2 becomes the ticked object
|
|
|
|
this->timable = &this->apple2;
|
2022-12-04 04:18:52 +00:00
|
|
|
}
|
2022-12-12 18:03:30 +00:00
|
|
|
|
2022-12-13 02:47:10 +00:00
|
|
|
void Emulator::powerOffComputer() {
|
|
|
|
this->apple2.powerOff();
|
|
|
|
this->screenImage.drawPower(false);
|
|
|
|
this->display.setNoise(true);
|
|
|
|
this->videoStatic.powerOn();
|
|
|
|
|
|
|
|
// The video static becomes the ticked object
|
|
|
|
this->timable = &this->videoStatic;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Emulator::paste() {
|
2022-12-12 18:03:30 +00:00
|
|
|
// Feed input from the clipboard to the Apple keyboard
|
2022-12-15 02:05:04 +00:00
|
|
|
wxString s = this->clip.getText();
|
|
|
|
s.Replace("\n\r", "\r\n");
|
|
|
|
s.Replace("\r\n", "\r");
|
|
|
|
s.Replace("\n", "\r");
|
|
|
|
s.MakeUpper();
|
|
|
|
for (auto c : s) {
|
|
|
|
this->keypresses.push(c);
|
2022-12-12 18:03:30 +00:00
|
|
|
}
|
|
|
|
}
|
2022-12-13 02:47:10 +00:00
|
|
|
|
|
|
|
void Emulator::monitorCycle() {
|
|
|
|
this->display.cycleType();
|
|
|
|
this->screenImage.cycleDisplayLabel();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Emulator::reset() {
|
|
|
|
this->apple2.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Emulator::toggleBuffered() {
|
|
|
|
this->buffered.toggleBuffered();
|
|
|
|
this->screenImage.toggleKdbBufferLabel();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Emulator::toggleFullScreen() {
|
|
|
|
this->screenImage.toggleFullScreen();
|
|
|
|
this->screenImage.drawPower(this->timable == &this->apple2);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Emulator::screenshot() {
|
|
|
|
this->screenImage.saveBMP();
|
|
|
|
}
|