Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1161 lines
33 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. // ServicesDlg.cpp : implementation file
  9. //
  10. #include "stdafx.h"
  11. #include "ServicesDlg.h"
  12. #include "vmpi.h"
  13. #include "bitbuf.h"
  14. #include "tier1/strtools.h"
  15. #include "patchtimeout.h"
  16. #include "SetPasswordDlg.h"
  17. #include "vmpi_browser_helpers.h"
  18. #include <io.h>
  19. #ifdef _DEBUG
  20. #define new DEBUG_NEW
  21. #undef THIS_FILE
  22. static char THIS_FILE[] = __FILE__;
  23. #endif
  24. #define SERVICE_OFF_TIMEOUT (20*1000) // If we haven't heard from a service in this long,
  25. // then we assume the service is off.
  26. #define SERVICES_PING_INTERVAL (3*1000) // ping the services every so often
  27. #define SERVICE_MAX_UPDATE_INTERVAL (8*1000) // Update each service in the listbox at least this often.
  28. int V_AfxMessageBox( int mbType, const char *pFormat, ... )
  29. {
  30. char msg[4096];
  31. va_list marker;
  32. va_start( marker, pFormat );
  33. _vsnprintf( msg, sizeof( msg ), pFormat, marker );
  34. va_end( marker );
  35. return AfxMessageBox( msg, mbType );
  36. }
  37. bool CServiceInfo::IsOff() const
  38. {
  39. return (Plat_MSTime() - m_LastPingTimeMS) > SERVICE_OFF_TIMEOUT;
  40. }
  41. // Returns the argument following pName.
  42. // If pName is the last argument on the command line, returns pEndArgDefault.
  43. // Returns NULL if there is no argument with pName.
  44. const char* FindArg( const char *pName, const char *pEndArgDefault="" )
  45. {
  46. for ( int i=0; i < __argc; i++ )
  47. {
  48. if ( stricmp( pName, __argv[i] ) == 0 )
  49. {
  50. if ( (i+1) < __argc )
  51. return __argv[i+1];
  52. else
  53. return pEndArgDefault;
  54. }
  55. }
  56. return NULL;
  57. }
  58. void AppendStr( char *dest, int destSize, const char *pFormat, ... )
  59. {
  60. char str[4096];
  61. va_list marker;
  62. va_start( marker, pFormat );
  63. _vsnprintf( str, sizeof( str ), pFormat, marker );
  64. va_end( marker );
  65. str[sizeof( str ) - 1] = 0;
  66. V_strncat( dest, str, destSize );
  67. }
  68. char* GetLastErrorString()
  69. {
  70. static char err[2048];
  71. LPVOID lpMsgBuf;
  72. FormatMessage(
  73. FORMAT_MESSAGE_ALLOCATE_BUFFER |
  74. FORMAT_MESSAGE_FROM_SYSTEM |
  75. FORMAT_MESSAGE_IGNORE_INSERTS,
  76. NULL,
  77. GetLastError(),
  78. MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
  79. (LPTSTR) &lpMsgBuf,
  80. 0,
  81. NULL
  82. );
  83. strncpy( err, (char*)lpMsgBuf, sizeof( err ) );
  84. LocalFree( lpMsgBuf );
  85. err[ sizeof( err ) - 1 ] = 0;
  86. return err;
  87. }
  88. const char* GetStatusString( CServiceInfo *pInfo )
  89. {
  90. if ( pInfo->IsOff() )
  91. return "off";
  92. else if ( pInfo->m_iState == VMPI_STATE_BUSY )
  93. return "busy";
  94. else if ( pInfo->m_iState == VMPI_STATE_PATCHING )
  95. return "patching";
  96. else if ( pInfo->m_iState == VMPI_STATE_DISABLED )
  97. return "disabled";
  98. else if ( pInfo->m_iState == VMPI_STATE_SCREENSAVER_DISABLED )
  99. return "disabled (screensaver)";
  100. else if ( pInfo->m_iState == VMPI_STATE_DOWNLOADING )
  101. return "downloading";
  102. else
  103. return "idle";
  104. }
  105. // --------------------------------------------------------------------------------------------------------- //
  106. // Column sort functions.
  107. // --------------------------------------------------------------------------------------------------------- //
  108. typedef int (CALLBACK *ServicesSortFn)( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam );
  109. static int CALLBACK SortByName( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam )
  110. {
  111. CServiceInfo *pInfo1 = (CServiceInfo*)iItem1;
  112. CServiceInfo *pInfo2 = (CServiceInfo*)iItem2;
  113. return strcmp( pInfo1->m_ComputerName, pInfo2->m_ComputerName );
  114. }
  115. static int CALLBACK SortByStatus( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam )
  116. {
  117. CServiceInfo *pInfo1 = (CServiceInfo*)iItem1;
  118. CServiceInfo *pInfo2 = (CServiceInfo*)iItem2;
  119. return -strcmp( GetStatusString( pInfo2 ), GetStatusString( pInfo1 ) );
  120. }
  121. static int CALLBACK SortByRunningTime( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam )
  122. {
  123. CServiceInfo *pInfo1 = (CServiceInfo*)iItem1;
  124. CServiceInfo *pInfo2 = (CServiceInfo*)iItem2;
  125. int v1 = pInfo1->m_LiveTimeMS / (1000*60); // Sort on minutes so it doesn't constantly resort the list.
  126. int v2 = pInfo2->m_LiveTimeMS / (1000*60);
  127. if ( v2 > v1 )
  128. return 1;
  129. else if ( v2 < v1 )
  130. return -1;
  131. else
  132. return 0;
  133. }
  134. static int CALLBACK SortByWorkerAppRunningTime( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam )
  135. {
  136. CServiceInfo *pInfo1 = (CServiceInfo*)iItem1;
  137. CServiceInfo *pInfo2 = (CServiceInfo*)iItem2;
  138. int v1 = pInfo1->m_WorkerAppTimeMS / (1000*60); // Sort on minutes so it doesn't constantly resort the list.
  139. int v2 = pInfo2->m_WorkerAppTimeMS / (1000*60);
  140. if ( v2 > v1 )
  141. return 1;
  142. else if ( v2 < v1 )
  143. return -1;
  144. else
  145. return 0;
  146. }
  147. static int CALLBACK SortByMasterName( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam )
  148. {
  149. CServiceInfo *pInfo1 = (CServiceInfo*)iItem1;
  150. CServiceInfo *pInfo2 = (CServiceInfo*)iItem2;
  151. return -strcmp( pInfo2->m_MasterName, pInfo1->m_MasterName );
  152. }
  153. static int CALLBACK SortByProtocol( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam )
  154. {
  155. CServiceInfo *pInfo1 = (CServiceInfo*)iItem1;
  156. CServiceInfo *pInfo2 = (CServiceInfo*)iItem2;
  157. return pInfo1->m_ProtocolVersion > pInfo2->m_ProtocolVersion;
  158. }
  159. static int CALLBACK SortByPassword( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam )
  160. {
  161. CServiceInfo *pInfo1 = (CServiceInfo*)iItem1;
  162. CServiceInfo *pInfo2 = (CServiceInfo*)iItem2;
  163. return -strcmp( pInfo2->m_Password, pInfo1->m_Password );
  164. }
  165. static int CALLBACK SortByServiceVersion( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam )
  166. {
  167. CServiceInfo *pInfo1 = (CServiceInfo*)iItem1;
  168. CServiceInfo *pInfo2 = (CServiceInfo*)iItem2;
  169. return -strcmp( pInfo2->m_ServiceVersion, pInfo1->m_ServiceVersion );
  170. }
  171. static int CALLBACK SortByExe( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam )
  172. {
  173. CServiceInfo *pInfo1 = (CServiceInfo*)iItem1;
  174. CServiceInfo *pInfo2 = (CServiceInfo*)iItem2;
  175. return -strcmp( pInfo2->m_ExeName, pInfo1->m_ExeName );
  176. }
  177. static int CALLBACK SortByMap( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam )
  178. {
  179. CServiceInfo *pInfo1 = (CServiceInfo*)iItem1;
  180. CServiceInfo *pInfo2 = (CServiceInfo*)iItem2;
  181. return -strcmp( pInfo2->m_MapName, pInfo1->m_MapName );
  182. }
  183. static int CALLBACK SortByCPUPercentage( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam )
  184. {
  185. CServiceInfo *pInfo1 = (CServiceInfo*)iItem1;
  186. CServiceInfo *pInfo2 = (CServiceInfo*)iItem2;
  187. if ( pInfo2->m_CPUPercentage > pInfo1->m_CPUPercentage )
  188. return 1;
  189. else if ( pInfo2->m_CPUPercentage < pInfo1->m_CPUPercentage )
  190. return -1;
  191. else
  192. return 0;
  193. }
  194. static int CALLBACK SortByMemUsage( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam )
  195. {
  196. CServiceInfo *pInfo1 = (CServiceInfo*)iItem1;
  197. CServiceInfo *pInfo2 = (CServiceInfo*)iItem2;
  198. if ( pInfo2->m_MemUsageMB > pInfo1->m_MemUsageMB )
  199. return 1;
  200. else if ( pInfo2->m_MemUsageMB < pInfo1->m_MemUsageMB )
  201. return -1;
  202. else
  203. return 0;
  204. }
  205. // --------------------------------------------------------------------------------------------------------- //
  206. // Column information.
  207. // --------------------------------------------------------------------------------------------------------- //
  208. struct
  209. {
  210. char *pText;
  211. int width;
  212. ServicesSortFn sortFn;
  213. } g_ColumnInfos[] =
  214. {
  215. {"Computer Name", 150, SortByName},
  216. {"Status", 95, SortByStatus},
  217. {"Service Run Time", 100, SortByRunningTime},
  218. {"Worker App Run Time", 125, SortByWorkerAppRunningTime},
  219. {"Master", 150, SortByMasterName},
  220. {"Password", 60, SortByPassword},
  221. {"Protocol", 60, SortByProtocol},
  222. {"Version", 50, SortByServiceVersion},
  223. {"CPU", 50, SortByCPUPercentage},
  224. {"Memory (MB)", 80, SortByMemUsage},
  225. {"Exe", 80, SortByExe},
  226. {"Map", 90, SortByMap},
  227. };
  228. #define COLUMN_COMPUTER_NAME 0
  229. #define COLUMN_STATUS 1
  230. #define COLUMN_RUNNING_TIME 2
  231. #define COLUMN_WORKER_APP_RUNNING_TIME 3
  232. #define COLUMN_MASTER_NAME 4
  233. #define COLUMN_PASSWORD 5
  234. #define COLUMN_PROTOCOL_VERSION 6
  235. #define COLUMN_SERVICE_VERSION 7
  236. #define COLUMN_CPU_PERCENTAGE 8
  237. #define COLUMN_MEM_USAGE 9
  238. #define COLUMN_EXE_NAME 10
  239. #define COLUMN_MAP_NAME 11
  240. // Used to sort all the columns.
  241. // When they click on a column, we add that index to entry 0 in here.
  242. // Then when sorting, if 2 columns are equal, we move to the next sort function.
  243. CUtlVector<int> g_SortColumns;
  244. static void PushSortColumn( int iColumn )
  245. {
  246. // First, get rid of all entries with this same index.
  247. int iPrev = g_SortColumns.Find( iColumn );
  248. if ( iPrev != g_SortColumns.InvalidIndex() )
  249. g_SortColumns.Remove( iPrev );
  250. // Now add this one.
  251. g_SortColumns.AddToTail( iColumn );
  252. }
  253. static int CALLBACK MainSortFn( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam )
  254. {
  255. int curStatus = 0;
  256. for ( int i = (int)g_SortColumns.Count()-1; i >= 0; i-- )
  257. {
  258. curStatus = g_ColumnInfos[g_SortColumns[i]].sortFn( iItem1, iItem2, lpParam );
  259. if ( curStatus != 0 )
  260. break;
  261. }
  262. return curStatus;
  263. }
  264. /////////////////////////////////////////////////////////////////////////////
  265. // CServicesDlg dialog
  266. CServicesDlg::CServicesDlg(CWnd* pParent /*=NULL*/)
  267. : CIdleDialog(CServicesDlg::IDD, pParent)
  268. {
  269. //{{AFX_DATA_INIT(CServicesDlg)
  270. // NOTE: the ClassWizard will add member initialization here
  271. //}}AFX_DATA_INIT
  272. m_pServicesPingSocket = NULL;
  273. }
  274. void CServicesDlg::DoDataExchange(CDataExchange* pDX)
  275. {
  276. CDialog::DoDataExchange(pDX);
  277. //{{AFX_DATA_MAP(CServicesDlg)
  278. DDX_Control(pDX, IDC_NUM_SERVICES, m_NumServicesControl);
  279. DDX_Control(pDX, IDC_NUM_DISABLED_SERVICES, m_NumDisabledServicesControl);
  280. DDX_Control(pDX, IDC_NUM_WORKING_SERVICES, m_NumWorkingServicesControl);
  281. DDX_Control(pDX, IDC_NUM_WAITING_SERVICES, m_NumWaitingServicesControl);
  282. DDX_Control(pDX, IDC_NUM_OFF_SERVICES, m_NumOffServicesControl);
  283. DDX_Control(pDX, IDC_CURRENT_PASSWORD, m_PasswordDisplay);
  284. DDX_Control(pDX, IDC_SERVICES_LIST, m_ServicesList);
  285. //}}AFX_DATA_MAP
  286. }
  287. BEGIN_MESSAGE_MAP(CServicesDlg, CIdleDialog)
  288. //{{AFX_MSG_MAP(CServicesDlg)
  289. ON_BN_CLICKED(ID_PATCH_SERVICES, OnPatchServices)
  290. ON_BN_CLICKED(ID_STOP_SERVICES, OnStopServices)
  291. ON_BN_CLICKED(ID_STOP_JOBS, OnStopJobs)
  292. ON_BN_CLICKED(ID_FILTER_BY_PASSWORD, OnFilterByPassword)
  293. ON_BN_CLICKED(ID_FORCE_PASSWORD, OnForcePassword)
  294. ON_BN_CLICKED(ID_COPY_TO_CLIPBOARD, OnCopyToClipboard)
  295. ON_NOTIFY(NM_DBLCLK, IDC_SERVICES_LIST, OnDblclkServicesList)
  296. ON_WM_SIZE()
  297. //}}AFX_MSG_MAP
  298. END_MESSAGE_MAP()
  299. /////////////////////////////////////////////////////////////////////////////
  300. // CServicesDlg message handlers
  301. BOOL CServicesDlg::OnInitDialog()
  302. {
  303. CDialog::OnInitDialog();
  304. // Initially, just sort by computer name.
  305. PushSortColumn( COLUMN_COMPUTER_NAME );
  306. HICON hIcon = LoadIcon( AfxGetInstanceHandle(), MAKEINTRESOURCE( IDR_MAINFRAME ) );
  307. SetIcon( hIcon, true );
  308. m_ServicesList.SetExtendedStyle( LVS_EX_FULLROWSELECT );
  309. // Setup the headers.
  310. for ( int i=0; i < ARRAYSIZE( g_ColumnInfos ); i++ )
  311. {
  312. m_ServicesList.InsertColumn( i, g_ColumnInfos[i].pText, LVCFMT_LEFT, g_ColumnInfos[i].width, i );
  313. }
  314. m_pServicesPingSocket = CreateIPSocket();
  315. if ( m_pServicesPingSocket )
  316. {
  317. m_pServicesPingSocket->BindToAny( 0 );
  318. }
  319. m_dwLastServicesPing = GetTickCount() - SERVICES_PING_INTERVAL;
  320. StartIdleProcessing( 100 ); // get idle messages every half second
  321. m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_SERVICES_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  322. m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  323. m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_DISABLED_SERVICES_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  324. m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_DISABLED_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  325. m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_WORKING_SERVICES_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  326. m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_WORKING_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  327. m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_WAITING_SERVICES_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  328. m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_WAITING_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  329. m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_OFF_SERVICES_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  330. m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_OFF_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  331. m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_CURRENT_PASSWORD_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  332. m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_CURRENT_PASSWORD ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  333. m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_PATCH_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  334. m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_STOP_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  335. m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_STOP_JOBS ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  336. m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_FORCE_PASSWORD ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  337. m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_FILTER_BY_PASSWORD ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  338. m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_COPY_TO_CLIPBOARD ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM );
  339. m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_SERVICES_LIST ), ANCHOR_LEFT, ANCHOR_TOP, ANCHOR_RIGHT, ANCHOR_BOTTOM );
  340. // Unless they specify admin mode, hide all the controls that can mess with the services.
  341. if ( !FindArg( "-Admin" ) )
  342. {
  343. ::ShowWindow( ::GetDlgItem( m_hWnd, ID_PATCH_SERVICES ), SW_HIDE );
  344. ::ShowWindow( ::GetDlgItem( m_hWnd, ID_STOP_SERVICES ), SW_HIDE );
  345. ::ShowWindow( ::GetDlgItem( m_hWnd, ID_STOP_JOBS ), SW_HIDE );
  346. ::ShowWindow( ::GetDlgItem( m_hWnd, ID_FORCE_PASSWORD ), SW_HIDE );
  347. }
  348. m_NetViewThread.Init();
  349. return TRUE; // return TRUE unless you set the focus to a control
  350. // EXCEPTION: OCX Property Pages should return FALSE
  351. }
  352. void CServicesDlg::BuildVMPIPingPacket( CUtlVector<char> &out, char cPacketID, unsigned char protocolVersion, bool bIgnorePassword )
  353. {
  354. out.Purge();
  355. out.AddToTail( protocolVersion );
  356. const char *pPassword = m_Password;
  357. if ( pPassword[0] == 0 || bIgnorePassword )
  358. {
  359. // If they haven't set a password to filter by, then we want all services to report in.
  360. out.AddToTail( VMPI_PASSWORD_OVERRIDE );
  361. out.AddToTail( 0 );
  362. }
  363. else
  364. {
  365. out.AddMultipleToTail( strlen( pPassword ) + 1, pPassword ); // password.
  366. }
  367. out.AddToTail( cPacketID );
  368. }
  369. void CServicesDlg::UpdateServicesFromNetMessages()
  370. {
  371. while ( 1 )
  372. {
  373. char in[1024];
  374. CIPAddr ipFrom;
  375. int len = m_pServicesPingSocket->RecvFrom( in, sizeof( in ), &ipFrom );
  376. if ( len < 4 )
  377. break;
  378. bf_read buf( in, len );
  379. unsigned char protocolVersion = buf.ReadByte();
  380. if ( protocolVersion == 4 || protocolVersion == VMPI_PROTOCOL_VERSION ) // Protocol version 4 is almost the same.
  381. {
  382. int packetID = buf.ReadByte();
  383. if ( packetID == VMPI_PING_RESPONSE )
  384. {
  385. int iState = buf.ReadByte();
  386. unsigned long liveTimeMS = (unsigned long)buf.ReadLong();
  387. int iPort = buf.ReadLong();
  388. char computerName[512];
  389. buf.ReadString( computerName, sizeof( computerName ) );
  390. char masterName[512];
  391. if ( buf.GetNumBitsLeft() )
  392. buf.ReadString( masterName, sizeof( masterName ) );
  393. else
  394. masterName[0] = 0;
  395. unsigned long workerAppTimeMS = 0;
  396. if ( buf.GetNumBitsLeft() )
  397. workerAppTimeMS = buf.ReadLong();
  398. char password[512] = {0};
  399. if ( protocolVersion == VMPI_PROTOCOL_VERSION )
  400. buf.ReadString( password, sizeof( password ) );
  401. char serviceVersion[32];
  402. if ( protocolVersion == VMPI_PROTOCOL_VERSION )
  403. buf.ReadString( serviceVersion, sizeof( serviceVersion ) );
  404. else
  405. V_strncpy( serviceVersion, "old", sizeof( serviceVersion ) );
  406. int cpuPercentage = -1;
  407. if ( buf.GetNumBytesLeft() >= 1 )
  408. cpuPercentage = buf.ReadByte();
  409. char exeName[512];
  410. if ( buf.GetNumBytesLeft() >= 1 )
  411. buf.ReadString( exeName, sizeof( exeName ) );
  412. else
  413. V_strncpy( exeName, "-", sizeof( exeName ) );
  414. short memUsage = -1;
  415. if ( buf.GetNumBytesLeft() >= 2 )
  416. memUsage = buf.ReadShort();
  417. char mapName[256];
  418. if ( buf.GetNumBytesLeft() >= 1 )
  419. buf.ReadString( mapName, sizeof( mapName ) );
  420. else
  421. V_strncpy( mapName, "-", sizeof( mapName ) );
  422. CServiceInfo *pInfo = FindServiceByComputerName( computerName );
  423. if ( !pInfo )
  424. {
  425. pInfo = new CServiceInfo;
  426. m_Services.AddToTail( pInfo );
  427. pInfo->m_ComputerName = computerName;
  428. pInfo->m_pLastStatusText = NULL;
  429. pInfo->m_Addr.port = iPort;
  430. pInfo->m_LastPingTimeMS = Plat_MSTime();
  431. pInfo->m_MasterName = "?";
  432. int iItem = m_ServicesList.InsertItem( COLUMN_COMPUTER_NAME, pInfo->m_ComputerName, NULL );
  433. m_ServicesList.SetItemData( iItem, (DWORD)pInfo );
  434. // Update the display of # of services.
  435. UpdateServiceCountDisplay();
  436. }
  437. pInfo->m_ProtocolVersion = protocolVersion;
  438. V_strncpy( pInfo->m_ServiceVersion, serviceVersion, sizeof( pInfo->m_ServiceVersion ) );
  439. pInfo->m_Addr = ipFrom;
  440. pInfo->m_CPUPercentage = cpuPercentage;
  441. pInfo->m_MemUsageMB = memUsage;
  442. pInfo->m_ExeName = exeName;
  443. pInfo->m_MapName = mapName;
  444. pInfo->m_MasterName = masterName;
  445. pInfo->m_LiveTimeMS = liveTimeMS;
  446. pInfo->m_WorkerAppTimeMS = workerAppTimeMS;
  447. pInfo->m_iState = iState;
  448. pInfo->m_LastPingTimeMS = Plat_MSTime();
  449. pInfo->m_Password = password;
  450. UpdateServiceInListbox( pInfo );
  451. }
  452. }
  453. }
  454. }
  455. void CServicesDlg::UpdateServicesFromNetView()
  456. {
  457. CUtlVector<char*> computerNames;
  458. m_NetViewThread.GetComputerNames( computerNames );
  459. for ( int i=0; i < computerNames.Count(); i++ )
  460. {
  461. CServiceInfo *pInfo = FindServiceByComputerName( computerNames[i] );
  462. if ( !pInfo )
  463. {
  464. pInfo = new CServiceInfo;
  465. m_Services.AddToTail( pInfo );
  466. pInfo->m_ComputerName = computerNames[i];
  467. pInfo->m_LastPingTimeMS = Plat_MSTime() - SERVICE_OFF_TIMEOUT*2; // so it's marked as "off"
  468. pInfo->m_LastLiveTimeMS = Plat_MSTime();
  469. pInfo->m_LiveTimeMS = 0;
  470. pInfo->m_WorkerAppTimeMS = 0;
  471. pInfo->m_ProtocolVersion = 0;
  472. pInfo->m_ServiceVersion[0] = 0;
  473. pInfo->m_CPUPercentage = -1;
  474. pInfo->m_MemUsageMB = -1;
  475. int iItem = m_ServicesList.InsertItem( COLUMN_COMPUTER_NAME, pInfo->m_ComputerName, NULL );
  476. m_ServicesList.SetItemData( iItem, (DWORD)pInfo );
  477. // Update the display of # of services.
  478. UpdateServiceCountDisplay();
  479. UpdateServiceInListbox( pInfo );
  480. }
  481. }
  482. computerNames.PurgeAndDeleteElements();
  483. }
  484. void CServicesDlg::OnIdle()
  485. {
  486. DWORD curTime = GetTickCount();
  487. if ( !m_pServicesPingSocket )
  488. return;
  489. // Broadcast out to all the services?
  490. if ( curTime - m_dwLastServicesPing >= SERVICES_PING_INTERVAL )
  491. {
  492. m_dwLastServicesPing = curTime;
  493. for ( int i=VMPI_SERVICE_PORT; i <= VMPI_LAST_SERVICE_PORT; i++ )
  494. {
  495. CUtlVector<char> data;
  496. BuildVMPIPingPacket( data, VMPI_PING_REQUEST );
  497. m_pServicesPingSocket->Broadcast( data.Base(), data.Count(), i );
  498. // Also send out a version 4 one because we understand a version 4 response.
  499. BuildVMPIPingPacket( data, VMPI_PING_REQUEST, 4 );
  500. m_pServicesPingSocket->Broadcast( data.Base(), data.Count(), i );
  501. }
  502. UpdateServiceCountDisplay();
  503. }
  504. m_ServicesList.SetRedraw( false );
  505. // Check for messages from services.
  506. UpdateServicesFromNetMessages();
  507. // Issue a "net view" command, parse the output, and add any computers it lists.
  508. // This lets us figure out which PCs on the network are not running the service.
  509. UpdateServicesFromNetView();
  510. FOR_EACH_LL( m_Services, iService )
  511. {
  512. CServiceInfo *pInfo = m_Services[iService];
  513. if ( Plat_MSTime() - pInfo->m_LastUpdateTime > SERVICE_MAX_UPDATE_INTERVAL )
  514. {
  515. UpdateServiceInListbox( pInfo );
  516. }
  517. }
  518. if ( m_bListChanged )
  519. {
  520. ResortItems();
  521. m_bListChanged = false;
  522. }
  523. m_ServicesList.SetRedraw( true );
  524. }
  525. CServiceInfo* CServicesDlg::FindServiceByComputerName( const char *pComputerName )
  526. {
  527. FOR_EACH_LL( m_Services, i )
  528. {
  529. if ( Q_stricmp( m_Services[i]->m_ComputerName, pComputerName ) == 0 )
  530. return m_Services[i];
  531. }
  532. return NULL;
  533. }
  534. void CServicesDlg::SendToSelectedServices( const char *pData, int len )
  535. {
  536. POSITION pos = m_ServicesList.GetFirstSelectedItemPosition();
  537. while ( pos )
  538. {
  539. int iItem = m_ServicesList.GetNextSelectedItem( pos );
  540. CServiceInfo *pInfo = (CServiceInfo*)m_ServicesList.GetItemData( iItem );
  541. m_pServicesPingSocket->SendTo( &pInfo->m_Addr, pData, len );
  542. }
  543. }
  544. void UpdateItemText( CListCtrl &ctrl, int iItem, int iColumn, const char *pNewVal )
  545. {
  546. CString str = ctrl.GetItemText( iItem, iColumn );
  547. if ( V_stricmp( str, pNewVal ) != 0 )
  548. ctrl.SetItemText( iItem, iColumn, pNewVal );
  549. }
  550. void CServicesDlg::UpdateServiceInListbox( CServiceInfo *pInfo )
  551. {
  552. // First, find this item in the listbox.
  553. LVFINDINFO info;
  554. info.flags = LVFI_PARAM;
  555. info.lParam = (LPARAM)pInfo;
  556. int iItem = m_ServicesList.FindItem( &info );
  557. if ( iItem != -1 )
  558. {
  559. UpdateItemText( m_ServicesList, iItem, COLUMN_COMPUTER_NAME, pInfo->m_ComputerName );
  560. const char *pText = GetStatusString( pInfo );
  561. UpdateItemText( m_ServicesList, iItem, COLUMN_STATUS, pText );
  562. char timeStr[512];
  563. FormatTimeString( pInfo->m_LiveTimeMS / 1000, timeStr, sizeof( timeStr ) );
  564. UpdateItemText( m_ServicesList, iItem, COLUMN_RUNNING_TIME, timeStr );
  565. FormatTimeString( pInfo->m_WorkerAppTimeMS / 1000, timeStr, sizeof( timeStr ) );
  566. UpdateItemText( m_ServicesList, iItem, COLUMN_WORKER_APP_RUNNING_TIME, timeStr );
  567. UpdateItemText( m_ServicesList, iItem, COLUMN_MASTER_NAME, pInfo->m_MasterName );
  568. char str[512];
  569. V_snprintf( str, sizeof( str ), "%d", pInfo->m_ProtocolVersion );
  570. UpdateItemText( m_ServicesList, iItem, COLUMN_PROTOCOL_VERSION, str );
  571. UpdateItemText( m_ServicesList, iItem, COLUMN_PASSWORD, pInfo->m_Password );
  572. UpdateItemText( m_ServicesList, iItem, COLUMN_SERVICE_VERSION, pInfo->m_ServiceVersion );
  573. if ( pInfo->m_CPUPercentage == -1 )
  574. V_snprintf( str, sizeof( str ), "-" );
  575. else if ( pInfo->m_CPUPercentage == 101 )
  576. V_snprintf( str, sizeof( str ), "(err)" );
  577. else
  578. V_snprintf( str, sizeof( str ), "%d%%", pInfo->m_CPUPercentage );
  579. UpdateItemText( m_ServicesList, iItem, COLUMN_CPU_PERCENTAGE, str );
  580. UpdateItemText( m_ServicesList, iItem, COLUMN_EXE_NAME, pInfo->m_ExeName );
  581. UpdateItemText( m_ServicesList, iItem, COLUMN_MAP_NAME, pInfo->m_MapName );
  582. if ( pInfo->m_MemUsageMB == -1 )
  583. V_snprintf( str, sizeof( str ), "-" );
  584. else
  585. V_snprintf( str, sizeof( str ), "%d", pInfo->m_MemUsageMB );
  586. UpdateItemText( m_ServicesList, iItem, COLUMN_MEM_USAGE, str );
  587. pInfo->m_pLastStatusText = pText;
  588. pInfo->m_LastLiveTimeMS = pInfo->m_LiveTimeMS;
  589. pInfo->m_LastMasterName = pInfo->m_MasterName;
  590. pInfo->m_LastUpdateTime = Plat_MSTime();
  591. // Detect changes.
  592. if ( !m_bListChanged && iItem > 0 )
  593. {
  594. CServiceInfo *pPrevItem = (CServiceInfo*)m_ServicesList.GetItemData( iItem-1 );
  595. if ( pPrevItem && MainSortFn( (LPARAM)pPrevItem, (LPARAM)pInfo, NULL ) > 0 )
  596. m_bListChanged = true;
  597. }
  598. if ( !m_bListChanged && (iItem+1) < m_ServicesList.GetItemCount() )
  599. {
  600. CServiceInfo *pNextItem = (CServiceInfo*)m_ServicesList.GetItemData( iItem+1 );
  601. if ( pNextItem && MainSortFn( (LPARAM)pInfo, (LPARAM)pNextItem, NULL ) > 0 )
  602. m_bListChanged = true;
  603. }
  604. }
  605. }
  606. void CServicesDlg::ResortItems()
  607. {
  608. m_ServicesList.SortItems( MainSortFn, (LPARAM)this );
  609. }
  610. void CServicesDlg::UpdateServiceCountDisplay()
  611. {
  612. char str[512];
  613. Q_snprintf( str, sizeof( str ), "%d", m_Services.Count() );
  614. m_NumServicesControl.SetWindowText( str );
  615. // Now count the various types.
  616. int nDisabled = 0, nWorking = 0, nWaiting = 0, nOff = 0;
  617. FOR_EACH_LL( m_Services, i )
  618. {
  619. if ( m_Services[i]->IsOff() )
  620. {
  621. ++nOff;
  622. }
  623. else if ( m_Services[i]->m_iState == VMPI_STATE_BUSY || m_Services[i]->m_iState == VMPI_STATE_DOWNLOADING )
  624. {
  625. ++nWorking;
  626. }
  627. else if ( m_Services[i]->m_iState == VMPI_STATE_IDLE )
  628. {
  629. ++nWaiting;
  630. }
  631. else
  632. {
  633. ++nDisabled;
  634. }
  635. }
  636. Q_snprintf( str, sizeof( str ), "%d", nDisabled );
  637. m_NumDisabledServicesControl.SetWindowText( str );
  638. Q_snprintf( str, sizeof( str ), "%d", nWorking );
  639. m_NumWorkingServicesControl.SetWindowText( str );
  640. Q_snprintf( str, sizeof( str ), "%d", nWaiting );
  641. m_NumWaitingServicesControl.SetWindowText( str );
  642. Q_snprintf( str, sizeof( str ), "%d", nOff );
  643. m_NumOffServicesControl.SetWindowText( str );
  644. }
  645. // This monstrosity is here because of the way they bundle string resources into groups in an exe file.
  646. // See http://support.microsoft.com/kb/q196774/.
  647. bool FindStringResourceEx( HINSTANCE hinst, UINT uId, UINT langId, char *pStr, int outLen )
  648. {
  649. // Convert the string ID into a bundle number
  650. bool bRet = false;
  651. HRSRC hrsrc = FindResourceEx(hinst, RT_STRING, MAKEINTRESOURCE(uId / 16 + 1), langId);
  652. if (hrsrc)
  653. {
  654. HGLOBAL hglob = LoadResource(hinst, hrsrc);
  655. if (hglob)
  656. {
  657. LPCWSTR pwsz = reinterpret_cast<LPCWSTR>( LockResource(hglob) );
  658. if (pwsz)
  659. {
  660. // okay now walk the string table
  661. for (UINT i = 0; i < (uId & 15); i++)
  662. {
  663. pwsz += 1 + (UINT)*pwsz;
  664. }
  665. // First word in the resource is the length and the rest is the data.
  666. int nChars = min( (int)pwsz[0], outLen-1 );
  667. ++pwsz;
  668. V_wcstostr( pwsz, nChars, pStr, outLen );
  669. pStr[nChars] = 0;
  670. bRet = true;
  671. }
  672. FreeResource(hglob);
  673. }
  674. }
  675. return bRet;
  676. }
  677. int CheckServiceVersion( const char *pPatchDir, char *pServiceVersion, int maxServiceVersionLen )
  678. {
  679. char filename[MAX_PATH];
  680. V_ComposeFileName( pPatchDir, "vmpi_service.exe", filename, sizeof( filename ) );
  681. int ret = IDCANCEL;
  682. HINSTANCE hInst = LoadLibrary( filename );
  683. if ( hInst )
  684. {
  685. bool bFound = FindStringResourceEx( hInst, VMPI_SERVICE_IDS_VERSION_STRING, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), pServiceVersion, maxServiceVersionLen );
  686. if ( bFound )
  687. {
  688. ret = V_AfxMessageBox( MB_YESNOCANCEL, "Service version in %s is %s.\n\nIs this correct?", filename, pServiceVersion );
  689. }
  690. else
  691. {
  692. V_AfxMessageBox( MB_OK, "Can't get IDS_VERSION_STRING resource from %s.", filename );
  693. }
  694. FreeLibrary( hInst );
  695. }
  696. else
  697. {
  698. V_AfxMessageBox( MB_OK, "Can't load %s to get service version.", filename );
  699. }
  700. return ret;
  701. }
  702. void CServicesDlg::OnPatchServices()
  703. {
  704. // Inquire about the timeout.
  705. CPatchTimeout dlg;
  706. dlg.m_PatchDirectory = "\\\\fileserver\\vmpi\\testservice";
  707. dlg.m_VMPITransferDirectory = dlg.m_PatchDirectory;
  708. dlg.m_bForcePatch = false;
  709. TryAgain:;
  710. if ( dlg.DoModal() == IDOK )
  711. {
  712. // Launch the transfer app.
  713. char commandLine[32 * 1024] = {0};
  714. char transferExe[MAX_PATH];
  715. V_ComposeFileName( dlg.m_VMPITransferDirectory, "vmpi_transfer.exe", transferExe, sizeof( transferExe ) );
  716. if ( _access( transferExe, 0 ) != 0 )
  717. {
  718. V_AfxMessageBox( MB_OK, "Can't find '%s' to run the patch.", transferExe );
  719. goto TryAgain;
  720. }
  721. char strServiceVersion[64];
  722. int ret = CheckServiceVersion( dlg.m_PatchDirectory, strServiceVersion, sizeof( strServiceVersion ) );
  723. if ( ret == IDCANCEL )
  724. return;
  725. else if ( ret == IDNO )
  726. goto TryAgain;
  727. AppendStr( commandLine, sizeof( commandLine ), "\"%s\" -PatchHost", transferExe );
  728. AppendStr( commandLine, sizeof( commandLine ), " -mpi_PatchVersion %s", strServiceVersion );
  729. AppendStr( commandLine, sizeof( commandLine ), " -mpi_PatchDirectory \"%s\"", (const char*)dlg.m_PatchDirectory );
  730. if ( dlg.m_bForcePatch )
  731. AppendStr( commandLine, sizeof( commandLine ), " -mpi_ForcePatch" );
  732. // Collect the list of addresses.
  733. CUtlVector<CIPAddr> addrs;
  734. POSITION pos = m_ServicesList.GetFirstSelectedItemPosition();
  735. while ( pos )
  736. {
  737. int iItem = m_ServicesList.GetNextSelectedItem( pos );
  738. CServiceInfo *pInfo = (CServiceInfo*)m_ServicesList.GetItemData( iItem );
  739. if ( pInfo->m_Addr.ip[0] != 0 ) // "off" services won't have an IP
  740. addrs.AddToTail( pInfo->m_Addr );
  741. }
  742. if ( addrs.Count() == 0 )
  743. {
  744. AfxMessageBox( "No workers selected, or they all are off." );
  745. return;
  746. }
  747. AppendStr( commandLine, sizeof( commandLine ), " -mpi_PatchWorkers %d", addrs.Count() );
  748. for ( int i=0; i < addrs.Count(); i++ )
  749. {
  750. AppendStr( commandLine, sizeof( commandLine ), " %d.%d.%d.%d", addrs[i].ip[0], addrs[i].ip[1], addrs[i].ip[2], addrs[i].ip[3] );
  751. }
  752. STARTUPINFO si;
  753. memset( &si, 0, sizeof( si ) );
  754. si.cb = sizeof( si );
  755. PROCESS_INFORMATION pi;
  756. memset( &pi, 0, sizeof( pi ) );
  757. if ( CreateProcess( NULL, commandLine,
  758. NULL, NULL, false,
  759. 0,
  760. NULL,
  761. (const char *)dlg.m_PatchDirectory,
  762. &si,
  763. &pi ) )
  764. {
  765. CloseHandle( pi.hProcess );
  766. CloseHandle( pi.hThread );
  767. V_AfxMessageBox( MB_OK, "Patch master successfully started.\nServices patching now.\nClose the patch master console app when finished." );
  768. }
  769. else
  770. {
  771. V_AfxMessageBox( MB_OK, "Error starting patch master: %s", GetLastErrorString() );
  772. }
  773. }
  774. }
  775. void CServicesDlg::OnStopServices()
  776. {
  777. if ( MessageBox( "Warning: if you stop these services, you won't be able to control them from this application, and must restart them manually. Contine?", "Warning", MB_YESNO ) == IDYES )
  778. {
  779. CUtlVector<char> data;
  780. BuildVMPIPingPacket( data, VMPI_STOP_SERVICE );
  781. SendToSelectedServices( data.Base(), data.Count() );
  782. }
  783. }
  784. void CServicesDlg::OnStopJobs()
  785. {
  786. CUtlVector<char> data;
  787. BuildVMPIPingPacket( data, VMPI_KILL_PROCESS );
  788. SendToSelectedServices( data.Base(), data.Count() );
  789. }
  790. void CServicesDlg::OnFilterByPassword()
  791. {
  792. CSetPasswordDlg dlg( IDD_SET_PASSWORD );
  793. dlg.m_Password = m_Password;
  794. if ( dlg.DoModal() == IDOK )
  795. {
  796. m_Password = dlg.m_Password;
  797. m_PasswordDisplay.SetWindowText( m_Password );
  798. m_Services.PurgeAndDeleteElements();
  799. m_ServicesList.DeleteAllItems();
  800. UpdateServiceCountDisplay();
  801. // Re-ping everyone immediately.
  802. m_dwLastServicesPing = GetTickCount() - SERVICES_PING_INTERVAL;
  803. }
  804. }
  805. // This sets a new password on the selected services.
  806. void CServicesDlg::OnForcePassword()
  807. {
  808. CSetPasswordDlg dlg( IDD_FORCE_PASSWORD );
  809. dlg.m_Password = "password";
  810. if ( dlg.DoModal() == IDOK )
  811. {
  812. CUtlVector<char> data;
  813. BuildVMPIPingPacket( data, VMPI_FORCE_PASSWORD_CHANGE, VMPI_PROTOCOL_VERSION, true );
  814. const char *pNewPassword = dlg.m_Password;
  815. data.AddMultipleToTail( V_strlen( pNewPassword ) + 1, pNewPassword );
  816. SendToSelectedServices( data.Base(), data.Count() );
  817. }
  818. }
  819. void CServicesDlg::OnDblclkServicesList(NMHDR* pNMHDR, LRESULT* pResult)
  820. {
  821. POSITION pos = m_ServicesList.GetFirstSelectedItemPosition();
  822. if ( pos )
  823. {
  824. int iItem = m_ServicesList.GetNextSelectedItem( pos );
  825. if ( iItem != -1 )
  826. {
  827. CServiceInfo *pInfo = (CServiceInfo*)m_ServicesList.GetItemData( iItem );
  828. if ( pInfo )
  829. {
  830. // Launch vmpi_browser_job_search and have it auto-select this worker.
  831. char cmdLine[1024];
  832. Q_snprintf( cmdLine, sizeof( cmdLine ), "vmpi_job_search -SelectWorker %s", (const char*)pInfo->m_ComputerName );
  833. STARTUPINFO si;
  834. memset( &si, 0, sizeof( si ) );
  835. si.cb = sizeof( si );
  836. PROCESS_INFORMATION pi;
  837. memset( &pi, 0, sizeof( pi ) );
  838. if ( !CreateProcess(
  839. NULL,
  840. (char*)(const char*)cmdLine,
  841. NULL, // security
  842. NULL,
  843. TRUE,
  844. 0, // flags
  845. NULL, // environment
  846. NULL, // current directory
  847. &si,
  848. &pi ) )
  849. {
  850. char err[512];
  851. Q_snprintf( err, sizeof( err ), "Can't run '%s'", (LPCTSTR)cmdLine );
  852. MessageBox( err, "Error", MB_OK );
  853. }
  854. }
  855. }
  856. }
  857. *pResult = 0;
  858. }
  859. void CServicesDlg::OnSize(UINT nType, int cx, int cy)
  860. {
  861. CIdleDialog::OnSize(nType, cx, cy);
  862. m_AnchorMgr.UpdateAnchors( this );
  863. }
  864. BOOL CServicesDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
  865. {
  866. NMHDR *pHdr = (NMHDR*)lParam;
  867. if ( pHdr->idFrom == IDC_SERVICES_LIST )
  868. {
  869. if ( pHdr->code == LVN_COLUMNCLICK )
  870. {
  871. LPNMLISTVIEW pListView = (LPNMLISTVIEW)lParam;
  872. // Now sort by this column.
  873. int iSortColumn = max( 0, min( pListView->iSubItem, (int)ARRAYSIZE( g_ColumnInfos ) - 1 ) );
  874. PushSortColumn( iSortColumn );
  875. ResortItems();
  876. }
  877. }
  878. return CIdleDialog::OnNotify(wParam, lParam, pResult);
  879. }
  880. void CServicesDlg::BuildClipboardText( CUtlVector<char> &clipboardText )
  881. {
  882. // Add the header information.
  883. CHeaderCtrl *pHeader = m_ServicesList.GetHeaderCtrl();
  884. for ( int i=0; i < pHeader->GetItemCount(); i++ )
  885. {
  886. char tempBuffer[512];
  887. HDITEM item;
  888. memset( &item, 0, sizeof( item ) );
  889. item.mask = HDI_TEXT;
  890. item.pszText = tempBuffer;
  891. item.cchTextMax = sizeof( tempBuffer ) - 1;
  892. if ( !pHeader->GetItem( i, &item ) )
  893. item.pszText = "<bug>";
  894. clipboardText.AddMultipleToTail( strlen( item.pszText ), item.pszText );
  895. clipboardText.AddToTail( '\t' );
  896. }
  897. clipboardText.AddMultipleToTail( 2, "\r\n" );
  898. // Now add each line of data.
  899. int nItem = -1;
  900. while ( (nItem = m_ServicesList.GetNextItem( nItem, LVNI_ALL )) != -1 )
  901. {
  902. char tempBuffer[512];
  903. LVITEM item;
  904. memset( &item, 0, sizeof( item ) );
  905. item.mask = LVIF_TEXT;
  906. item.iItem = nItem;
  907. item.pszText = tempBuffer;
  908. item.cchTextMax = sizeof( tempBuffer ) - 1;
  909. for ( int i=0; i < pHeader->GetItemCount(); i++ )
  910. {
  911. item.iSubItem = i;
  912. if ( !m_ServicesList.GetItem( &item ) )
  913. {
  914. item.pszText = "<bug>";
  915. }
  916. clipboardText.AddMultipleToTail( strlen( item.pszText ), item.pszText );
  917. clipboardText.AddToTail( '\t' );
  918. }
  919. clipboardText.AddMultipleToTail( 2, "\r\n" );
  920. }
  921. clipboardText.AddToTail( 0 );
  922. }
  923. void CServicesDlg::OnCopyToClipboard()
  924. {
  925. // Open and clear the clipboard.
  926. if ( !OpenClipboard() )
  927. return;
  928. EmptyClipboard();
  929. // Setup the clipboard text.
  930. CUtlVector<char> clipboardText;
  931. BuildClipboardText( clipboardText );
  932. // Put the clipboard text into a global memory object.
  933. HANDLE hMem = GlobalAlloc( GMEM_MOVEABLE, clipboardText.Count() );
  934. void *ptr = GlobalLock( hMem );
  935. memcpy( ptr, clipboardText.Base(), clipboardText.Count() );
  936. GlobalUnlock( hMem );
  937. // Put it in the clipboard.
  938. SetClipboardData( CF_TEXT, hMem );
  939. // Cleanup.
  940. GlobalFree( hMem );
  941. CloseClipboard();
  942. }