/* * snes9express * interface.cc * Copyright (C) 1998-2004 David Nordlund * * 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. * * For further details, please read the included COPYING file, * or go to http://www.gnu.org/copyleft/gpl.html */ #include #include #include #include #include #include #include #include #include #include #include #include #include "interface.h" #include "s9xskin.h" const char*Authors[] = { "David Nordlund", (const char*)0 }; const char *Copyright = "1998-2004, David Nordlund"; const char s9xoutcode = '\x11', s9xerrcode = '\x12', s9xincode = '\x13', s9xlogcode = '\x14'; int main(int argc, char*argv[]) { int exitcode; fr_ArgList args; if((argc >= 2)&&(strcmp(argv[1], "skin")==0)) return s9xskin_main(argc, argv); fr_init(PROG, argc, argv, args); s9x_Interface snes9express(args); exitcode = snes9express.run(); return exitcode; } s9x_Interface::s9x_Interface(const fr_ArgList& args): fr_MainProgram(PROG, args), NoteBook(this), Quit(this), Tip(this), Power(this, "Power"), Reset(this, "Reset"), Eject(this, "Eject"), console_buttons(this), power_btn(this, FR_STOCK_RUN, "Power"), reset_btn(this, FR_STOCK_CLEAR, "Reset"), eject_btn(this, FR_STOCK_EDIT, "Eject"), defaultnotebooksize(512, 180), defaultbuttonsize(512, 32), defaultwindowsize(512, 212), console_button_place(0, 180), LastArgs(), LogFile("snes9x.log"), AboutWindow(this, "about: " PROG "-" VER), LogWindow(this, "snes9x log"), AboutText(this), LogText(this), AboutClose(this, "Close"), LogBtn(this, "Close"), LogLabel(this, "snes9x log"), Prefs(this), ROM(&NoteBook), Sound(&NoteBook), Video(&NoteBook), CPU(&NoteBook), Controller(&NoteBook), NetPlay(&NoteBook), Snapshot(&NoteBook), Extra(&NoteBook), Profiler(this), Skin(0) { fr_Size padding(3, 3); CreateMenu(); CreateNotebook(); console_buttons.AddButton(power_btn); console_buttons.AddButton(eject_btn); console_buttons.AddButton(reset_btn); defaultnotebooksize = NoteBook.getSize() + padding; defaultbuttonsize = console_buttons.getSize() + padding; defaultwindowsize = fr_Size(defaultnotebooksize.x(), defaultnotebooksize.y()+defaultbuttonsize.y()+3); SetSpaceSize(defaultwindowsize.x(), defaultwindowsize.y()); Place(NoteBook, 0, 0); console_button_place = fr_Point((defaultnotebooksize.x() - defaultbuttonsize.x() + 1)/2, defaultnotebooksize.y()); Place(console_buttons, console_button_place.x(), console_button_place.y()); SetSize(defaultwindowsize.x(), defaultwindowsize.y()); Set9xVersion(SNES9X_VERSION); //Prefs.Apply(); AddListener(this); CreateAboutInfo(); LogText.setSizeForText(80,25); LogWindow.SetName("snes9x output"); LogWindow.SetGridSize(1, 3, false); LogWindow.SetVisibility(false); LogWindow.Pack(LogLabel); LogWindow.Pack(LogText); LogWindow.Pack(LogBtn); LogWindow.AddListener(this); LogBtn.AddListener(this); std::ifstream infile; try { LogFile.open(infile); LastArgs.ignoreNext(); // ignore the executable infile >> LastArgs; } catch(...) { // no big deal } LogFile.close(infile); } s9x_Interface::~s9x_Interface() { if(Skin) delete Skin; } void s9x_Interface::CreateMenu() { static fr_MenuBarItem FileMenu[] = { {"Select ROM ...", &ROM, 0}, {"Select Snapshot ...", &Snapshot, 0}, {"-", (fr_Element*)0, 0}, {"Quit", &Quit, 0}, {(const char*)0, (fr_Element*)0, 0} }; static fr_MenuBarItem ConsoleMenu[] = { {"Preferences ...", &Prefs, 0}, {"-", (fr_Element*)0, 0}, {"Run snes9x (Power)", &Power, 0}, {"Open Profiler (Eject) ...", &Eject, 0}, {"Reset", &Reset, 0}, {"-", (fr_Element*)0, 0}, {"View last output", &LogText, 0}, {(const char*)0, (fr_Element*)0, 0} }; static fr_MenuBarItem HelpMenu[] = { {"About " PROG " ...", &AboutWindow, 0}, {"-", (fr_Element*)0, 0}, {"Random tip ...", &Tip, 0}, {(const char*)0, (fr_Element*)0, 0} }; Quit.AddListener(this); Tip.AddListener(this); Power.AddListener(this); Reset.AddListener(this); Eject.AddListener(this); power_btn.AddListener(this); reset_btn.AddListener(this); eject_btn.AddListener(this); LogText.AddListener(this); AddMenu("File", FileMenu); AddMenu("Console", ConsoleMenu); AddMenu("Help", HelpMenu); } void s9x_Interface::CreateNotebook() { NoteBook.SetPadding(3, 3); NoteBook.AddPage(ROM); NoteBook.AddPage(Sound); NoteBook.AddPage(Video); NoteBook.AddPage(Controller); NoteBook.AddPage(CPU); NoteBook.AddPage(NetPlay); NoteBook.AddPage(Snapshot); NoteBook.AddPage(Extra); } void s9x_Interface::SetToDefaults() { ROM.SetToDefaults(); Sound.SetToDefaults(); Video.SetToDefaults(); CPU.SetToDefaults(); Controller.SetToDefaults(); NetPlay.SetToDefaults(); Snapshot.SetToDefaults(); Extra.SetToDefaults(); } void s9x_Interface::Set9xVersion(double version) { ROM.Set9xVersion(version); Sound.Set9xVersion(version); Video.Set9xVersion(version); CPU.Set9xVersion(version); Controller.Set9xVersion(version); NetPlay.Set9xVersion(version); } void s9x_Interface::SiftArgs(fr_ArgList& L) { static bool BeenHereBefore = false; bool firsttime = false; L.ClearMarks(); if(!BeenHereBefore) { BeenHereBefore = firsttime = true; if(Prefs.StartToProfile()) Profiler.ApplyDefaultProfile(); if(Prefs.StartToLast()) SiftArgs(LastArgs); } Prefs.SiftArgs(L); Prefs.Apply(); Controller.SiftArgs(L); Sound.SiftArgs(L); Video.SiftArgs(L); CPU.SiftArgs(L); NetPlay.SiftArgs(L); Snapshot.SiftArgs(L); ROM.SiftArgs(L); //ROM should be 2nd last Extra.SiftArgs(L); //And Extra must be last if(firsttime) SetVisibility(true); } void s9x_Interface::CompileArgs(fr_ArgList& L) { Sound.CompileArgs(L); Video.CompileArgs(L); Controller.CompileArgs(L); CPU.CompileArgs(L); NetPlay.CompileArgs(L); Snapshot.CompileArgs(L); ROM.CompileArgs(L); Extra.CompileArgs(L); } void s9x_Interface::ApplySkin(const std::string& SkinDir) { #ifdef S9X_SKINABLE if((Skin)&&(SkinDir.size())&&(Skin->equals(SkinDir))) { // std::cout << "Skin " << SkinDir << " already loaded" << std::endl; return; } if((!Skin)&&(!SkinDir.size())) return; std::string errmsg(""); SetVisibility(false); fr_Flush(); ShedSkin(); if(!SkinDir.size()) { SetVisibility(true); return; } Remove(NoteBook); console_buttons.SetVisibility(false); try { Skin = new s9x_Skin(this, SkinDir); s9x_SkinSection* icons = Skin->getSection("icons"); s9x_SkinSection* cons = Skin->getSection("console"); s9x_SkinSection* notebook_skin = Skin->getSection("panel"); fr_Image *bg_img=0, *windowIcon; windowIcon = icons?icons->getImage("logo"):0; setIcon(windowIcon); AboutWindow.setIcon(windowIcon); LogWindow.setIcon(windowIcon); if(cons) { bg_img = cons->getImage("image"); if(bg_img) SetSize(bg_img->getWidth(), bg_img->getHeight()); setBackdrop(bg_img); } if(notebook_skin) { int notebook_w = notebook_skin->getInt("width"), notebook_h = notebook_skin->getInt("height"), notebook_x = notebook_skin->getInt("pos-x"), notebook_y = notebook_skin->getInt("pos-y"); if((notebook_w>0)&&(notebook_h>0)) NoteBook.SetSize(notebook_w, notebook_h); Place(NoteBook, notebook_x, notebook_y); } else Place(NoteBook, 0, 0); ROM.ApplySkin(Skin); Sound.ApplySkin(Skin); Video.ApplySkin(Skin); CPU.ApplySkin(Skin); Controller.ApplySkin(Skin); NetPlay.ApplySkin(Skin); Snapshot.ApplySkin(Skin); Extra.ApplySkin(Skin); Profiler.ApplySkin(Skin); Prefs.ApplySkin(Skin); s9x_SkinSection* btnSkin = Skin->getSection("eject"); fr_Image *btn_up, *btn_dn; int posx, posy; if(btnSkin) { posx = btnSkin->getInt("pos-x"); posy = btnSkin->getInt("pos-y"); btn_up = btnSkin->getImage("up"); btn_dn = btnSkin->getImage("down"); Eject.setBgImg(bg_img, posx, posy); Eject.setUpImg(btn_up); Eject.setDnImg(btn_dn); Eject.SetSize(btn_up->getWidth(), btn_up->getHeight()); Place(Eject, posx, posy); } btnSkin = Skin->getSection("power"); if(btnSkin) { posx = btnSkin->getInt("pos-x"); posy = btnSkin->getInt("pos-y"); btn_up = btnSkin->getImage("up"); btn_dn = btnSkin->getImage("down"); Power.setBgImg(bg_img, posx, posy); Power.setUpImg(btn_up); Power.setDnImg(btn_dn); Power.SetSize(btn_up->getWidth(), btn_up->getHeight()); Place(Power, posx, posy); } btnSkin = Skin->getSection("reset"); if(btnSkin) { posx = btnSkin->getInt("pos-x"); posy = btnSkin->getInt("pos-y"); btn_up = btnSkin->getImage("up"); btn_dn = btnSkin->getImage("down"); Reset.setBgImg(bg_img, posx, posy); Reset.setUpImg(btn_up); Reset.setDnImg(btn_dn); Reset.SetSize(btn_up->getWidth(), btn_up->getHeight()); Place(Reset, posx, posy); } SetVisibility(true); } catch(const std::string& emsgstr) { errmsg = emsgstr; } catch(const char*emsgc) { errmsg = emsgc; } catch(...) { errmsg = "unidentified exception"; } if(errmsg.size()) { ShedSkin(); SetVisibility(true); Mesg("Error loading skin: " + errmsg, true); } #else ShedSkin(); SetVisibility(true); #endif } void s9x_Interface::ShedSkin() { RemoveAll(); setBackdrop((const fr_Image*)0); setIcon(0); ROM.ApplySkin(0); Sound.ApplySkin(0); Video.ApplySkin(0); CPU.ApplySkin(0); Controller.ApplySkin(0); NetPlay.ApplySkin(0); Snapshot.ApplySkin(0); Extra.ApplySkin(0); Profiler.ApplySkin(0); Prefs.ApplySkin(0); SetSize(defaultwindowsize.x(), defaultwindowsize.y()); NoteBook.SetSize(defaultnotebooksize.x(), defaultnotebooksize.y()); console_buttons.SetVisibility(false); Place(NoteBook, 0, 0); Place(console_buttons, console_button_place.x(), console_button_place.y()); if(Skin) delete Skin; Skin = 0; } void s9x_Interface::EventOccurred(fr_Event*e) { if(e->Is(Power) || e->Is(power_btn)) hitPower(); else if(e->Is(Eject) || e->Is(eject_btn)) hitEject(); else if(e->Is(Reset) || e->Is(reset_btn)) hitReset(); else if(e->Is(AboutWindow, fr_Destroy) || e->Is(AboutClose)) AboutWindow.SetVisibility(false); else if(e->Is(AboutWindow)) AboutWindow.SetVisibility(true); else if(e->Is(Tip)) s9x_RandomTip(); else if(e->Is(LogText)) viewOutput(); else if((e->Is(LogBtn))||(e->Is(LogWindow))) LogWindow.SetVisibility(false); else if(e->Is(Quit)||e->Is(this, fr_Destroy)) s9x_Interface::exit(0); } void s9x_Interface::hitPower() { if(ROM.GetFileName().size()) play(); else Mesg("You need to select a ROM file."); } void s9x_Interface::hitEject() { Profiler.SetVisibility(true); } void s9x_Interface::hitReset() { SetToDefaults(); if(Prefs.ResetToProfile()) Profiler.ApplyDefaultProfile(); if(Prefs.ResetToArgs()) SiftArgs(commandlineargs); if(Prefs.ResetToLast()) SiftArgs(LastArgs); } void s9x_Interface::play() { int status, s9xin[2], s9xout[2], s9xerr[2], n, r, b, exit_code; char buf[1024]; const char *term, *arg; bool pass_in, pass_out, pass_err, clr_in, clr_out, clr_err, colorterm; bool reading = true; std::ostringstream msgs; const static char *bin_clr ="\033[1;31;49m", *opt_clr ="\033[1;35;49m", *file_clr="\033[0;36;49m", *in_clr ="\033[0;36;49m", *out_clr ="\033[0;39;49m", *err_clr ="\033[1;33;40m", *log_clr ="\033[1;35;49m", *nrm_clr ="\033[0;39;49m"; fd_set rfds; pid_t snes9xpid; std::string msg, snapdir, bin(getExecutable()), ebin, bbin, viewmsg; std::ofstream s9xlog; mode_t m; snapdir = Prefs.GetSnapDir(); m = S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH; ebin = fr_EscapeArg(bin); bbin = fr_EscapeArg(fr_Basename(bin)); LastArgs.clear(9); LastArgs << bin; LastArgs.Mark(0); CompileArgs(LastArgs); if(snapdir.size()) // make the dir in case it does not exist mkdir(snapdir.c_str(), m); ROM.doPrePlay(); if(pipe(s9xin) || pipe(s9xout) || pipe(s9xerr)) { Mesg("pipe():\n" + fr_syserr(), true); return; } switch((snes9xpid = fork())) { case -1: // error Mesg("fork():\n" + fr_syserr(), true); break; case 0: // snes9x // capture snes9x output dup2(s9xin[0], STDIN_FILENO); dup2(s9xout[1], STDOUT_FILENO); dup2(s9xerr[1], STDERR_FILENO); close(s9xin[1]); close(s9xout[0]); close(s9xerr[0]); fr_exec(LastArgs); _exit(255); default: // snes9express SaveWindowPos(); fr_Window::SetVisibility(false); fr_Flush(); close(s9xin[0]); close(s9xout[1]); close(s9xerr[1]); term = getenv("TERM"); pass_in = isatty(STDIN_FILENO); pass_out = isatty(STDOUT_FILENO) && pass_in; pass_err = isatty(STDERR_FILENO); colorterm = (term && (pass_out || pass_err) && (strncmp(term, "vt", 2) || strstr(term, "xterm"))); clr_out = pass_out && colorterm; clr_err = pass_err && colorterm; clr_in = pass_in && clr_out; try { LogFile.open(s9xlog); n = LastArgs.CountArgs(); if(clr_out) { std::cout << bin_clr << ebin; for(r=1; r 0) { if(FD_ISSET(s9xout[0], &rfds)) { b = read(s9xout[0], buf, sizeof(buf)-1); //std::cerr << "--- read " << b << " bytes from s9xout ---" << std::endl; if(b > 0) { buf[b] = 0; if(clr_out) std::cout << out_clr << buf << std::flush; else if(pass_out) std::cout << buf << std::flush; s9xlog << s9xoutcode << buf; reading = true; if((b > 2) && (!pass_in) && (buf[b-2]==':') && (buf[b-1]==' ')) { // snes9x wants a filename from stdin. // prevent snes9x from eternally blocking on stdin // maybe in the next version I'll make a file dialog for this write(s9xin[1], "\n", 1); s9xlog << s9xincode << "\n"; } } } if(FD_ISSET(s9xerr[0], &rfds)) { b = read(s9xerr[0], buf, sizeof(buf)-1); //std::cerr << "--- read " << b << " bytes from s9xerr ---" << std::endl; if(b > 0) { buf[b] = 0; if(pass_err && !pass_in) { std::cerr << std::endl; if(clr_err) std::cerr << log_clr; std::cerr << "[" << bbin << "] "; } if(clr_err) std::cerr << err_clr; if(pass_err) std::cerr << buf << std::flush; s9xlog << s9xerrcode << buf; reading = true; } } if((pass_in)&&(FD_ISSET(STDIN_FILENO, &rfds))) { b = read(STDIN_FILENO, buf, sizeof(buf)-1); if(b > 0) { buf[b] = 0; write(s9xin[1], buf, b); s9xlog << s9xincode << buf; reading = true; } } } } while(reading); } catch(...) { if(clr_err) std::cerr << log_clr; std::cerr << "An unknown error occured executing snes9x" << std::endl; } close(s9xin[1]); close(s9xout[0]); close(s9xerr[0]); //std::cerr << "--- waiting for " << snes9xpid << " to die ---" << std::endl; waitpid(snes9xpid, &status, 0); s9xlog << s9xlogcode << std::endl; if(pass_err) std::cerr << std::endl; if(clr_err) std::cerr << log_clr; if(snapdir.size()) // remove the snapdir IF it is empty rmdir(snapdir.c_str()); fr_Window::SetVisibility(true); RestoreWindowPos(); if(WIFEXITED(status)) { exit_code = WEXITSTATUS(status); switch(exit_code) { case 0: msg = bbin + " returned exit code 0 (OK)"; ROM.doPostPlay(); break; case 255: viewmsg = "- could not execute " + ebin; msg = viewmsg + "\n" "- make sure you have the correct snes9x path\n" " set in Preferences / Paths."; break; default: msgs << bbin << " returned error code " << exit_code; msg = msgs.str(); viewmsg = msg; } } else if(WIFSIGNALED(status)) { exit_code = WTERMSIG(status); msgs << bbin << " terminated by signal " << exit_code #ifdef _GNU_SOURCE << " \"" << strsignal(exit_code) << "\""; #else << " [" << sys_siglist[exit_code] << "]"; #endif msg = msgs.str(); viewmsg = msg; } else { msg = bbin + " terminated abnormally."; viewmsg = msg; } if(msg.size()) { if(clr_out) std::cout << log_clr << msg << nrm_clr << std::endl; else if(pass_out) std::cout << msg << std::endl; s9xlog << msg << std::endl; } LogFile.close(s9xlog); if(viewmsg.size()) viewOutput(viewmsg); } } void s9x_Interface::CreateAboutInfo() { std::string ind(" "), sep(" >>> "); fr_Color lbl_clr(0xffffff), sep_clr(0x9966cc), val_clr(0x00deda); AboutText.setSizeForText(60, 14); AboutText.setDefaultColors(fr_Colors(0xffffff, 0x000000)); AboutWindow.SetGridSize(1, 2, false); AboutWindow.Pack(AboutText); AboutWindow.Pack(AboutClose); AboutWindow.AddListener(this); AboutClose.AddListener(this); AboutText << "\n" << ind << fr_Color(0xff0000); AboutText << "S N E S 9 E X P R E S S\n"; AboutText << ind << fr_Colors(0x000000,0xff0000); AboutText << " snes9x front - end "; AboutText << fr_Colors(0xcccccc, 0x000000) << "\n\n"; AboutText << ind << PROG " is a graphical front-end for\n" << ind << "snes9x, the SNES game console emulator.\n\n"; AboutText << lbl_clr << ind << "Version " << sep_clr << sep << val_clr << VER << ind << sep_clr << "(" << val_clr << RELEASE_DATE << sep_clr << ")\n"; AboutText << lbl_clr << ind << "Author " << sep_clr << sep << val_clr << Authors[0] << "\n"; AboutText << lbl_clr << ind << "Email " << sep_clr << sep << val_clr << SNES9EXPRESS_EMAIL << "\n"; AboutText << lbl_clr << ind << "Website " << sep_clr << sep << val_clr << SNES9EXPRESS_WEBSITE << "\n\n"; AboutText << lbl_clr << ind << "Copyright" << sep_clr << " (C) " << val_clr << Copyright << "\n"; } void s9x_Interface::viewOutput(const std::string& msg) { int i, c, r, repeated=0; const char*arg; char logcode=s9xoutcode, nextcode=0, buf[1024]; bool reading=true, flush=false; std::string lastline, thisline; fr_ArgList lastrun(LastArgs.CountArgs()); fr_Color exec_clr(0xff0000), arg_clr(0xcc99ff), file_clr(0x00deda), in_clr(0x00deda), out_clr(0xcccccc), err_clr(0xffff00), log_clr(0xcc99ff), *current_clr; LogLabel.SetLabel(msg.size()?msg.c_str():"output from last snes9x execution"); LogText.clear(); LogText.setDefaultColors(fr_Colors(out_clr, 0x000000)); std::ifstream infile; try { //std::cerr << "Reading LogFile..." << std::endl; LogFile.open(infile); infile >> lastrun; LogText << exec_clr << fr_EscapeArg(lastrun[0]); c = lastrun.CountArgs(); for(i=1; i 1) { LogText << log_clr << "(last line repeated " << repeated << " more times)" //<< exec_clr << "\n \"" << lastline << "\"" << *current_clr << "\n"; repeated = 0; } flush = (repeated==0); } else if(((c==s9xoutcode)||(c==s9xerrcode)||(c==s9xincode)||(c==s9xlogcode))) { flush = true; if(logcode != c) nextcode = c; } else thisline += c; if(flush) { if(thisline.size()) { LogText << thisline; repeated = 0; lastline = thisline; thisline = ""; } if(nextcode) { switch(nextcode) { case s9xoutcode: current_clr = &out_clr; break; case s9xerrcode: current_clr = &err_clr; break; case s9xincode : current_clr = &in_clr ; break; case s9xlogcode: current_clr = &log_clr; break; } LogText << *current_clr; logcode = nextcode; nextcode = 0; } flush = false; } } } //std::cerr << "done." << std::endl; LogWindow.SetVisibility(true); } catch(std::string e) { Mesg("Could not open snes9x output log:\n" + e, true); } catch(...) { Mesg("Could not open snes9x output log", true); } LogFile.close(infile); }