//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// // vmpi_service_ui.cpp : Defines the entry point for the application. // #include "stdafx.h" #include "consolewnd.h" #include "resource.h" #include "tier0/dbg.h" #include "tier1/strtools.h" #include "shell_icon_mgr.h" #include "vmpi.h" #include "service_conn_mgr.h" #include #include void UpdatePopupMenuState(); const char *g_pIconTooltip = VMPI_SERVICE_NAME; IConsoleWnd *g_pConsoleWnd = NULL; HINSTANCE g_hInstance = NULL; #define MYWM_NOTIFYICON (WM_APP+100) CShellIconMgr g_ShellIconMgr; bool g_bHighlightIconWhenBusy = false; // STATE THE SERVICE OWNS. int g_iCurState = 0; // One of the VMPI_SERVICE_STATE_ defines. char *g_pPassword = NULL; bool g_bScreensaverMode = false; void LogString( const char *pStr, ... ) { #ifdef VMPI_SERVICE_LOGS char str[4096]; va_list marker; va_start( marker, pStr ); _vsnprintf( str, sizeof( str ), pStr, marker ); va_end( marker ); static FILE *fp = fopen( "c:\\vmpi_service_ui.log", "wt" ); if ( fp ) { fprintf( fp, "%s", str ); fflush( fp ); } #endif } char* GetLastErrorString() { static char err[2048]; LPVOID lpMsgBuf; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL ); strncpy( err, (char*)lpMsgBuf, sizeof( err ) ); LocalFree( lpMsgBuf ); err[ sizeof( err ) - 1 ] = 0; return err; } // ------------------------------------------------------------------------------------------ // // Persistent state in the registry. // ------------------------------------------------------------------------------------------ // void LoadStateFromRegistry() { HKEY hKey = NULL; RegCreateKey( HKEY_LOCAL_MACHINE, VMPI_SERVICE_KEY, &hKey ); if ( hKey ) { DWORD val = 0; DWORD type = REG_DWORD; DWORD size = sizeof( val ); if ( RegQueryValueEx( hKey, "HighlightIconWhenBusy", 0, &type, (unsigned char*)&val, &size ) == ERROR_SUCCESS && type == REG_DWORD && size == sizeof( val ) ) { g_bHighlightIconWhenBusy = (val != 0); } } } void SaveStateToRegistry() { HKEY hKey = NULL; RegCreateKey( HKEY_LOCAL_MACHINE, VMPI_SERVICE_KEY, &hKey ); if ( hKey ) { DWORD val = g_bHighlightIconWhenBusy; RegSetValueEx( hKey, "HighlightIconWhenBusy", 0, REG_DWORD, (unsigned char*)&val, sizeof( val ) ); } } // ------------------------------------------------------------------------------------------ // // Our CServiceConnMgr packet handler. // ------------------------------------------------------------------------------------------ // void UpdateAppIcon() { if ( g_iCurState == VMPI_SERVICE_STATE_IDLE ) { g_ShellIconMgr.ChangeIcon( IDI_WAITING_ICON ); } else if ( g_iCurState == VMPI_SERVICE_STATE_BUSY ) { if ( g_bHighlightIconWhenBusy ) g_ShellIconMgr.ChangeIcon( IDI_BUSY_ICON ); else g_ShellIconMgr.ChangeIcon( IDI_WAITING_ICON ); } else { g_ShellIconMgr.ChangeIcon( IDI_DISABLED_ICON ); } } class CUIConnMgr : public CServiceConnMgr { public: virtual void HandlePacket( const char *pData, int len ); }; void CUIConnMgr::HandlePacket( const char *pData, int len ) { if ( pData[0] != VMPI_SERVICE_UI_PROTOCOL_VERSION ) return; int packetID = pData[1]; int offset = 2; if ( packetID == VMPI_SERVICE_TO_UI_CONSOLE_TEXT ) { Msg( &pData[offset] ); } else if ( packetID == VMPI_SERVICE_TO_UI_STATE ) { // Get the new state out.. g_iCurState = *((int*)&pData[offset]); offset += 4; LogString( "New UI state: %d.\n", g_iCurState ); // Update our icon. UpdateAppIcon(); g_bScreensaverMode = (*((char*)&pData[offset]) != 0); ++offset; // Store the current password. if ( g_pPassword ) delete [] g_pPassword; const char *pStr = &pData[offset]; g_pPassword = new char[ strlen( pStr ) + 1 ]; strcpy( g_pPassword, pStr ); offset += strlen( pStr ) + 1; UpdatePopupMenuState(); } else if ( packetID == VMPI_SERVICE_TO_UI_PATCHING ) { LogString( "Got a VMPI_SERVICE_TO_UI_PATCHING packet.\n" ); int bExitAfter = pData[offset]; ++offset; char workingDir[MAX_PATH], commandLine[4096]; V_strncpy( workingDir, &pData[offset], sizeof( workingDir ) ); offset += V_strlen( workingDir ) + 1; V_strncpy( commandLine, &pData[offset], sizeof( commandLine ) ); offset += V_strlen( commandLine ) + 1; // Run whatever they said to run. STARTUPINFO si; memset( &si, 0, sizeof( si ) ); si.cb = sizeof( si ); PROCESS_INFORMATION pi; memset( &pi, 0, sizeof( pi ) ); if ( CreateProcess( NULL, commandLine, NULL, NULL, false, CREATE_NO_WINDOW, NULL, workingDir, &si, &pi ) ) { LogString( "CreateProcess succeeded:\n%s\n", commandLine ); CloseHandle( pi.hProcess ); CloseHandle( pi.hThread ); if ( bExitAfter ) PostQuitMessage( 0 ); } else { LogString( "CreateProcess failed: %s", GetLastErrorString() ); } } else if ( packetID == VMPI_SERVICE_TO_UI_EXIT ) { // Exit. PostQuitMessage( 0 ); } } // ------------------------------------------------------------------------------------------ // // Helpers. // ------------------------------------------------------------------------------------------ // void InternalSpew( const char *pMsg ) { if ( g_pConsoleWnd ) { g_pConsoleWnd->PrintToConsole( pMsg ); } OutputDebugString( pMsg ); } SpewRetval_t MySpewOutputFunc( SpewType_t spewType, const char *pMsg ) { // Prepend the time. time_t aclock; time( &aclock ); struct tm *newtime = localtime( &aclock ); // Get rid of the \n. char timeString[512]; Q_strncpy( timeString, asctime( newtime ), sizeof( timeString ) ); char *pEnd = strstr( timeString, "\n" ); if ( pEnd ) *pEnd = 0; InternalSpew( timeString ); InternalSpew( " - " ); InternalSpew( pMsg ); if ( spewType == SPEW_ASSERT ) return SPEW_DEBUGGER; else if( spewType == SPEW_ERROR ) return SPEW_ABORT; else return SPEW_CONTINUE; } CUIConnMgr g_ConnMgr; void InitConsoleWindow() { // Only initialize it once. if ( g_pConsoleWnd ) return; g_pConsoleWnd = CreateConsoleWnd( g_hInstance, IDD_VMPI_SERVICE, IDC_DEBUG_OUTPUT, false ); } // ------------------------------------------------------------------------------------------ // // Implementation of IShellIconMgrHelper. // ------------------------------------------------------------------------------------------ // HMENU g_hMenu = NULL; HMENU g_hPopupMenu = NULL; // This is just a submenu of g_hMenu. bool LoadPopupMenu() { g_hMenu = LoadMenu( g_hInstance, MAKEINTRESOURCE( IDR_POPUP_MENU ) ); if ( !g_hMenu ) { Assert( false ); Warning( "LoadMenu failed.\n" ); return false; } g_hPopupMenu = GetSubMenu( g_hMenu, 0 ); return true; } void TermPopupMenu() { if ( g_hMenu ) { DestroyMenu( g_hMenu ); g_hMenu = NULL; } g_hPopupMenu = NULL; } void UpdatePopupMenuState() { bool bEnabled = (g_iCurState == VMPI_SERVICE_STATE_IDLE || g_iCurState == VMPI_SERVICE_STATE_BUSY); EnableMenuItem( g_hPopupMenu, ID_ENABLE_WORKER, bEnabled ? MF_GRAYED : MF_ENABLED ); EnableMenuItem( g_hPopupMenu, ID_DISABLE_WORKER, !bEnabled ? MF_GRAYED : MF_ENABLED ); // Enable or disable console items. EnableMenuItem( g_hPopupMenu, ID_SHOW_CONSOLE_WINDOW, g_pConsoleWnd->IsVisible() ? MF_GRAYED : MF_ENABLED ); EnableMenuItem( g_hPopupMenu, ID_HIDE_CONSOLE_WINDOW, !g_pConsoleWnd->IsVisible() ? MF_GRAYED : MF_ENABLED ); CheckMenuItem( g_hPopupMenu, ID_SCREENSAVER_MODE, g_bScreensaverMode ? MF_CHECKED : MF_UNCHECKED ); CheckMenuItem( g_hPopupMenu, ID_HIGHLIGHT_ICON_WHEN_BUSY, g_bHighlightIconWhenBusy ? MF_CHECKED : MF_UNCHECKED ); } int CALLBACK SetPasswordDlgProc( HWND hwndDlg, // handle to dialog box UINT uMsg, // message WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ) { switch( uMsg ) { case WM_INITDIALOG: { if ( g_pPassword ) { HWND hWnd = GetDlgItem( hwndDlg, IDC_PASSWORD ); SetWindowText( hWnd, g_pPassword ); } } break; case WM_COMMAND: { switch( wParam ) { case IDOK: { // Set our new password. HWND hWnd = GetDlgItem( hwndDlg, IDC_PASSWORD ); if ( hWnd ) { char tempBuf[512]; GetWindowText( hWnd, tempBuf, sizeof( tempBuf ) ); // Send it to the service. CUtlVector data; data.AddToTail( VMPI_SERVICE_UPDATE_PASSWORD ); data.AddMultipleToTail( strlen( tempBuf ) + 1, tempBuf ); g_ConnMgr.SendPacket( -1, data.Base(), data.Count() ); } EndDialog( hwndDlg, 0 ); } break; case IDCANCEL: { EndDialog( hwndDlg, 0 ); } break; } } break; } return FALSE; } class CShellIconMgrHelper : public IShellIconMgrHelper { public: virtual HINSTANCE GetHInstance() { return g_hInstance; } virtual int WindowProc( void *pWnd, int uMsg, long wParam, long lParam ) { HWND hWnd = (HWND)pWnd; switch( uMsg ) { // Right button brings up the popup menu. case MYWM_NOTIFYICON: { if ( lParam == WM_RBUTTONDOWN ) { POINT cursorPos; GetCursorPos( &cursorPos ); UpdatePopupMenuState(); // Make a popup menu. SetForegroundWindow( hWnd ); TrackPopupMenu( g_hPopupMenu, TPM_RIGHTALIGN | TPM_BOTTOMALIGN, cursorPos.x, cursorPos.y, 0, hWnd, NULL ); return 0; } else if ( lParam == WM_LBUTTONDOWN ) { // Left button brings up the console. g_pConsoleWnd->SetVisible( true ); UpdatePopupMenuState(); return 0; } } break; case WM_COMMAND: { switch( wParam ) { case ID_ENABLE_WORKER: { char cPacket = VMPI_SERVICE_ENABLE; g_ConnMgr.SendPacket( -1, &cPacket, 1 ); } break; case ID_DISABLE_WORKER: { char cPacket = VMPI_SERVICE_DISABLE; g_ConnMgr.SendPacket( -1, &cPacket, 1 ); } break; case ID_KILLCURRENTJOB: { char cPacket = VMPI_KILL_PROCESS; g_ConnMgr.SendPacket( -1, &cPacket, 1 ); } break; case ID_HIGHLIGHT_ICON_WHEN_BUSY: { g_bHighlightIconWhenBusy = !g_bHighlightIconWhenBusy; SaveStateToRegistry(); UpdateAppIcon(); UpdatePopupMenuState(); } break; case ID_SCREENSAVER_MODE: { g_bScreensaverMode = !g_bScreensaverMode; char cPacket[2] = { VMPI_SERVICE_SCREENSAVER_MODE, g_bScreensaverMode }; g_ConnMgr.SendPacket( -1, cPacket, sizeof( cPacket ) ); } break; case ID_SHOW_CONSOLE_WINDOW: { g_pConsoleWnd->SetVisible( true ); UpdatePopupMenuState(); } break; case ID_HIDE_CONSOLE_WINDOW: { g_pConsoleWnd->SetVisible( false ); UpdatePopupMenuState(); } break; case ID_SET_PASSWORD: { DialogBox( g_hInstance, MAKEINTRESOURCE( IDD_SET_PASSWORD ), NULL, SetPasswordDlgProc ); } break; case ID_EXIT_SERVICE: { // Quit the service app.. char cPacket = VMPI_SERVICE_EXIT; g_ConnMgr.SendPacket( -1, &cPacket, 1 ); // Stop showing the icon. g_ShellIconMgr.Term(); // Wait for a bit for the connection to go away. DWORD startTime = GetTickCount(); while ( GetTickCount()-startTime < 2000 ) { g_ConnMgr.Update(); if ( !g_ConnMgr.IsConnected() ) break; else Sleep( 10 ); } // Quit the UI app. PostQuitMessage( 0 ); return 1; } break; } } break; } return DefWindowProc( (HWND)hWnd, uMsg, wParam, lParam ); } }; CShellIconMgrHelper g_ShellIconMgrHelper; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { g_hInstance = hInstance; LogString( "vmpi_service_ui startup.\n" ); // Don't run multiple instances. HANDLE hMutex = CreateMutex( NULL, FALSE, "vmpi_service_ui_mutex" ); if ( hMutex && GetLastError() == ERROR_ALREADY_EXISTS ) return 1; // Hook spew output. SpewOutputFunc( MySpewOutputFunc ); InitConsoleWindow(); LogString( "Setup console window.\n" ); LoadStateFromRegistry(); // Setup the popup menu. if( !LoadPopupMenu() ) { return false; } UpdatePopupMenuState(); // Setup the tray icon. Msg( "Waiting for jobs...\n" ); if ( !g_ShellIconMgr.Init( &g_ShellIconMgrHelper, g_pIconTooltip, MYWM_NOTIFYICON, IDI_WAITING_ICON ) ) { return false; } // Connect to the VMPI service. g_ConnMgr.InitClient(); LogString( "Entering main loop.\n" ); while ( 1 ) { MSG msg; msg.message = !WM_QUIT; // So it doesn't accidentally exit. while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) { if ( msg.message == WM_QUIT ) break; TranslateMessage( &msg ); DispatchMessage( &msg ); } if ( msg.message == WM_QUIT ) break; g_ConnMgr.Update(); if ( !g_ConnMgr.IsConnected() ) { g_ShellIconMgr.ChangeIcon( IDI_UNCONNECTED ); } Sleep( 30 ); } // Important that we call this instead of letting the destructor do it because it deletes its // socket and it needs to cleanup some threads. g_ConnMgr.Term(); g_ShellIconMgr.Term(); return 0; }