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