///////////////////////////////////////////////////////////////// // $Id: wxmain.cc,v 1.147 2006-09-09 08:05:07 vruppert Exp $ ///////////////////////////////////////////////////////////////// // // wxmain.cc implements the wxWidgets frame, toolbar, menus, and dialogs. // When the application starts, the user is given a chance to choose/edit/save // a configuration. When they decide to start the simulation, functions in // main.cc are called in a separate thread to initialize and run the Bochs // simulator. // // Most ports to different platforms implement only the VGA window and // toolbar buttons. The wxWidgets port is the first to implement both // the VGA display and the configuration interface, so the boundaries // between them are somewhat blurry. See the extensive comments at // the top of siminterface for the rationale behind this separation. // // The separation between wxmain.cc and wx.cc is as follows: // - wxmain.cc implements a Bochs configuration interface (CI), // which is the wxWidgets equivalent of textconfig.cc. wxmain creates // a frame with several menus and a toolbar, and allows the user to // choose the machine configuration and start the simulation. Note // that wxmain.cc does NOT include bochs.h. All interactions // between the CI and the simulator are through the siminterface // object. // - wx.cc implements a VGA display screen using wxWidgets. It is // is the wxWidgets equivalent of x.cc, win32.cc, macos.cc, etc. // wx.cc includes bochs.h and has access to all Bochs devices. // The VGA panel accepts only paint, key, and mouse events. As it // receives events, it builds BxEvents and places them into a // thread-safe BxEvent queue. The simulation thread periodically // processes events from the BxEvent queue (bx_gui_c::handle_events) // and notifies the appropriate emulated I/O device. // ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // includes ////////////////////////////////////////////////////////////////////// // Define BX_PLUGGABLE in files that can be compiled into plugins. For // platforms that require a special tag on exported symbols, BX_PLUGGABLE // is used to know when we are exporting symbols and when we are importing. #define BX_PLUGGABLE #include "config.h" // definitions based on configure script #if BX_WITH_WX // For compilers that support precompilation, includes "wx/wx.h". #include #ifdef __BORLANDC__ #pragma hdrstop #endif #ifndef WX_PRECOMP #include #endif #include #include #include "osdep.h" // workarounds for missing stuff #include "gui/siminterface.h" // interface to the simulator #include "bxversion.h" // get version string #include "wxdialog.h" // custom dialog boxes #include "wxmain.h" // wxwidgets shared stuff #include "extplugin.h" // include XPM icons #include "bitmaps/cdromd.xpm" #include "bitmaps/copy.xpm" #include "bitmaps/floppya.xpm" #include "bitmaps/floppyb.xpm" #include "bitmaps/paste.xpm" #include "bitmaps/power.xpm" #include "bitmaps/reset.xpm" #include "bitmaps/snapshot.xpm" #include "bitmaps/mouse.xpm" //#include "bitmaps/configbutton.xpm" #include "bitmaps/userbutton.xpm" #if BX_SUPPORT_SAVE_RESTORE #include "bitmaps/saverestore.xpm" #endif #ifdef __WXGTK__ #include "icon_bochs.xpm" #endif // FIXME: ugly global variables that the bx_gui_c object in wx.cc can use // to access the MyFrame and the MyPanel. MyFrame *theFrame = NULL; MyPanel *thePanel = NULL; // The wxBochsClosing flag is used to keep track of when the wxWidgets GUI is // shutting down. Shutting down can be somewhat complicated because the // simulation may be running for a while in another thread before it realizes // that it should shut down. The wxBochsClosing flag is a global variable, as // opposed to a field of some C++ object, so that it will be valid at any stage // of the program. wxBochsClosing starts out false (not wxBochsClosing). When // the GUI decides to shut down, it sets wxBochsClosing=true. If there // is not a simulation running, everything is quite simple and it can just // call Close(TRUE). If a simulation is running, it calls OnKillSim to // ask the simulation to stop. When the simulation thread stops, it calls // Close(TRUE) on the frame. During the time that the simulation is // still running and afterward, the wxBochsClosing flag is used to suppress // any events that might reference parts of the GUI or create new dialogs. bool wxBochsClosing = false; // The wxBochsStopSim flag is used to select the right way when the simulation // thread stops. It is set to 'true' when the stop simulation menu item is // clicked instead of the power button. bool wxBochsStopSim = false; bool isSimThread() { if (wxThread::IsMain()) return false; wxThread *current = wxThread::This(); if (current == (wxThread*) theFrame->GetSimThread()) { // wxLogDebug("isSimThread? yes"); return true; } // wxLogDebug("isSimThread? no"); return false; } ////////////////////////////////////////////////////////////////////// // class declarations ////////////////////////////////////////////////////////////////////// class MyApp: public wxApp { virtual bool OnInit(); virtual int OnExit(); public: // This default callback is installed when the simthread is NOT running, // so that events coming from the simulator code can be handled. // The primary culprit is panics which cause an BX_SYNC_EVT_LOG_ASK. static BxEvent *DefaultCallback(void *thisptr, BxEvent *event); }; // SimThread is the thread in which the Bochs simulator runs. It is created // by MyFrame::OnStartSim(). The SimThread::Entry() function calls a // function in main.cc called bx_continue_after_config_interface() which // initializes the devices and starts up the simulation. All events from // the simulator class SimThread: public wxThread { MyFrame *frame; // when the sim thread sends a synchronous event to the GUI thread, the // response is stored in sim2gui_mailbox. // FIXME: this would be cleaner and more reusable if I made a general // thread-safe mailbox class. BxEvent *sim2gui_mailbox; wxCriticalSection sim2gui_mailbox_lock; public: SimThread(MyFrame *_frame) { frame = _frame; sim2gui_mailbox = NULL; } virtual ExitCode Entry(); void OnExit(); // called by the siminterface code, with the pointer to the sim thread // in the thisptr arg. static BxEvent *SiminterfaceCallback(void *thisptr, BxEvent *event); BxEvent *SiminterfaceCallback2(BxEvent *event); // methods to coordinate synchronous response mailbox void ClearSyncResponse(); void SendSyncResponse(BxEvent *); BxEvent *GetSyncResponse(); }; ////////////////////////////////////////////////////////////////////// // wxWidgets startup ////////////////////////////////////////////////////////////////////// static int ci_callback(void *userdata, ci_command_t command) { switch (command) { case CI_START: #ifdef __WXMSW__ // on Windows only, wxEntry needs some data that is passed into WinMain. // So, in main.cc we define WinMain and fill in the bx_startup_flags // structure with the data, so that when we're ready to call wxEntry // it has access to the data. wxEntry( bx_startup_flags.hInstance, bx_startup_flags.hPrevInstance, bx_startup_flags.m_lpCmdLine, bx_startup_flags.nCmdShow); #else wxEntry(bx_startup_flags.argc, bx_startup_flags.argv); #endif break; case CI_RUNTIME_CONFIG: fprintf(stderr, "wxmain.cc: runtime config not implemented\n"); break; case CI_SHUTDOWN: fprintf(stderr, "wxmain.cc: shutdown not implemented\n"); break; } return 0; } extern "C" int libwx_LTX_plugin_init(plugin_t *plugin, plugintype_t type, int argc, char *argv[]) { wxLogDebug(wxT("plugin_init for wxmain.cc")); wxLogDebug(wxT("installing wxWidgets as the configuration interface")); SIM->register_configuration_interface("wx", ci_callback, NULL); wxLogDebug(wxT("installing %s as the Bochs GUI"), wxT("wxWidgets")); SIM->get_param_enum(BXPN_SEL_DISPLAY_LIBRARY)->set_enabled(0); MyPanel::OnPluginInit(); bx_list_c *list = new bx_list_c(SIM->get_param("."), "wxdebug", "subtree for the wx debugger", 30); bx_list_c *cpu = new bx_list_c(list, "cpu", "CPU State", BX_MAX_SMP_THREADS_SUPPORTED); cpu->get_options()->set(bx_list_c::USE_TAB_WINDOW); return 0; // success } extern "C" void libwx_LTX_plugin_fini() { // fprintf(stderr, "plugin_fini for wxmain.cc\n"); } ////////////////////////////////////////////////////////////////////// // MyApp: the wxWidgets application ////////////////////////////////////////////////////////////////////// IMPLEMENT_APP_NO_MAIN(MyApp) // this is the entry point of the wxWidgets code. It is called as follows: // 1. main() loads the wxWidgets plugin (if necessary) and calls // libwx_LTX_plugin_init, which installs a function pointer to the // ci_callback() function. // 2. main() calls SIM->configuration_interface. // 3. bx_real_sim_c::configuration_interface calls the function pointer that // points to ci_callback() in this file, with command=CI_START. // 4. ci_callback() calls wxEntry() in the wxWidgets library // 5. wxWidgets library creates the app and calls OnInit(). // // Before this code is called, the command line has already been parsed, and a // .bochsrc has been loaded if it could be found. See main() for details. bool MyApp::OnInit() { // wxLog::AddTraceMask(wxT("mime")); wxLog::SetActiveTarget(new wxLogStderr()); bx_init_siminterface(); // Install callback function to handle anything that occurs before the // simulation begins. This is responsible for displaying any error // dialogs during bochsrc and command line processing. SIM->set_notify_callback(&MyApp::DefaultCallback, this); MyFrame *frame = new MyFrame(wxT("Bochs x86 Emulator"), wxPoint(50,50), wxSize(450,340), wxMINIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION); theFrame = frame; // hack alert frame->Show(TRUE); SetTopWindow(frame); wxTheClipboard->UsePrimarySelection(true); // if quickstart is enabled, kick off the simulation if (SIM->get_param_enum(BXPN_BOCHS_START)->get() == BX_QUICK_START) { wxCommandEvent unusedEvent; frame->OnStartSim(unusedEvent); } return TRUE; } int MyApp::OnExit() { return 0; } // these are only called when the simthread is not running. BxEvent * MyApp::DefaultCallback(void *thisptr, BxEvent *event) { wxLogDebug(wxT("DefaultCallback: event type %d"), event->type); event->retcode = -1; // default return code switch (event->type) { case BX_ASYNC_EVT_LOG_MSG: case BX_SYNC_EVT_LOG_ASK: { wxLogDebug(wxT("DefaultCallback: log ask event")); wxString text; text.Printf(wxT("Error: %s"), event->u.logmsg.msg); if (wxBochsClosing) { // gui closing down, do something simple and nongraphical. fprintf(stderr, "%s\n", (const char *)text.mb_str(wxConvUTF8)); } else { wxMessageBox(text, wxT("Error"), wxOK | wxICON_ERROR ); // maybe I can make OnLogMsg display something that looks appropriate. // theFrame->OnLogMsg(event); } event->retcode = BX_LOG_ASK_CHOICE_DIE; // There is only one thread at this point. if I choose DIE here, it will // call fatal() and kill the whole app. break; } case BX_SYNC_EVT_TICK: if (wxBochsClosing) event->retcode = -1; break; case BX_ASYNC_EVT_REFRESH: case BX_ASYNC_EVT_DBG_MSG: break; // ignore case BX_SYNC_EVT_ASK_PARAM: case BX_SYNC_EVT_GET_DBG_COMMAND: break; // ignore default: wxLogDebug(wxT("DefaultCallback: unknown event type %d"), event->type); } if (BX_EVT_IS_ASYNC(event->type)) { delete event; event = NULL; } return event; } ////////////////////////////////////////////////////////////////////// // MyFrame: the top level frame for the Bochs application ////////////////////////////////////////////////////////////////////// BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(ID_Config_New, MyFrame::OnConfigNew) EVT_MENU(ID_Config_Read, MyFrame::OnConfigRead) EVT_MENU(ID_Config_Save, MyFrame::OnConfigSave) EVT_MENU(ID_State_Restore, MyFrame::OnStateRestore) EVT_MENU(ID_Quit, MyFrame::OnQuit) EVT_MENU(ID_Help_About, MyFrame::OnAbout) EVT_MENU(ID_Simulate_Start, MyFrame::OnStartSim) EVT_MENU(ID_Simulate_PauseResume, MyFrame::OnPauseResumeSim) EVT_MENU(ID_Simulate_Stop, MyFrame::OnKillSim) EVT_MENU(ID_Sim2CI_Event, MyFrame::OnSim2CIEvent) EVT_MENU(ID_Edit_ATA0, MyFrame::OnEditATA) EVT_MENU(ID_Edit_ATA1, MyFrame::OnEditATA) EVT_MENU(ID_Edit_ATA2, MyFrame::OnEditATA) EVT_MENU(ID_Edit_ATA3, MyFrame::OnEditATA) EVT_MENU(ID_Edit_CPU, MyFrame::OnEditCPU) EVT_MENU(ID_Edit_Memory, MyFrame::OnEditMemory) EVT_MENU(ID_Edit_Clock_Cmos, MyFrame::OnEditClockCmos) EVT_MENU(ID_Edit_PCI, MyFrame::OnEditPCI) EVT_MENU(ID_Edit_Display, MyFrame::OnEditDisplay) EVT_MENU(ID_Edit_Keyboard, MyFrame::OnEditKeyboard) EVT_MENU(ID_Edit_Boot, MyFrame::OnEditBoot) EVT_MENU(ID_Edit_Serial_Parallel, MyFrame::OnEditSerialParallel) EVT_MENU(ID_Edit_Network, MyFrame::OnEditNet) EVT_MENU(ID_Edit_Sound, MyFrame::OnEditSound) EVT_MENU(ID_Edit_Other, MyFrame::OnEditOther) EVT_MENU(ID_Log_Prefs, MyFrame::OnLogPrefs) EVT_MENU(ID_Log_PrefsDevice, MyFrame::OnLogPrefsDevice) EVT_MENU(ID_Debug_ShowCpu, MyFrame::OnShowCpu) EVT_MENU(ID_Debug_ShowKeyboard, MyFrame::OnShowKeyboard) #if BX_DEBUGGER EVT_MENU(ID_Debug_Console, MyFrame::OnDebugLog) #endif // toolbar events EVT_TOOL(ID_Edit_FD_0, MyFrame::OnToolbarClick) EVT_TOOL(ID_Edit_FD_1, MyFrame::OnToolbarClick) EVT_TOOL(ID_Edit_Cdrom, MyFrame::OnToolbarClick) EVT_TOOL(ID_Toolbar_Reset, MyFrame::OnToolbarClick) EVT_TOOL(ID_Toolbar_Power, MyFrame::OnToolbarClick) #if BX_SUPPORT_SAVE_RESTORE EVT_TOOL(ID_Toolbar_SaveRestore, MyFrame::OnToolbarClick) #endif EVT_TOOL(ID_Toolbar_Copy, MyFrame::OnToolbarClick) EVT_TOOL(ID_Toolbar_Paste, MyFrame::OnToolbarClick) EVT_TOOL(ID_Toolbar_Snapshot, MyFrame::OnToolbarClick) EVT_TOOL(ID_Toolbar_Config, MyFrame::OnToolbarClick) EVT_TOOL(ID_Toolbar_Mouse_en, MyFrame::OnToolbarClick) EVT_TOOL(ID_Toolbar_User, MyFrame::OnToolbarClick) END_EVENT_TABLE() ////////////////////////////////////////////////////////////////// // Menu layout (approximate) // // The actual menus will be changing so this probably isn't up // to date, but having it in text form was useful in planning. ////////////////////////////////////////////////////////////////// // - File // +----------------------+ // | New Configuration | // | Read Configuration | // | Save Configuration | // +----------------------+ // | Quit | // +----------------------+ // - Edit // +----------------------+ // | Floppy Disk 0... | // | Floppy Disk 1... | // | Hard Disk 0... | // | Hard Disk 1... | // | Cdrom... | // | Boot... | // | VGA... | // | Memory... | // | Sound... | // | Networking... | // | Keyboard... | // | Other... | // +----------------------+ // - Simulate // +----------------------+ // | Start | // | Pause/Resume | // | Stop | // +----------------------| // - Debug // +----------------------| // | Show CPU | // | Show Memory | // | ? what else ? | // +----------------------| // - Event Log // +----------------------+ // | View | // | Preferences... | // | By Device... | // +----------------------+ // - Help // +----------------------+ // | About Bochs... | // +----------------------+ ////////////////////////////////////////////////////////////////// MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size, const long style) : wxFrame((wxFrame *)NULL, -1, title, pos, size, style) { SetIcon(wxICON(icon_bochs)); // init variables sim_thread = NULL; start_bochs_times = 0; showCpu = NULL; showKbd = NULL; debugCommand = NULL; debugCommandEvent = NULL; // set up the gui menuConfiguration = new wxMenu; menuConfiguration->Append(ID_Config_New, wxT("&New Configuration")); menuConfiguration->Append(ID_Config_Read, wxT("&Read Configuration")); menuConfiguration->Append(ID_Config_Save, wxT("&Save Configuration")); menuConfiguration->AppendSeparator(); menuConfiguration->Append(ID_State_Restore, wxT("&Restore State")); menuConfiguration->AppendSeparator(); menuConfiguration->Append(ID_Quit, wxT("&Quit")); menuEdit = new wxMenu; menuEdit->Append(ID_Edit_FD_0, wxT("Floppy Disk &0...")); menuEdit->Append(ID_Edit_FD_1, wxT("Floppy Disk &1...")); menuEdit->Append(ID_Edit_ATA0, wxT("ATA Channel 0...")); menuEdit->Append(ID_Edit_ATA1, wxT("ATA Channel 1...")); menuEdit->Append(ID_Edit_ATA2, wxT("ATA Channel 2...")); menuEdit->Append(ID_Edit_ATA3, wxT("ATA Channel 3...")); menuEdit->Append(ID_Edit_CPU, wxT("&CPU...")); menuEdit->Append(ID_Edit_Memory, wxT("&Memory...")); menuEdit->Append(ID_Edit_Clock_Cmos, wxT("C&lock/Cmos...")); menuEdit->Append(ID_Edit_PCI, wxT("&PCI...")); menuEdit->Append(ID_Edit_Display, wxT("&Display + Interface...")); menuEdit->Append(ID_Edit_Keyboard, wxT("&Keyboard + Mouse...")); menuEdit->Append(ID_Edit_Boot, wxT("&Boot...")); menuEdit->Append(ID_Edit_Serial_Parallel, wxT("&Serial/Parallel...")); menuEdit->Append(ID_Edit_Network, wxT("&Network...")); menuEdit->Append(ID_Edit_Sound, wxT("S&ound...")); menuEdit->Append(ID_Edit_Other, wxT("&Other...")); menuSimulate = new wxMenu; menuSimulate->Append(ID_Simulate_Start, wxT("&Start...")); menuSimulate->Append(ID_Simulate_PauseResume, wxT("&Pause...")); menuSimulate->Append(ID_Simulate_Stop, wxT("S&top...")); menuSimulate->AppendSeparator(); menuSimulate->Enable(ID_Simulate_PauseResume, FALSE); menuSimulate->Enable(ID_Simulate_Stop, FALSE); menuDebug = new wxMenu; menuDebug->Append(ID_Debug_ShowCpu, wxT("Show &CPU")); menuDebug->Append(ID_Debug_ShowKeyboard, wxT("Show &Keyboard")); #if BX_DEBUGGER menuDebug->Append(ID_Debug_Console, wxT("Debug Console")); #endif menuDebug->Append(ID_Debug_ShowMemory, wxT("Show &memory")); menuLog = new wxMenu; menuLog->Append(ID_Log_View, wxT("&View")); menuLog->Append(ID_Log_Prefs, wxT("&Preferences...")); menuLog->Append(ID_Log_PrefsDevice, wxT("By &Device...")); menuHelp = new wxMenu; menuHelp->Append(ID_Help_About, wxT("&About...")); wxMenuBar *menuBar = new wxMenuBar; menuBar->Append(menuConfiguration, wxT("&File")); menuBar->Append(menuEdit, wxT("&Edit")); menuBar->Append(menuSimulate, wxT("&Simulate")); menuBar->Append(menuDebug, wxT("&Debug")); menuBar->Append(menuLog, wxT("&Log")); menuBar->Append(menuHelp, wxT("&Help")); SetMenuBar(menuBar); // disable things that don't work yet menuDebug->Enable(ID_Debug_ShowMemory, FALSE); // not implemented menuLog->Enable(ID_Log_View, FALSE); // not implemented // enable ATA channels in menu menuEdit->Enable(ID_Edit_ATA1, BX_MAX_ATA_CHANNEL > 1); menuEdit->Enable(ID_Edit_ATA2, BX_MAX_ATA_CHANNEL > 2); menuEdit->Enable(ID_Edit_ATA3, BX_MAX_ATA_CHANNEL > 3); // enable restore state if present menuConfiguration->Enable(ID_State_Restore, BX_SUPPORT_SAVE_RESTORE); CreateStatusBar(); wxStatusBar *sb = GetStatusBar(); sb->SetFieldsCount(12); const int sbwidth[12] = {160, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, -1}; sb->SetStatusWidths(12, sbwidth); CreateToolBar(wxNO_BORDER|wxHORIZONTAL|wxTB_FLAT); bxToolBar = GetToolBar(); bxToolBar->SetToolBitmapSize(wxSize(32, 32)); #define BX_ADD_TOOL(id, xpm_name, tooltip) do { \ bxToolBar->AddTool(id, wxBitmap(xpm_name), tooltip); \ } while (0) BX_ADD_TOOL(ID_Edit_FD_0, floppya_xpm, wxT("Change Floppy A")); BX_ADD_TOOL(ID_Edit_FD_1, floppyb_xpm, wxT("Change Floppy B")); BX_ADD_TOOL(ID_Edit_Cdrom, cdromd_xpm, wxT("Change CDROM")); BX_ADD_TOOL(ID_Toolbar_Reset, reset_xpm, wxT("Reset the system")); BX_ADD_TOOL(ID_Toolbar_Power, power_xpm, wxT("Turn power on/off")); #if BX_SUPPORT_SAVE_RESTORE BX_ADD_TOOL(ID_Toolbar_SaveRestore, saverestore_xpm, wxT("Save simulation state")); #endif BX_ADD_TOOL(ID_Toolbar_Copy, copy_xpm, wxT("Copy to clipboard")); BX_ADD_TOOL(ID_Toolbar_Paste, paste_xpm, wxT("Paste from clipboard")); BX_ADD_TOOL(ID_Toolbar_Snapshot, snapshot_xpm, wxT("Save screen snapshot")); // Omit config button because the whole wxWidgets interface is like // one really big config button. //BX_ADD_TOOL(ID_Toolbar_Config, configbutton_xpm, "Runtime Configuration"); BX_ADD_TOOL(ID_Toolbar_Mouse_en, mouse_xpm, wxT("Enable/disable mouse capture\nThere is also a shortcut for this: a CTRL key + the middle mouse button.")); BX_ADD_TOOL(ID_Toolbar_User, userbutton_xpm, wxT("Keyboard shortcut")); bxToolBar->Realize(); // create a MyPanel that covers the whole frame panel = new MyPanel(this, -1); panel->SetBackgroundColour(wxColour(0,0,0)); panel->SetFocus(); wxGridSizer *sz = new wxGridSizer(1, 1); sz->Add(panel, 0, wxGROW); SetAutoLayout(TRUE); SetSizer(sz); #if BX_DEBUGGER // create the debug log dialog box immediately so that we can write output // to it. showDebugLog = new DebugLogDialog(this, -1); showDebugLog->Init(); #endif } MyFrame::~MyFrame() { delete panel; wxLogDebug(wxT("MyFrame destructor")); theFrame = NULL; } void MyFrame::OnConfigNew(wxCommandEvent& WXUNUSED(event)) { int answer = wxMessageBox(wxT("This will reset all settings back to their default values.\nAre you sure you want to do this?"), wxT("Are you sure?"), wxYES_NO | wxCENTER, this); if (answer == wxYES) SIM->reset_all_param(); } void MyFrame::OnConfigRead(wxCommandEvent& WXUNUSED(event)) { char bochsrc[512]; long style = wxOPEN; wxFileDialog *fdialog = new wxFileDialog(this, wxT("Read configuration"), wxT(""), wxT(""), wxT("*.*"), style); if (fdialog->ShowModal() == wxID_OK) { strncpy(bochsrc, fdialog->GetPath().mb_str(wxConvUTF8), sizeof(bochsrc)); SIM->reset_all_param(); SIM->read_rc(bochsrc); } delete fdialog; } void MyFrame::OnConfigSave(wxCommandEvent& WXUNUSED(event)) { char bochsrc[512]; long style = wxSAVE | wxOVERWRITE_PROMPT; wxFileDialog *fdialog = new wxFileDialog(this, wxT("Save configuration"), wxT(""), wxT(""), wxT("*.*"), style); if (fdialog->ShowModal() == wxID_OK) { strncpy(bochsrc, fdialog->GetPath().mb_str(wxConvUTF8), sizeof(bochsrc)); SIM->write_rc(bochsrc, 1); } delete fdialog; } void MyFrame::OnStateRestore(wxCommandEvent& WXUNUSED(event)) { #if BX_SUPPORT_SAVE_RESTORE char sr_path[512]; // pass some initial dir to wxDirDialog wxString dirSaveRestore; wxGetHomeDir(&dirSaveRestore); wxDirDialog ddialog(this, wxT("Select folder with save/restore data"), dirSaveRestore, wxDD_DEFAULT_STYLE); if (ddialog.ShowModal() == wxID_OK) { strncpy(sr_path, ddialog.GetPath().mb_str(wxConvUTF8), sizeof(sr_path)); SIM->get_param_bool(BXPN_RESTORE_FLAG)->set(1); SIM->get_param_string(BXPN_RESTORE_PATH)->set(sr_path); } #endif } void MyFrame::OnEditCPU(wxCommandEvent& WXUNUSED(event)) { ParamDialog dlg(this, -1); bx_list_c *list = (bx_list_c*) SIM->get_param("cpu"); dlg.SetTitle(wxString(list->get_title()->getptr(), wxConvUTF8)); dlg.AddParam(list); dlg.ShowModal(); } void MyFrame::OnEditMemory(wxCommandEvent& WXUNUSED(event)) { ParamDialog dlg(this, -1); bx_list_c *list = (bx_list_c*) SIM->get_param("memory"); dlg.SetTitle(wxString(list->get_title()->getptr(), wxConvUTF8)); dlg.AddParam(list); dlg.ShowModal(); } void MyFrame::OnEditClockCmos(wxCommandEvent& WXUNUSED(event)) { ParamDialog dlg(this, -1); bx_list_c *list = (bx_list_c*) SIM->get_param("clock_cmos"); dlg.SetTitle(wxString(list->get_title()->getptr(), wxConvUTF8)); dlg.AddParam(list); dlg.ShowModal(); } void MyFrame::OnEditPCI(wxCommandEvent& WXUNUSED(event)) { ParamDialog dlg(this, -1); bx_list_c *list = (bx_list_c*) SIM->get_param("pci"); dlg.SetTitle(wxString(list->get_title()->getptr(), wxConvUTF8)); dlg.AddParam(list); dlg.ShowModal(); } void MyFrame::OnEditDisplay(wxCommandEvent& WXUNUSED(event)) { ParamDialog dlg(this, -1); bx_list_c *list = (bx_list_c*) SIM->get_param("display"); dlg.SetTitle(wxString(list->get_title()->getptr(), wxConvUTF8)); dlg.AddParam(list); dlg.SetRuntimeFlag(sim_thread != NULL); dlg.ShowModal(); } void MyFrame::OnEditKeyboard(wxCommandEvent& WXUNUSED(event)) { ParamDialog dlg(this, -1); bx_list_c *list = (bx_list_c*) SIM->get_param("keyboard_mouse"); dlg.SetTitle(wxString(list->get_title()->getptr(), wxConvUTF8)); dlg.AddParam(list); dlg.SetRuntimeFlag(sim_thread != NULL); dlg.ShowModal(); } void MyFrame::OnEditBoot(wxCommandEvent& WXUNUSED(event)) { int bootDevices = 0; bx_param_enum_c *floppy = SIM->get_param_enum(BXPN_FLOPPYA_DEVTYPE); if (floppy->get() != BX_FLOPPY_NONE) { bootDevices++; } bx_param_c *firsthd = SIM->get_first_hd(); if (firsthd != NULL) { bootDevices++; } bx_param_c *firstcd = SIM->get_first_cdrom(); if (firstcd != NULL) { bootDevices++; } if (bootDevices == 0) { wxMessageBox(wxT("All the possible boot devices are disabled right now!\nYou must enable the first floppy drive, a hard drive, or a CD-ROM."), wxT("None enabled"), wxOK | wxICON_ERROR, this ); return; } ParamDialog dlg(this, -1); bx_list_c *list = (bx_list_c*) SIM->get_param("boot_params"); dlg.SetTitle(wxString(list->get_title()->getptr(), wxConvUTF8)); dlg.AddParam(list); dlg.ShowModal(); } void MyFrame::OnEditSerialParallel(wxCommandEvent& WXUNUSED(event)) { ParamDialog dlg(this, -1); bx_list_c *list = (bx_list_c*) SIM->get_param("ports"); dlg.SetTitle(wxString(list->get_title()->getptr(), wxConvUTF8)); dlg.AddParam(list); dlg.SetRuntimeFlag(sim_thread != NULL); dlg.ShowModal(); } void MyFrame::OnEditNet(wxCommandEvent& WXUNUSED(event)) { ParamDialog dlg(this, -1); bx_list_c *list = (bx_list_c*) SIM->get_param("network"); dlg.SetTitle(wxString(list->get_title()->getptr(), wxConvUTF8)); dlg.AddParam(list); dlg.ShowModal(); } void MyFrame::OnEditSound(wxCommandEvent& WXUNUSED(event)) { ParamDialog dlg(this, -1); bx_list_c *list = (bx_list_c*) SIM->get_param(BXPN_SB16); dlg.SetTitle(wxString(list->get_title()->getptr(), wxConvUTF8)); dlg.AddParam(list); dlg.SetRuntimeFlag(sim_thread != NULL); dlg.ShowModal(); } void MyFrame::OnEditOther(wxCommandEvent& WXUNUSED(event)) { ParamDialog dlg(this, -1); bx_list_c *list = (bx_list_c*) SIM->get_param("misc"); dlg.SetTitle(wxString(list->get_title()->getptr(), wxConvUTF8)); dlg.AddParam(list); dlg.SetRuntimeFlag(sim_thread != NULL); dlg.ShowModal(); } void MyFrame::OnLogPrefs(wxCommandEvent& WXUNUSED(event)) { // Ideally I would use the siminterface methods to fill in the fields // of the LogOptionsDialog, but in fact several things are hardcoded. // At least I can verify that the expected numbers are the same. wxASSERT(SIM->get_max_log_level() == LOG_OPTS_N_TYPES); LogOptionsDialog dlg(this, -1); // The inital values of the dialog are complicated. If the panic action // for all modules is "ask", then clearly the inital value in the dialog // for panic action should be "ask". This informs the user what the // previous value was, and if they click Ok it won't do any harm. But if // some devices are set to "ask" and others are set to "report", then the // initial value should be "no change". With "no change", clicking on Ok // will not destroy the settings for individual devices. You would only // start to see "no change" if you've been messing around in the advanced // menu already. int level, nlevel = SIM->get_max_log_level(); for (level=0; levelget_log_action(mod, level); bool consensus = true; // now compare all others to first. If all match, then use "first" as // the initial value. for (mod=1; modget_n_log_modules(); mod++) { if (first != SIM->get_log_action(mod, level)) { consensus = false; break; } } if (consensus) dlg.SetAction(level, first); else dlg.SetAction(level, LOG_OPTS_NO_CHANGE); } int n = dlg.ShowModal(); // show the dialog! if (n == wxID_OK) { for (level=0; levelset_default_log_action(level, action); // apply that action to all modules (devices) SIM->set_log_action(-1, level, action); } } } } void MyFrame::OnLogPrefsDevice(wxCommandEvent& WXUNUSED(event)) { wxASSERT(SIM->get_max_log_level() == ADVLOG_OPTS_N_TYPES); AdvancedLogOptionsDialog dlg(this, -1); dlg.ShowModal(); } // How is this going to work? // The dialog box shows the value of CPU registers, which will be changing // all the time. What causes the dialog to reread the register value and // display it? Brainstorm: // 1) The update could be controlled by a real-time timer. // 2) It could be triggered by periodic BX_SYNC_EVT_TICK events. // 3) It could be triggered by changes in the actual value. This is // good for values that rarely change, but horrible for values like // EIP that change constantly. // 4) An update can be forced by explictly calling an update function. For // example after a single-step you would want to force an update. If you // interrupt the simulation, you want to force an update. If you manually // change a parameter, you would force an update. // When simulation is free running, #1 or #2 might make sense. Try #2. void MyFrame::OnShowCpu(wxCommandEvent& WXUNUSED(event)) { if (SIM->get_param(BXPN_WX_CPU0_STATE) == NULL) { // if params not initialized yet, then give up wxMessageBox(wxT("Cannot show the debugger window until the simulation has begun."), wxT("Sim not started"), wxOK | wxICON_ERROR, this); return; } if (showCpu == NULL) { showCpu = new CpuRegistersDialog(this, -1); #if BX_DEBUGGER showCpu->SetTitle(wxT("Bochs Debugger")); #else showCpu->SetTitle(wxT("CPU Registers")); #endif showCpu->Init(); } else { showCpu->CopyParamToGui(); } showCpu->Show(TRUE); } void MyFrame::OnShowKeyboard(wxCommandEvent& WXUNUSED(event)) { if (SIM->get_param(BXPN_WX_KBD_STATE) == NULL) { // if params not initialized yet, then give up wxMessageBox(wxT("Cannot show the debugger window until the simulation has begun."), wxT("Sim not started"), wxOK | wxICON_ERROR, this ); return; } if (showKbd == NULL) { showKbd = new ParamDialog(this, -1); showKbd->SetTitle(wxT("Keyboard State (incomplete, this is a demo)")); showKbd->AddParam(SIM->get_param(BXPN_WX_KBD_STATE)); showKbd->Init(); } else { showKbd->CopyParamToGui(); } showKbd->Show(TRUE); } #if BX_DEBUGGER void MyFrame::OnDebugLog(wxCommandEvent& WXUNUSED(event)) { wxASSERT(showDebugLog != NULL); showDebugLog->CopyParamToGui(); showDebugLog->Show(TRUE); } void MyFrame::DebugBreak() { if (debugCommand) { delete debugCommand; debugCommand = NULL; } wxASSERT(showDebugLog != NULL); showDebugLog->AppendCommand("*** break ***"); SIM->debug_break(); } void MyFrame::DebugCommand(wxString cmd) { char buf[1024]; safeWxStrcpy(buf, cmd, sizeof(buf)); DebugCommand(buf); } void MyFrame::DebugCommand(const char *cmd) { wxLogDebug(wxT("debugger command: %s"), cmd); wxASSERT(showDebugLog != NULL); showDebugLog->AppendCommand(cmd); if (debugCommand != NULL) { // one is already waiting wxLogDebug(wxT("multiple debugger commands, discarding the earlier one")); delete debugCommand; debugCommand = NULL; } int len = strlen(cmd); char *tmp = new char[len+1]; strncpy(tmp, cmd, len+1); // if an event is waiting for us, fill it an send back to sim_thread. if (debugCommandEvent != NULL) { wxLogDebug(wxT("sim_thread was waiting for this command '%s'"), tmp); wxASSERT(debugCommandEvent->type == BX_SYNC_EVT_GET_DBG_COMMAND); debugCommandEvent->u.debugcmd.command = tmp; debugCommandEvent->retcode = 1; sim_thread->SendSyncResponse(debugCommandEvent); wxASSERT(debugCommand == NULL); debugCommandEvent = NULL; } else { // store this command in debugCommand for the future wxLogDebug(wxT("storing debugger command '%s'"), tmp); debugCommand = tmp; } } #endif void MyFrame::OnQuit(wxCommandEvent& event) { wxBochsClosing = true; bx_user_quit = 1; if (!sim_thread) { // no simulation thread is running. Just close the window. Close( TRUE ); } else { SIM->set_notify_callback(&MyApp::DefaultCallback, this); // ask the simulator to stop. When it stops it will close this frame. SetStatusText(wxT("Waiting for simulation to stop...")); OnKillSim(event); } } void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event)) { wxString str(wxT("Bochs x86 Emulator version ")); str += wxString(VER_STRING, wxConvUTF8); str += wxT(" (wxWidgets port)"); wxMessageBox(str, wxT("About Bochs"), wxOK | wxICON_INFORMATION, this ); } // update the menu items, status bar, etc. void MyFrame::simStatusChanged(StatusChange change, bx_bool popupNotify) { char ata_name[20]; bx_list_c *base; switch (change) { case Start: // running wxLogStatus(wxT("Starting Bochs simulation")); menuSimulate->Enable(ID_Simulate_Start, FALSE); menuSimulate->Enable(ID_Simulate_PauseResume, TRUE); menuSimulate->Enable(ID_Simulate_Stop, TRUE); menuSimulate->SetLabel(ID_Simulate_PauseResume, wxT("&Pause")); break; case Stop: // not running wxLogStatus(wxT("Simulation stopped")); menuSimulate->Enable(ID_Simulate_Start, TRUE); menuSimulate->Enable(ID_Simulate_PauseResume, FALSE); menuSimulate->Enable(ID_Simulate_Stop, FALSE); menuSimulate->SetLabel(ID_Simulate_PauseResume, wxT("&Pause")); #if BX_DEBUGGER showDebugLog->Show(FALSE); #endif // This should only be used if the simulation stops due to error. // Obviously if the user asked it to stop, they don't need to be told. if (popupNotify) wxMessageBox(wxT("Bochs simulation has stopped."), wxT("Bochs Stopped"), wxOK | wxICON_INFORMATION, this); break; case Pause: // pause wxLogStatus(wxT("Pausing simulation")); menuSimulate->SetLabel(ID_Simulate_PauseResume, wxT("&Resume")); break; case Resume: // resume wxLogStatus(wxT("Resuming simulation")); menuSimulate->SetLabel(ID_Simulate_PauseResume, wxT("&Pause")); break; } bool canConfigure = (change == Stop); menuConfiguration->Enable(ID_Config_New, canConfigure); menuConfiguration->Enable(ID_Config_Read, canConfigure); #if BX_SUPPORT_SAVE_RESTORE menuConfiguration->Enable(ID_State_Restore, canConfigure); #endif // only enabled ATA channels with a cdrom connected are available at runtime for (unsigned i=0; iget_param(ata_name); if (!SIM->get_param_bool("enabled", base)->get()) { menuEdit->Enable(ID_Edit_ATA0+i, canConfigure); } else { sprintf(ata_name, "ata.%d.master", i); base = (bx_list_c*) SIM->get_param(ata_name); if (SIM->get_param_enum("type", base)->get() != BX_ATA_DEVICE_CDROM) { sprintf(ata_name, "ata.%d.slave", i); base = (bx_list_c*) SIM->get_param(ata_name); if (SIM->get_param_enum("type", base)->get() != BX_ATA_DEVICE_CDROM) { menuEdit->Enable(ID_Edit_ATA0+i, canConfigure); } } } } menuEdit->Enable(ID_Edit_CPU, canConfigure); menuEdit->Enable(ID_Edit_Memory, canConfigure); menuEdit->Enable(ID_Edit_Clock_Cmos, canConfigure); menuEdit->Enable(ID_Edit_PCI, canConfigure); menuEdit->Enable(ID_Edit_Boot, canConfigure); menuEdit->Enable(ID_Edit_Network, canConfigure); // during simulation, certain menu options like the floppy disk // can be modified under some circumstances. A floppy drive can // only be edited if it was enabled at boot time. Bit64u value; value = SIM->get_param_enum(BXPN_FLOPPYA_DEVTYPE)->get(); menuEdit->Enable(ID_Edit_FD_0, canConfigure || (value != BX_FLOPPY_NONE)); bxToolBar->EnableTool(ID_Edit_FD_0, canConfigure || (value != BX_FLOPPY_NONE)); value = SIM->get_param_enum(BXPN_FLOPPYB_DEVTYPE)->get(); menuEdit->Enable(ID_Edit_FD_1, canConfigure || (value != BX_FLOPPY_NONE)); bxToolBar->EnableTool(ID_Edit_FD_1, canConfigure || (value != BX_FLOPPY_NONE)); bxToolBar->EnableTool(ID_Edit_Cdrom, canConfigure || (SIM->get_first_cdrom() != NULL)); } void MyFrame::OnStartSim(wxCommandEvent& event) { wxCriticalSectionLocker lock(sim_thread_lock); if (sim_thread != NULL) { wxMessageBox( wxT("Can't start Bochs simulator, because it is already running"), wxT("Already Running"), wxOK | wxICON_ERROR, this); return; } // check that display library is set to wx. If not, give a warning and // change it to wx. It is technically possible to use other vga libraries // with the wx config interface, but there are still some significant // problems. bx_param_enum_c *gui_param = SIM->get_param_enum(BXPN_SEL_DISPLAY_LIBRARY); char *gui_name = gui_param->get_selected(); if (strcmp(gui_name, "wx") != 0) { wxMessageBox(wxT( "The display library was not set to wxWidgets. When you use the\n" "wxWidgets configuration interface, you must also select the wxWidgets\n" "display library. I will change it to 'wx' now."), wxT("display library error"), wxOK | wxICON_WARNING, this); if (!gui_param->set_by_name("wx")) { wxASSERT(0 && "Could not set display library setting to 'wx"); } } // give warning about restarting the simulation start_bochs_times++; if (start_bochs_times>1) { wxMessageBox(wxT( "You have already started the simulator once this session. Due to memory leaks and bugs in init code, you may get unstable behavior."), wxT("2nd time warning"), wxOK | wxICON_WARNING, this); } num_events = 0; // clear the queue of events for bochs to handle sim_thread = new SimThread(this); sim_thread->Create(); sim_thread->Run(); wxLogDebug(wxT("Simulator thread has started.")); // set up callback for events from simulator thread SIM->set_notify_callback(&SimThread::SiminterfaceCallback, sim_thread); simStatusChanged(Start); } void MyFrame::OnPauseResumeSim(wxCommandEvent& WXUNUSED(event)) { wxCriticalSectionLocker lock(sim_thread_lock); if (sim_thread) { if (sim_thread->IsPaused()) { simStatusChanged(Resume); sim_thread->Resume(); } else { simStatusChanged(Pause); sim_thread->Pause(); } } } void MyFrame::OnKillSim(wxCommandEvent& WXUNUSED(event)) { // DON'T use a critical section here. Delete implicitly calls // OnSimThreadExit, which also tries to lock sim_thread_lock. // If we grab the lock at this level, deadlock results. wxLogDebug(wxT("OnKillSim()")); #if BX_DEBUGGER // the sim_thread may be waiting for a debugger command. If so, send // it a "quit" DebugCommand("quit"); #endif if (sim_thread) { wxBochsStopSim = true; sim_thread->Delete(); // Next time the simulator reaches bx_real_sim_c::periodic() it // will quit. This is better than killing the thread because it // gives it a chance to clean up after itself. } if (!wxBochsClosing) { theFrame->simStatusChanged(theFrame->Stop, true); } } void MyFrame::OnSimThreadExit() { wxCriticalSectionLocker lock(sim_thread_lock); sim_thread = NULL; } int MyFrame::HandleAskParamString(bx_param_string_c *param) { wxLogDebug(wxT("HandleAskParamString start")); bx_param_num_c *opt = param->get_options(); wxASSERT(opt != NULL); int n_opt = opt->get(); const char *msg = param->get_label(); if ((msg == NULL) || (strlen(msg) == 0)) { msg = param->get_name(); } char newval[512]; newval[0] = 0; wxDialog *dialog = NULL; if (n_opt & param->SELECT_FOLDER_DLG) { // pass some initial dir to wxDirDialog wxString homeDir; wxGetHomeDir(&homeDir); wxDirDialog *ddialog = new wxDirDialog(this, wxString(msg, wxConvUTF8), homeDir, wxDD_DEFAULT_STYLE); if (ddialog->ShowModal() == wxID_OK) strncpy(newval, ddialog->GetPath().mb_str(wxConvUTF8), sizeof(newval)); dialog = ddialog; // so I can delete it } else if (n_opt & param->IS_FILENAME) { // use file open dialog long style = (n_opt & param->SAVE_FILE_DIALOG) ? wxSAVE|wxOVERWRITE_PROMPT : wxOPEN; wxFileDialog *fdialog = new wxFileDialog(this, wxString(msg, wxConvUTF8), wxT(""), wxString(param->getptr(), wxConvUTF8), wxT("*.*"), style); if (fdialog->ShowModal() == wxID_OK) strncpy(newval, fdialog->GetPath().mb_str(wxConvUTF8), sizeof(newval)); dialog = fdialog; // so I can delete it } else { // use simple string dialog long style = wxOK|wxCANCEL; wxTextEntryDialog *tdialog = new wxTextEntryDialog(this, wxString(msg, wxConvUTF8), wxT("Enter new value"), wxString(param->getptr(), wxConvUTF8), style); if (tdialog->ShowModal() == wxID_OK) strncpy(newval, tdialog->GetValue().mb_str(wxConvUTF8), sizeof(newval)); dialog = tdialog; // so I can delete it } if (strlen(newval) > 0) { // change floppy path to this value. wxLogDebug(wxT("Setting param %s to '%s'"), param->get_name(), newval); param->set(newval); delete dialog; return 1; } delete dialog; return -1; } // This is called when the simulator needs to ask the user to choose // a value or setting. For example, when the user indicates that he wants // to change the floppy disk image for drive A, an ask-param event is created // with the parameter id set to BXP_FLOPPYA_PATH. The simulator blocks until // the gui has displayed a dialog and received a selection from the user. // In the current implemention, the GUI will look up the parameter's // data structure using SIM->get_param() and then call the set method on the // parameter to change the param. The return value only needs to return // success or failure (failure = cancelled, or not implemented). // Returns 1 if the user chose a value and the param was modified. // Returns 0 if the user cancelled. // Returns -1 if the gui doesn't know how to ask for that param. int MyFrame::HandleAskParam(BxEvent *event) { wxASSERT(event->type == BX_SYNC_EVT_ASK_PARAM); bx_param_c *param = event->u.param.param; Raise(); // bring window to front so that you will see the dialog switch (param->get_type()) { case BXT_PARAM_STRING: return HandleAskParamString((bx_param_string_c *)param); case BXT_PARAM_BOOL: { long style = wxYES_NO; if (((bx_param_bool_c *)param)->get() == 0) style |= wxNO_DEFAULT; ((bx_param_bool_c *)param)->set(wxMessageBox(wxString(param->get_description(), wxConvUTF8), wxString(param->get_label(), wxConvUTF8), style, this) == wxYES); return 0; } default: { wxString msg; msg.Printf(wxT("ask param for parameter type %d is not implemented in wxWidgets"), param->get_type()); wxMessageBox(msg, wxT("not implemented"), wxOK | wxICON_ERROR, this ); return -1; } } return -1; // could not display } // This is called from the wxWidgets GUI thread, when a Sim2CI event // is found. (It got there via wxPostEvent in SiminterfaceCallback2, which is // executed in the simulator Thread.) void MyFrame::OnSim2CIEvent(wxCommandEvent& event) { IFDBG_EVENT(wxLogDebug(wxT("received a bochs event in the GUI thread"))); BxEvent *be = (BxEvent *) event.GetEventObject(); IFDBG_EVENT(wxLogDebug(wxT("event type = %d"), (int)be->type)); // all cases should return. sync event handlers MUST send back a // response. async event handlers MUST delete the event. switch (be->type) { case BX_ASYNC_EVT_REFRESH: RefreshDialogs(); break; case BX_SYNC_EVT_ASK_PARAM: wxLogDebug(wxT("before HandleAskParam")); be->retcode = HandleAskParam(be); wxLogDebug(wxT("after HandleAskParam")); // return a copy of the event back to the sender. sim_thread->SendSyncResponse(be); wxLogDebug(wxT("after SendSyncResponse")); break; #if BX_DEBUGGER case BX_ASYNC_EVT_DBG_MSG: showDebugLog->AppendText(wxString(be->u.logmsg.msg, wxConvUTF8)); // free the char* which was allocated in dbg_printf delete [] ((char*) be->u.logmsg.msg); break; #endif case BX_SYNC_EVT_LOG_ASK: case BX_ASYNC_EVT_LOG_MSG: OnLogMsg(be); break; case BX_SYNC_EVT_GET_DBG_COMMAND: wxLogDebug(wxT("BX_SYNC_EVT_GET_DBG_COMMAND received")); if (debugCommand == NULL) { // no debugger command is ready to send, so don't send a response yet. // When a command is issued, MyFrame::DebugCommand will fill in the // event and call SendSyncResponse() so that the simulation thread can // continue. debugCommandEvent = be; // if (showCpu == NULL || !showCpu->IsShowing()) { wxCommandEvent unused; OnShowCpu(unused); } } else { // a debugger command is waiting for us! wxLogDebug(wxT("sending debugger command '%s' that was waiting"), debugCommand); be->u.debugcmd.command = debugCommand; debugCommand = NULL; // ready for the next one debugCommandEvent = NULL; be->retcode = 1; sim_thread->SendSyncResponse(be); } break; default: wxLogDebug(wxT("OnSim2CIEvent: event type %d ignored"), (int)be->type); if (!BX_EVT_IS_ASYNC(be->type)) { // if it's a synchronous event, and we fail to send back a response, // the sim thread will wait forever. So send something! sim_thread->SendSyncResponse(be); } break; } if (BX_EVT_IS_ASYNC(be->type)) delete be; } void MyFrame::OnLogMsg(BxEvent *be) { wxLogDebug(wxT("log msg: level=%d, prefix='%s', msg='%s'"), be->u.logmsg.level, be->u.logmsg.prefix, be->u.logmsg.msg); if (be->type == BX_ASYNC_EVT_LOG_MSG) return; // we don't have any place to display log messages else wxASSERT(be->type == BX_SYNC_EVT_LOG_ASK); wxString levelName(SIM->get_log_level_name(be->u.logmsg.level), wxConvUTF8); LogMsgAskDialog dlg(this, -1, levelName); // panic, error, etc. #if !BX_DEBUGGER dlg.EnableButton(dlg.DEBUG, FALSE); #endif dlg.SetContext(wxString(be->u.logmsg.prefix, wxConvUTF8)); dlg.SetMessage(wxString(be->u.logmsg.msg, wxConvUTF8)); int n = dlg.ShowModal(); // turn the return value into the constant that logfunctions::ask is // expecting. 0=continue, 1=continue but ignore future messages from this // device, 2=die, 3=dump core, 4=debugger. if (n==BX_LOG_ASK_CHOICE_CONTINUE) { if (dlg.GetDontAsk()) n = BX_LOG_ASK_CHOICE_CONTINUE_ALWAYS; } be->retcode = n; wxLogDebug(wxT("you chose %d"), n); // This can be called from two different contexts: // 1) before sim_thread starts, the default application callback can // call OnLogMsg to display messages. // 2) after the sim_thread starts, the sim_thread callback can call // OnLogMsg to display messages if (sim_thread) sim_thread->SendSyncResponse(be); // only for case #2 } bool MyFrame::editFloppyValidate(FloppyConfigDialog *dialog) { // haven't done anything with this 'feature' return true; } void MyFrame::editFloppyConfig(int drive) { FloppyConfigDialog dlg(this, -1); dlg.SetDriveName(wxString(drive==0? BX_FLOPPY0_NAME : BX_FLOPPY1_NAME, wxConvUTF8)); dlg.SetCapacityChoices(n_floppy_type_names, floppy_type_names); bx_list_c *list = (bx_list_c*) SIM->get_param((drive==0)? BXPN_FLOPPYA : BXPN_FLOPPYB); if (!list) { wxLogError(wxT("floppy object param is null")); return; } bx_param_filename_c *fname = (bx_param_filename_c*) list->get_by_name("path"); bx_param_enum_c *disktype = (bx_param_enum_c *) list->get_by_name("type"); bx_param_enum_c *status = (bx_param_enum_c *) list->get_by_name("status"); if (fname->get_type() != BXT_PARAM_STRING || disktype->get_type() != BXT_PARAM_ENUM || status->get_type() != BXT_PARAM_ENUM) { wxLogError(wxT("floppy params have wrong type")); return; } if (sim_thread == NULL) { dlg.AddRadio(wxT("Not Present"), wxT("")); } dlg.AddRadio(wxT("Ejected"), wxT("none")); #if defined(__linux__) dlg.AddRadio(wxT("Physical floppy drive /dev/fd0"), wxT("/dev/fd0")); dlg.AddRadio(wxT("Physical floppy drive /dev/fd1"), wxT("/dev/fd1")); #elif defined(WIN32) dlg.AddRadio(wxT("Physical floppy drive A:"), wxT("A:")); dlg.AddRadio(wxT("Physical floppy drive B:"), wxT("B:")); #else // add your favorite operating system here #endif dlg.SetCapacity(disktype->get() - disktype->get_min()); dlg.SetFilename(wxString(fname->getptr(), wxConvUTF8)); dlg.SetValidateFunc(editFloppyValidate); if (disktype->get() == BX_FLOPPY_NONE) { dlg.SetRadio(0); } else if ((status->get() == BX_EJECTED) || (!strcmp("none", fname->getptr()))) { dlg.SetRadio((sim_thread == NULL)?1:0); } else { // otherwise the SetFilename() should have done the right thing. } int n = dlg.ShowModal(); wxLogMessage(wxT("floppy config returned %d"), n); if (n==wxID_OK) { char filename[1024]; wxString fn(dlg.GetFilename()); strncpy(filename, fn.mb_str(wxConvUTF8), sizeof(filename)); wxLogMessage(wxT("filename is '%s'"), filename); wxLogMessage(wxT("capacity = %d (%s)"), dlg.GetCapacity(), floppy_type_names[dlg.GetCapacity()]); fname->set(filename); disktype->set(disktype->get_min() + dlg.GetCapacity()); if (sim_thread == NULL) { if (dlg.GetRadio() == 0) { disktype->set(BX_FLOPPY_NONE); } } else { if (dlg.GetRadio() > 0) { status->set(BX_INSERTED); } } } } void MyFrame::editFirstCdrom() { bx_param_c *firstcd = SIM->get_first_cdrom(); if (!firstcd) { wxMessageBox(wxT("No CDROM drive is enabled. Use Edit:ATA to set one up."), wxT("No CDROM"), wxOK | wxICON_ERROR, this ); return; } ParamDialog dlg(this, -1); dlg.SetTitle(wxT("Configure CDROM")); dlg.AddParam(firstcd); dlg.SetRuntimeFlag(sim_thread != NULL); dlg.ShowModal(); } void MyFrame::OnEditATA(wxCommandEvent& event) { int id = event.GetId(); int channel = id - ID_Edit_ATA0; char ata_name[10]; sprintf(ata_name, "ata.%d", channel); ParamDialog dlg(this, -1); bx_list_c *list = (bx_list_c*) SIM->get_param(ata_name); dlg.SetTitle(wxString(list->get_title()->getptr(), wxConvUTF8)); dlg.AddParam(list); dlg.SetRuntimeFlag(sim_thread != NULL); dlg.ShowModal(); } void MyFrame::OnToolbarClick(wxCommandEvent& event) { wxLogDebug(wxT("clicked toolbar thingy")); bx_toolbar_buttons which = BX_TOOLBAR_UNDEFINED; int id = event.GetId(); switch (id) { case ID_Toolbar_Power: which = BX_TOOLBAR_POWER; wxBochsStopSim = false; break; case ID_Toolbar_Reset: which = BX_TOOLBAR_RESET; break; #if BX_SUPPORT_SAVE_RESTORE case ID_Toolbar_SaveRestore: which = BX_TOOLBAR_SAVE_RESTORE; break; #endif case ID_Edit_FD_0: // floppy config dialog box editFloppyConfig(0); break; case ID_Edit_FD_1: // floppy config dialog box editFloppyConfig(1); break; case ID_Edit_Cdrom: // cdrom config dialog box (first cd only) editFirstCdrom(); break; case ID_Toolbar_Copy: which = BX_TOOLBAR_COPY; break; case ID_Toolbar_Paste: which = BX_TOOLBAR_PASTE; break; case ID_Toolbar_Snapshot: which = BX_TOOLBAR_SNAPSHOT; break; case ID_Toolbar_Config: which = BX_TOOLBAR_CONFIG; break; case ID_Toolbar_Mouse_en: which = BX_TOOLBAR_MOUSE_EN; break; case ID_Toolbar_User: which = BX_TOOLBAR_USER; break; default: wxLogError(wxT("unknown toolbar id %d"), id); } if (num_events < MAX_EVENTS) { event_queue[num_events].type = BX_ASYNC_EVT_TOOLBAR; event_queue[num_events].u.toolbar.button = which; num_events++; } } // warning: This can be called from the simulator thread!!! bool MyFrame::WantRefresh() { bool anyShowing = false; if (showCpu!=NULL && showCpu->IsShowing()) anyShowing = true; if (showKbd!=NULL && showKbd->IsShowing()) anyShowing = true; return anyShowing; } void MyFrame::RefreshDialogs() { if (showCpu!=NULL && showCpu->IsShowing()) showCpu->CopyParamToGui(); if (showKbd!=NULL && showKbd->IsShowing()) showKbd->CopyParamToGui(); } ////////////////////////////////////////////////////////////////////// // Simulation Thread ////////////////////////////////////////////////////////////////////// void * SimThread::Entry(void) { // run all the rest of the Bochs simulator code. This function will // run forever, unless a "kill_bochs_request" is issued. The shutdown // procedure is as follows: // - user selects "Kill Simulation" or GUI decides to kill bochs // - GUI calls sim_thread->Delete() // - sim continues to run until the next time it reaches SIM->periodic(). // - SIM->periodic() sends a synchronous tick event to the GUI, which // finally calls TestDestroy() and realizes it needs to stop. It // sets the sync event return code to -1. SIM->periodic() notices // the -1 and calls quit_sim, which longjumps to quit_context, which is // right here in SimThread::Entry. // - Entry() exits and the thread stops. Whew. wxLogDebug(wxT("in SimThread, starting at bx_continue_after_config_interface")); static jmp_buf context; // this must not go out of scope. maybe static not needed if (setjmp(context) == 0) { SIM->set_quit_context(&context); SIM->begin_simulation(bx_startup_flags.argc, bx_startup_flags.argv); wxLogDebug(wxT("in SimThread, SIM->begin_simulation() exited normally")); } else { wxLogDebug(wxT("in SimThread, SIM->begin_simulation() exited by longjmp")); } SIM->set_quit_context(NULL); // it is possible that the whole interface has already been shut down. // If so, we must end immediately. // we're in the sim thread, so we must get a gui mutex before calling // wxwidgets methods. wxLogDebug(wxT("SimThread::Entry: get gui mutex")); wxMutexGuiEnter(); if (!wxBochsClosing) { if (!wxBochsStopSim) { wxLogDebug(wxT("SimThread::Entry: sim thread ending. call simStatusChanged")); theFrame->simStatusChanged(theFrame->Stop, true); } } else { wxLogMessage(wxT("SimThread::Entry: the gui is waiting for sim to finish. Now that it has finished, I will close the frame.")); theFrame->Close(TRUE); } wxMutexGuiLeave(); return NULL; } void SimThread::OnExit() { // notify the MyFrame that the bochs thread has died. I can't adjust // the sim_thread directly because it's private. frame->OnSimThreadExit(); // don't use this SimThread's callback function anymore. Use the // application default callback. SIM->set_notify_callback(&MyApp::DefaultCallback, this); } // Event handler function for BxEvents coming from the simulator. // This function is declared static so that I can get a usable // function pointer for it. The function pointer is passed to // SIM->set_notify_callback so that the siminterface can call this // function when it needs to contact the gui. It will always be // called with a pointer to the SimThread as the first argument, and // it will be called from the simulator thread, not the GUI thread. BxEvent * SimThread::SiminterfaceCallback(void *thisptr, BxEvent *event) { SimThread *me = (SimThread *)thisptr; // call the normal non-static method now that we know the this pointer. return me->SiminterfaceCallback2(event); } // callback function for sim thread events. This is called from // the sim thread, not the GUI thread. So any GUI actions must be // thread safe. Most events are handled by packaging up the event // in a wxEvent of some kind, and posting it to the GUI thread for // processing. BxEvent * SimThread::SiminterfaceCallback2(BxEvent *event) { // wxLogDebug(wxT("SiminterfaceCallback with event type=%d"), (int)event->type); event->retcode = 0; // default return code int async = BX_EVT_IS_ASYNC(event->type); if (!async) { // for synchronous events, clear away any previous response. There // can only be one synchronous event pending at a time. ClearSyncResponse(); event->retcode = -1; // default to error } // tick event must be handled right here in the bochs thread. if (event->type == BX_SYNC_EVT_TICK) { if (TestDestroy()) { // tell simulator to quit event->retcode = -1; } else { event->retcode = 0; } return event; } // prune refresh events if the frame is going to ignore them anyway if (event->type == BX_ASYNC_EVT_REFRESH && !theFrame->WantRefresh()) { delete event; return NULL; } //encapsulate the bxevent in a wxwidgets event wxCommandEvent wxevent(wxEVT_COMMAND_MENU_SELECTED, ID_Sim2CI_Event); wxevent.SetEventObject((wxEvent *)event); if (isSimThread()) { IFDBG_EVENT(wxLogDebug(wxT("Sending an event to the window"))); wxPostEvent(frame, wxevent); // if it is an asynchronous event, return immediately. The event will be // freed by the recipient in the GUI thread. if (async) return NULL; wxLogDebug(wxT("SiminterfaceCallback2: synchronous event; waiting for response")); // now wait forever for the GUI to post a response. BxEvent *response = NULL; while (response == NULL) { response = GetSyncResponse(); if (!response) { //wxLogDebug ("no sync response yet, waiting"); this->Sleep(20); } // don't get stuck here if the gui is trying to close. if (wxBochsClosing) { wxLogDebug(wxT("breaking out of sync event wait because gui is closing")); event->retcode = -1; return event; } } wxASSERT(response != NULL); return response; } else { wxLogDebug(wxT("sim2ci event sent from the GUI thread. calling handler directly")); theFrame->OnSim2CIEvent(wxevent); return event; } } void SimThread::ClearSyncResponse() { wxCriticalSectionLocker lock(sim2gui_mailbox_lock); if (sim2gui_mailbox != NULL) { wxLogDebug(wxT("WARNING: ClearSyncResponse is throwing away an event that was previously in the mailbox")); } sim2gui_mailbox = NULL; } void SimThread::SendSyncResponse(BxEvent *event) { wxCriticalSectionLocker lock(sim2gui_mailbox_lock); if (sim2gui_mailbox != NULL) { wxLogDebug(wxT("WARNING: SendSyncResponse is throwing away an event that was previously in the mailbox")); } sim2gui_mailbox = event; } BxEvent * SimThread::GetSyncResponse() { wxCriticalSectionLocker lock(sim2gui_mailbox_lock); BxEvent *event = sim2gui_mailbox; sim2gui_mailbox = NULL; return event; } /////////////////////////////////////////////////////////////////// // utility /////////////////////////////////////////////////////////////////// void safeWxStrcpy(char *dest, wxString src, int destlen) { wxString tmp(src); strncpy(dest, tmp.mb_str(wxConvUTF8), destlen); dest[destlen-1] = 0; } #endif /* if BX_WITH_WX */