2 Copyright (c) 2001, Loki software, inc.
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
8 Redistributions of source code must retain the above copyright notice, this list
9 of conditions and the following disclaimer.
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.
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
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.
31 //-----------------------------------------------------------------------------
34 // monitoring window for running BSP processes (and possibly various other stuff)
39 #include <gtk/gtkmain.h>
43 #include "string/string.h"
44 #include "stream/stringstream.h"
46 #include "gtkutil/messagebox.h"
49 #include "preferences.h"
52 #include "mainframe.h"
55 void message_flush(message_info_t* self)
57 Sys_Print(self->msg_level, self->m_buffer, self->m_length);
61 void message_print(message_info_t* self, const char* characters, std::size_t length)
63 const char* end = characters + length;
64 while(characters != end)
66 std::size_t space = message_info_t::bufsize - 1 - self->m_length;
73 std::size_t size = std::min(space, std::size_t(end - characters));
74 memcpy(self->m_buffer + self->m_length, characters, size);
75 self->m_length += size;
82 #include <glib/gtimer.h>
83 #include <glib/garray.h>
89 // a flag we have set to true when using an external BSP plugin
90 // the resulting code with that is a bit dirty, cleaner solution would be to seperate the succession of commands from the listening loop
91 // (in two seperate classes probably)
94 // EIdle: we are not listening
95 // DoMonitoringLoop will change state to EBeginStep
96 // EBeginStep: the socket is up for listening, we are expecting incoming connection
97 // incoming connection will change state to EWatching
98 // EWatching: we have a connection, monitor it
99 // connection closed will see if we start a new step (EBeginStep) or launch Quake3 and end (EIdle)
100 enum EWatchBSPState { EIdle, EBeginStep, EWatching } m_eState;
101 socket_t *m_pListenSocket;
102 socket_t *m_pInSocket;
105 // used to timeout EBeginStep
107 std::size_t m_iCurrentStep;
108 // name of the map so we can run the engine
110 // buffer we use in push mode to receive data directly from the network
111 xmlParserInputBufferPtr m_xmlInputBuffer;
112 xmlParserInputPtr m_xmlInput;
113 xmlParserCtxtPtr m_xmlParserCtxt;
114 // call this to switch the set listening mode
115 bool SetupListening();
116 // start a new EBeginStep
118 // the xml and sax parser state
119 char m_xmlBuf[MAX_NETMESSAGE];
120 bool m_bNeedCtxtInit;
121 message_info_t m_message_info;
127 m_bBSPPlugin = false;
128 m_pListenSocket = NULL;
131 m_pTimer = g_timer_new();
133 m_xmlInputBuffer = NULL;
134 m_bNeedCtxtInit = true;
141 g_timer_destroy(m_pTimer);
144 bool HasBSPPlugin() const
145 { return m_bBSPPlugin; }
147 // called regularly to keep listening
148 void RoutineProcessing();
149 // start a monitoring loop with the following steps
150 void DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName );
151 void EndMonitoringLoop()
156 string_release(m_sBSPName, string_length(m_sBSPName));
161 g_ptr_array_free(m_pCmd, TRUE);
165 // close everything - may be called from the outside to abort the process
167 // start a listening loop for an external process, possibly a BSP plugin
168 void ExternalListen();
171 CWatchBSP* g_pWatchBSP;
173 // watch the BSP process through network connections
174 // true: trigger the BSP steps one by one and monitor them through the network
175 // false: create a BAT / .sh file and execute it. don't bother monitoring it.
176 bool g_WatchBSP_Enabled = true;
177 // do we stop the compilation process if we come accross a leak?
178 bool g_WatchBSP_LeakStop = true;
179 bool g_WatchBSP_RunQuake = false;
180 // store prefs setting for automatic sleep mode activation
181 bool g_WatchBSP_DoSleep = true;
182 // timeout when beginning a step (in seconds)
183 // if we don't get a connection quick enough we assume something failed and go back to idling
184 int g_WatchBSP_Timeout = 10;
187 void Build_constructPreferences(PreferencesPage& page)
189 GtkWidget* monitorbsp = page.appendCheckBox("", "Enable Build Process Monitoring", g_WatchBSP_Enabled);
190 GtkWidget* leakstop = page.appendCheckBox("", "Stop Compilation on Leak", g_WatchBSP_LeakStop);
191 GtkWidget* runengine = page.appendCheckBox("", "Run Engine After Compile", g_WatchBSP_RunQuake);
192 GtkWidget* sleep = page.appendCheckBox("", "Sleep When Running the Engine", g_WatchBSP_DoSleep);
193 Widget_connectToggleDependency(leakstop, monitorbsp);
194 Widget_connectToggleDependency(runengine, monitorbsp);
195 Widget_connectToggleDependency(sleep, runengine);
197 void Build_constructPage(PreferenceGroup& group)
199 PreferencesPage page(group.createPage("Build", "Build Preferences"));
200 Build_constructPreferences(page);
202 void Build_registerPreferencesPage()
204 PreferencesDialog_addSettingsPage(FreeCaller1<PreferenceGroup&, Build_constructPage>());
207 #include "preferencesystem.h"
208 #include "stringio.h"
210 void BuildMonitor_Construct()
212 g_pWatchBSP = new CWatchBSP();
214 g_WatchBSP_Enabled = !string_equal(g_pGameDescription->getKeyValue("no_bsp_monitor"), "1");
216 GlobalPreferenceSystem().registerPreference("WatchBSP", BoolImportStringCaller(g_WatchBSP_Enabled), BoolExportStringCaller(g_WatchBSP_Enabled));
217 GlobalPreferenceSystem().registerPreference("RunQuake2Run", BoolImportStringCaller(g_WatchBSP_RunQuake), BoolExportStringCaller(g_WatchBSP_RunQuake));
218 GlobalPreferenceSystem().registerPreference("LeakStop", BoolImportStringCaller(g_WatchBSP_LeakStop), BoolExportStringCaller(g_WatchBSP_LeakStop));
219 GlobalPreferenceSystem().registerPreference("SleepMode", BoolImportStringCaller(g_WatchBSP_DoSleep), BoolExportStringCaller(g_WatchBSP_DoSleep));
221 Build_registerPreferencesPage();
224 void BuildMonitor_Destroy()
229 CWatchBSP *GetWatchBSP()
234 void BuildMonitor_Run(GPtrArray* commands, const char* mapName)
236 GetWatchBSP()->DoMonitoringLoop(commands, mapName);
240 // Static functions for the SAX callbacks -------------------------------------------------------
242 // utility for saxStartElement below
243 static void abortStream(message_info_t *data)
245 GetWatchBSP()->EndMonitoringLoop();
246 // tell there has been an error
248 if (GetWatchBSP()->HasBSPPlugin())
249 g_BSPFrontendTable.m_pfnEndListen(2);
251 // yeah this doesn't look good.. but it's needed so that everything will be ignored until the stream goes out
252 data->ignore_depth = -1;
256 #include "stream_version.h"
258 static void saxStartElement(message_info_t *data, const xmlChar *name, const xmlChar **attrs)
261 globalOutputStream() << "<" << name;
264 for(const xmlChar** p = attrs; *p != 0; p += 2)
266 globalOutputStream() << " " << p[0] << "=" << makeQuoted(p[1]);
269 globalOutputStream() << ">\n";
272 if (data->ignore_depth == 0)
274 if(data->pGeometry != 0)
277 data->pGeometry->saxStartElement (data, name, attrs);
281 if (strcmp(reinterpret_cast<const char*>(name), "q3map_feedback") == 0)
283 // check the correct version
284 // old q3map don't send a version attribute
285 // the ones we support .. send Q3MAP_STREAM_VERSION
286 if (!attrs[0] || !attrs[1] || (strcmp(reinterpret_cast<const char*>(attrs[0]), "version") != 0))
289 globalErrorStream() << "No stream version given in the feedback stream, this is an old q3map version.\n"
290 "Please turn off monitored compiling if you still wish to use this q3map executable\n";
294 else if (strcmp(reinterpret_cast<const char*>(attrs[1]), Q3MAP_STREAM_VERSION) != 0)
297 globalErrorStream() <<
298 "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"
299 "Please make sure your versions of Radiant and q3map are matching.\n";
304 // we don't treat locally
305 else if (strcmp(reinterpret_cast<const char*>(name), "message") == 0)
307 int msg_level = atoi(reinterpret_cast<const char*>(attrs[1]));
308 if(msg_level != data->msg_level)
311 data->msg_level = msg_level;
314 else if (strcmp(reinterpret_cast<const char*>(name), "polyline") == 0)
315 // polyline has a particular status .. right now we only use it for leakfile ..
317 data->geometry_depth = data->recurse;
318 data->pGeometry = &g_pointfile;
319 data->pGeometry->saxStartElement (data, name, attrs);
321 else if (strcmp(reinterpret_cast<const char*>(name), "select") == 0)
323 CSelectMsg *pSelect = new CSelectMsg();
324 data->geometry_depth = data->recurse;
325 data->pGeometry = pSelect;
326 data->pGeometry->saxStartElement (data, name, attrs);
328 else if (strcmp(reinterpret_cast<const char*>(name), "pointmsg") == 0)
330 CPointMsg *pPoint = new CPointMsg();
331 data->geometry_depth = data->recurse;
332 data->pGeometry = pPoint;
333 data->pGeometry->saxStartElement (data, name, attrs);
335 else if (strcmp(reinterpret_cast<const char*>(name), "windingmsg") == 0)
337 CWindingMsg *pWinding = new CWindingMsg();
338 data->geometry_depth = data->recurse;
339 data->pGeometry = pWinding;
340 data->pGeometry->saxStartElement (data, name, attrs);
344 globalErrorStream() << "Warning: ignoring unrecognized node in XML stream (" << reinterpret_cast<const char*>(name) << ")\n";
345 // we don't recognize this node, jump over it
346 // (NOTE: the ignore mechanism is a bit screwed, only works when starting an ignore at the highest level)
347 data->ignore_depth = data->recurse;
354 static void saxEndElement(message_info_t *data, const xmlChar *name)
357 globalOutputStream() << "<" << name << "/>\n";
361 // we are out of an ignored chunk
362 if(data->recurse == data->ignore_depth)
364 data->ignore_depth = 0;
367 if(data->pGeometry != 0)
369 data->pGeometry->saxEndElement (data, name);
370 // we add the object to the debug window
371 if(data->geometry_depth == data->recurse)
373 g_DbgDlg.Push(data->pGeometry);
377 if (data->recurse == data->stop_depth)
381 globalOutputStream() << "Received error msg .. shutting down..\n";
383 GetWatchBSP()->EndMonitoringLoop();
384 // tell there has been an error
386 if (GetWatchBSP()->HasBSPPlugin())
387 g_BSPFrontendTable.m_pfnEndListen(2);
393 class MessageOutputStream : public TextOutputStream
395 message_info_t* m_data;
397 MessageOutputStream(message_info_t* data) : m_data(data)
400 std::size_t write(const char* buffer, std::size_t length)
402 if(m_data->pGeometry != 0)
404 m_data->pGeometry->saxCharacters(m_data, reinterpret_cast<const xmlChar*>(buffer), int(length));
408 if (m_data->ignore_depth == 0)
410 // output the message using the level
411 message_print(m_data, buffer, length);
412 // if this message has error level flag, we mark the depth to stop the compilation when we get out
413 // we don't set the msg level if we don't stop on leak
414 if (m_data->msg_level == 3)
416 m_data->stop_depth = m_data->recurse-1;
426 inline MessageOutputStream& operator<<(MessageOutputStream& ostream, const T& t)
428 return ostream_write(ostream, t);
431 static void saxCharacters(message_info_t *data, const xmlChar *ch, int len)
433 MessageOutputStream ostream(data);
434 ostream << StringRange(reinterpret_cast<const char*>(ch), reinterpret_cast<const char*>(ch + len));
437 static void saxComment(void *ctx, const xmlChar *msg)
439 globalOutputStream() << "XML comment: " << reinterpret_cast<const char*>(msg) << "\n";
442 static void saxWarning(void *ctx, const char *msg, ...)
444 char saxMsgBuffer[4096];
448 vsprintf (saxMsgBuffer, msg, args);
450 globalOutputStream() << "XML warning: " << saxMsgBuffer << "\n";
453 static void saxError(void *ctx, const char *msg, ...)
455 char saxMsgBuffer[4096];
459 vsprintf (saxMsgBuffer, msg, args);
461 globalErrorStream() << "XML error: " << saxMsgBuffer << "\n";
464 static void saxFatal(void *ctx, const char *msg, ...)
471 vsprintf (buffer, msg, args);
473 globalErrorStream() << "XML fatal error: " << buffer << "\n";
476 static xmlSAXHandler saxParser = {
477 0, /* internalSubset */
478 0, /* isStandalone */
479 0, /* hasInternalSubset */
480 0, /* hasExternalSubset */
481 0, /* resolveEntity */
484 0, /* notationDecl */
485 0, /* attributeDecl */
487 0, /* unparsedEntityDecl */
488 0, /* setDocumentLocator */
489 0, /* startDocument */
491 (startElementSAXFunc)saxStartElement, /* startElement */
492 (endElementSAXFunc)saxEndElement, /* endElement */
494 (charactersSAXFunc)saxCharacters, /* characters */
495 0, /* ignorableWhitespace */
496 0, /* processingInstruction */
497 (commentSAXFunc)saxComment, /* comment */
498 (warningSAXFunc)saxWarning, /* warning */
499 (errorSAXFunc)saxError, /* error */
500 (fatalErrorSAXFunc)saxFatal, /* fatalError */
511 // ------------------------------------------------------------------------------------------------
515 static gint watchbsp_routine(gpointer data)
517 reinterpret_cast<CWatchBSP*>(data)->RoutineProcessing();
521 void CWatchBSP::Reset()
525 Net_Disconnect(m_pInSocket);
530 Net_Disconnect(m_pListenSocket);
531 m_pListenSocket = NULL;
533 if (m_xmlInputBuffer)
535 xmlFreeParserInputBuffer (m_xmlInputBuffer);
536 m_xmlInputBuffer = NULL;
540 gtk_timeout_remove(s_routine_id);
543 bool CWatchBSP::SetupListening()
548 globalOutputStream() << "ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n";
552 globalOutputStream() << "Setting up\n";
554 m_pListenSocket = Net_ListenSocket(39000);
555 if (m_pListenSocket == NULL)
557 globalOutputStream() << "Listening...\n";
561 void CWatchBSP::DoEBeginStep()
564 if (SetupListening() == false)
566 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";
567 globalOutputStream() << msg;
568 gtk_MessageBox(GTK_WIDGET(MainFrame_getWindow()), msg, "Build monitoring", eMB_OK, eMB_ICONERROR);
571 // set the timer for timeouts and step cancellation
572 g_timer_reset( m_pTimer );
573 g_timer_start( m_pTimer );
577 globalOutputStream() << "=== running build command ===\n"
578 << static_cast<const char*>(g_ptr_array_index( m_pCmd, m_iCurrentStep )) << "\n";
580 if (!Q_Exec(NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true, false ))
582 StringOutputStream msg(256);
583 msg << "Failed to execute the following command: ";
584 msg << reinterpret_cast<const char*>(g_ptr_array_index(m_pCmd, m_iCurrentStep));
585 msg << "\nCheck that the file exists and that you don't run out of system resources.\n";
586 globalOutputStream() << msg.c_str();
587 gtk_MessageBox(GTK_WIDGET(MainFrame_getWindow()), msg.c_str(), "Build monitoring", eMB_OK, eMB_ICONERROR );
590 // re-initialise the debug window
591 if (m_iCurrentStep == 0)
594 m_eState = EBeginStep;
595 s_routine_id = gtk_timeout_add(25, watchbsp_routine, this);
600 #define ENGINE_ATTRIBUTE "engine_win32"
601 #define MP_ENGINE_ATTRIBUTE "mp_engine_win32"
602 #elif defined(__linux__) || defined (__FreeBSD__)
603 #define ENGINE_ATTRIBUTE "engine_linux"
604 #define MP_ENGINE_ATTRIBUTE "mp_engine_linux"
605 #elif defined(__APPLE__)
606 #define ENGINE_ATTRIBUTE "engine_macos"
607 #define MP_ENGINE_ATTRIBUTE "mp_engine_macos"
609 #error "unsupported platform"
612 class RunEngineConfiguration
615 const char* executable;
616 const char* mp_executable;
619 RunEngineConfiguration() :
620 executable(g_pGameDescription->getRequiredKeyValue(ENGINE_ATTRIBUTE)),
621 mp_executable(g_pGameDescription->getKeyValue(MP_ENGINE_ATTRIBUTE))
623 do_sp_mp = !string_empty(mp_executable);
627 inline void GlobalGameDescription_string_write_mapparameter(StringOutputStream& string, const char* mapname)
629 if(g_pGameDescription->mGameType == "q2"
630 || g_pGameDescription->mGameType == "heretic2")
632 string << ". +exec radiant.cfg +map " << mapname;
636 string << "+set sv_pure 0 ";
637 // TTimo: a check for vm_* but that's all fine
638 //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
639 const char* fs_game = gamename_get();
640 if (!string_equal(fs_game, basegame_get()))
642 string << "+set fs_game " << fs_game << " ";
644 if(g_pGameDescription->mGameType == "wolf"
645 || g_pGameDescription->mGameType == "et")
647 if (string_equal(gamemode_get(), "mp"))
650 string << "+devmap " << mapname;
655 string << "+set nextmap \"spdevmap " << mapname << "\"";
660 string << "+devmap " << mapname;
666 void CWatchBSP::RoutineProcessing()
671 // timeout: if we don't get an incoming connection fast enough, go back to idle
672 if ( g_timer_elapsed( m_pTimer, NULL ) > g_WatchBSP_Timeout )
674 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 );
679 // status == 1 : didn't get the connection
680 g_BSPFrontendTable.m_pfnEndListen(1);
687 if (!m_pListenSocket)
689 globalErrorStream() << "ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n";
693 // we are not connected yet, accept any incoming connection
694 m_pInSocket = Net_Accept(m_pListenSocket);
697 globalOutputStream() << "Connected.\n";
698 // prepare the message info struct for diving in
699 memset (&m_message_info, 0, sizeof(message_info_t));
700 // a dumb flag to make sure we init the push parser context when first getting a msg
701 m_bNeedCtxtInit = true;
702 m_eState = EWatching;
711 globalErrorStream() << "ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n";
716 int ret = Net_Wait(m_pInSocket, 0, 0);
719 globalOutputStream() << "WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n";
720 globalOutputStream() << "Terminating the connection.\n";
727 // the socket has been identified, there's something (message or disconnection)
728 // see if there's anything in input
729 ret = Net_Receive( m_pInSocket, &msg );
732 // unsigned int size = msg.size; //++timo just a check
733 strcpy (m_xmlBuf, NMSG_ReadString (&msg));
736 m_xmlParserCtxt = NULL;
737 m_xmlParserCtxt = xmlCreatePushParserCtxt (&saxParser, &m_message_info, m_xmlBuf, static_cast<int>(strlen(m_xmlBuf)), NULL);
739 if (m_xmlParserCtxt == NULL)
741 globalErrorStream() << "Failed to create the XML parser (incoming stream began with: " << m_xmlBuf << ")\n";
744 m_bNeedCtxtInit = false;
748 xmlParseChunk(m_xmlParserCtxt, m_xmlBuf, static_cast<int>(strlen(m_xmlBuf)), 0);
753 message_flush(&m_message_info);
754 // error or connection closed/reset
755 // NOTE: if we get an error down the XML stream we don't reach here
756 Net_Disconnect( m_pInSocket );
758 globalOutputStream() << "Connection closed.\n";
763 // let the BSP plugin know that the job is done
764 g_BSPFrontendTable.m_pfnEndListen(0);
768 // move to next step or finish
770 if (m_iCurrentStep < m_pCmd->len )
776 // launch the engine .. OMG
777 if (g_WatchBSP_RunQuake)
780 // do we enter sleep mode before?
781 if (g_WatchBSP_DoSleep)
783 globalOutputStream() << "Going into sleep mode..\n";
784 g_pParentWnd->OnSleep();
787 globalOutputStream() << "Running engine...\n";
788 StringOutputStream cmd(256);
789 // build the command line
790 cmd << EnginePath_get();
791 // this is game dependant
793 RunEngineConfiguration engineConfig;
795 if(engineConfig.do_sp_mp)
797 if (string_equal(gamemode_get(), "mp"))
799 cmd << engineConfig.mp_executable;
803 cmd << engineConfig.executable;
808 cmd << engineConfig.executable;
811 StringOutputStream cmdline;
813 GlobalGameDescription_string_write_mapparameter(cmdline, m_sBSPName);
815 globalOutputStream() << cmd.c_str() << " " << cmdline.c_str() << "\n";
818 if (!Q_Exec(cmd.c_str(), (char *)cmdline.c_str(), EnginePath_get(), false, false))
820 StringOutputStream msg;
821 msg << "Failed to execute the following command: " << cmd.c_str() << cmdline.c_str();
822 globalOutputStream() << msg.c_str();
823 gtk_MessageBox(GTK_WIDGET(MainFrame_getWindow()), msg.c_str(), "Build monitoring", eMB_OK, eMB_ICONERROR );
837 GPtrArray* str_ptr_array_clone(GPtrArray* array)
839 GPtrArray* cloned = g_ptr_array_sized_new(array->len);
840 for(guint i = 0; i < array->len; ++i)
842 g_ptr_array_add(cloned, g_strdup((char*)g_ptr_array_index(array, i)));
847 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName )
849 m_sBSPName = string_clone(sBSPName);
850 if (m_eState != EIdle)
852 globalOutputStream() << "WatchBSP got a monitoring request while not idling...\n";
853 // prompt the user, should we cancel the current process and go ahead?
854 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?",
855 "Build process monitoring", eMB_YESNO ) == eIDYES)
857 // disconnect and set EIdle state
861 m_pCmd = str_ptr_array_clone(pCmd);
866 void CWatchBSP::ExternalListen()
872 // the part of the watchbsp interface we export to plugins
873 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
874 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
877 // open the listening socket
878 GetWatchBSP()->ExternalListen();