Bochs/bochs/gui/wxmain.cc
Volker Ruppert 4cece81589 - started implementation of the parameter tree based on patch.param-tree
* memory parameter handling rewritten (not yet complete)
  * new reset() method for bx_list_c for recursive reset of all list members
  * memory options in wx now handled in a ParamDialog (ConfigMemoryDialog removed)
  * removed obsolete BXP_* constants in siminterface.h
  * updated proposed parameter tree
- constant BX_N_OPTRAM_IMAGES for optional RAM images limit added
2006-02-16 21:44:17 +00:00

1533 lines
55 KiB
C++

/////////////////////////////////////////////////////////////////
// $Id: wxmain.cc,v 1.112 2006-02-16 21:44:17 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 <wx/wxprec.h>
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include <wx/image.h>
#include <wx/clipbrd.h>
#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"
#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;
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:
//fprintf (stderr, "wxmain.cc: start\n");
#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 ("plugin_init for wxmain.cc");
wxLogDebug ("installing wxWidgets as the configuration interface");
SIM->register_configuration_interface ("wx", ci_callback, NULL);
wxLogDebug ("installing %s as the Bochs GUI", "wxWidgets");
MyPanel::OnPluginInit ();
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 (_T("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( "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(BXP_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 ("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 ("DefaultCallback: log ask event");
wxString text;
text.Printf ("Error: %s", event->u.logmsg.msg);
if (wxBochsClosing) {
// gui closing down, do something simple and nongraphical.
fprintf (stderr, "%s\n", text.c_str ());
} else {
wxMessageBox (text, "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 ("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_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_Boot, MyFrame::OnEditBoot)
EVT_MENU(ID_Edit_Memory, MyFrame::OnEditMemory)
EVT_MENU(ID_Edit_PCI, MyFrame::OnEditPCI)
EVT_MENU(ID_Edit_Sound, MyFrame::OnEditSound)
EVT_MENU(ID_Edit_Timing, MyFrame::OnEditTiming)
EVT_MENU(ID_Edit_Network, MyFrame::OnEditNet)
EVT_MENU(ID_Edit_Keyboard, MyFrame::OnEditKeyboard)
EVT_MENU(ID_Edit_Serial_Parallel, MyFrame::OnEditSerialParallel)
EVT_MENU(ID_Edit_LoadHack, MyFrame::OnEditLoadHack)
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)
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, "&New Configuration" );
menuConfiguration->Append( ID_Config_Read, "&Read Configuration" );
menuConfiguration->Append( ID_Config_Save, "&Save Configuration" );
menuConfiguration->AppendSeparator ();
menuConfiguration->Append (ID_Quit, "&Quit");
menuEdit = new wxMenu;
menuEdit->Append( ID_Edit_FD_0, "Floppy Disk &0..." );
menuEdit->Append( ID_Edit_FD_1, "Floppy Disk &1..." );
menuEdit->Append( ID_Edit_ATA0, "ATA Channel 0..." );
menuEdit->Append( ID_Edit_ATA1, "ATA Channel 1..." );
menuEdit->Append( ID_Edit_ATA2, "ATA Channel 2..." );
menuEdit->Append( ID_Edit_ATA3, "ATA Channel 3..." );
menuEdit->Append( ID_Edit_Boot, "&Boot..." );
menuEdit->Append( ID_Edit_Memory, "&Memory..." );
menuEdit->Append( ID_Edit_PCI, "&PCI..." );
menuEdit->Append( ID_Edit_Sound, "S&ound..." );
menuEdit->Append( ID_Edit_Timing, "&Timing..." );
menuEdit->Append( ID_Edit_Network, "&Network..." );
menuEdit->Append( ID_Edit_Keyboard, "&Keyboard..." );
menuEdit->Append( ID_Edit_Serial_Parallel, "&Serial/Parallel..." );
menuEdit->Append( ID_Edit_LoadHack, "&Loader Hack..." );
menuEdit->Append( ID_Edit_Other, "&Other..." );
menuSimulate = new wxMenu;
menuSimulate->Append( ID_Simulate_Start, "&Start...");
menuSimulate->Append( ID_Simulate_PauseResume, "&Pause...");
menuSimulate->Append( ID_Simulate_Stop, "S&top...");
menuSimulate->AppendSeparator ();
menuSimulate->Enable (ID_Simulate_PauseResume, FALSE);
menuSimulate->Enable (ID_Simulate_Stop, FALSE);
menuDebug = new wxMenu;
menuDebug->Append (ID_Debug_ShowCpu, "Show &CPU");
menuDebug->Append (ID_Debug_ShowKeyboard, "Show &Keyboard");
#if BX_DEBUGGER
menuDebug->Append (ID_Debug_Console, "Debug Console");
#endif
menuDebug->Append (ID_Debug_ShowMemory, "Show &memory");
menuLog = new wxMenu;
menuLog->Append (ID_Log_View, "&View");
menuLog->Append (ID_Log_Prefs, "&Preferences...");
menuLog->Append (ID_Log_PrefsDevice, "By &Device...");
menuHelp = new wxMenu;
menuHelp->Append( ID_Help_About, "&About..." );
wxMenuBar *menuBar = new wxMenuBar;
menuBar->Append( menuConfiguration, "&File" );
menuBar->Append( menuEdit, "&Edit" );
menuBar->Append( menuSimulate, "&Simulate" );
menuBar->Append( menuDebug, "&Debug" );
menuBar->Append( menuLog, "&Log" );
menuBar->Append( menuHelp, "&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
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);
wxToolBar *tb = GetToolBar();
tb->SetToolBitmapSize(wxSize(32, 32));
#define BX_ADD_TOOL(id, xpm_name, tooltip) do { \
tb->AddTool(id, wxBitmap(xpm_name), tooltip); \
} while (0)
BX_ADD_TOOL(ID_Edit_FD_0, floppya_xpm, "Change Floppy A");
BX_ADD_TOOL(ID_Edit_FD_1, floppyb_xpm, "Change Floppy B");
BX_ADD_TOOL(ID_Edit_Cdrom, cdromd_xpm, "Change CDROM");
BX_ADD_TOOL(ID_Toolbar_Reset, reset_xpm, "Reset the system");
BX_ADD_TOOL(ID_Toolbar_Power, power_xpm, "Turn power on/off");
BX_ADD_TOOL(ID_Toolbar_Copy, copy_xpm, "Copy to clipboard");
BX_ADD_TOOL(ID_Toolbar_Paste, paste_xpm, "Paste from clipboard");
BX_ADD_TOOL(ID_Toolbar_Snapshot, snapshot_xpm, "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, "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, "Keyboard shortcut");
tb->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 ("MyFrame destructor");
theFrame = NULL;
}
void MyFrame::OnConfigNew(wxCommandEvent& WXUNUSED(event))
{
int answer = wxMessageBox ("This will reset all settings back to their default values.\nAre you sure you want to do this?",
"Are you sure?", wxYES_NO | wxCENTER, this);
if (answer == wxYES) SIM->reset_all_param ();
}
void MyFrame::OnConfigRead(wxCommandEvent& WXUNUSED(event))
{
char *bochsrc;
long style = wxOPEN;
wxFileDialog *fdialog = new wxFileDialog (this, "Read configuration", "", "", "*.*", style);
if (fdialog->ShowModal() == wxID_OK) {
bochsrc = (char *)fdialog->GetPath().c_str ();
SIM->reset_all_param ();
SIM->read_rc (bochsrc);
}
delete fdialog;
}
void MyFrame::OnConfigSave(wxCommandEvent& WXUNUSED(event))
{
char *bochsrc;
long style = wxSAVE | wxOVERWRITE_PROMPT;
wxFileDialog *fdialog = new wxFileDialog (this, "Save configuration", "", "", "*.*", style);
if (fdialog->ShowModal() == wxID_OK) {
bochsrc = (char *)fdialog->GetPath().c_str ();
SIM->write_rc (bochsrc, 1);
}
delete fdialog;
}
void MyFrame::OnEditBoot(wxCommandEvent& WXUNUSED(event))
{
#define MAX_BOOT_DEVICES 3
int bootDevices = 0;
int dev_id[MAX_BOOT_DEVICES];
bx_param_enum_c *floppy = SIM->get_param_enum (BXP_FLOPPYA_DEVTYPE);
if (floppy->get () != BX_FLOPPY_NONE) {
dev_id[bootDevices++] = BX_BOOT_FLOPPYA;
}
bx_param_c *firsthd = SIM->get_first_hd ();
if (firsthd != NULL) {
dev_id[bootDevices++] = BX_BOOT_DISKC;
}
bx_param_c *firstcd = SIM->get_first_cdrom ();
if (firstcd != NULL) {
dev_id[bootDevices++] = BX_BOOT_CDROM;
}
if (bootDevices == 0) {
wxMessageBox( "All the possible boot devices are disabled right now!\nYou must enable the first floppy drive, a hard drive, or a CD-ROM.",
"None enabled", wxOK | wxICON_ERROR, this );
return;
}
ParamDialog dlg (this, -1);
bx_list_c *list = (bx_list_c*) SIM->get_param (BXP_BOOT);
dlg.SetTitle (list->get_name ());
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(list->get_label());
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 (BXP_PCI);
dlg.SetTitle (list->get_name ());
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 (BXP_SB16);
dlg.SetTitle (list->get_name ());
dlg.AddParam (list);
dlg.SetRuntimeFlag (sim_thread != NULL);
dlg.ShowModal ();
}
void MyFrame::OnEditTiming(wxCommandEvent& WXUNUSED(event))
{
ParamDialog dlg (this, -1);
bx_list_c *list = (bx_list_c*) SIM->get_param (BXP_CLOCK);
dlg.SetTitle (list->get_name ());
dlg.AddParam (list);
dlg.ShowModal ();
}
void MyFrame::OnEditNet(wxCommandEvent& WXUNUSED(event))
{
ParamDialog dlg (this, -1);
bx_list_c *list = (bx_list_c*) SIM->get_param (BXP_NETWORK);
dlg.SetTitle (list->get_name ());
dlg.AddParam (list);
dlg.ShowModal ();
}
void MyFrame::OnEditKeyboard(wxCommandEvent& WXUNUSED(event))
{
ParamDialog dlg(this, -1);
bx_list_c *list = (bx_list_c*) SIM->get_param (BXP_MENU_KEYBOARD);
dlg.SetTitle (list->get_name ());
dlg.AddParam (list);
dlg.SetRuntimeFlag (sim_thread != NULL);
dlg.ShowModal ();
}
void MyFrame::OnEditSerialParallel(wxCommandEvent& WXUNUSED(event))
{
ParamDialog dlg(this, -1);
bx_list_c *list = (bx_list_c*) SIM->get_param (BXP_MENU_SERIAL_PARALLEL);
dlg.SetTitle (list->get_name ());
dlg.AddParam (list);
dlg.SetRuntimeFlag (sim_thread != NULL);
dlg.ShowModal ();
}
void MyFrame::OnEditLoadHack(wxCommandEvent& WXUNUSED(event))
{
ParamDialog dlg(this, -1);
bx_list_c *list = (bx_list_c*) SIM->get_param (BXP_LOAD32BITOS);
dlg.SetTitle (list->get_name ());
dlg.AddParam (list);
dlg.ShowModal ();
}
void MyFrame::OnEditOther(wxCommandEvent& WXUNUSED(event))
{
ParamDialog dlg(this, -1);
bx_list_c *list = (bx_list_c*) SIM->get_param (BXP_MENU_MISC_2);
dlg.SetTitle (list->get_name ());
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);
bx_param_string_c *logfile = SIM->get_param_string (BXP_LOG_FILENAME);
dlg.SetLogfile (wxString (logfile->getptr ()));
bx_param_string_c *debuggerlogfile = SIM->get_param_string (BXP_DEBUGGER_LOG_FILENAME);
dlg.SetDebuggerlogfile (wxString (debuggerlogfile->getptr ()));
// 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; level<nlevel; level++) {
int mod = 0;
int first = SIM->get_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; mod<SIM->get_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) {
char buf[1024];
safeWxStrcpy (buf, dlg.GetLogfile (), sizeof (buf));
logfile->set (buf);
safeWxStrcpy (buf, dlg.GetDebuggerlogfile (), sizeof (buf));
debuggerlogfile->set (buf);
for (level=0; level<nlevel; level++) {
// ask the dialog what action the user chose for this type of event
int action = dlg.GetAction (level);
if (action != LOG_OPTS_NO_CHANGE) {
// set new default
SIM->set_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 (BXP_CPU_EAX) == NULL) {
// if params not initialized yet, then give up
wxMessageBox ("Cannot show the debugger window until the simulation has begun.",
"Sim not started", wxOK | wxICON_ERROR, this );
return;
}
if (showCpu == NULL) {
showCpu = new CpuRegistersDialog (this, -1);
#if BX_DEBUGGER
showCpu->SetTitle ("Bochs Debugger");
#else
showCpu->SetTitle ("CPU Registers");
#endif
showCpu->Init ();
} else {
showCpu->CopyParamToGui ();
}
showCpu->Show (TRUE);
}
void MyFrame::OnShowKeyboard(wxCommandEvent& WXUNUSED(event))
{
if (SIM->get_param (BXP_KBD_PARAMETERS) == NULL) {
// if params not initialized yet, then give up
wxMessageBox ("Cannot show the debugger window until the simulation has begun.",
"Sim not started", wxOK | wxICON_ERROR, this );
return;
}
if (showKbd == NULL) {
showKbd = new ParamDialog (this, -1);
showKbd->SetTitle ("Keyboard State (incomplete, this is a demo)");
showKbd->AddParam (SIM->get_param (BXP_KBD_PARAMETERS));
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 ("debugger command: %s", cmd);
wxASSERT (showDebugLog != NULL);
showDebugLog->AppendCommand (cmd);
if (debugCommand != NULL) {
// one is already waiting
wxLogDebug ("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 ("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 ("storing debugger command '%s'", tmp);
debugCommand = tmp;
}
}
#endif
void MyFrame::OnQuit(wxCommandEvent& event)
{
wxBochsClosing = true;
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 ("Waiting for simulation to stop...");
OnKillSim (event);
}
}
void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
{
wxString str;
str.Printf ("Bochs x86 Emulator version %s (wxWidgets port)", VER_STRING);
wxMessageBox( str, "About Bochs", wxOK | wxICON_INFORMATION, this );
}
// update the menu items, status bar, etc.
void MyFrame::simStatusChanged (StatusChange change, bx_bool popupNotify) {
switch (change) {
case Start: // running
wxLogStatus ("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, "&Pause");
break;
case Stop: // not running
wxLogStatus ("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, "&Pause");
// 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("Bochs simulation has stopped.", "Bochs Stopped",
wxOK | wxICON_INFORMATION, this);
break;
case Pause: // pause
wxLogStatus ("Pausing simulation");
menuSimulate->SetLabel (ID_Simulate_PauseResume, "&Resume");
break;
case Resume: // resume
wxLogStatus ("Resuming simulation");
menuSimulate->SetLabel (ID_Simulate_PauseResume, "&Pause");
break;
}
bool canConfigure = (change == Stop);
menuConfiguration->Enable (ID_Config_New, canConfigure);
menuConfiguration->Enable (ID_Config_Read, canConfigure);
// only enabled ATA channels with a cdrom connected are available at runtime
for (unsigned i=0; i<4; i++) {
if (!SIM->get_param_bool((bx_id)(BXP_ATA0_PRESENT+i))->get ()) {
menuEdit->Enable (ID_Edit_ATA0+i, canConfigure);
} else {
if ( (SIM->get_param_num((bx_id)(BXP_ATA0_MASTER_TYPE+i*2))->get () != BX_ATA_DEVICE_CDROM) &&
(SIM->get_param_num((bx_id)(BXP_ATA0_SLAVE_TYPE+i*2))->get () != BX_ATA_DEVICE_CDROM) ) {
menuEdit->Enable (ID_Edit_ATA0+i, canConfigure);
}
}
}
menuEdit->Enable( ID_Edit_Boot, canConfigure);
menuEdit->Enable( ID_Edit_Memory, canConfigure);
menuEdit->Enable( ID_Edit_PCI, canConfigure);
menuEdit->Enable( ID_Edit_Timing, canConfigure);
menuEdit->Enable( ID_Edit_Network, canConfigure);
menuEdit->Enable( ID_Edit_LoadHack, 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.
bx_param_c *param;
param = SIM->get_param(BXP_FLOPPYA);
menuEdit->Enable (ID_Edit_FD_0, canConfigure || param->get_enabled ());
param = SIM->get_param(BXP_FLOPPYB);
menuEdit->Enable (ID_Edit_FD_1, canConfigure || param->get_enabled ());
}
void MyFrame::OnStartSim(wxCommandEvent& event)
{
wxCriticalSectionLocker lock(sim_thread_lock);
if (sim_thread != NULL) {
wxMessageBox (
"Can't start Bochs simulator, because it is already running",
"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(BXP_SEL_DISPLAY_LIBRARY);
char *gui_name = gui_param->get_choice (gui_param->get ());
if (strcmp (gui_name, "wx") != 0) {
wxMessageBox (
"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.",
"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 (
"You have already started the simulator once this session. Due to memory leaks and bugs in init code, you may get unstable behavior.",
"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 ("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 ("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) {
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.
}
}
void
MyFrame::OnSimThreadExit () {
wxCriticalSectionLocker lock (sim_thread_lock);
sim_thread = NULL;
}
int
MyFrame::HandleAskParamString (bx_param_string_c *param)
{
wxLogDebug ("HandleAskParamString start");
bx_param_num_c *opt = param->get_options ();
wxASSERT (opt != NULL);
int n_opt = opt->get ();
char *msg = param->get_name ();
char *newval = NULL;
wxDialog *dialog = NULL;
if (n_opt & param->IS_FILENAME) {
// use file open dialog
long style =
(n_opt & param->SAVE_FILE_DIALOG) ? wxSAVE|wxOVERWRITE_PROMPT : wxOPEN;
wxLogDebug ("HandleAskParamString: create dialog");
wxFileDialog *fdialog = new wxFileDialog (this, msg, "", wxString(param->getptr ()), "*.*", style);
wxLogDebug ("HandleAskParamString: before showmodal");
if (fdialog->ShowModal() == wxID_OK)
newval = (char *)fdialog->GetPath().c_str ();
wxLogDebug ("HandleAskParamString: after showmodal");
dialog = fdialog; // so I can delete it
} else {
// use simple string dialog
long style = wxOK|wxCANCEL;
wxTextEntryDialog *tdialog = new wxTextEntryDialog (this, msg, "Enter new value", wxString(param->getptr ()), style);
if (tdialog->ShowModal() == wxID_OK)
newval = (char *)tdialog->GetValue().c_str ();
dialog = tdialog; // so I can delete it
}
// newval points to memory inside the dialog. As soon as dialog is deleted,
// newval points to junk. So be sure to copy the text out before deleting
// it!
if (newval && strlen(newval)>0) {
// change floppy path to this value.
wxLogDebug ("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);
default:
{
wxString msg;
msg.Printf ("ask param for parameter type %d is not implemented in wxWidgets",
param->get_type ());
wxMessageBox( msg, "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 ("received a bochs event in the GUI thread"));
BxEvent *be = (BxEvent *) event.GetEventObject ();
IFDBG_EVENT (wxLogDebug ("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 ("before HandleAskParam");
be->retcode = HandleAskParam (be);
wxLogDebug ("after HandleAskParam");
// return a copy of the event back to the sender.
sim_thread->SendSyncResponse(be);
wxLogDebug ("after SendSyncResponse");
break;
#if BX_DEBUGGER
case BX_ASYNC_EVT_DBG_MSG:
showDebugLog->AppendText (be->u.logmsg.msg);
// 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 ("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 ("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 ("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 ("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));
LogMsgAskDialog dlg (this, -1, levelName); // panic, error, etc.
#if !BX_DEBUGGER
dlg.EnableButton (dlg.DEBUG, FALSE);
#endif
dlg.SetContext (be->u.logmsg.prefix);
dlg.SetMessage (be->u.logmsg.msg);
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 ("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));
dlg.SetCapacityChoices (n_floppy_type_names, floppy_type_names);
bx_list_c *list = (bx_list_c*) SIM->get_param ((drive==0)? BXP_FLOPPYA : BXP_FLOPPYB);
if (!list) { wxLogError ("floppy object param is null"); return; }
bx_param_filename_c *fname = (bx_param_filename_c*) list->get(0);
bx_param_enum_c *disktype = (bx_param_enum_c *) list->get(1);
bx_param_enum_c *status = (bx_param_enum_c *) list->get(2);
if (fname->get_type () != BXT_PARAM_STRING
|| disktype->get_type () != BXT_PARAM_ENUM
|| status->get_type() != BXT_PARAM_ENUM) {
wxLogError ("floppy params have wrong type");
return;
}
if (sim_thread == NULL) {
dlg.AddRadio ("Not Present", "");
}
dlg.AddRadio ("Ejected", "none");
#if defined(__linux__)
dlg.AddRadio ("Physical floppy drive /dev/fd0", "/dev/fd0");
dlg.AddRadio ("Physical floppy drive /dev/fd1", "/dev/fd1");
#elif defined(WIN32)
dlg.AddRadio ("Physical floppy drive A:", "A:");
dlg.AddRadio ("Physical floppy drive B:", "B:");
#else
// add your favorite operating system here
#endif
dlg.SetCapacity (disktype->get () - disktype->get_min ());
dlg.SetFilename (fname->getptr ());
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 ("floppy config returned %d", n);
if (n==wxID_OK) {
char filename[1024];
wxString fn (dlg.GetFilename ());
strncpy (filename, fn.c_str (), sizeof(filename));
wxLogMessage ("filename is '%s'", filename);
wxLogMessage ("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 ("No CDROM drive is enabled. Use Edit:ATA to set one up.",
"No CDROM", wxOK | wxICON_ERROR, this );
return;
}
ParamDialog dlg (this, -1);
dlg.SetTitle ("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;
ParamDialog dlg (this, -1);
wxString str;
str.Printf ("Configure ATA%d", channel);
dlg.SetTitle (str);
dlg.SetRuntimeFlag (sim_thread != NULL);
dlg.AddParam (SIM->get_param ((bx_id)(BXP_ATA0_MENU+channel)));
dlg.ShowModal ();
}
void MyFrame::OnToolbarClick(wxCommandEvent& event)
{
wxLogDebug ("clicked toolbar thingy");
bx_toolbar_buttons which = BX_TOOLBAR_UNDEFINED;
int id = event.GetId ();
switch (id) {
case ID_Toolbar_Power:which = BX_TOOLBAR_POWER; break;
case ID_Toolbar_Reset: which = BX_TOOLBAR_RESET; break;
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 ("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 ("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 ("in SimThread, SIM->begin_simulation() exited normally");
} else {
wxLogDebug ("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 ("SimThread::Entry: get gui mutex");
wxMutexGuiEnter();
if (!wxBochsClosing) {
wxLogDebug ("SimThread::Entry: sim thread ending. call simStatusChanged");
theFrame->simStatusChanged (theFrame->Stop, true);
} else {
wxLogMessage ("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 ("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 ("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 ("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 ("breaking out of sync event wait because gui is closing");
event->retcode = -1;
return event;
}
}
wxASSERT (response != NULL);
return response;
} else {
wxLogDebug ("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 ("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 ("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.c_str (), destlen);
dest[destlen-1] = 0;
}
#endif /* if BX_WITH_WX */