]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/watchbsp.cpp
-DGTK_DISABLE_DEPRECATED
[xonotic/netradiant.git] / radiant / watchbsp.cpp
1 /*
2    Copyright (c) 2001, Loki software, inc.
3    All rights reserved.
4
5    Redistribution and use in source and binary forms, with or without modification,
6    are permitted provided that the following conditions are met:
7
8    Redistributions of source code must retain the above copyright notice, this list
9    of conditions and the following disclaimer.
10
11    Redistributions in binary form must reproduce the above copyright notice, this
12    list of conditions and the following disclaimer in the documentation and/or
13    other materials provided with the distribution.
14
15    Neither the name of Loki software nor the names of its contributors may be used
16    to endorse or promote products derived from this software without specific prior
17    written permission.
18
19    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
20    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22    DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
23    DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26    ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 //-----------------------------------------------------------------------------
32 //
33 // DESCRIPTION:
34 // monitoring window for running BSP processes (and possibly various other stuff)
35
36 #include "watchbsp.h"
37
38 #include <algorithm>
39 #include <gtk/gtk.h>
40
41 #include "cmdlib.h"
42 #include "convert.h"
43 #include "string/string.h"
44 #include "stream/stringstream.h"
45
46 #include "gtkutil/messagebox.h"
47 #include "xmlstuff.h"
48 #include "console.h"
49 #include "preferences.h"
50 #include "points.h"
51 #include "feedback.h"
52 #include "mainframe.h"
53 #include "sockets.h"
54
55 void message_flush( message_info_t* self ){
56         Sys_Print( self->msg_level, self->m_buffer, self->m_length );
57         self->m_length = 0;
58 }
59
60 void message_print( message_info_t* self, const char* characters, std::size_t length ){
61         const char* end = characters + length;
62         while ( characters != end )
63         {
64                 std::size_t space = message_info_t::bufsize - 1 - self->m_length;
65                 if ( space == 0 ) {
66                         message_flush( self );
67                 }
68                 else
69                 {
70                         std::size_t size = std::min( space, std::size_t( end - characters ) );
71                         memcpy( self->m_buffer + self->m_length, characters, size );
72                         self->m_length += size;
73                         characters += size;
74                 }
75         }
76 }
77
78
79 #include <glib.h>
80 #include <uilib/uilib.h>
81 #include "xmlstuff.h"
82
83 class CWatchBSP
84 {
85 private:
86         // a flag we have set to true when using an external BSP plugin
87         // the resulting code with that is a bit dirty, cleaner solution would be to seperate the succession of commands from the listening loop
88         // (in two seperate classes probably)
89         bool m_bBSPPlugin;
90
91         // EIdle: we are not listening
92         //   DoMonitoringLoop will change state to EBeginStep
93         // EBeginStep: the socket is up for listening, we are expecting incoming connection
94         //   incoming connection will change state to EWatching
95         // EWatching: we have a connection, monitor it
96         //   connection closed will see if we start a new step (EBeginStep) or launch Quake3 and end (EIdle)
97         enum EWatchBSPState { EIdle, EBeginStep, EWatching } m_eState;
98         socket_t *m_pListenSocket;
99         socket_t *m_pInSocket;
100         netmessage_t msg;
101         GPtrArray *m_pCmd;
102         // used to timeout EBeginStep
103         GTimer    *m_pTimer;
104         std::size_t m_iCurrentStep;
105         // name of the map so we can run the engine
106         char    *m_sBSPName;
107         // buffer we use in push mode to receive data directly from the network
108         xmlParserInputBufferPtr m_xmlInputBuffer;
109         xmlParserInputPtr m_xmlInput;
110         xmlParserCtxtPtr m_xmlParserCtxt;
111         // call this to switch the set listening mode
112         bool SetupListening();
113         // start a new EBeginStep
114         void DoEBeginStep();
115         // the xml and sax parser state
116         char m_xmlBuf[MAX_NETMESSAGE];
117         bool m_bNeedCtxtInit;
118         message_info_t m_message_info;
119
120 public:
121         CWatchBSP(){
122                 m_pCmd = 0;
123                 m_bBSPPlugin = false;
124                 m_pListenSocket = NULL;
125                 m_pInSocket = NULL;
126                 m_eState = EIdle;
127                 m_pTimer = g_timer_new();
128                 m_sBSPName = NULL;
129                 m_xmlInputBuffer = NULL;
130                 m_bNeedCtxtInit = true;
131         }
132         virtual ~CWatchBSP(){
133                 EndMonitoringLoop();
134                 Net_Shutdown();
135
136                 g_timer_destroy( m_pTimer );
137         }
138
139         bool HasBSPPlugin() const
140         { return m_bBSPPlugin; }
141
142         // called regularly to keep listening
143         void RoutineProcessing();
144         // start a monitoring loop with the following steps
145         void DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName );
146         void EndMonitoringLoop(){
147                 Reset();
148                 if ( m_sBSPName ) {
149                         string_release( m_sBSPName, string_length( m_sBSPName ) );
150                         m_sBSPName = 0;
151                 }
152                 if ( m_pCmd ) {
153                         g_ptr_array_free( m_pCmd, TRUE );
154                         m_pCmd = 0;
155                 }
156         }
157         // close everything - may be called from the outside to abort the process
158         void Reset();
159         // start a listening loop for an external process, possibly a BSP plugin
160         void ExternalListen();
161 };
162
163 CWatchBSP* g_pWatchBSP;
164
165 // watch the BSP process through network connections
166 // true: trigger the BSP steps one by one and monitor them through the network
167 // false: create a BAT / .sh file and execute it. don't bother monitoring it.
168 bool g_WatchBSP_Enabled = true;
169 // do we stop the compilation process if we come accross a leak?
170 bool g_WatchBSP_LeakStop = true;
171 bool g_WatchBSP_RunQuake = false;
172 // store prefs setting for automatic sleep mode activation
173 bool g_WatchBSP_DoSleep = true;
174 // timeout when beginning a step (in seconds)
175 // if we don't get a connection quick enough we assume something failed and go back to idling
176 int g_WatchBSP_Timeout = 10;
177
178
179 void Build_constructPreferences( PreferencesPage& page ){
180         ui::CheckButton monitorbsp = page.appendCheckBox( "", "Enable Build Process Monitoring", g_WatchBSP_Enabled );
181         ui::CheckButton leakstop = page.appendCheckBox( "", "Stop Compilation on Leak", g_WatchBSP_LeakStop );
182         ui::CheckButton runengine = page.appendCheckBox( "", "Run Engine After Compile", g_WatchBSP_RunQuake );
183         ui::CheckButton sleep = page.appendCheckBox ( "", "Sleep When Running the Engine", g_WatchBSP_DoSleep );
184         Widget_connectToggleDependency( leakstop, monitorbsp );
185         Widget_connectToggleDependency( runengine, monitorbsp );
186         Widget_connectToggleDependency( sleep, runengine );
187 }
188 void Build_constructPage( PreferenceGroup& group ){
189         PreferencesPage page( group.createPage( "Build", "Build Preferences" ) );
190         Build_constructPreferences( page );
191 }
192 void Build_registerPreferencesPage(){
193         PreferencesDialog_addSettingsPage( FreeCaller1<PreferenceGroup&, Build_constructPage>() );
194 }
195
196 #include "preferencesystem.h"
197 #include "stringio.h"
198
199 void BuildMonitor_Construct(){
200         g_pWatchBSP = new CWatchBSP();
201
202         g_WatchBSP_Enabled = !string_equal( g_pGameDescription->getKeyValue( "no_bsp_monitor" ), "1" );
203
204         GlobalPreferenceSystem().registerPreference( "WatchBSP", BoolImportStringCaller( g_WatchBSP_Enabled ), BoolExportStringCaller( g_WatchBSP_Enabled ) );
205         GlobalPreferenceSystem().registerPreference( "RunQuake2Run", BoolImportStringCaller( g_WatchBSP_RunQuake ), BoolExportStringCaller( g_WatchBSP_RunQuake ) );
206         GlobalPreferenceSystem().registerPreference( "LeakStop", BoolImportStringCaller( g_WatchBSP_LeakStop ), BoolExportStringCaller( g_WatchBSP_LeakStop ) );
207         GlobalPreferenceSystem().registerPreference( "SleepMode", BoolImportStringCaller( g_WatchBSP_DoSleep ), BoolExportStringCaller( g_WatchBSP_DoSleep ) );
208
209         Build_registerPreferencesPage();
210 }
211
212 void BuildMonitor_Destroy(){
213         delete g_pWatchBSP;
214         g_pWatchBSP = nullptr;
215 }
216
217 CWatchBSP *GetWatchBSP(){
218         return g_pWatchBSP;
219 }
220
221 void BuildMonitor_Run( GPtrArray* commands, const char* mapName ){
222         GetWatchBSP()->DoMonitoringLoop( commands, mapName );
223 }
224
225
226 // Static functions for the SAX callbacks -------------------------------------------------------
227
228 // utility for saxStartElement below
229 static void abortStream( message_info_t *data ){
230         GetWatchBSP()->EndMonitoringLoop();
231         // tell there has been an error
232 #if 0
233         if ( GetWatchBSP()->HasBSPPlugin() ) {
234                 g_BSPFrontendTable.m_pfnEndListen( 2 );
235         }
236 #endif
237         // yeah this doesn't look good.. but it's needed so that everything will be ignored until the stream goes out
238         data->ignore_depth = -1;
239         data->recurse++;
240 }
241
242 #include "stream_version.h"
243
244 static void saxStartElement( message_info_t *data, const xmlChar *name, const xmlChar **attrs ){
245 #if 0
246         globalOutputStream() << "<" << name;
247         if ( attrs != 0 ) {
248                 for ( const xmlChar** p = attrs; *p != 0; p += 2 )
249                 {
250                         globalOutputStream() << " " << p[0] << "=" << makeQuoted( p[1] );
251                 }
252         }
253         globalOutputStream() << ">\n";
254 #endif
255
256         if ( data->ignore_depth == 0 ) {
257                 if ( data->pGeometry != 0 ) {
258                         // we have a handler
259                         data->pGeometry->saxStartElement( data, name, attrs );
260                 }
261                 else
262                 {
263                         if ( strcmp( reinterpret_cast<const char*>( name ), "q3map_feedback" ) == 0 ) {
264                                 // check the correct version
265                                 // old q3map don't send a version attribute
266                                 // the ones we support .. send Q3MAP_STREAM_VERSION
267                                 if ( !attrs[0] || !attrs[1] || ( strcmp( reinterpret_cast<const char*>( attrs[0] ), "version" ) != 0 ) ) {
268                                         message_flush( data );
269                                         globalErrorStream() << "No stream version given in the feedback stream, this is an old q3map version.\n"
270                                                                                    "Please turn off monitored compiling if you still wish to use this q3map executable\n";
271                                         abortStream( data );
272                                         return;
273                                 }
274                                 else if ( q3map::stream_version() !=  reinterpret_cast<const char*>( attrs[1] ) ) {
275                                         message_flush( data );
276                                         globalErrorStream() << "This version of Radiant reads version "
277                                                 << q3map::stream_version()
278                                                 << " debug streams, I got an incoming connection with version "
279                                                 << reinterpret_cast<const char*>( attrs[1] )
280                                                 << "\nPlease make sure your versions of Radiant and q3map are matching.\n";
281                                         abortStream( data );
282                                         return;
283                                 }
284                         }
285                         // we don't treat locally
286                         else if ( strcmp( reinterpret_cast<const char*>( name ), "message" ) == 0 ) {
287                                 int msg_level = atoi( reinterpret_cast<const char*>( attrs[1] ) );
288                                 if ( msg_level != data->msg_level ) {
289                                         message_flush( data );
290                                         data->msg_level = msg_level;
291                                 }
292                         }
293                         else if ( strcmp( reinterpret_cast<const char*>( name ), "polyline" ) == 0 ) {
294                                 // polyline has a particular status .. right now we only use it for leakfile ..
295                                 data->geometry_depth = data->recurse;
296                                 data->pGeometry = &g_pointfile;
297                                 data->pGeometry->saxStartElement( data, name, attrs );
298                         }
299                         else if ( strcmp( reinterpret_cast<const char*>( name ), "select" ) == 0 ) {
300                                 CSelectMsg *pSelect = new CSelectMsg();
301                                 data->geometry_depth = data->recurse;
302                                 data->pGeometry = pSelect;
303                                 data->pGeometry->saxStartElement( data, name, attrs );
304                         }
305                         else if ( strcmp( reinterpret_cast<const char*>( name ), "pointmsg" ) == 0 ) {
306                                 CPointMsg *pPoint = new CPointMsg();
307                                 data->geometry_depth = data->recurse;
308                                 data->pGeometry = pPoint;
309                                 data->pGeometry->saxStartElement( data, name, attrs );
310                         }
311                         else if ( strcmp( reinterpret_cast<const char*>( name ), "windingmsg" ) == 0 ) {
312                                 CWindingMsg *pWinding = new CWindingMsg();
313                                 data->geometry_depth = data->recurse;
314                                 data->pGeometry = pWinding;
315                                 data->pGeometry->saxStartElement( data, name, attrs );
316                         }
317                         else
318                         {
319                                 globalErrorStream() << "Warning: ignoring unrecognized node in XML stream (" << reinterpret_cast<const char*>( name ) << ")\n";
320                                 // we don't recognize this node, jump over it
321                                 // (NOTE: the ignore mechanism is a bit screwed, only works when starting an ignore at the highest level)
322                                 data->ignore_depth = data->recurse;
323                         }
324                 }
325         }
326         data->recurse++;
327 }
328
329 static void saxEndElement( message_info_t *data, const xmlChar *name ){
330 #if 0
331         globalOutputStream() << "<" << name << "/>\n";
332 #endif
333
334         data->recurse--;
335         // we are out of an ignored chunk
336         if ( data->recurse == data->ignore_depth ) {
337                 data->ignore_depth = 0;
338                 return;
339         }
340         if ( data->pGeometry != 0 ) {
341                 data->pGeometry->saxEndElement( data, name );
342                 // we add the object to the debug window
343                 if ( data->geometry_depth == data->recurse ) {
344                         g_DbgDlg.Push( data->pGeometry );
345                         data->pGeometry = 0;
346                 }
347         }
348         if ( data->recurse == data->stop_depth ) {
349                 message_flush( data );
350 #ifdef _DEBUG
351                 globalOutputStream() << "Received error msg .. shutting down..\n";
352 #endif
353                 GetWatchBSP()->EndMonitoringLoop();
354                 // tell there has been an error
355 #if 0
356                 if ( GetWatchBSP()->HasBSPPlugin() ) {
357                         g_BSPFrontendTable.m_pfnEndListen( 2 );
358                 }
359 #endif
360                 return;
361         }
362 }
363
364 class MessageOutputStream : public TextOutputStream
365 {
366         message_info_t* m_data;
367 public:
368         MessageOutputStream( message_info_t* data ) : m_data( data ){
369         }
370
371         std::size_t write( const char* buffer, std::size_t length ){
372                 if ( m_data->pGeometry != 0 ) {
373                         m_data->pGeometry->saxCharacters( m_data, reinterpret_cast<const xmlChar*>( buffer ), int(length) );
374                 }
375                 else
376                 {
377                         if ( m_data->ignore_depth == 0 ) {
378                                 // output the message using the level
379                                 message_print( m_data, buffer, length );
380                                 // if this message has error level flag, we mark the depth to stop the compilation when we get out
381                                 // we don't set the msg level if we don't stop on leak
382                                 if ( m_data->msg_level == 3 ) {
383                                         m_data->stop_depth = m_data->recurse - 1;
384                                 }
385                         }
386                 }
387
388                 return length;
389         }
390 };
391
392 template<typename T>
393 inline MessageOutputStream& operator<<( MessageOutputStream& ostream, const T& t ){
394         return ostream_write( ostream, t );
395 }
396
397 static void saxCharacters( message_info_t *data, const xmlChar *ch, int len ){
398         MessageOutputStream ostream( data );
399         ostream << StringRange( reinterpret_cast<const char*>( ch ), reinterpret_cast<const char*>( ch + len ) );
400 }
401
402 static void saxComment( void *ctx, const xmlChar *msg ){
403         globalOutputStream() << "XML comment: " << reinterpret_cast<const char*>( msg ) << "\n";
404 }
405
406 static void saxWarning( void *ctx, const char *msg, ... ){
407         char saxMsgBuffer[4096];
408         va_list args;
409
410         va_start( args, msg );
411         vsprintf( saxMsgBuffer, msg, args );
412         va_end( args );
413         globalOutputStream() << "XML warning: " << saxMsgBuffer << "\n";
414 }
415
416 static void saxError( void *ctx, const char *msg, ... ){
417         char saxMsgBuffer[4096];
418         va_list args;
419
420         va_start( args, msg );
421         vsprintf( saxMsgBuffer, msg, args );
422         va_end( args );
423         globalErrorStream() << "XML error: " << saxMsgBuffer << "\n";
424 }
425
426 static void saxFatal( void *ctx, const char *msg, ... ){
427         char buffer[4096];
428
429         va_list args;
430
431         va_start( args, msg );
432         vsprintf( buffer, msg, args );
433         va_end( args );
434         globalErrorStream() << "XML fatal error: " << buffer << "\n";
435 }
436
437 static xmlSAXHandler saxParser = {
438         0, /* internalSubset */
439         0, /* isStandalone */
440         0, /* hasInternalSubset */
441         0, /* hasExternalSubset */
442         0, /* resolveEntity */
443         0, /* getEntity */
444         0, /* entityDecl */
445         0, /* notationDecl */
446         0, /* attributeDecl */
447         0, /* elementDecl */
448         0, /* unparsedEntityDecl */
449         0, /* setDocumentLocator */
450         0, /* startDocument */
451         0, /* endDocument */
452         (startElementSAXFunc)saxStartElement, /* startElement */
453         (endElementSAXFunc)saxEndElement, /* endElement */
454         0, /* reference */
455         (charactersSAXFunc)saxCharacters, /* characters */
456         0, /* ignorableWhitespace */
457         0, /* processingInstruction */
458         (commentSAXFunc)saxComment, /* comment */
459         (warningSAXFunc)saxWarning, /* warning */
460         (errorSAXFunc)saxError, /* error */
461         (fatalErrorSAXFunc)saxFatal, /* fatalError */
462         0,
463         0,
464         0,
465         0,
466         0,
467         0,
468         0,
469         0
470 };
471
472 // ------------------------------------------------------------------------------------------------
473
474
475 guint s_routine_id;
476 static gint watchbsp_routine( gpointer data ){
477         reinterpret_cast<CWatchBSP*>( data )->RoutineProcessing();
478         return TRUE;
479 }
480
481 void CWatchBSP::Reset(){
482         if ( m_pInSocket ) {
483                 Net_Disconnect( m_pInSocket );
484                 m_pInSocket = NULL;
485         }
486         if ( m_pListenSocket ) {
487                 Net_Disconnect( m_pListenSocket );
488                 m_pListenSocket = NULL;
489         }
490         if ( m_xmlInputBuffer ) {
491                 xmlFreeParserInputBuffer( m_xmlInputBuffer );
492                 m_xmlInputBuffer = NULL;
493         }
494         m_eState = EIdle;
495         if ( s_routine_id ) {
496                 g_source_remove( s_routine_id );
497                 s_routine_id = 0;
498         }
499 }
500
501 bool CWatchBSP::SetupListening(){
502 #ifdef _DEBUG
503         if ( m_pListenSocket ) {
504                 globalOutputStream() << "ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n";
505                 return false;
506         }
507 #endif
508         globalOutputStream() << "Setting up\n";
509         Net_Setup();
510         m_pListenSocket = Net_ListenSocket( 39000 );
511         if ( m_pListenSocket == NULL ) {
512                 return false;
513         }
514         globalOutputStream() << "Listening...\n";
515         return true;
516 }
517
518 void CWatchBSP::DoEBeginStep(){
519         Reset();
520         if ( SetupListening() == false ) {
521                 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";
522                 globalOutputStream() << msg;
523                 MainFrame_getWindow().alert( msg, "Build monitoring", ui::alert_type::OK, ui::alert_icon::ERROR );
524                 return;
525         }
526         // set the timer for timeouts and step cancellation
527         g_timer_reset( m_pTimer );
528         g_timer_start( m_pTimer );
529
530         if ( !m_bBSPPlugin ) {
531                 globalOutputStream() << "=== running build command ===\n"
532                                                          << static_cast<const char*>( g_ptr_array_index( m_pCmd, m_iCurrentStep ) ) << "\n";
533
534                 if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true, false ) ) {
535                         StringOutputStream msg( 256 );
536                         msg << "Failed to execute the following command: ";
537                         msg << reinterpret_cast<const char*>( g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
538                         msg << "\nCheck that the file exists and that you don't run out of system resources.\n";
539                         globalOutputStream() << msg.c_str();
540                         MainFrame_getWindow().alert( msg.c_str(), "Build monitoring", ui::alert_type::OK, ui::alert_icon::ERROR );
541                         return;
542                 }
543                 // re-initialise the debug window
544                 if ( m_iCurrentStep == 0 ) {
545                         g_DbgDlg.Init();
546                 }
547         }
548         m_eState = EBeginStep;
549         s_routine_id = g_timeout_add( 25, watchbsp_routine, this );
550 }
551
552
553 #if defined( WIN32 )
554 #define ENGINE_ATTRIBUTE "engine_win32"
555 #define MP_ENGINE_ATTRIBUTE "mp_engine_win32"
556 #elif defined( __linux__ ) || defined ( __FreeBSD__ )
557 #define ENGINE_ATTRIBUTE "engine_linux"
558 #define MP_ENGINE_ATTRIBUTE "mp_engine_linux"
559 #elif defined( __APPLE__ )
560 #define ENGINE_ATTRIBUTE "engine_macos"
561 #define MP_ENGINE_ATTRIBUTE "mp_engine_macos"
562 #else
563 #error "unsupported platform"
564 #endif
565
566 class RunEngineConfiguration
567 {
568 public:
569         const char* executable;
570         const char* mp_executable;
571         bool do_sp_mp;
572
573         RunEngineConfiguration() :
574                 executable( g_pGameDescription->getRequiredKeyValue( ENGINE_ATTRIBUTE ) ),
575                 mp_executable( g_pGameDescription->getKeyValue( MP_ENGINE_ATTRIBUTE ) ){
576                 do_sp_mp = !string_empty( mp_executable );
577         }
578 };
579
580 inline void GlobalGameDescription_string_write_mapparameter( StringOutputStream& string, const char* mapname ){
581         if ( g_pGameDescription->mGameType == "q2"
582                  || g_pGameDescription->mGameType == "heretic2" ) {
583                 string << ". +exec radiant.cfg +map " << mapname;
584         }
585         else
586         {
587                 string << "+set sv_pure 0 ";
588                 // TTimo: a check for vm_* but that's all fine
589                 //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
590                 const char* fs_game = gamename_get();
591                 if ( !string_equal( fs_game, basegame_get() ) ) {
592                         string << "+set fs_game " << fs_game << " ";
593                 }
594                 if ( g_pGameDescription->mGameType == "wolf" ) {
595                         //|| g_pGameDescription->mGameType == "et")
596                         if ( string_equal( gamemode_get(), "mp" ) ) {
597                                 // MP
598                                 string << "+devmap " << mapname;
599                         }
600                         else
601                         {
602                                 // SP
603                                 string << "+set nextmap \"spdevmap " << mapname << "\"";
604                         }
605                 }
606                 else
607                 {
608                         string << "+devmap " << mapname;
609                 }
610         }
611 }
612
613
614 void CWatchBSP::RoutineProcessing(){
615         switch ( m_eState )
616         {
617         case EBeginStep:
618                 // timeout: if we don't get an incoming connection fast enough, go back to idle
619                 if ( g_timer_elapsed( m_pTimer, NULL ) > g_WatchBSP_Timeout ) {
620                         MainFrame_getWindow().alert(  "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", ui::alert_type::OK );
621                         EndMonitoringLoop();
622 #if 0
623                         if ( m_bBSPPlugin ) {
624                                 // status == 1 : didn't get the connection
625                                 g_BSPFrontendTable.m_pfnEndListen( 1 );
626                         }
627 #endif
628                         return;
629                 }
630 #ifdef _DEBUG
631                 // some debug checks
632                 if ( !m_pListenSocket ) {
633                         globalErrorStream() << "ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n";
634                         return;
635                 }
636 #endif
637                 // we are not connected yet, accept any incoming connection
638                 m_pInSocket = Net_Accept( m_pListenSocket );
639                 if ( m_pInSocket ) {
640                         globalOutputStream() << "Connected.\n";
641                         // prepare the message info struct for diving in
642                         memset( &m_message_info, 0, sizeof( message_info_t ) );
643                         // a dumb flag to make sure we init the push parser context when first getting a msg
644                         m_bNeedCtxtInit = true;
645                         m_eState = EWatching;
646                 }
647                 break;
648         case EWatching:
649         {
650 #ifdef _DEBUG
651                 // some debug checks
652                 if ( !m_pInSocket ) {
653                         globalErrorStream() << "ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n";
654                         return;
655                 }
656 #endif
657
658                 int ret = Net_Wait( m_pInSocket, 0, 0 );
659                 if ( ret == -1 ) {
660                         globalOutputStream() << "WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n";
661                         globalOutputStream() << "Terminating the connection.\n";
662                         EndMonitoringLoop();
663                         return;
664                 }
665
666                 if ( ret == 1 ) {
667                         // the socket has been identified, there's something (message or disconnection)
668                         // see if there's anything in input
669                         ret = Net_Receive( m_pInSocket, &msg );
670                         if ( ret > 0 ) {
671                                 //        unsigned int size = msg.size; //++timo just a check
672                                 strcpy( m_xmlBuf, NMSG_ReadString( &msg ) );
673                                 if ( m_bNeedCtxtInit ) {
674                                         m_xmlParserCtxt = NULL;
675                                         m_xmlParserCtxt = xmlCreatePushParserCtxt( &saxParser, &m_message_info, m_xmlBuf, static_cast<int>( strlen( m_xmlBuf ) ), NULL );
676
677                                         if ( m_xmlParserCtxt == NULL ) {
678                                                 globalErrorStream() << "Failed to create the XML parser (incoming stream began with: " << m_xmlBuf << ")\n";
679                                                 EndMonitoringLoop();
680                                         }
681                                         m_bNeedCtxtInit = false;
682                                 }
683                                 else
684                                 {
685                                         xmlParseChunk( m_xmlParserCtxt, m_xmlBuf, static_cast<int>( strlen( m_xmlBuf ) ), 0 );
686                                 }
687                         }
688                         else
689                         {
690                                 message_flush( &m_message_info );
691                                 // error or connection closed/reset
692                                 // NOTE: if we get an error down the XML stream we don't reach here
693                                 Net_Disconnect( m_pInSocket );
694                                 m_pInSocket = NULL;
695                                 globalOutputStream() << "Connection closed.\n";
696 #if 0
697                                 if ( m_bBSPPlugin ) {
698                                         EndMonitoringLoop();
699                                         // let the BSP plugin know that the job is done
700                                         g_BSPFrontendTable.m_pfnEndListen( 0 );
701                                         return;
702                                 }
703 #endif
704                                 // move to next step or finish
705                                 m_iCurrentStep++;
706                                 if ( m_iCurrentStep < m_pCmd->len ) {
707                                         DoEBeginStep();
708                                 }
709                                 else
710                                 {
711                                         // launch the engine .. OMG
712                                         if ( g_WatchBSP_RunQuake ) {
713 #if 0
714                                                 // do we enter sleep mode before?
715                                                 if ( g_WatchBSP_DoSleep ) {
716                                                         globalOutputStream() << "Going into sleep mode..\n";
717                                                         g_pParentWnd->OnSleep();
718                                                 }
719 #endif
720                                                 globalOutputStream() << "Running engine...\n";
721                                                 StringOutputStream cmd( 256 );
722                                                 // build the command line
723                                                 cmd << EnginePath_get();
724                                                 // this is game dependant
725
726                                                 RunEngineConfiguration engineConfig;
727
728                                                 if ( engineConfig.do_sp_mp ) {
729                                                         if ( string_equal( gamemode_get(), "mp" ) ) {
730                                                                 cmd << engineConfig.mp_executable;
731                                                         }
732                                                         else
733                                                         {
734                                                                 cmd << engineConfig.executable;
735                                                         }
736                                                 }
737                                                 else
738                                                 {
739                                                         cmd << engineConfig.executable;
740                                                 }
741
742                                                 StringOutputStream cmdline;
743
744                                                 GlobalGameDescription_string_write_mapparameter( cmdline, m_sBSPName );
745
746                                                 globalOutputStream() << cmd.c_str() << " " << cmdline.c_str() << "\n";
747
748                                                 // execute now
749                                                 if ( !Q_Exec( cmd.c_str(), (char *)cmdline.c_str(), EnginePath_get(), false, false ) ) {
750                                                         StringOutputStream msg;
751                                                         msg << "Failed to execute the following command: " << cmd.c_str() << cmdline.c_str();
752                                                         globalOutputStream() << msg.c_str();
753                                                         MainFrame_getWindow().alert( msg.c_str(), "Build monitoring", ui::alert_type::OK, ui::alert_icon::ERROR );
754                                                 }
755                                         }
756                                         EndMonitoringLoop();
757                                 }
758                         }
759                 }
760         }
761         break;
762         default:
763                 break;
764         }
765 }
766
767 GPtrArray* str_ptr_array_clone( GPtrArray* array ){
768         GPtrArray* cloned = g_ptr_array_sized_new( array->len );
769         for ( guint i = 0; i < array->len; ++i )
770         {
771                 g_ptr_array_add( cloned, g_strdup( (char*)g_ptr_array_index( array, i ) ) );
772         }
773         return cloned;
774 }
775
776 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName ){
777         m_sBSPName = string_clone( sBSPName );
778         if ( m_eState != EIdle ) {
779                 globalOutputStream() << "WatchBSP got a monitoring request while not idling...\n";
780                 // prompt the user, should we cancel the current process and go ahead?
781                 if ( MainFrame_getWindow().alert( "I am already monitoring a Build process.\nDo you want me to override and start a new compilation?",
782                                                          "Build process monitoring", ui::alert_type::YESNO ) == ui::alert_response::YES ) {
783                         // disconnect and set EIdle state
784                         Reset();
785                 }
786         }
787         m_pCmd = str_ptr_array_clone( pCmd );
788         m_iCurrentStep = 0;
789         DoEBeginStep();
790 }
791
792 void CWatchBSP::ExternalListen(){
793         m_bBSPPlugin = true;
794         DoEBeginStep();
795 }
796
797 // the part of the watchbsp interface we export to plugins
798 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
799 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
800 void QERApp_Listen(){
801         // open the listening socket
802         GetWatchBSP()->ExternalListen();
803 }