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"
56 //#include <winsock2.h>
59 #if defined (__linux__) || defined (__APPLE__)
61 #define SOCKET_ERROR -1
69 void message_flush(message_info_t* self)
71 Sys_Print(self->msg_level, self->m_buffer, self->m_length);
75 void message_print(message_info_t* self, const char* characters, std::size_t length)
77 const char* end = characters + length;
78 while(characters != end)
80 std::size_t space = message_info_t::bufsize - 1 - self->m_length;
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;
96 #include "l_net/l_net.h"
97 #include <glib/gtimer.h>
98 #include <glib/garray.h>
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)
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;
120 // used to timeout EBeginStep
122 std::size_t m_iCurrentStep;
123 // name of the map so we can run the engine
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
133 // the xml and sax parser state
134 char m_xmlBuf[MAX_NETMESSAGE];
135 bool m_bNeedCtxtInit;
136 message_info_t m_message_info;
142 m_bBSPPlugin = false;
143 m_pListenSocket = NULL;
146 m_pTimer = g_timer_new();
148 m_xmlInputBuffer = NULL;
149 m_bNeedCtxtInit = true;
156 g_timer_destroy(m_pTimer);
159 bool HasBSPPlugin() const
160 { return m_bBSPPlugin; }
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()
171 string_release(m_sBSPName, string_length(m_sBSPName));
176 g_ptr_array_free(m_pCmd, TRUE);
180 // close everything - may be called from the outside to abort the process
182 // start a listening loop for an external process, possibly a BSP plugin
183 void ExternalListen();
186 CWatchBSP* g_pWatchBSP;
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;
202 void Build_constructPreferences(PreferencesPage& page)
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);
212 void Build_constructPage(PreferenceGroup& group)
214 PreferencesPage page(group.createPage("Build", "Build Preferences"));
215 Build_constructPreferences(page);
217 void Build_registerPreferencesPage()
219 PreferencesDialog_addSettingsPage(FreeCaller1<PreferenceGroup&, Build_constructPage>());
222 #include "preferencesystem.h"
223 #include "stringio.h"
225 void BuildMonitor_Construct()
227 g_pWatchBSP = new CWatchBSP();
229 g_WatchBSP_Enabled = !string_empty(g_pGameDescription->getKeyValue("no_bsp_monitor"));
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));
236 Build_registerPreferencesPage();
239 void BuildMonitor_Destroy()
244 CWatchBSP *GetWatchBSP()
249 void BuildMonitor_Run(GPtrArray* commands, const char* mapName)
251 GetWatchBSP()->DoMonitoringLoop(commands, mapName);
255 // Static functions for the SAX callbacks -------------------------------------------------------
257 // utility for saxStartElement below
258 static void abortStream(message_info_t *data)
260 GetWatchBSP()->EndMonitoringLoop();
261 // tell there has been an error
263 if (GetWatchBSP()->HasBSPPlugin())
264 g_BSPFrontendTable.m_pfnEndListen(2);
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;
271 #include "stream_version.h"
273 static void saxStartElement(message_info_t *data, const xmlChar *name, const xmlChar **attrs)
276 globalOutputStream() << "<" << name;
279 for(const xmlChar** p = attrs; *p != 0; p += 2)
281 globalOutputStream() << " " << p[0] << "=" << makeQuoted(p[1]);
284 globalOutputStream() << ">\n";
287 if (data->ignore_depth == 0)
289 if(data->pGeometry != 0)
292 data->pGeometry->saxStartElement (data, name, attrs);
296 if (strcmp(reinterpret_cast<const char*>(name), "q3map_feedback") == 0)
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))
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";
309 else if (strcmp(reinterpret_cast<const char*>(attrs[1]), Q3MAP_STREAM_VERSION) != 0)
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";
319 // we don't treat locally
320 else if (strcmp(reinterpret_cast<const char*>(name), "message") == 0)
322 int msg_level = atoi(reinterpret_cast<const char*>(attrs[1]));
323 if(msg_level != data->msg_level)
326 data->msg_level = msg_level;
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 ..
332 data->geometry_depth = data->recurse;
333 data->pGeometry = &g_pointfile;
334 data->pGeometry->saxStartElement (data, name, attrs);
336 else if (strcmp(reinterpret_cast<const char*>(name), "select") == 0)
338 CSelectMsg *pSelect = new CSelectMsg();
339 data->geometry_depth = data->recurse;
340 data->pGeometry = pSelect;
341 data->pGeometry->saxStartElement (data, name, attrs);
343 else if (strcmp(reinterpret_cast<const char*>(name), "pointmsg") == 0)
345 CPointMsg *pPoint = new CPointMsg();
346 data->geometry_depth = data->recurse;
347 data->pGeometry = pPoint;
348 data->pGeometry->saxStartElement (data, name, attrs);
350 else if (strcmp(reinterpret_cast<const char*>(name), "windingmsg") == 0)
352 CWindingMsg *pWinding = new CWindingMsg();
353 data->geometry_depth = data->recurse;
354 data->pGeometry = pWinding;
355 data->pGeometry->saxStartElement (data, name, attrs);
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;
369 static void saxEndElement(message_info_t *data, const xmlChar *name)
372 globalOutputStream() << "<" << name << "/>\n";
376 // we are out of an ignored chunk
377 if(data->recurse == data->ignore_depth)
379 data->ignore_depth = 0;
382 if(data->pGeometry != 0)
384 data->pGeometry->saxEndElement (data, name);
385 // we add the object to the debug window
386 if(data->geometry_depth == data->recurse)
388 g_DbgDlg.Push(data->pGeometry);
392 if (data->recurse == data->stop_depth)
396 globalOutputStream() << "Received error msg .. shutting down..\n";
398 GetWatchBSP()->EndMonitoringLoop();
399 // tell there has been an error
401 if (GetWatchBSP()->HasBSPPlugin())
402 g_BSPFrontendTable.m_pfnEndListen(2);
408 class MessageOutputStream : public TextOutputStream
410 message_info_t* m_data;
412 MessageOutputStream(message_info_t* data) : m_data(data)
415 std::size_t write(const char* buffer, std::size_t length)
417 if(m_data->pGeometry != 0)
419 m_data->pGeometry->saxCharacters(m_data, reinterpret_cast<const xmlChar*>(buffer), int(length));
423 if (m_data->ignore_depth == 0)
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)
431 m_data->stop_depth = m_data->recurse-1;
441 inline MessageOutputStream& operator<<(MessageOutputStream& ostream, const T& t)
443 return ostream_write(ostream, t);
446 static void saxCharacters(message_info_t *data, const xmlChar *ch, int len)
448 MessageOutputStream ostream(data);
449 ostream << ConvertUTF8ToLocale(StringRange(reinterpret_cast<const char*>(ch), reinterpret_cast<const char*>(ch + len)));
452 static void saxComment(void *ctx, const xmlChar *msg)
454 globalOutputStream() << "XML comment: " << reinterpret_cast<const char*>(msg) << "\n";
457 static void saxWarning(void *ctx, const char *msg, ...)
459 char saxMsgBuffer[4096];
463 vsprintf (saxMsgBuffer, msg, args);
465 globalOutputStream() << "XML warning: " << saxMsgBuffer << "\n";
468 static void saxError(void *ctx, const char *msg, ...)
470 char saxMsgBuffer[4096];
474 vsprintf (saxMsgBuffer, msg, args);
476 globalErrorStream() << "XML error: " << saxMsgBuffer << "\n";
479 static void saxFatal(void *ctx, const char *msg, ...)
486 vsprintf (buffer, msg, args);
488 globalErrorStream() << "XML fatal error: " << buffer << "\n";
491 static xmlSAXHandler saxParser = {
492 0, /* internalSubset */
493 0, /* isStandalone */
494 0, /* hasInternalSubset */
495 0, /* hasExternalSubset */
496 0, /* resolveEntity */
499 0, /* notationDecl */
500 0, /* attributeDecl */
502 0, /* unparsedEntityDecl */
503 0, /* setDocumentLocator */
504 0, /* startDocument */
506 (startElementSAXFunc)saxStartElement, /* startElement */
507 (endElementSAXFunc)saxEndElement, /* endElement */
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 */
526 // ------------------------------------------------------------------------------------------------
530 static gint watchbsp_routine(gpointer data)
532 reinterpret_cast<CWatchBSP*>(data)->RoutineProcessing();
536 void CWatchBSP::Reset()
540 Net_Disconnect(m_pInSocket);
545 Net_Disconnect(m_pListenSocket);
546 m_pListenSocket = NULL;
548 if (m_xmlInputBuffer)
550 xmlFreeParserInputBuffer (m_xmlInputBuffer);
551 m_xmlInputBuffer = NULL;
555 gtk_timeout_remove(s_routine_id);
558 bool CWatchBSP::SetupListening()
563 globalOutputStream() << "ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n";
567 globalOutputStream() << "Setting up\n";
569 m_pListenSocket = Net_ListenSocket(39000);
570 if (m_pListenSocket == NULL)
572 globalOutputStream() << "Listening...\n";
576 void CWatchBSP::DoEBeginStep()
579 if (SetupListening() == false)
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);
586 // set the timer for timeouts and step cancellation
587 g_timer_reset( m_pTimer );
588 g_timer_start( m_pTimer );
592 globalOutputStream() << "=== running build command ===\n"
593 << static_cast<const char*>(g_ptr_array_index( m_pCmd, m_iCurrentStep )) << "\n";
595 if (!Q_Exec(NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true ))
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 );
605 // re-initialise the debug window
606 if (m_iCurrentStep == 0)
609 m_eState = EBeginStep;
610 s_routine_id = gtk_timeout_add(25, watchbsp_routine, this);
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"
624 #error "unknown platform"
627 class RunEngineConfiguration
630 const char* executable;
631 const char* mp_executable;
634 RunEngineConfiguration() :
635 executable(g_pGameDescription->getRequiredKeyValue(ENGINE_ATTRIBUTE)),
636 mp_executable(g_pGameDescription->getKeyValue(MP_ENGINE_ATTRIBUTE))
638 do_sp_mp = !string_empty(mp_executable);
642 inline void GlobalGameDescription_string_write_mapparameter(StringOutputStream& string, const char* mapname)
644 if(g_pGameDescription->mGameType == "q2"
645 || g_pGameDescription->mGameType == "heretic2")
647 string << ". +exec radiant.cfg +map " << mapname;
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()))
657 string << "+set fs_game " << fs_game << " ";
659 if(g_pGameDescription->mGameType == "wolf"
660 || g_pGameDescription->mGameType == "et")
662 if (string_equal(gamemode_get(), "mp"))
665 string << "+devmap " << mapname;
670 string << "+set nextmap \"spdevmap " << mapname << "\"";
675 string << "+devmap " << mapname;
685 void CWatchBSP::RoutineProcessing()
689 TIMEVAL tout = { 0, 0 };
691 #if defined (__linux__) || defined (__APPLE__)
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 )
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 );
708 // status == 1 : didn't get the connection
709 g_BSPFrontendTable.m_pfnEndListen(1);
716 if (!m_pListenSocket)
718 globalErrorStream() << "ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n";
722 // we are not connected yet, accept any incoming connection
723 m_pInSocket = Net_Accept(m_pListenSocket);
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;
739 globalErrorStream() << "ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n";
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
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)
755 globalOutputStream() << "WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n";
756 globalOutputStream() << "Terminating the connection.\n";
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";
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 );
777 // unsigned int size = msg.size; //++timo just a check
778 strcpy (m_xmlBuf, NMSG_ReadString (&msg));
781 m_xmlParserCtxt = NULL;
782 m_xmlParserCtxt = xmlCreatePushParserCtxt (&saxParser, &m_message_info, m_xmlBuf, static_cast<int>(strlen(m_xmlBuf)), NULL);
784 if (m_xmlParserCtxt == NULL)
786 globalErrorStream() << "Failed to create the XML parser (incoming stream began with: " << m_xmlBuf << ")\n";
789 m_bNeedCtxtInit = false;
793 xmlParseChunk(m_xmlParserCtxt, m_xmlBuf, static_cast<int>(strlen(m_xmlBuf)), 0);
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 );
803 globalOutputStream() << "Connection closed.\n";
808 // let the BSP plugin know that the job is done
809 g_BSPFrontendTable.m_pfnEndListen(0);
813 // move to next step or finish
815 if (m_iCurrentStep < m_pCmd->len )
821 // launch the engine .. OMG
822 if (g_WatchBSP_RunQuake)
825 // do we enter sleep mode before?
826 if (g_WatchBSP_DoSleep)
828 globalOutputStream() << "Going into sleep mode..\n";
829 g_pParentWnd->OnSleep();
832 globalOutputStream() << "Running engine...\n";
833 StringOutputStream cmd(256);
834 // build the command line
835 cmd << EnginePath_get();
836 // this is game dependant
838 RunEngineConfiguration engineConfig;
840 if(engineConfig.do_sp_mp)
842 if (string_equal(gamemode_get(), "mp"))
844 cmd << engineConfig.mp_executable;
848 cmd << engineConfig.executable;
853 cmd << engineConfig.executable;
856 StringOutputStream cmdline;
858 GlobalGameDescription_string_write_mapparameter(cmdline, m_sBSPName);
860 globalOutputStream() << cmd.c_str() << " " << cmdline.c_str() << "\n";
863 if (!Q_Exec(cmd.c_str(), (char *)cmdline.c_str(), EnginePath_get(), false))
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 );
881 GPtrArray* str_ptr_array_clone(GPtrArray* array)
883 GPtrArray* cloned = g_ptr_array_sized_new(array->len);
884 for(guint i = 0; i < array->len; ++i)
886 g_ptr_array_add(cloned, g_strdup((char*)g_ptr_array_index(array, i)));
891 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName )
893 m_sBSPName = string_clone(sBSPName);
894 if (m_eState != EIdle)
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)
901 // disconnect and set EIdle state
905 m_pCmd = str_ptr_array_clone(pCmd);
910 void CWatchBSP::ExternalListen()
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)
921 // open the listening socket
922 GetWatchBSP()->ExternalListen();