]> de.git.xonotic.org Git - xonotic/netradiant.git/blobdiff - radiant/watchbsp.cpp
fix invalid use of <glib/...> headers
[xonotic/netradiant.git] / radiant / watchbsp.cpp
index 29ce371e36353e60b366991f544212f662b865fb..f3d1ae6f77438bf431f3acaead01255488a17192 100644 (file)
 // DESCRIPTION:
 // monitoring window for running BSP processes (and possibly various other stuff)
 
-#include "stdafx.h"
 #include "watchbsp.h"
+
+#include <algorithm>
+#include <gtk/gtkmain.h>
+
+#include "cmdlib.h"
+#include "convert.h"
+#include "string/string.h"
+#include "stream/stringstream.h"
+
+#include "gtkutil/messagebox.h"
+#include "xmlstuff.h"
+#include "console.h"
+#include "preferences.h"
+#include "points.h"
 #include "feedback.h"
+#include "mainframe.h"
+#include "sockets.h"
 
-#ifdef _WIN32
-#include <winsock2.h>
-#endif
+void message_flush( message_info_t* self ){
+       Sys_Print( self->msg_level, self->m_buffer, self->m_length );
+       self->m_length = 0;
+}
 
-#if defined ( __linux__ ) || defined ( __APPLE__ )
-#include <sys/time.h>
-#define SOCKET_ERROR -1
-#endif
+void message_print( message_info_t* self, const char* characters, std::size_t length ){
+       const char* end = characters + length;
+       while ( characters != end )
+       {
+               std::size_t space = message_info_t::bufsize - 1 - self->m_length;
+               if ( space == 0 ) {
+                       message_flush( self );
+               }
+               else
+               {
+                       std::size_t size = std::min( space, std::size_t( end - characters ) );
+                       memcpy( self->m_buffer + self->m_length, characters, size );
+                       self->m_length += size;
+                       characters += size;
+               }
+       }
+}
 
-#ifdef __APPLE__
-#include <unistd.h>
-#endif
 
