nestopia/source/fltkui/fltkui.cpp

900 lines
25 KiB
C++

/*
* Nestopia UE
*
* Copyright (C) 2012-2024 R. Danbrook
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
#include <algorithm>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <regex>
#include <set>
#include <epoxy/gl.h>
#include <FL/Fl.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Menu_Bar.H>
#ifdef __APPLE__
#include <FL/Fl_Sys_Menu_Bar.H>
#endif
#include <FL/Fl_Native_File_Chooser.H>
#include <FL/Fl_PNG_Image.H>
#include <FL/Fl_Gl_Window.H>
#include <FL/Fl_Table_Row.H>
#include <FL/gl.h>
#include <SDL.h>
#include "audiomanager.h"
#include "chtmanager.h"
#include "inputmanager.h"
#include "jgmanager.h"
#include "setmanager.h"
#include "videomanager.h"
#include "fltkui.h"
#include "fltkui_archive.h"
#include "fltkui_cheats.h"
#include "fltkui_settings.h"
#include "logdriver.h"
#include "version.h"
namespace {
int paused{0};
int speed{1};
int video_fullscreen{0};
int syncmode{0};
int refreshrate{60};
int screennum{0};
int frames{0};
int framefrags{0};
bool fdsgame{false};
NstWindow *nstwin{nullptr};
#ifdef __APPLE__
Fl_Sys_Menu_Bar *menubar{nullptr};
#else
Fl_Menu_Bar *menubar{nullptr};
#endif
NstGlArea *glarea{nullptr};
NstChtWindow *chtwin{nullptr};
NstSettingsWindow *setwin{nullptr};
JGManager *jgm{nullptr};
SettingManager *setmgr{nullptr};
InputManager *inputmgr{nullptr};
AudioManager *audiomgr{nullptr};
VideoManager *videomgr{nullptr};
CheatManager *chtmgr{nullptr};
std::vector<uint8_t> game;
Fl_Menu_Item menutable[] = {
{"&File", FL_ALT + 'f', 0, 0, FL_SUBMENU},
{"Open", 0, FltkUi::rom_open, 0, FL_MENU_DIVIDER},
{"Load State...", 0, FltkUi::state_load, 0, FL_MENU_INACTIVE},
{"Save State...", 0, FltkUi::state_save, 0, FL_MENU_DIVIDER|FL_MENU_INACTIVE},
{"Quick Load", 0, 0, 0, FL_SUBMENU|FL_MENU_INACTIVE},
{"Slot 0", 0, FltkUi::state_qload, (void*)"0", FL_MENU_INACTIVE},
{"Slot 1", 0, FltkUi::state_qload, (void*)"1", FL_MENU_INACTIVE},
{"Slot 2", 0, FltkUi::state_qload, (void*)"2", FL_MENU_INACTIVE},
{"Slot 3", 0, FltkUi::state_qload, (void*)"3", FL_MENU_INACTIVE},
{"Slot 4", 0, FltkUi::state_qload, (void*)"4", FL_MENU_INACTIVE},
{0},
{"Quick Save", 0, 0, 0, FL_SUBMENU|FL_MENU_DIVIDER|FL_MENU_INACTIVE},
{"Slot 0", 0, FltkUi::state_qsave, (void*)"0", FL_MENU_INACTIVE},
{"Slot 1", 0, FltkUi::state_qsave, (void*)"1", FL_MENU_INACTIVE},
{"Slot 2", 0, FltkUi::state_qsave, (void*)"2", FL_MENU_INACTIVE},
{"Slot 3", 0, FltkUi::state_qsave, (void*)"3", FL_MENU_INACTIVE},
{"Slot 4", 0, FltkUi::state_qsave, (void*)"4", FL_MENU_INACTIVE},
{0},
{"Open Palette...", 0, FltkUi::palette_open, 0, FL_MENU_DIVIDER},
{"Screenshot...", 0, FltkUi::screenshot_save, 0, FL_MENU_DIVIDER|FL_MENU_INACTIVE},
#ifndef __APPLE__
{"&Quit", FL_ALT + 'q', FltkUi::quit, 0, 0},
#endif
{0}, // End File
{"&Emulator", FL_ALT + 'e', 0, 0, FL_SUBMENU},
{"Pause", 0, FltkUi::pause, 0, FL_MENU_DIVIDER|FL_MENU_INACTIVE},
{"Reset (Soft)", 0, FltkUi::reset, (void*)"0", FL_MENU_INACTIVE},
{"Reset (Hard)", 0, FltkUi::reset, (void*)"1", FL_MENU_DIVIDER|FL_MENU_INACTIVE},
{"Fullscreen", 0, FltkUi::fullscreen, 0, FL_MENU_DIVIDER|FL_MENU_INACTIVE},
{"Switch Disk Side", 0, FltkUi::fds_next, 0, FL_MENU_INACTIVE},
{"Insert/Eject Disk", 0, FltkUi::fds_insert, 0, FL_MENU_DIVIDER|FL_MENU_INACTIVE},
{"Cheats...", 0, FltkUi::chtwin_open, 0, FL_MENU_DIVIDER|FL_MENU_INACTIVE},
{"Settings...", 0, FltkUi::setwin_open, 0, 0},
{0}, // End Emulator
#ifndef __APPLE__
{"&Help", FL_ALT + 'h', 0, 0, FL_SUBMENU},
{"About", 0, FltkUi::about, 0, 0},
{0}, // End Help
#endif
{0} // End Menu
};
Fl_Menu_Item *get_menuitem(std::string label) {
Fl_Menu_Item *m = menutable->first();
for (int i = 0; i < menutable->size(); ++i) {
if (m->label() != nullptr && std::string(m->label()) == label) {
return m;
}
++m;
}
return nullptr;
}
void update_refreshrate(void) {
// Get the screen refresh rate using an SDL window
if (syncmode) { // Don't use this in "Timer" sync mode
return;
}
#ifdef __APPLE__
refreshrate = 120; // Dirty hack for modern macOS
return;
#endif
SDL_Window *sdlwin;
sdlwin = SDL_CreateWindow(
"refreshrate",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
1, 1, SDL_WINDOW_HIDDEN
);
SDL_DisplayMode dm;
SDL_GetCurrentDisplayMode(screennum, &dm);
refreshrate = dm.refresh_rate;
SDL_DestroyWindow(sdlwin);
}
void exec_emu_vsync(void*) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
inputmgr->event(event);
}
int fps{jgm->get_frametime()};
frames = (fps / refreshrate);
framefrags += fps % refreshrate;
if (framefrags >= refreshrate) {
frames++;
framefrags -= refreshrate;
}
if (!paused) {
for (int i = 0; i < frames * speed; i++) {
jgm->exec_frame();
}
}
glarea->redraw();
}
void exec_emu_timer(void*) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
inputmgr->event(event);
}
if (!paused) {
for (int i = 0; i < speed; i++) {
jgm->exec_frame();
}
}
Fl::repeat_timeout(1.0 / jgm->get_frametime(), exec_emu_timer);
glarea->redraw();
}
}
void FltkUi::chtwin_open(Fl_Widget *w, void *data) {
if (!jgm->is_loaded()) {
return;
}
chtwin->refresh();
chtwin->show();
}
void FltkUi::setwin_open(Fl_Widget *w, void *data) {
setwin->show();
}
void FltkUi::load_file(const char *filename) {
// First, check if it's an archive
std::set<std::string> archive_exts = { ".zip", ".7z", ".gz", ".bz2", ".xz", ".zst" };
std::string fileext = std::filesystem::path(filename).extension().string();
std::transform(fileext.begin(), fileext.end(), fileext.begin(),
[](unsigned char c) { return std::tolower(c); });
if (archive_exts.find(fileext) != archive_exts.end()) {
std::string arcname{};
if (fltkui_archive_select(filename, arcname)) {
if (arcname.empty()) {
return;
}
game.clear();
fltkui_archive_load_file(filename, arcname, game);
fileext = std::filesystem::path(arcname).extension().string();
jgm->load_game(arcname.c_str(), game);
}
else {
LogDriver::log(LogLevel::OSD, "No valid files in archive");
}
}
else {
std::ifstream stream(filename, std::ios::in | std::ios::binary);
if (!stream.is_open()) {
return;
}
game.clear();
game = std::vector<uint8_t>(std::istreambuf_iterator<char>(stream),
std::istreambuf_iterator<char>());
stream.close();
jgm->load_game(filename, game);
}
std::transform(fileext.begin(), fileext.end(), fileext.begin(),
[](unsigned char c) { return std::tolower(c); });
fdsgame = fileext == ".fds";
}
void FltkUi::rom_open(Fl_Widget *w, void *data) {
// Create native chooser
Fl_Native_File_Chooser fc;
fc.title("Select a ROM");
fc.type(Fl_Native_File_Chooser::BROWSE_FILE);
fc.filter("NES Games\t*.{nes,unf,fds,bin,zip,7z,gz,bz2,xz,xml,zst}");
run_emulation(false);
// Show file chooser
switch (fc.show()) {
case -1:
LogDriver::log(LogLevel::Error, std::string(fc.errmsg()));
break;
case 1: break; // Cancel
default:
if (fc.filename()) {
load_file(fc.filename());
if (jgm->is_loaded()) {
chtmgr->clear();
chtwin->refresh();
FltkUi::enable_menu();
nstwin->label(jgm->get_gamename().c_str());
jgm->setup_audio();
jgm->setup_video();
inputmgr->reassign();
audiomgr->unpause();
}
}
break;
}
run_emulation();
}
void FltkUi::screenshot(std::string filename) {
if (filename.empty()) {
auto now = std::chrono::system_clock::now();
auto epoch = std::chrono::system_clock::from_time_t(0);
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>
(now.time_since_epoch()).count();
std::string msecs = std::to_string(duration);
filename = jgm->get_basepath() + "/screenshots/" + msecs + ".png";
}
videomgr->screenshot(filename);
LogDriver::log(LogLevel::OSD, "Screenshot Saved");
}
void FltkUi::screenshot_save(Fl_Widget *w, void *data) {
// Create native chooser
if (!jgm->is_loaded()) {
return;
}
run_emulation(false);
Fl_Native_File_Chooser fc;
fc.title("Save Screenshot");
fc.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE);
fc.filter("Screenshots\t*.png");
fc.options(Fl_Native_File_Chooser::SAVEAS_CONFIRM | Fl_Native_File_Chooser::USE_FILTER_EXT);
// Show file chooser
if (fc.show()) {
return;
}
screenshot(fc.filename());
run_emulation();
}
void FltkUi::state_load(Fl_Widget *w, void *userdata) {
// Create native chooser
if (!jgm->is_loaded()) {
return;
}
run_emulation(false);
Fl_Native_File_Chooser fc;
fc.title("Load State");
fc.type(Fl_Native_File_Chooser::BROWSE_FILE);
//fc.directory((const char*)nstpaths.statepath);
fc.filter("Nestopia States\t*.nst");
// Show file chooser
switch (fc.show()) {
case -1:
LogDriver::log(LogLevel::Error, std::string(fc.errmsg()));
break;
case 1: break; // Cancel
default:
if (fc.filename()) {
std::string statefile{fc.filename()};
jgm->state_load(statefile);
}
break;
}
run_emulation();
}
void FltkUi::state_save(Fl_Widget *w, void *data) {
// Create native chooser
if (!jgm->is_loaded()) {
return;
}
run_emulation(false);
Fl_Native_File_Chooser fc;
fc.title("Save State");
fc.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE);
fc.filter("Nestopia States\t*.nst");
fc.options(Fl_Native_File_Chooser::SAVEAS_CONFIRM | Fl_Native_File_Chooser::USE_FILTER_EXT);
// Show file chooser
if (fc.show()) {
run_emulation();
return;
}
std::string statefile{fc.filename()};
jgm->state_save(statefile);
run_emulation();
}
void FltkUi::palette_open(Fl_Widget *w, void *data) {
// Create native chooser
Fl_Native_File_Chooser fc;
fc.title("Select a Palette");
fc.type(Fl_Native_File_Chooser::BROWSE_FILE);
fc.filter("NES Palettes\t*.pal");
// Show file chooser
switch (fc.show()) {
case -1:
LogDriver::log(LogLevel::Error, std::string(fc.errmsg()));
break;
case 1: break; // Cancel
default:
if (fc.filename()) {
// Overwrite the custom.pal file with the loaded one
std::filesystem::path src = fc.filename();
std::filesystem::path dst = jgm->get_basepath() + "/custom.pal";
std::filesystem::copy_file(src, dst, std::filesystem::copy_options::overwrite_existing);
// Force custom palette
jgm->get_setting("palette")->val = 11;
jgm->rehash();
setwin->set_choice_value("Emulator", "Palette", 11);
}
break;
}
}
void FltkUi::state_qload(Fl_Widget *w, void *data) {
int slot = atoi((const char*)data);
jgm->state_qload(slot);
}
void FltkUi::state_qsave(Fl_Widget *w, void *data) {
int slot = atoi((const char*)data);
jgm->state_qsave(slot);
}
void FltkUi::pause(Fl_Widget *w, void *data) {
Fl_Menu_Item* m = nullptr;
m = w ? const_cast<Fl_Menu_Item*>(((Fl_Menu_Bar*)w)->mvalue()) :
get_menuitem(paused ? "Play" : "Pause");
if (m == nullptr) {
LogDriver::log(LogLevel::Warn, "Menu item does not exist");
return;
}
paused ^= 1;
if (paused) {
audiomgr->pause();
}
else {
audiomgr->unpause();
}
m->label(paused ? "Play" : "Pause");
#ifdef __APPLE__
menubar->update();
#endif
}
void FltkUi::reset(Fl_Widget *w, void *data) {
jgm->reset(atoi((const char*)data));
}
void NstWindow::resize(int x, int y, int w, int h) {
Fl_Double_Window::resize(x, y, w, h);
int nscreennum = Fl::screen_num(x, y, w, h);
if (nscreennum != screennum) { // Window moved to a different screen
screennum = nscreennum;
update_refreshrate();
}
videomgr->set_dpiscale(glarea->pixels_per_unit());
if (video_fullscreen) {
glarea->resize(0, 0, w, h);
videomgr->resize(w, h);
}
else {
glarea->resize(0, UI_MBARHEIGHT, w, h - UI_MBARHEIGHT);
videomgr->resize(w, h - UI_MBARHEIGHT);
}
}
void FltkUi::rehash() {
LogDriver::set_level(setmgr->get_setting("l_loglevel")->val);
videomgr->rehash(true);
audiomgr->rehash();
}
void FltkUi::fullscreen(Fl_Widget *w, void *data) {
if (!jgm->is_loaded()) {
return;
}
video_fullscreen ^= 1;
if (video_fullscreen) {
menubar->hide();
nstwin->fullscreen();
}
else {
menubar->show();
nstwin->fullscreen_off();
}
}
void FltkUi::fds_next(Fl_Widget *w, void *data) {
jgm->media_select();
}
void FltkUi::fds_insert(Fl_Widget *w, void *data) {
jgm->media_insert();
}
void FltkUi::about_close(Fl_Widget *w, void *data) {
Fl_Window *about = (Fl_Window*)data;
about->hide();
run_emulation();
}
void FltkUi::about(Fl_Widget *w, void *data) {
Fl_Window about(460, 440);
Fl_Box iconbox(166, 16, 128, 128);
Fl_Box text0(0, 144, 460, UI_SPACING, "Nestopia UE");
text0.labelfont(FL_BOLD);
Fl_Box text1(0, 166, 460, UI_SPACING, JG_VERSION);
Fl_Box text2(0, 208, 460, UI_SPACING, "Cycle-Accurate Nintendo Entertainment System Emulator");
Fl_Box text3(0, 256, 460, UI_SPACING,
"FLTK Frontend\n(c) 2012-2024, R. Danbrook\n\n"
"Portions derived from The Jolly Good Reference Frontend\n"
"(c) 2020-2024, Rupert Carmichael\n");
text3.labelsize(10);
Fl_Box text4(0, 320, 460, UI_SPACING,
"Nestopia Emulator\n"
"(c) 2020-2024, Rupert Carmichael\n"
"(c) 2012-2020, Nestopia UE Contributors\n"
"(c) 2003-2008, Martin Freij");
text4.labelsize(10);
Fl_Box text5(0, 360, 460, UI_SPACING, "Icon based on drawing by Trollekop");
text5.labelsize(10);
// Set up the icon
std::string iconpath{"icons/128/nestopia.png"};
if (!std::filesystem::exists(std::filesystem::path{iconpath})) {
iconpath = std::string(NST_DATAROOTDIR) + "/icons/hicolor/128x128/apps/nestopia.png";
}
Fl_PNG_Image nsticon(iconpath.c_str());
iconbox.image(nsticon);
Fl_Button close(360, 400, 80, UI_SPACING, "&Close");
close.shortcut(FL_ALT + 'c');
close.callback(FltkUi::about_close, (void*)&about);
about.set_modal();
run_emulation(false);
about.show();
while (about.shown()) {
Fl::wait();
}
}
void FltkUi::quit(Fl_Widget *w, void *data) {
videomgr->renderer_deinit();
nstwin->hide();
}
int FltkUi::handle(int e) {
// This is used to stop Esc from exiting the program
return (e == FL_SHORTCUT); // eat all keystrokes
}
int NstWindow::handle(int e) {
switch (e) {
case FL_KEYDOWN: case FL_KEYUP: {
#ifdef __APPLE__
inputmgr->event(Fl::event_key(), e == FL_KEYDOWN);
#else
// GNOME has a bad habit of spamming keyup events while holding
// a button down, so Fl::get_key is required here.
int key = Fl::event_key();
bool down = Fl::get_key(key);
inputmgr->event(key, down);
#endif
if (jgm->is_loaded()) {
inputmgr->ui_events();
}
break;
}
}
return Fl_Double_Window::handle(e);
}
void NstGlArea::draw() {
videomgr->render();
}
int NstGlArea::handle(int e) {
int xc, yc;
switch (e) {
case FL_ENTER:
if (inputmgr->get_lightgun()) {
cursor(setmgr->get_setting("m_hidecrosshair")->val ? FL_CURSOR_NONE : FL_CURSOR_CROSS);
}
else if (setmgr->get_setting("m_hidecursor")->val) {
cursor(FL_CURSOR_NONE);
}
break;
case FL_LEAVE:
cursor(FL_CURSOR_DEFAULT);
break;
case FL_PUSH:
inputmgr->event(Fl::event_button() + 1000, true);
return 1; // Must return 1 to receive drag events
case FL_RELEASE:
inputmgr->event(Fl::event_button() + 1000, false);
break;
case FL_MOVE:
videomgr->get_scaled_coords(Fl::event_x(), Fl::event_y(), &xc, &yc);
inputmgr->event(xc, yc);
break;
case FL_DRAG:
videomgr->get_scaled_coords(Fl::event_x(), Fl::event_y(), &xc, &yc);
inputmgr->event(xc, yc);
inputmgr->event(Fl::event_button() + 1000, Fl::event_state() ? true : false);
break;
case FL_DND_ENTER: // return 1 for these events to accept Drag and Drop
case FL_DND_DRAG:
case FL_DND_RELEASE:
return 1;
case FL_PASTE: { // handle the actual drop event
std::string filepath{std::regex_replace(std::string(Fl::event_text()),
std::regex("\\n|file://"), "")};
FltkUi::load_file(filepath.c_str());
if (jgm->is_loaded()) {
FltkUi::enable_menu();
nstwin->label(jgm->get_gamename().c_str());
jgm->setup_audio();
jgm->setup_video();
inputmgr->reassign();
audiomgr->unpause();
// Restart if in timer sync mode
FltkUi::run_emulation(false);
FltkUi::run_emulation();
}
return 1;
}
}
return Fl_Gl_Window::handle(e);
}
void FltkUi::enable_menu() {
Fl_Menu_Item *mtable = (Fl_Menu_Item*)menubar->menu();
for (int i = 0; i < mtable[0].size(); ++i) {
if (!fdsgame && mtable[i].label() &&
std::string(mtable[i].label()).find("Disk") != std::string::npos) {
mtable[i].deactivate();
}
else {
mtable[i].activate();
}
}
#ifdef __APPLE__
menubar->update();
#endif
}
void FltkUi::show_inputmsg(int show) {
setwin->show_inputmsg(show);
}
void FltkUi::nstwin_open() {
int rw, rh;
videomgr->set_dimensions();
videomgr->get_dimensions(&rw, &rh);
Fl::add_handler(FltkUi::handle);
// Cheats Window
chtwin = new NstChtWindow(720, 500, "Cheat Manager", *chtmgr);
chtwin->populate();
// Settings Window
setwin = new NstSettingsWindow(500, 550, "Settings", *jgm, *setmgr, *inputmgr);
setwin->set_crt_active(setmgr->get_setting("v_postproc")->val == 3);
// Main Window
nstwin = new NstWindow(rw, rh + UI_MBARHEIGHT);
nstwin->color(FL_BLACK);
nstwin->xclass("nestopia");
#ifdef __APPLE__
// Apple style menu bar (top of screen)
menubar = new Fl_Sys_Menu_Bar(0, 0, nstwin->w(), UI_MBARHEIGHT);
// Set the "About" callback and "Window" menu style
Fl_Sys_Menu_Bar::about(about, nullptr);
Fl_Sys_Menu_Bar::window_menu_style(Fl_Sys_Menu_Bar::tabbing_mode_none);
#else
// Normal menu bar and window icon
menubar = new Fl_Menu_Bar(0, 0, nstwin->w(), UI_MBARHEIGHT);
// Set up the window icon
std::string iconpath{"icons/96/nestopia.png"};
if (!std::filesystem::exists(std::filesystem::path{iconpath})) {
iconpath = std::string(NST_DATAROOTDIR) + "/icons/hicolor/96x96/apps/nestopia.png";
}
Fl_PNG_Image nsticon(iconpath.c_str());
nstwin->default_icon(&nsticon);
#endif
menubar->box(FL_FLAT_BOX);
menubar->menu(menutable);
menubar->selection_color(NstGreen);
glarea = new NstGlArea(0, UI_MBARHEIGHT, nstwin->w(), nstwin->h() - UI_MBARHEIGHT);
nstwin->resizable(glarea);
glarea->color(FL_BLACK);
#ifdef __APPLE__
Fl::use_high_res_GL(1);
glarea->mode(FL_RGB | FL_RGB8 | FL_INDEX | FL_DOUBLE | FL_ACCUM |
FL_ALPHA | FL_DEPTH | FL_STENCIL |
(setmgr->get_setting("v_renderer")->val ? 0 : FL_OPENGL3));
#endif
glarea->end();
nstwin->end();
}
void FltkUi::set_ffspeed(bool on) {
if (on) {
speed = setmgr->get_setting("m_ffspeed")->val;
}
else {
speed = 1;
}
audiomgr->set_speed(speed);
}
void FltkUi::run_emulation(bool run) {
if (run) {
if (syncmode) {
Fl::add_timeout(0.001, exec_emu_timer);
}
else {
Fl::add_idle(exec_emu_vsync);
}
}
else {
if (syncmode) {
Fl::remove_timeout(exec_emu_timer);
}
else {
Fl::remove_idle(exec_emu_vsync);
}
}
}
int main(int argc, char *argv[]) {
// Parse command line arguments
std::string filename{};
std::vector<std::string> flags{};
for (int i = 1; i < argc; ++i) {
if (filename.empty() && argv[i][0] != '-') {
// The first non-flag argument is considered the filename
filename = std::string{argv[i]};
}
else if (argv[i][0] == '-') {
flags.push_back(std::string{argv[i]});
}
}
// Set default config options
setmgr = new SettingManager();
// Initialize SDL Audio and Joystick
#ifdef _WIN32
putenv("SDL_AUDIODRIVER=directsound");
#endif
if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) < 0) {
LogDriver::log(LogLevel::Error, "Failed to initialize SDL: " + std::string(SDL_GetError()));
return 1;
}
jgm = new JGManager();
// Read frontend and emulator settings
setmgr->read(*jgm);
LogDriver::set_level(setmgr->get_setting("l_loglevel")->val);
// Bring up Audio/Video managers
audiomgr = new AudioManager(*jgm, *setmgr);
videomgr = new VideoManager(*jgm, *setmgr);
inputmgr = new InputManager(*jgm, *setmgr);
chtmgr = new CheatManager(*jgm);
// Load a rom from the command line
if (!filename.empty()) {
FltkUi::load_file(filename.c_str());
if (jgm->is_loaded()) {
jgm->setup_audio();
jgm->setup_video();
inputmgr->reassign();
video_fullscreen = setmgr->get_setting("v_fullscreen")->val ||
std::find(flags.begin(), flags.end(), "-f") != flags.end() ||
std::find(flags.begin(), flags.end(), "--fullscreen") != flags.end();
audiomgr->unpause();
}
}
FltkUi::nstwin_open();
screennum = Fl::screen_num(nstwin->x_root(), nstwin->y_root());
if (jgm->is_loaded()) {
nstwin->label(jgm->get_gamename().c_str());
FltkUi::enable_menu();
}
else {
nstwin->label("Nestopia UE");
if (!filename.empty()) {
LogDriver::log(LogLevel::OSD, "Failed to load file from CLI");
}
}
nstwin->show();
menubar->show();
glarea->make_current();
glarea->show();
videomgr->renderer_init();
videomgr->set_dpiscale(glarea->pixels_per_unit());
videomgr->resize(glarea->w(), glarea->h());
if (video_fullscreen) {
video_fullscreen = 0;
FltkUi::fullscreen(NULL, NULL);
}
syncmode = setmgr->get_setting("m_syncmode")->val;
LogDriver::log(LogLevel::Debug, syncmode ?
"Synchronization Mode: Timer" :
"Synchronization Mode: VSync");
update_refreshrate();
FltkUi::run_emulation();
while (nstwin->shown()) {
Fl::wait();
}
// Write frontend and emulator settings
setmgr->write(*jgm);
if (audiomgr) {
delete audiomgr;
}
if (videomgr) {
delete videomgr;
}
if (inputmgr) {
delete inputmgr;
}
if (jgm) {
delete jgm;
}
if (setmgr) {
delete setmgr;
}
if (chtmgr) {
delete chtmgr;
}
delete chtwin;
delete setwin;
delete glarea;
delete menubar;
delete nstwin;
return 0;
}