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