-#include <assert.h>
+#include <glib.h>
+#include "xmlstuff.h"
+
+class CWatchBSP
+{
+private:
+// a flag we have set to true when using an external BSP plugin
+// the resulting code with that is a bit dirty, cleaner solution would be to seperate the succession of commands from the listening loop
+// (in two seperate classes probably)
+bool m_bBSPPlugin;
+
+// EIdle: we are not listening
+//   DoMonitoringLoop will change state to EBeginStep
+// EBeginStep: the socket is up for listening, we are expecting incoming connection
+//   incoming connection will change state to EWatching
+// EWatching: we have a connection, monitor it
+//   connection closed will see if we start a new step (EBeginStep) or launch Quake3 and end (EIdle)
+enum EWatchBSPState { EIdle, EBeginStep, EWatching } m_eState;
+socket_t *m_pListenSocket;
+socket_t *m_pInSocket;
+netmessage_t msg;
+GPtrArray *m_pCmd;
+// used to timeout EBeginStep
+GTimer    *m_pTimer;
+std::size_t m_iCurrentStep;
+// name of the map so we can run the engine
+char    *m_sBSPName;
+// buffer we use in push mode to receive data directly from the network
+xmlParserInputBufferPtr m_xmlInputBuffer;
+xmlParserInputPtr m_xmlInput;
+xmlParserCtxtPtr m_xmlParserCtxt;
+// call this to switch the set listening mode
+bool SetupListening();
+// start a new EBeginStep
+void DoEBeginStep();
+// the xml and sax parser state
+char m_xmlBuf[MAX_NETMESSAGE];
+bool m_bNeedCtxtInit;
+message_info_t m_message_info;
+
+public:
+CWatchBSP(){
+       m_pCmd = 0;
+       m_bBSPPlugin = false;
+       m_pListenSocket = NULL;
+       m_pInSocket = NULL;
+       m_eState = EIdle;
+       m_pTimer = g_timer_new();
+       m_sBSPName = NULL;
+       m_xmlInputBuffer = NULL;
+       m_bNeedCtxtInit = true;
+}
+virtual ~CWatchBSP(){
+       EndMonitoringLoop();
+       Net_Shutdown();
+
+       g_timer_destroy( m_pTimer );
+}
+
+bool HasBSPPlugin() const
+{ return m_bBSPPlugin; }
+
+// called regularly to keep listening
+void RoutineProcessing();
+// start a monitoring loop with the following steps
+void DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName );
+void EndMonitoringLoop(){
+       Reset();
+       if ( m_sBSPName ) {
+               string_release( m_sBSPName, string_length( m_sBSPName ) );
+               m_sBSPName = 0;
+       }
+       if ( m_pCmd ) {
+               g_ptr_array_free( m_pCmd, TRUE );
+               m_pCmd = 0;
+       }
+}
+// close everything - may be called from the outside to abort the process
+void Reset();
+// start a listening loop for an external process, possibly a BSP plugin
+void ExternalListen();
+};
+
+CWatchBSP* g_pWatchBSP;
+
+// watch the BSP process through network connections
+// true: trigger the BSP steps one by one and monitor them through the network
+// false: create a BAT / .sh file and execute it. don't bother monitoring it.
+bool g_WatchBSP_Enabled = true;
+// do we stop the compilation process if we come accross a leak?
+bool g_WatchBSP_LeakStop = true;
+bool g_WatchBSP_RunQuake = false;
+// store prefs setting for automatic sleep mode activation
+bool g_WatchBSP_DoSleep = true;
+// timeout when beginning a step (in seconds)
+// if we don't get a connection quick enough we assume something failed and go back to idling
+int g_WatchBSP_Timeout = 10;
+
+
+void Build_constructPreferences( PreferencesPage& page ){
+       GtkWidget* monitorbsp = page.appendCheckBox( "", "Enable Build Process Monitoring", g_WatchBSP_Enabled );
+       GtkWidget* leakstop = page.appendCheckBox( "", "Stop Compilation on Leak", g_WatchBSP_LeakStop );
+       GtkWidget* runengine = page.appendCheckBox( "", "Run Engine After Compile", g_WatchBSP_RunQuake );
+       GtkWidget* sleep = page.appendCheckBox ( "", "Sleep When Running the Engine", g_WatchBSP_DoSleep );
+       Widget_connectToggleDependency( leakstop, monitorbsp );
+       Widget_connectToggleDependency( runengine, monitorbsp );
+       Widget_connectToggleDependency( sleep, runengine );
+}
+void Build_constructPage( PreferenceGroup& group ){
+       PreferencesPage page( group.createPage( "Build", "Build Preferences" ) );
+       Build_constructPreferences( page );
+}
+void Build_registerPreferencesPage(){
+       PreferencesDialog_addSettingsPage( FreeCaller1<PreferenceGroup&, Build_constructPage>() );
+}
+
+#include "preferencesystem.h"
+#include "stringio.h"
+
+void BuildMonitor_Construct(){
+       g_pWatchBSP = new CWatchBSP();
+
+       g_WatchBSP_Enabled = !string_equal( g_pGameDescription->getKeyValue( "no_bsp_monitor" ), "1" );
+
+       GlobalPreferenceSystem().registerPreference( "WatchBSP", BoolImportStringCaller( g_WatchBSP_Enabled ), BoolExportStringCaller( g_WatchBSP_Enabled ) );
+       GlobalPreferenceSystem().registerPreference( "RunQuake2Run", BoolImportStringCaller( g_WatchBSP_RunQuake ), BoolExportStringCaller( g_WatchBSP_RunQuake ) );
+       GlobalPreferenceSystem().registerPreference( "LeakStop", BoolImportStringCaller( g_WatchBSP_LeakStop ), BoolExportStringCaller( g_WatchBSP_LeakStop ) );
+       GlobalPreferenceSystem().registerPreference( "SleepMode", BoolImportStringCaller( g_WatchBSP_DoSleep ), BoolExportStringCaller( g_WatchBSP_DoSleep ) );
+
+       Build_registerPreferencesPage();
+}
+
+void BuildMonitor_Destroy(){
+       delete g_pWatchBSP;
+}
+
+CWatchBSP *GetWatchBSP(){
+       return g_pWatchBSP;
+}
+
+void BuildMonitor_Run( GPtrArray* commands, const char* mapName ){
+       GetWatchBSP()->DoMonitoringLoop( commands, mapName );
+}
+
 
 // Static functions for the SAX callbacks -------------------------------------------------------
 
 // utility for saxStartElement below
 static void abortStream( message_info_t *data ){
-       g_pParentWnd->GetWatchBSP()->Reset();
+       GetWatchBSP()->EndMonitoringLoop();
        // tell there has been an error
-       if ( g_pParentWnd->GetWatchBSP()->HasBSPPlugin() ) {
+#if 0
+       if ( GetWatchBSP()->HasBSPPlugin() ) {
                g_BSPFrontendTable.m_pfnEndListen( 2 );
        }
+#endif
        // yeah this doesn't look good.. but it's needed so that everything will be ignored until the stream goes out
        data->ignore_depth = -1;
        data->recurse++;
@@ -69,62 +240,79 @@ static void abortStream( message_info_t *data ){
 #include "stream_version.h"
 
 static void saxStartElement( message_info_t *data, const xmlChar *name, const xmlChar **attrs ){
+#if 0
+       globalOutputStream() << "<" << name;
+       if ( attrs != 0 ) {
+               for ( const xmlChar** p = attrs; *p != 0; p += 2 )
+               {
+                       globalOutputStream() << " " << p[0] << "=" << makeQuoted( p[1] );
+               }
+       }
+       globalOutputStream() << ">\n";
+#endif
+
        if ( data->ignore_depth == 0 ) {
-               if ( data->bGeometry ) {
+               if ( data->pGeometry != 0 ) {
                        // we have a handler
                        data->pGeometry->saxStartElement( data, name, attrs );
                }
                else
                {
-                       if ( strcmp( (char *)name, "q3map_feedback" ) == 0 ) {
+                       if ( strcmp( reinterpret_cast<const char*>( name ), "q3map_feedback" ) == 0 ) {
                                // check the correct version
                                // old q3map don't send a version attribute
                                // the ones we support .. send Q3MAP_STREAM_VERSION
-                               if ( !attrs[0] || !attrs[1] || ( strcmp( (char*)attrs[0],"version" ) != 0 ) ) {
-                                       Sys_FPrintf( SYS_ERR, "No stream version given in the feedback stream, this is an old q3map version.\n"
-                                                                                 "Please turn off monitored compiling if you still wish to use this q3map executable\n" );
+                               if ( !attrs[0] || !attrs[1] || ( strcmp( reinterpret_cast<const char*>( attrs[0] ), "version" ) != 0 ) ) {
+                                       message_flush( data );
+                                       globalErrorStream() << "No stream version given in the feedback stream, this is an old q3map version.\n"
+                                                                                  "Please turn off monitored compiling if you still wish to use this q3map executable\n";
                                        abortStream( data );
                                        return;
                                }
-                               else if ( strcmp( (char*)attrs[1],Q3MAP_STREAM_VERSION ) != 0 ) {
-                                       Sys_FPrintf( SYS_ERR,
-                                                                "This version of Radiant reads version %s debug streams, I got an incoming connection with version %s\n"
-                                                                "Please make sure your versions of Radiant and q3map are matching.\n", Q3MAP_STREAM_VERSION, (char*)attrs[1] );
+                               else if ( strcmp( reinterpret_cast<const char*>( attrs[1] ), Q3MAP_STREAM_VERSION ) != 0 ) {
+                                       message_flush( data );
+                                       globalErrorStream() <<
+                                       "This version of Radiant reads version " Q3MAP_STREAM_VERSION " debug streams, I got an incoming connection with version " << reinterpret_cast<const char*>( attrs[1] ) << "\n"
+                                                                                                                                                                                                                                                                                                                                                                                          "Please make sure your versions of Radiant and q3map are matching.\n";
                                        abortStream( data );
                                        return;
                                }
                        }
                        // we don't treat locally
-                       else if ( strcmp( (char *)name, "message" ) == 0 ) {
-                               data->msg_level = atoi( (char *)attrs[1] );
+                       else if ( strcmp( reinterpret_cast<const char*>( name ), "message" ) == 0 ) {
+                               int msg_level = atoi( reinterpret_cast<const char*>( attrs[1] ) );
+                               if ( msg_level != data->msg_level ) {
+                                       message_flush( data );
+                                       data->msg_level = msg_level;
+                               }
                        }
-                       else if ( strcmp( (char *)name, "polyline" ) == 0 ) {
+                       else if ( strcmp( reinterpret_cast<const char*>( name ), "polyline" ) == 0 ) {
                                // polyline has a particular status .. right now we only use it for leakfile ..
-                               data->bGeometry = true;
+                               data->geometry_depth = data->recurse;
                                data->pGeometry = &g_pointfile;
                                data->pGeometry->saxStartElement( data, name, attrs );
                        }
-                       else if ( strcmp( (char *)name, "select" ) == 0 ) {
+                       else if ( strcmp( reinterpret_cast<const char*>( name ), "select" ) == 0 ) {
                                CSelectMsg *pSelect = new CSelectMsg();
-                               data->bGeometry = true;
+                               data->geometry_depth = data->recurse;
                                data->pGeometry = pSelect;
                                data->pGeometry->saxStartElement( data, name, attrs );
                        }
-                       else if ( strcmp( (char *)name, "pointmsg" ) == 0 ) {
+                       else if ( strcmp( reinterpret_cast<const char*>( name ), "pointmsg" ) == 0 ) {
                                CPointMsg *pPoint = new CPointMsg();
-                               data->bGeometry = true;
+                               data->geometry_depth = data->recurse;
                                data->pGeometry = pPoint;
                                data->pGeometry->saxStartElement( data, name, attrs );
                        }
-                       else if ( strcmp( (char *)name, "windingmsg" ) == 0 ) {
+                       else if ( strcmp( reinterpret_cast<const char*>( name ), "windingmsg" ) == 0 ) {
                                CWindingMsg *pWinding = new CWindingMsg();
-                               data->bGeometry = true;
+                               data->geometry_depth = data->recurse;
                                data->pGeometry = pWinding;
                                data->pGeometry->saxStartElement( data, name, attrs );
                        }
                        else
                        {
-                               Sys_FPrintf( SYS_WRN, "WARNING: ignoring unrecognized node in XML stream (%s)\n", name );
+                               globalErrorStream() << "Warning: ignoring unrecognized node in XML stream (" << reinterpret_cast<const char*>( name ) << ")\n";
                                // we don't recognize this node, jump over it
                                // (NOTE: the ignore mechanism is a bit screwed, only works when starting an ignore at the highest level)
                                data->ignore_depth = data->recurse;
@@ -134,57 +322,80 @@ static void saxStartElement( message_info_t *data, const xmlChar *name, const xm
        data->recurse++;
 }
 
-static void saxEndElement( message_info_t *data, const xmlChar *name ) {
+static void saxEndElement( message_info_t *data, const xmlChar *name ){
+#if 0
+       globalOutputStream() << "<" << name << "/>\n";
+#endif
+
        data->recurse--;
        // we are out of an ignored chunk
        if ( data->recurse == data->ignore_depth ) {
                data->ignore_depth = 0;
                return;
        }
-       if ( data->bGeometry ) {
+       if ( data->pGeometry != 0 ) {
                data->pGeometry->saxEndElement( data, name );
                // we add the object to the debug window
-               if ( !data->bGeometry ) {
+               if ( data->geometry_depth == data->recurse ) {
                        g_DbgDlg.Push( data->pGeometry );
+                       data->pGeometry = 0;
                }
        }
        if ( data->recurse == data->stop_depth ) {
+               message_flush( data );
 #ifdef _DEBUG
-               Sys_Printf( "Received error msg .. shutting down..\n" );
+               globalOutputStream() << "Received error msg .. shutting down..\n";
 #endif
-               g_pParentWnd->GetWatchBSP()->Reset();
+               GetWatchBSP()->EndMonitoringLoop();
                // tell there has been an error
-               if ( g_pParentWnd->GetWatchBSP()->HasBSPPlugin() ) {
+#if 0
+               if ( GetWatchBSP()->HasBSPPlugin() ) {
                        g_BSPFrontendTable.m_pfnEndListen( 2 );
                }
+#endif
                return;
        }
 }
 
-static void saxCharacters( message_info_t *data, const xmlChar *ch, int len ){
-       if ( data->bGeometry ) {
-               data->pGeometry->saxCharacters( data, ch, len );
+class MessageOutputStream : public TextOutputStream
+{
+message_info_t* m_data;
+public:
+MessageOutputStream( message_info_t* data ) : m_data( data ){
+}
+std::size_t write( const char* buffer, std::size_t length ){
+       if ( m_data->pGeometry != 0 ) {
+               m_data->pGeometry->saxCharacters( m_data, reinterpret_cast<const xmlChar*>( buffer ), int(length) );
        }
        else
        {
-               if ( data->ignore_depth != 0 ) {
-                       return;
-               }
-               // output the message using the level
-               char buf[1024];
-               memcpy( buf, ch, len );
-               buf[len] = '\0';
-               Sys_FPrintf( data->msg_level, "%s", buf );
-               // if this message has error level flag, we mark the depth to stop the compilation when we get out
-               // we don't set the msg level if we don't stop on leak
-               if ( data->msg_level == 3 ) {
-                       data->stop_depth = data->recurse - 1;
+               if ( m_data->ignore_depth == 0 ) {
+                       // output the message using the level
+                       message_print( m_data, buffer, length );
+                       // if this message has error level flag, we mark the depth to stop the compilation when we get out
+                       // we don't set the msg level if we don't stop on leak
+                       if ( m_data->msg_level == 3 ) {
+                               m_data->stop_depth = m_data->recurse - 1;
+                       }
                }
        }
+
+       return length;
+}
+};
+
+template<typename T>
+inline MessageOutputStream& operator<<( MessageOutputStream& ostream, const T& t ){
+       return ostream_write( ostream, t );
+}
+
+static void saxCharacters( message_info_t *data, const xmlChar *ch, int len ){
+       MessageOutputStream ostream( data );
+       ostream << StringRange( reinterpret_cast<const char*>( ch ), reinterpret_cast<const char*>( ch + len ) );
 }
 
 static void saxComment( void *ctx, const xmlChar *msg ){
-       Sys_Printf( "XML comment: %s\n", msg );
+       globalOutputStream() << "XML comment: " << reinterpret_cast<const char*>( msg ) << "\n";
 }
 
 static void saxWarning( void *ctx, const char *msg, ... ){
@@ -194,7 +405,7 @@ static void saxWarning( void *ctx, const char *msg, ... ){
        va_start( args, msg );
        vsprintf( saxMsgBuffer, msg, args );
        va_end( args );
-       Sys_FPrintf( SYS_WRN, "XML warning: %s\n", saxMsgBuffer );
+       globalOutputStream() << "XML warning: " << saxMsgBuffer << "\n";
 }
 
 static void saxError( void *ctx, const char *msg, ... ){
@@ -204,7 +415,7 @@ static void saxError( void *ctx, const char *msg, ... ){
        va_start( args, msg );
        vsprintf( saxMsgBuffer, msg, args );
        va_end( args );
-       Sys_FPrintf( SYS_ERR, "XML error: %s\n", saxMsgBuffer );
+       globalErrorStream() << "XML error: " << saxMsgBuffer << "\n";
 }
 
 static void saxFatal( void *ctx, const char *msg, ... ){
@@ -215,7 +426,7 @@ static void saxFatal( void *ctx, const char *msg, ... ){
        va_start( args, msg );
        vsprintf( buffer, msg, args );
        va_end( args );
-       Sys_FPrintf( SYS_ERR, "XML fatal error: %s\n", buffer );
+       globalErrorStream() << "XML fatal error: " << buffer << "\n";
 }
 
 static xmlSAXHandler saxParser = {
@@ -243,17 +454,23 @@ static xmlSAXHandler saxParser = {
        (warningSAXFunc)saxWarning, /* warning */
        (errorSAXFunc)saxError, /* error */
        (fatalErrorSAXFunc)saxFatal, /* fatalError */
+       0,
+       0,
+       0,
+       0,
+       0,
+       0,
+       0,
+       0
 };
 
 // ------------------------------------------------------------------------------------------------
 
-CWatchBSP::~CWatchBSP(){
-       Reset();
-       if ( m_sBSPName ) {
-               delete[] m_sBSPName;
-               m_sBSPName = NULL;
-       }
-       Net_Shutdown();
+
+guint s_routine_id;
+static gint watchbsp_routine( gpointer data ){
+       reinterpret_cast<CWatchBSP*>( data )->RoutineProcessing();
+       return TRUE;
 }
 
 void CWatchBSP::Reset(){
@@ -270,36 +487,34 @@ void CWatchBSP::Reset(){
                m_xmlInputBuffer = NULL;
        }
        m_eState = EIdle;
+       if ( s_routine_id ) {
+               gtk_timeout_remove( s_routine_id );
+       }
 }
 
 bool CWatchBSP::SetupListening(){
 #ifdef _DEBUG
        if ( m_pListenSocket ) {
-               Sys_Printf( "ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n" );
+               globalOutputStream() << "ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n";
                return false;
        }
 #endif
-       Sys_Printf( "Setting up\n" );
-       if ( !Net_Setup() ) {
-               return false;
-       }
-
+       globalOutputStream() << "Setting up\n";
+       Net_Setup();
        m_pListenSocket = Net_ListenSocket( 39000 );
        if ( m_pListenSocket == NULL ) {
                return false;
        }
-
-       Sys_Printf( "Listening...\n" );
+       globalOutputStream() << "Listening...\n";
        return true;
 }
 
-void CWatchBSP::DoEBeginStep() {
+void CWatchBSP::DoEBeginStep(){
        Reset();
-       if ( !SetupListening() ) {
-               CString msg;
-               msg = "Failed to get a listening socket on port 39000.\nTry running with BSP monitoring disabled if you can't fix this.\n";
-               Sys_Printf( msg );
-               gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
+       if ( SetupListening() == false ) {
+               const char* msg = "Failed to get a listening socket on port 39000.\nTry running with Build monitoring disabled if you can't fix this.\n";
+               globalOutputStream() << msg;
+               gtk_MessageBox( GTK_WIDGET( MainFrame_getWindow() ), msg, "Build monitoring", eMB_OK, eMB_ICONERROR );
                return;
        }
        // set the timer for timeouts and step cancellation
@@ -307,15 +522,16 @@ void CWatchBSP::DoEBeginStep() {
        g_timer_start( m_pTimer );
 
        if ( !m_bBSPPlugin ) {
-               Sys_Printf( "=== running BSP command ===\n%s\n", g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
-
-               if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true ) ) {
-                       CString msg;
-                       msg = "Failed to execute the following command: ";
-                       msg += (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
-                       msg += "\nCheck that the file exists and that you don't run out of system resources.\n";
-                       Sys_Printf( msg );
-                       gtk_MessageBox( g_pParentWnd->m_pWidget,  msg, "BSP monitoring", MB_OK | MB_ICONERROR );
+               globalOutputStream() << "=== running build command ===\n"
+                                                        << static_cast<const char*>( g_ptr_array_index( m_pCmd, m_iCurrentStep ) ) << "\n";
+
+               if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true, false ) ) {
+                       StringOutputStream msg( 256 );
+                       msg << "Failed to execute the following command: ";
+                       msg << reinterpret_cast<const char*>( g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
+                       msg << "\nCheck that the file exists and that you don't run out of system resources.\n";
+                       globalOutputStream() << msg.c_str();
+                       gtk_MessageBox( GTK_WIDGET( MainFrame_getWindow() ), msg.c_str(), "Build monitoring", eMB_OK, eMB_ICONERROR );
                        return;
                }
                // re-initialise the debug window
@@ -324,83 +540,123 @@ void CWatchBSP::DoEBeginStep() {
                }
        }
        m_eState = EBeginStep;
+       s_routine_id = gtk_timeout_add( 25, watchbsp_routine, this );
 }
 
-void CWatchBSP::RoutineProcessing(){
-       // used for select()
-#ifdef _WIN32
-       TIMEVAL tout = { 0, 0 };
-#endif
-#if defined ( __linux__ ) || defined ( __APPLE__ )
-       timeval tout;
-       tout.tv_sec = 0;
-       tout.tv_usec = 0;
+
+#if defined( WIN32 )
+#define ENGINE_ATTRIBUTE "engine_win32"
+#define MP_ENGINE_ATTRIBUTE "mp_engine_win32"
+#elif defined( __linux__ ) || defined ( __FreeBSD__ )
+#define ENGINE_ATTRIBUTE "engine_linux"
+#define MP_ENGINE_ATTRIBUTE "mp_engine_linux"
+#elif defined( __APPLE__ )
+#define ENGINE_ATTRIBUTE "engine_macos"
+#define MP_ENGINE_ATTRIBUTE "mp_engine_macos"
+#else
+#error "unsupported platform"
 #endif
 
+class RunEngineConfiguration
+{
+public:
+const char* executable;
+const char* mp_executable;
+bool do_sp_mp;
+
+RunEngineConfiguration() :
+       executable( g_pGameDescription->getRequiredKeyValue( ENGINE_ATTRIBUTE ) ),
+       mp_executable( g_pGameDescription->getKeyValue( MP_ENGINE_ATTRIBUTE ) ){
+       do_sp_mp = !string_empty( mp_executable );
+}
+};
+
+inline void GlobalGameDescription_string_write_mapparameter( StringOutputStream& string, const char* mapname ){
+       if ( g_pGameDescription->mGameType == "q2"
+                || g_pGameDescription->mGameType == "heretic2" ) {
+               string << ". +exec radiant.cfg +map " << mapname;
+       }
+       else
+       {
+               string << "+set sv_pure 0 ";
+               // TTimo: a check for vm_* but that's all fine
+               //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
+               const char* fs_game = gamename_get();
+               if ( !string_equal( fs_game, basegame_get() ) ) {
+                       string << "+set fs_game " << fs_game << " ";
+               }
+               if ( g_pGameDescription->mGameType == "wolf" ) {
+                       //|| g_pGameDescription->mGameType == "et")
+                       if ( string_equal( gamemode_get(), "mp" ) ) {
+                               // MP
+                               string << "+devmap " << mapname;
+                       }
+                       else
+                       {
+                               // SP
+                               string << "+set nextmap \"spdevmap " << mapname << "\"";
+                       }
+               }
+               else
+               {
+                       string << "+devmap " << mapname;
+               }
+       }
+}
+
+
+void CWatchBSP::RoutineProcessing(){
        switch ( m_eState )
        {
        case EBeginStep:
                // timeout: if we don't get an incoming connection fast enough, go back to idle
-               if ( g_timer_elapsed( m_pTimer, NULL ) > g_PrefsDlg.m_iTimeout ) {
-                       gtk_MessageBox( g_pParentWnd->m_pWidget,  "The connection timed out, assuming the BSP process failed\nMake sure you are using a networked version of Q3Map?\nOtherwise you need to disable BSP Monitoring in prefs.", "BSP process monitoring", MB_OK );
-                       Reset();
+               if ( g_timer_elapsed( m_pTimer, NULL ) > g_WatchBSP_Timeout ) {
+                       gtk_MessageBox( GTK_WIDGET( MainFrame_getWindow() ),  "The connection timed out, assuming the build process failed\nMake sure you are using a networked version of Q3Map?\nOtherwise you need to disable BSP Monitoring in prefs.", "BSP process monitoring", eMB_OK );
+                       EndMonitoringLoop();
+#if 0
                        if ( m_bBSPPlugin ) {
                                // status == 1 : didn't get the connection
                                g_BSPFrontendTable.m_pfnEndListen( 1 );
                        }
+#endif
                        return;
                }
 #ifdef _DEBUG
                // some debug checks
                if ( !m_pListenSocket ) {
-                       Sys_Printf( "ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n" );
+                       globalErrorStream() << "ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n";
                        return;
                }
 #endif
                // we are not connected yet, accept any incoming connection
                m_pInSocket = Net_Accept( m_pListenSocket );
                if ( m_pInSocket ) {
-                       Sys_Printf( "Connected.\n" );
+                       globalOutputStream() << "Connected.\n";
                        // prepare the message info struct for diving in
-                       memset( &m_message_info, 0, sizeof( message_info_s ) );
+                       memset( &m_message_info, 0, sizeof( message_info_t ) );
                        // a dumb flag to make sure we init the push parser context when first getting a msg
                        m_bNeedCtxtInit = true;
                        m_eState = EWatching;
                }
                break;
        case EWatching:
+       {
 #ifdef _DEBUG
                // some debug checks
                if ( !m_pInSocket ) {
-                       Sys_Printf( "ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n" );
+                       globalErrorStream() << "ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n";
                        return;
                }
 #endif
-               // select() will identify if the socket needs an update
-               // if the socket is identified that means there's either a message or the connection has been closed/reset/terminated
-               fd_set readfds;
-               int ret;
-               FD_ZERO( &readfds );
-               FD_SET( ( (unsigned int)m_pInSocket->socket ), &readfds );
-               // from select man page:
-               // n is the highest-numbered descriptor in any of the three sets, plus 1
-               // (no use on windows)
-               ret = select( m_pInSocket->socket + 1, &readfds, NULL, NULL, &tout );
-               if ( ret == SOCKET_ERROR ) {
-                       Sys_Printf( "WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n" );
-                       Sys_Printf( "Terminating the connection.\n" );
-                       Reset();
-                       return;
-               }
-#ifdef _DEBUG
+
+               int ret = Net_Wait( m_pInSocket, 0, 0 );
                if ( ret == -1 ) {
-                       // we are non-blocking?? we should never get timeout errors
-                       Sys_Printf( "WARNING: unexpected timeout expired in CWatchBSP::Processing\n" );
-                       Sys_Printf( "Terminating the connection.\n" );
-                       Reset();
+                       globalOutputStream() << "WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n";
+                       globalOutputStream() << "Terminating the connection.\n";
+                       EndMonitoringLoop();
                        return;
                }
-#endif
+
                if ( ret == 1 ) {
                        // the socket has been identified, there's something (message or disconnection)
                        // see if there's anything in input
@@ -410,31 +666,35 @@ void CWatchBSP::RoutineProcessing(){
                                strcpy( m_xmlBuf, NMSG_ReadString( &msg ) );
                                if ( m_bNeedCtxtInit ) {
                                        m_xmlParserCtxt = NULL;
-                                       m_xmlParserCtxt = xmlCreatePushParserCtxt( &saxParser, &m_message_info, m_xmlBuf, strlen( m_xmlBuf ), NULL );
+                                       m_xmlParserCtxt = xmlCreatePushParserCtxt( &saxParser, &m_message_info, m_xmlBuf, static_cast<int>( strlen( m_xmlBuf ) ), NULL );
+
                                        if ( m_xmlParserCtxt == NULL ) {
-                                               Sys_FPrintf( SYS_ERR, "Failed to create the XML parser (incoming stream began with: %s)\n", m_xmlBuf );
-                                               Reset();
+                                               globalErrorStream() << "Failed to create the XML parser (incoming stream began with: " << m_xmlBuf << ")\n";
+                                               EndMonitoringLoop();
                                        }
                                        m_bNeedCtxtInit = false;
                                }
                                else
                                {
-                                       xmlParseChunk( m_xmlParserCtxt, m_xmlBuf, strlen( m_xmlBuf ), 0 );
+                                       xmlParseChunk( m_xmlParserCtxt, m_xmlBuf, static_cast<int>( strlen( m_xmlBuf ) ), 0 );
                                }
                        }
                        else
                        {
+                               message_flush( &m_message_info );
                                // error or connection closed/reset
                                // NOTE: if we get an error down the XML stream we don't reach here
                                Net_Disconnect( m_pInSocket );
                                m_pInSocket = NULL;
-                               Sys_Printf( "Connection closed.\n" );
+                               globalOutputStream() << "Connection closed.\n";
+#if 0
                                if ( m_bBSPPlugin ) {
-                                       Reset();
+                                       EndMonitoringLoop();
                                        // let the BSP plugin know that the job is done
                                        g_BSPFrontendTable.m_pfnEndListen( 0 );
                                        return;
                                }
+#endif
                                // move to next step or finish
                                m_iCurrentStep++;
                                if ( m_iCurrentStep < m_pCmd->len ) {
@@ -442,114 +702,83 @@ void CWatchBSP::RoutineProcessing(){
                                }
                                else
                                {
-                                       // release the GPtrArray and the strings
-                                       if ( m_pCmd != NULL ) {
-                                               for ( m_iCurrentStep = 0; m_iCurrentStep < m_pCmd->len; m_iCurrentStep++ )
-                                               {
-                                                       delete[] (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
-                                               }
-                                               g_ptr_array_free( m_pCmd, false );
-                                       }
-                                       m_pCmd = NULL;
                                        // launch the engine .. OMG
-                                       if ( g_PrefsDlg.m_bRunQuake ) {
+                                       if ( g_WatchBSP_RunQuake ) {
+#if 0
                                                // do we enter sleep mode before?
-                                               if ( g_PrefsDlg.m_bDoSleep ) {
-                                                       Sys_Printf( "Going into sleep mode..\n" );
+                                               if ( g_WatchBSP_DoSleep ) {
+                                                       globalOutputStream() << "Going into sleep mode..\n";
                                                        g_pParentWnd->OnSleep();
                                                }
-                                               Sys_Printf( "Running engine...\n" );
-                                               Str cmd;
+#endif
+                                               globalOutputStream() << "Running engine...\n";
+                                               StringOutputStream cmd( 256 );
                                                // build the command line
-                                               cmd = g_pGameDescription->mEnginePath.GetBuffer();
+                                               cmd << EnginePath_get();
                                                // this is game dependant
-                                               if ( !strcmp( ValueForKey( g_qeglobals.d_project_entity, "gamemode" ),"mp" ) ) {
-                                                       // MP
-                                                       cmd += g_pGameDescription->mMultiplayerEngine.GetBuffer();
-                                               }
-                                               else
-                                               {
-                                                       // SP
-                                                       cmd += g_pGameDescription->mEngine.GetBuffer();
-                                               }
-#ifdef _WIN32
-                                               // NOTE: we are using unix pathnames and CreateProcess doesn't like / in the program path
-                                               // FIXME: This isn't true anymore, doesn't it?
-                                               FindReplace( cmd, "/", "\\" );
-#endif
-                                               Str cmdline;
-                                               if ( g_pGameDescription->quake2 ) {
-                                                       cmdline = ". +exec radiant.cfg +map ";
-                                                       cmdline += m_sBSPName;
-                                               }
-                                               else
-                                               {
-                                                       cmdline = "+set sv_pure 0 ";
-                                                       // TTimo: a check for vm_* but that's all fine
-                                                       //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
-                                                       if ( *ValueForKey( g_qeglobals.d_project_entity, "gamename" ) != '\0' ) {
-                                                               cmdline += "+set fs_game ";
-                                                               cmdline += ValueForKey( g_qeglobals.d_project_entity, "gamename" );
-                                                               cmdline += " ";
-                                                       }
-                                                       //!\todo Read the start-map args from a config file.
-                                                       if ( g_pGameDescription->mGameFile == "wolf.game" ) {
-                                                               if ( !strcmp( ValueForKey( g_qeglobals.d_project_entity, "gamemode" ),"mp" ) ) {
-                                                                       // MP
-                                                                       cmdline += "+devmap ";
-                                                                       cmdline += m_sBSPName;
-                                                               }
-                                                               else
-                                                               {
-                                                                       // SP
-                                                                       cmdline += "+set nextmap \"spdevmap ";
-                                                                       cmdline += m_sBSPName;
-                                                                       cmdline += "\"";
-                                                               }
+
+                                               RunEngineConfiguration engineConfig;
+
+                                               if ( engineConfig.do_sp_mp ) {
+                                                       if ( string_equal( gamemode_get(), "mp" ) ) {
+                                                               cmd << engineConfig.mp_executable;
                                                        }
                                                        else
                                                        {
-                                                               cmdline += "+devmap ";
-                                                               cmdline += m_sBSPName;
+                                                               cmd << engineConfig.executable;
                                                        }
                                                }
+                                               else
+                                               {
+                                                       cmd << engineConfig.executable;
+                                               }
 
-                                               Sys_Printf( "%s %s\n", cmd.GetBuffer(), cmdline.GetBuffer() );
+                                               StringOutputStream cmdline;
+
+                                               GlobalGameDescription_string_write_mapparameter( cmdline, m_sBSPName );
+
+                                               globalOutputStream() << cmd.c_str() << " " << cmdline.c_str() << "\n";
 
                                                // execute now
-                                               if ( !Q_Exec( cmd.GetBuffer(), (char *)cmdline.GetBuffer(), g_pGameDescription->mEnginePath.GetBuffer(), false ) ) {
-                                                       CString msg;
-                                                       msg = "Failed to execute the following command: ";
-                                                       msg += cmd; msg += cmdline;
-                                                       Sys_Printf( msg );
-                                                       gtk_MessageBox( g_pParentWnd->m_pWidget,  msg, "BSP monitoring", MB_OK | MB_ICONERROR );
+                                               if ( !Q_Exec( cmd.c_str(), (char *)cmdline.c_str(), EnginePath_get(), false, false ) ) {
+                                                       StringOutputStream msg;
+                                                       msg << "Failed to execute the following command: " << cmd.c_str() << cmdline.c_str();
+                                                       globalOutputStream() << msg.c_str();
+                                                       gtk_MessageBox( GTK_WIDGET( MainFrame_getWindow() ),  msg.c_str(), "Build monitoring", eMB_OK, eMB_ICONERROR );
                                                }
                                        }
-                                       Reset();
+                                       EndMonitoringLoop();
                                }
                        }
                }
-               break;
+       }
+       break;
        default:
                break;
        }
 }
 
-void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, char *sBSPName ){
-       if ( m_sBSPName ) {
-               delete[] m_sBSPName;
+GPtrArray* str_ptr_array_clone( GPtrArray* array ){
+       GPtrArray* cloned = g_ptr_array_sized_new( array->len );
+       for ( guint i = 0; i < array->len; ++i )
+       {
+               g_ptr_array_add( cloned, g_strdup( (char*)g_ptr_array_index( array, i ) ) );
        }
-       m_sBSPName = sBSPName;
+       return cloned;
+}
+
+void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName ){
+       m_sBSPName = string_clone( sBSPName );
        if ( m_eState != EIdle ) {
-               Sys_Printf( "WatchBSP got a monitoring request while not idling...\n" );
+               globalOutputStream() << "WatchBSP got a monitoring request while not idling...\n";
                // prompt the user, should we cancel the current process and go ahead?
-               if ( gtk_MessageBox( g_pParentWnd->m_pWidget,  "I am already monitoring a BSP process.\nDo you want me to override and start a new compilation?",
-                                                        "BSP process monitoring", MB_YESNO ) == IDYES ) {
+               if ( gtk_MessageBox( GTK_WIDGET( MainFrame_getWindow() ),  "I am already monitoring a Build process.\nDo you want me to override and start a new compilation?",
+                                                        "Build process monitoring", eMB_YESNO ) == eIDYES ) {
                        // disconnect and set EIdle state
                        Reset();
                }
        }
-       m_pCmd = pCmd;
+       m_pCmd = str_ptr_array_clone( pCmd );
        m_iCurrentStep = 0;
        DoEBeginStep();
 }
@@ -562,7 +791,7 @@ void CWatchBSP::ExternalListen(){
 // the part of the watchbsp interface we export to plugins
 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
-void WINAPI QERApp_Listen(){
+void QERApp_Listen(){
        // open the listening socket
-       g_pParentWnd->GetWatchBSP()->ExternalListen();
+       GetWatchBSP()->ExternalListen();
 }