2007-11-04 03:53:53 +00:00
/*
Copyright ( c ) 2001 , Loki software , inc .
All rights reserved .
2008-07-05 10:21:36 +00:00
Redistribution and use in source and binary forms , with or without modification ,
2007-11-04 03:53:53 +00:00
are permitted provided that the following conditions are met :
2008-07-05 10:21:36 +00:00
Redistributions of source code must retain the above copyright notice , this list
2007-11-04 03:53:53 +00:00
of conditions and the following disclaimer .
Redistributions in binary form must reproduce the above copyright notice , this
list of conditions and the following disclaimer in the documentation and / or
other materials provided with the distribution .
2008-07-05 10:21:36 +00:00
Neither the name of Loki software nor the names of its contributors may be used
to endorse or promote products derived from this software without specific prior
written permission .
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ` ` AS IS ' '
AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED . IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES
( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ;
LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
2007-11-04 03:53:53 +00:00
*/
//-----------------------------------------------------------------------------
//
// DESCRIPTION:
// monitoring window for running BSP processes (and possibly various other stuff)
# include "stdafx.h"
# include "watchbsp.h"
# include "feedback.h"
# ifdef _WIN32
# include <winsock2.h>
# endif
# if defined (__linux__) || defined (__APPLE__)
# include <sys/time.h>
# define SOCKET_ERROR -1
# endif
# ifdef __APPLE__
# include <unistd.h>
# endif
# include <assert.h>
// Static functions for the SAX callbacks -------------------------------------------------------
// utility for saxStartElement below
static void abortStream ( message_info_t * data )
{
g_pParentWnd - > GetWatchBSP ( ) - > Reset ( ) ;
// tell there has been an error
if ( g_pParentWnd - > GetWatchBSP ( ) - > HasBSPPlugin ( ) )
g_BSPFrontendTable . m_pfnEndListen ( 2 ) ;
// yeah this doesn't look good.. but it's needed so that everything will be ignored until the stream goes out
data - > ignore_depth = - 1 ;
data - > recurse + + ;
}
# include "stream_version.h"
2008-07-05 10:21:36 +00:00
static void saxStartElement ( message_info_t * data , const xmlChar * name , const xmlChar * * attrs )
2007-11-04 03:53:53 +00:00
{
if ( data - > ignore_depth = = 0 )
{
if ( data - > bGeometry )
// we have a handler
{
data - > pGeometry - > saxStartElement ( data , name , attrs ) ;
}
else
{
if ( strcmp ( ( char * ) name , " q3map_feedback " ) = = 0 )
{
// check the correct version
// old q3map don't send a version attribute
// the ones we support .. send Q3MAP_STREAM_VERSION
if ( ! attrs [ 0 ] | | ! attrs [ 1 ] | | ( strcmp ( ( char * ) attrs [ 0 ] , " version " ) ! = 0 ) )
{
Sys_FPrintf ( SYS_ERR , " No stream version given in the feedback stream, this is an old q3map version. \n "
" Please turn off monitored compiling if you still wish to use this q3map executable \n " ) ;
abortStream ( data ) ;
return ;
}
else if ( strcmp ( ( char * ) attrs [ 1 ] , Q3MAP_STREAM_VERSION ) ! = 0 )
{
2008-07-05 10:21:36 +00:00
Sys_FPrintf ( SYS_ERR ,
2007-11-04 03:53:53 +00:00
" This version of Radiant reads version %s debug streams, I got an incoming connection with version %s \n "
" Please make sure your versions of Radiant and q3map are matching. \n " , Q3MAP_STREAM_VERSION , ( char * ) attrs [ 1 ] ) ;
abortStream ( data ) ;
return ;
}
}
// we don't treat locally
else if ( strcmp ( ( char * ) name , " message " ) = = 0 )
{
data - > msg_level = atoi ( ( char * ) attrs [ 1 ] ) ;
}
2008-07-05 10:21:36 +00:00
else if ( strcmp ( ( char * ) name , " polyline " ) = = 0 )
2007-11-04 03:53:53 +00:00
// polyline has a particular status .. right now we only use it for leakfile ..
{
data - > bGeometry = true ;
data - > pGeometry = & g_pointfile ;
2007-11-04 03:59:18 +00:00
data - > pGeometry - > saxStartElement ( data , name , attrs ) ;
2007-11-04 03:53:53 +00:00
}
else if ( strcmp ( ( char * ) name , " select " ) = = 0 )
{
CSelectMsg * pSelect = new CSelectMsg ( ) ;
data - > bGeometry = true ;
data - > pGeometry = pSelect ;
2007-11-04 03:59:18 +00:00
data - > pGeometry - > saxStartElement ( data , name , attrs ) ;
2007-11-04 03:53:53 +00:00
}
else if ( strcmp ( ( char * ) name , " pointmsg " ) = = 0 )
{
CPointMsg * pPoint = new CPointMsg ( ) ;
data - > bGeometry = true ;
data - > pGeometry = pPoint ;
2007-11-04 03:59:18 +00:00
data - > pGeometry - > saxStartElement ( data , name , attrs ) ;
2007-11-04 03:53:53 +00:00
}
else if ( strcmp ( ( char * ) name , " windingmsg " ) = = 0 )
{
CWindingMsg * pWinding = new CWindingMsg ( ) ;
data - > bGeometry = true ;
data - > pGeometry = pWinding ;
2007-11-04 03:59:18 +00:00
data - > pGeometry - > saxStartElement ( data , name , attrs ) ;
2007-11-04 03:53:53 +00:00
}
else
{
Sys_FPrintf ( SYS_WRN , " WARNING: ignoring unrecognized node in XML stream (%s) \n " , name ) ;
// we don't recognize this node, jump over it
// (NOTE: the ignore mechanism is a bit screwed, only works when starting an ignore at the highest level)
data - > ignore_depth = data - > recurse ;
}
}
}
data - > recurse + + ;
}
2007-11-04 03:59:18 +00:00
static void saxEndElement ( message_info_t * data , const xmlChar * name ) {
data - > recurse - - ;
// we are out of an ignored chunk
if ( data - > recurse = = data - > ignore_depth ) {
data - > ignore_depth = 0 ;
return ;
}
if ( data - > bGeometry ) {
data - > pGeometry - > saxEndElement ( data , name ) ;
// we add the object to the debug window
if ( ! data - > bGeometry ) {
g_DbgDlg . Push ( data - > pGeometry ) ;
}
}
if ( data - > recurse = = data - > stop_depth ) {
2007-11-04 03:53:53 +00:00
# ifdef _DEBUG
2007-11-04 03:59:18 +00:00
Sys_Printf ( " Received error msg .. shutting down.. \n " ) ;
2007-11-04 03:53:53 +00:00
# endif
2007-11-04 03:59:18 +00:00
g_pParentWnd - > GetWatchBSP ( ) - > Reset ( ) ;
// tell there has been an error
if ( g_pParentWnd - > GetWatchBSP ( ) - > HasBSPPlugin ( ) ) {
g_BSPFrontendTable . m_pfnEndListen ( 2 ) ;
}
return ;
}
2007-11-04 03:53:53 +00:00
}
static void saxCharacters ( message_info_t * data , const xmlChar * ch , int len )
{
if ( data - > bGeometry )
{
data - > pGeometry - > saxCharacters ( data , ch , len ) ;
}
else
{
if ( data - > ignore_depth ! = 0 )
return ;
// output the message using the level
char buf [ 1024 ] ;
memcpy ( buf , ch , len ) ;
buf [ len ] = ' \0 ' ;
Sys_FPrintf ( data - > msg_level , " %s " , buf ) ;
// if this message has error level flag, we mark the depth to stop the compilation when we get out
// we don't set the msg level if we don't stop on leak
if ( data - > msg_level = = 3 )
{
data - > stop_depth = data - > recurse - 1 ;
}
}
}
static void saxComment ( void * ctx , const xmlChar * msg )
{
Sys_Printf ( " XML comment: %s \n " , msg ) ;
}
static void saxWarning ( void * ctx , const char * msg , . . . )
{
char saxMsgBuffer [ 4096 ] ;
va_list args ;
2008-07-05 10:21:36 +00:00
2007-11-04 03:53:53 +00:00
va_start ( args , msg ) ;
vsprintf ( saxMsgBuffer , msg , args ) ;
va_end ( args ) ;
Sys_FPrintf ( SYS_WRN , " XML warning: %s \n " , saxMsgBuffer ) ;
}
static void saxError ( void * ctx , const char * msg , . . . )
{
char saxMsgBuffer [ 4096 ] ;
va_list args ;
2008-07-05 10:21:36 +00:00
2007-11-04 03:53:53 +00:00
va_start ( args , msg ) ;
vsprintf ( saxMsgBuffer , msg , args ) ;
va_end ( args ) ;
Sys_FPrintf ( SYS_ERR , " XML error: %s \n " , saxMsgBuffer ) ;
}
static void saxFatal ( void * ctx , const char * msg , . . . )
{
char buffer [ 4096 ] ;
2008-07-05 10:21:36 +00:00
2007-11-04 03:53:53 +00:00
va_list args ;
2008-07-05 10:21:36 +00:00
2007-11-04 03:53:53 +00:00
va_start ( args , msg ) ;
vsprintf ( buffer , msg , args ) ;
va_end ( args ) ;
Sys_FPrintf ( SYS_ERR , " XML fatal error: %s \n " , buffer ) ;
}
static xmlSAXHandler saxParser = {
0 , /* internalSubset */
0 , /* isStandalone */
0 , /* hasInternalSubset */
0 , /* hasExternalSubset */
0 , /* resolveEntity */
0 , /* getEntity */
0 , /* entityDecl */
0 , /* notationDecl */
0 , /* attributeDecl */
0 , /* elementDecl */
0 , /* unparsedEntityDecl */
0 , /* setDocumentLocator */
0 , /* startDocument */
0 , /* endDocument */
( startElementSAXFunc ) saxStartElement , /* startElement */
( endElementSAXFunc ) saxEndElement , /* endElement */
0 , /* reference */
( charactersSAXFunc ) saxCharacters , /* characters */
0 , /* ignorableWhitespace */
0 , /* processingInstruction */
( commentSAXFunc ) saxComment , /* comment */
( warningSAXFunc ) saxWarning , /* warning */
( errorSAXFunc ) saxError , /* error */
( fatalErrorSAXFunc ) saxFatal , /* fatalError */
} ;
// ------------------------------------------------------------------------------------------------
CWatchBSP : : ~ CWatchBSP ( )
{
Reset ( ) ;
if ( m_sBSPName )
{
delete [ ] m_sBSPName ;
m_sBSPName = NULL ;
}
Net_Shutdown ( ) ;
}
void CWatchBSP : : Reset ( )
{
if ( m_pInSocket )
{
Net_Disconnect ( m_pInSocket ) ;
m_pInSocket = NULL ;
}
if ( m_pListenSocket )
{
Net_Disconnect ( m_pListenSocket ) ;
m_pListenSocket = NULL ;
}
if ( m_xmlInputBuffer )
{
xmlFreeParserInputBuffer ( m_xmlInputBuffer ) ;
m_xmlInputBuffer = NULL ;
}
m_eState = EIdle ;
}
bool CWatchBSP : : SetupListening ( )
{
# ifdef _DEBUG
if ( m_pListenSocket )
{
Sys_Printf ( " ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening \n " ) ;
return false ;
}
# endif
Sys_Printf ( " Setting up \n " ) ;
2008-04-14 20:15:34 +00:00
if ( ! Net_Setup ( ) )
return false ;
m_pListenSocket = Net_ListenSocket ( 39000 ) ;
2007-11-04 03:53:53 +00:00
if ( m_pListenSocket = = NULL )
return false ;
2008-04-14 20:15:34 +00:00
2007-11-04 03:53:53 +00:00
Sys_Printf ( " Listening... \n " ) ;
return true ;
}
2007-11-04 03:59:18 +00:00
void CWatchBSP : : DoEBeginStep ( ) {
Reset ( ) ;
if ( ! SetupListening ( ) ) {
CString msg ;
msg = " Failed to get a listening socket on port 39000. \n Try running with BSP monitoring disabled if you can't fix this. \n " ;
Sys_Printf ( msg ) ;
gtk_MessageBox ( g_pParentWnd - > m_pWidget , msg , " BSP monitoring " , MB_OK | MB_ICONERROR ) ;
return ;
}
// set the timer for timeouts and step cancellation
g_timer_reset ( m_pTimer ) ;
g_timer_start ( m_pTimer ) ;
2007-11-04 03:53:53 +00:00
2007-11-04 03:59:18 +00:00
if ( ! m_bBSPPlugin ) {
Sys_Printf ( " === running BSP command === \n %s \n " , g_ptr_array_index ( m_pCmd , m_iCurrentStep ) ) ;
if ( ! Q_Exec ( NULL , ( char * ) g_ptr_array_index ( m_pCmd , m_iCurrentStep ) , NULL , true ) ) {
CString msg ;
msg = " Failed to execute the following command: " ;
msg + = ( char * ) g_ptr_array_index ( m_pCmd , m_iCurrentStep ) ;
msg + = " \n Check that the file exists and that you don't run out of system resources. \n " ;
Sys_Printf ( msg ) ;
gtk_MessageBox ( g_pParentWnd - > m_pWidget , msg , " BSP monitoring " , MB_OK | MB_ICONERROR ) ;
return ;
}
// re-initialise the debug window
if ( m_iCurrentStep = = 0 ) {
g_DbgDlg . Init ( ) ;
}
}
m_eState = EBeginStep ;
2007-11-04 03:53:53 +00:00
}
void CWatchBSP : : RoutineProcessing ( )
{
// used for select()
# ifdef _WIN32
TIMEVAL tout = { 0 , 0 } ;
# endif
# if defined (__linux__) || defined (__APPLE__)
timeval tout ;
tout . tv_sec = 0 ;
tout . tv_usec = 0 ;
# endif
switch ( m_eState )
{
case EBeginStep :
// timeout: if we don't get an incoming connection fast enough, go back to idle
if ( g_timer_elapsed ( m_pTimer , NULL ) > g_PrefsDlg . m_iTimeout )
{
gtk_MessageBox ( g_pParentWnd - > m_pWidget , " The connection timed out, assuming the BSP process failed \n Make sure you are using a networked version of Q3Map? \n Otherwise you need to disable BSP Monitoring in prefs. " , " BSP process monitoring " , MB_OK ) ;
Reset ( ) ;
if ( m_bBSPPlugin )
{
// status == 1 : didn't get the connection
g_BSPFrontendTable . m_pfnEndListen ( 1 ) ;
}
return ;
}
# ifdef _DEBUG
// some debug checks
if ( ! m_pListenSocket )
{
Sys_Printf ( " ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state \n " ) ;
return ;
}
# endif
// we are not connected yet, accept any incoming connection
m_pInSocket = Net_Accept ( m_pListenSocket ) ;
if ( m_pInSocket )
{
Sys_Printf ( " Connected. \n " ) ;
// prepare the message info struct for diving in
2008-07-05 10:21:36 +00:00
memset ( & m_message_info , 0 , sizeof ( message_info_s ) ) ;
2007-11-04 03:53:53 +00:00
// a dumb flag to make sure we init the push parser context when first getting a msg
m_bNeedCtxtInit = true ;
m_eState = EWatching ;
}
break ;
case EWatching :
# ifdef _DEBUG
// some debug checks
if ( ! m_pInSocket )
{
Sys_Printf ( " ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state \n " ) ;
return ;
}
# endif
// select() will identify if the socket needs an update
// if the socket is identified that means there's either a message or the connection has been closed/reset/terminated
fd_set readfds ;
int ret ;
FD_ZERO ( & readfds ) ;
FD_SET ( ( ( unsigned int ) m_pInSocket - > socket ) , & readfds ) ;
// from select man page:
// n is the highest-numbered descriptor in any of the three sets, plus 1
// (no use on windows)
ret = select ( m_pInSocket - > socket + 1 , & readfds , NULL , NULL , & tout ) ;
if ( ret = = SOCKET_ERROR )
{
Sys_Printf ( " WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing \n " ) ;
Sys_Printf ( " Terminating the connection. \n " ) ;
Reset ( ) ;
return ;
}
# ifdef _DEBUG
if ( ret = = - 1 )
{
// we are non-blocking?? we should never get timeout errors
Sys_Printf ( " WARNING: unexpected timeout expired in CWatchBSP::Processing \n " ) ;
Sys_Printf ( " Terminating the connection. \n " ) ;
Reset ( ) ;
return ;
}
# endif
if ( ret = = 1 )
{
// the socket has been identified, there's something (message or disconnection)
// see if there's anything in input
ret = Net_Receive ( m_pInSocket , & msg ) ;
if ( ret > 0 )
{
// unsigned int size = msg.size; //++timo just a check
strcpy ( m_xmlBuf , NMSG_ReadString ( & msg ) ) ;
if ( m_bNeedCtxtInit )
{
m_xmlParserCtxt = NULL ;
m_xmlParserCtxt = xmlCreatePushParserCtxt ( & saxParser , & m_message_info , m_xmlBuf , strlen ( m_xmlBuf ) , NULL ) ;
if ( m_xmlParserCtxt = = NULL )
{
Sys_FPrintf ( SYS_ERR , " Failed to create the XML parser (incoming stream began with: %s) \n " , m_xmlBuf ) ;
Reset ( ) ;
}
m_bNeedCtxtInit = false ;
}
else
{
xmlParseChunk ( m_xmlParserCtxt , m_xmlBuf , strlen ( m_xmlBuf ) , 0 ) ;
}
}
else
{
// error or connection closed/reset
// NOTE: if we get an error down the XML stream we don't reach here
Net_Disconnect ( m_pInSocket ) ;
m_pInSocket = NULL ;
Sys_Printf ( " Connection closed. \n " ) ;
if ( m_bBSPPlugin )
{
Reset ( ) ;
// let the BSP plugin know that the job is done
g_BSPFrontendTable . m_pfnEndListen ( 0 ) ;
return ;
}
// move to next step or finish
m_iCurrentStep + + ;
if ( m_iCurrentStep < m_pCmd - > len )
{
DoEBeginStep ( ) ;
}
else
{
// release the GPtrArray and the strings
if ( m_pCmd ! = NULL )
{
for ( m_iCurrentStep = 0 ; m_iCurrentStep < m_pCmd - > len ; m_iCurrentStep + + )
{
delete [ ] ( char * ) g_ptr_array_index ( m_pCmd , m_iCurrentStep ) ;
}
g_ptr_array_free ( m_pCmd , false ) ;
}
m_pCmd = NULL ;
// launch the engine .. OMG
if ( g_PrefsDlg . m_bRunQuake )
{
// do we enter sleep mode before?
if ( g_PrefsDlg . m_bDoSleep )
{
Sys_Printf ( " Going into sleep mode.. \n " ) ;
g_pParentWnd - > OnSleep ( ) ;
}
Sys_Printf ( " Running engine... \n " ) ;
Str cmd ;
// build the command line
cmd = g_pGameDescription - > mEnginePath . GetBuffer ( ) ;
// this is game dependant
2008-07-05 10:21:36 +00:00
if ( ! strcmp ( ValueForKey ( g_qeglobals . d_project_entity , " gamemode " ) , " mp " ) )
2007-11-04 03:53:53 +00:00
{
2008-07-05 10:21:36 +00:00
// MP
cmd + = g_pGameDescription - > mMultiplayerEngine . GetBuffer ( ) ;
2007-11-04 03:53:53 +00:00
}
else
{
2008-07-05 10:21:36 +00:00
// SP
2007-11-04 03:53:53 +00:00
cmd + = g_pGameDescription - > mEngine . GetBuffer ( ) ;
}
# ifdef _WIN32
// NOTE: we are using unix pathnames and CreateProcess doesn't like / in the program path
2008-07-05 10:21:36 +00:00
// FIXME: This isn't true anymore, doesn't it?
2007-11-04 03:53:53 +00:00
FindReplace ( cmd , " / " , " \\ " ) ;
# endif
Str cmdline ;
2008-07-05 10:21:36 +00:00
if ( g_pGameDescription - > quake2 )
2007-11-04 03:53:53 +00:00
{
cmdline = " . +exec radiant.cfg +map " ;
cmdline + = m_sBSPName ;
}
else
{
cmdline = " +set sv_pure 0 " ;
// TTimo: a check for vm_* but that's all fine
//cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
if ( * ValueForKey ( g_qeglobals . d_project_entity , " gamename " ) ! = ' \0 ' )
{
cmdline + = " +set fs_game " ;
cmdline + = ValueForKey ( g_qeglobals . d_project_entity , " gamename " ) ;
cmdline + = " " ;
}
//!\todo Read the start-map args from a config file.
if ( g_pGameDescription - > mGameFile = = " wolf.game " )
{
if ( ! strcmp ( ValueForKey ( g_qeglobals . d_project_entity , " gamemode " ) , " mp " ) )
{
// MP
cmdline + = " +devmap " ;
cmdline + = m_sBSPName ;
}
else
{
2008-07-05 10:21:36 +00:00
// SP
2007-11-04 03:53:53 +00:00
cmdline + = " +set nextmap \" spdevmap " ;
cmdline + = m_sBSPName ;
cmdline + = " \" " ;
}
}
else
{
cmdline + = " +devmap " ;
cmdline + = m_sBSPName ;
}
}
Sys_Printf ( " %s %s \n " , cmd . GetBuffer ( ) , cmdline . GetBuffer ( ) ) ;
// execute now
if ( ! Q_Exec ( cmd . GetBuffer ( ) , ( char * ) cmdline . GetBuffer ( ) , g_pGameDescription - > mEnginePath . GetBuffer ( ) , false ) )
{
CString msg ;
msg = " Failed to execute the following command: " ;
msg + = cmd ; msg + = cmdline ;
Sys_Printf ( msg ) ;
gtk_MessageBox ( g_pParentWnd - > m_pWidget , msg , " BSP monitoring " , MB_OK | MB_ICONERROR ) ;
}
}
Reset ( ) ;
}
}
}
break ;
default :
break ;
}
}
void CWatchBSP : : DoMonitoringLoop ( GPtrArray * pCmd , char * sBSPName )
{
if ( m_sBSPName )
{
delete [ ] m_sBSPName ;
}
m_sBSPName = sBSPName ;
if ( m_eState ! = EIdle )
{
Sys_Printf ( " WatchBSP got a monitoring request while not idling... \n " ) ;
// prompt the user, should we cancel the current process and go ahead?
2008-07-05 10:21:36 +00:00
if ( gtk_MessageBox ( g_pParentWnd - > m_pWidget , " I am already monitoring a BSP process. \n Do you want me to override and start a new compilation? " ,
2007-11-04 03:53:53 +00:00
" BSP process monitoring " , MB_YESNO ) = = IDYES )
{
// disconnect and set EIdle state
Reset ( ) ;
}
}
m_pCmd = pCmd ;
m_iCurrentStep = 0 ;
DoEBeginStep ( ) ;
}
void CWatchBSP : : ExternalListen ( )
{
m_bBSPPlugin = true ;
DoEBeginStep ( ) ;
}
// the part of the watchbsp interface we export to plugins
// NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
// for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
void WINAPI QERApp_Listen ( )
{
// open the listening socket
g_pParentWnd - > GetWatchBSP ( ) - > ExternalListen ( ) ;
}