/* Copyright (c) 2012-2025 R. Danbrook All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __APPLE__ #include #endif #include #include #include #include #include #include #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 muted{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 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}, {"Mute", 0, FltkUi::mute, 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 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(std::istreambuf_iterator(stream), std::istreambuf_iterator()); 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->pause(false); jgm->reset(2); } } 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 (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()) { run_emulation(); 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 = 12; jgm->rehash(); setwin->set_choice_value("Emulator", "Palette", 12); } 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_Bar*)w)->mvalue()) : get_menuitem(paused ? "Play" : "Pause"); if (m == nullptr) { LogDriver::log(LogLevel::Warn, "Menu item does not exist"); return; } paused ^= 1; audiomgr->pause(paused); m->label(paused ? "Play" : "Pause"); #ifdef __APPLE__ menubar->update(); #endif } void FltkUi::mute(Fl_Widget *w, void *data) { Fl_Menu_Item* m = nullptr; m = w ? const_cast(((Fl_Menu_Bar*)w)->mvalue()) : get_menuitem(muted ? "Unmute" : "Mute"); muted ^= 1; audiomgr->mute(muted); m->label(muted ? "Unmute" : "Mute"); #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-2025, R. Danbrook\n\n" "Portions derived from The Jolly Good Reference Frontend\n" "(c) 2020-2025, Rupert Carmichael\n"); text3.labelsize(10); Fl_Box text4(0, 320, 460, UI_SPACING, "Nestopia Emulator\n" "(c) 2020-2025, 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->pause(false); jgm->reset(2); // 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, 600, "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_idle(exec_emu_vsync); } else { Fl::add_timeout(0.001, exec_emu_timer); } } else { if (syncmode) { Fl::remove_idle(exec_emu_vsync); } else { Fl::remove_timeout(exec_emu_timer); } } } int main(int argc, char *argv[]) { // Parse command line arguments std::string filename{}; std::vector 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]}); } } if (std::find(flags.begin(), flags.end(), "-v") != flags.end() || std::find(flags.begin(), flags.end(), "--version") != flags.end() || std::find(flags.begin(), flags.end(), "--help") != flags.end()) { std::cout << "Nestopia UE " << JG_VERSION << std::endl; return 0; } // 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); // Change "Mute" menubar label based on whether audio is muted at // startup Fl_Menu_Item *m = get_menuitem("Mute"); if (setmgr->get_setting("a_mute")->val) { m->label("Unmute"); muted = 1; } // 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->pause(false); jgm->reset(2); } } 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); } if (std::find(flags.begin(), flags.end(), "-m") != flags.end() || std::find(flags.begin(), flags.end(), "--mute") != flags.end()) { FltkUi::mute(); } syncmode = setmgr->get_setting("m_syncmode")->val; LogDriver::log(LogLevel::Debug, syncmode ? "Synchronization Mode: VSync" : "Synchronization Mode: Timer"); 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; }