// ====================================================================== // // Os.cpp // // Portions copyright 1998 Bootprint Entertainment // Portions copyright 2000-2002 Sony Online Entertainment // All Rights Reserved // // ====================================================================== #include "sharedFoundation/FirstSharedFoundation.h" #include "sharedFoundation/Os.h" #include "sharedDebug/DebugFlags.h" #include "sharedDebug/DebugKey.h" #include "sharedDebug/DebugMonitor.h" #include "sharedDebug/Profiler.h" #include "sharedFoundation/Clock.h" #include "sharedFoundation/ConfigFile.h" #include "sharedFoundation/ConfigSharedFoundation.h" #include "sharedFoundation/CrashReportInformation.h" #include "sharedFoundation/ExitChain.h" #include "sharedFoundation/FloatingPointUnit.h" #include "sharedFoundation/Production.h" #include "sharedFoundation/StringCompare.h" #include "sharedFoundation/WindowsWrapper.h" #include "shellapi.h" #include #include #include #include // ====================================================================== namespace OsNamespace { void applyWindowChanges(); void updateMousePosition(int x, int y); const int PROGRAM_NAME_SIZE = 512; bool ms_installed; bool ms_processMessagePump = true; #if PRODUCTION == 0 bool ms_validateGuardPatterns; bool ms_validateFreePatterns; bool ms_allowPopupDebugMenu; #endif int ms_numberOfUpdates; HWND ms_window; HCURSOR ms_cursorArrow; bool ms_engineOwnsWindow; bool ms_wasFocusLost; bool ms_gameOver; bool ms_shouldReturnFromAbort; bool ms_wantPopupDebugMenu; bool ms_threadDied; bool ms_mouseMoveInClient; bool ms_clickToMove; char ms_programName[PROGRAM_NAME_SIZE]; char *ms_shortProgramName; char ms_programStartupDirectory[MAX_PATH]; Os::ThreadId ms_mainThreadId; Os::IsGdiVisibleHookFunction ms_isGdiVisibleHookFunction; Os::LostFocusHookFunction ms_lostFocusHookFunction; Os::AcquiredFocusHookFunction ms_acquiredFocusHookFunction; Os::AcquiredFocusHookFunction ms_acquiredFocusHookFunction2; Os::QueueCharacterHookFunction ms_queueCharacterHookFunction; Os::SetSystemMouseCursorPositionHookFunction ms_setSystemMouseCursorPositionHookFunction; Os::SetSystemMouseCursorPositionHookFunction ms_setSystemMouseCursorPositionHookFunction2; Os::GetHardwareMouseCursorEnabled ms_getHardwareMouseCursorEnabled; Os::GetOtherAdapterRectsHookFunction ms_getOtherAdapterRectsHookFunction; Os::WindowPositionChangedHookFunction ms_windowPositionChangedHookFunction; Os::DisplayModeChangedHookFunction ms_displayModeChangedHookFunction; Os::InputLanguageChangedHookFunction ms_inputLanguageChangedHookFunction; Os::IMEHookFunction ms_IMEHookFunction; Os::QueueKeyDownHookFunction ms_queueKeyDownHookFunction; int ms_processorCount; int ms_debugKeyIndex; int ms_SystemMouseCursorPositionX; int ms_SystemMouseCursorPositionY; std::vector ms_otherAdapterRects; char ms_keyboardLayout[KL_NAMELENGTH]; bool ms_focused; int const ms_hotKeyId = 0xBEEF; extern "C" WINBASEAPI BOOL WINAPI IsDebuggerPresent(VOID); }; using namespace OsNamespace; static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); // ====================================================================== /** * Install the Os subsystem for games * * This routine is supported for all platforms, although different platforms may * require different arguments to this routine. * * This routine registers the window class and creates the window for the application. * * This routine will add Os::remove to the ExitChain. * * @param instance Handle to the instance for this application * @param windowName Name for the window title bar * @paran normalIcon Normal icon for the game * @param smallIcon Small icon for the task bar * @see Os::remove() */ void Os::install(HINSTANCE instance, const char *windowName, HICON normalIcon, HICON smallIcon) { installCommon(); // setup the window class WNDCLASSEX wclass; Zero(wclass); wclass.cbSize = sizeof(wclass); wclass.style = CS_BYTEALIGNCLIENT; wclass.lpfnWndProc = WindowProc; wclass.hInstance = instance; wclass.hIcon = normalIcon; wclass.hCursor = NULL; wclass.hbrBackground = reinterpret_cast(GetStockObject(BLACK_BRUSH)); wclass.lpszClassName = windowName; wclass.hIconSm = smallIcon; // register the window class ATOM atom = RegisterClassEx(&wclass); FATAL(atom == 0, ("RegisterClassEx failed")); // create the window ms_window = CreateWindow( windowName, // pointer to registered class name windowName, // pointer to window name WS_POPUP, // window style 0, // horizontal position of window 0, // vertical position of window 640, // window width 480, // window height NULL, // handle to parent or owner window NULL, // handle to menu or child-window identifier instance, // handle to application instance NULL); // pointer to window-creation data FATAL(!ms_window, ("CreateWindow failed")); ms_engineOwnsWindow = true; // load the arrow cursor ms_cursorArrow = LoadCursor(NULL, IDC_ARROW); FATAL(ms_cursorArrow == NULL, ("LoadCursor failed")); } // ---------------------------------------------------------------------- /** * Install the Os subsystem for non-games. * * This routine is supported for all platforms, although different platforms may * require different arguments to this routine.F * * This routine will add Os::remove to the ExitChain. * * @see Os::remove() */ void Os::install(HWND newWindow, bool newProcessMessagePump) { installCommon(); ms_window = newWindow; ms_processMessagePump = newProcessMessagePump; } // ---------------------------------------------------------------------- /** * Install the Os subsystem for non-games. * * This routine is supported for all platforms, although different platforms may * require different arguments to this routine. * * This routine will add Os::remove to the ExitChain. * * @see Os::remove() */ void Os::install() { installCommon(); } // ---------------------------------------------------------------------- /** * This routine will remove the Os subsystem. * * This routine should not be called directly. It will be called from the ExitChain. * * @see Os::install() */ void Os::remove() { DEBUG_FATAL(!ms_installed, ("not installed")); ms_installed = false; } // ---------------------------------------------------------------------- void Os::installCommon() { DEBUG_FATAL(ms_installed, ("already installed")); ExitChain::add(Os::remove, "Os::remove", 0, true); // get startup folder. GetCurrentDirectory(sizeof(ms_programStartupDirectory), ms_programStartupDirectory); ms_numberOfUpdates = 0; ms_mainThreadId = GetCurrentThreadId(); setThreadName(ms_mainThreadId, "Main"); #if PRODUCTION == 0 ms_allowPopupDebugMenu = ConfigFile::getKeyBool("SharedFoundation", "allowPopupDebugMenu", false); #endif // get the name of the executable DWORD result = GetModuleFileName(NULL, ms_programName, sizeof(ms_programName)); FATAL(result == 0, ("GetModuleFileName failed")); // get the file name without the path ms_shortProgramName = strrchr(ms_programName, '\\'); if (ms_shortProgramName) ++ms_shortProgramName; else ms_shortProgramName = ms_programName; // switch into single-precision floating point mode FloatingPointUnit::install(); // get the amount of memory MEMORYSTATUS memoryStatus; GlobalMemoryStatus(&memoryStatus); CrashReportInformation::addStaticText("Ram: %dmb\n", memoryStatus.dwTotalPhys / (1024 * 1024)); // log the os information { OSVERSIONINFO versionInfo; Zero(versionInfo); versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&versionInfo); CrashReportInformation::addStaticText("Os1: %d.%d.%d\n", versionInfo.dwMajorVersion, versionInfo.dwMinorVersion, versionInfo.dwBuildNumber); char const * os = "Unknown"; if (versionInfo.dwMajorVersion == 4 && versionInfo.dwMinorVersion == 10) os = "Windows 98"; else if (versionInfo.dwMajorVersion == 4 && versionInfo.dwMinorVersion == 90) os = "Windows Me"; else if (versionInfo.dwMajorVersion == 5 && versionInfo.dwMinorVersion == 0) os = "Windows 2000"; else if (versionInfo.dwMajorVersion == 5 && versionInfo.dwMinorVersion == 1) os = "Windows XP"; else if (versionInfo.dwMajorVersion == 5 && versionInfo.dwMinorVersion == 2) os = "Windows Server 2003"; else if (versionInfo.dwMajorVersion == 6 && versionInfo.dwMinorVersion == 0) os = "Windows Vista"; CrashReportInformation::addStaticText("Os2: %s %s\n", os, versionInfo.szCSDVersion); } // get the number of processors SYSTEM_INFO si; GetSystemInfo(&si); ms_processorCount = static_cast(si.dwNumberOfProcessors); REPORT_LOG (ConfigSharedFoundation::getVerboseHardwareLogging(), ("Processor Count: %i\n", ms_processorCount)); CrashReportInformation::addStaticText("NumProc: %d\n", ms_processorCount); { HKEY key; LONG result = RegOpenKeyEx (HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_EXECUTE, &key); if (result == ERROR_SUCCESS) { DWORD data; DWORD type = REG_DWORD; DWORD size = sizeof (data); result = RegQueryValueEx (key, "~MHz", NULL, &type, reinterpret_cast (&data), &size); if ((result == ERROR_SUCCESS) && (size > 0)) REPORT_LOG (ConfigSharedFoundation::getVerboseHardwareLogging(), ("Processor Speed: %i MHz\n", data)); RegCloseKey (key); } } if (!GetKeyboardLayoutName(ms_keyboardLayout)) ms_keyboardLayout[0] = '\0'; ms_installed = true; #if PRODUCTION == 0 DebugFlags::registerFlag(ms_validateGuardPatterns, "SharedFoundation", "validateGuardPatterns"); DebugFlags::registerFlag(ms_validateFreePatterns, "SharedFoundation", "validateFreePatterns"); #endif switch (ConfigSharedFoundation::getProcessPriority()) { case -1: setProcessPriority(P_low); break; case 0: setProcessPriority(P_normal); break; case 1: setProcessPriority(P_high); break; default: DEBUG_WARNING(true, ("invalid process priority, %d should be betweein [-1..1]", ConfigSharedFoundation::getProcessPriority())); break; } } // ====================================================================== // Return the window handle // // Return value: // // Handle to the window for this application // // Remarks: // // This routine is only supported on the Win* platforms, and should not be used by the // game or the engine if portability is required. HWND Os::getWindow() { return ms_window; } // ---------------------------------------------------------------------- bool Os::engineOwnsWindow() { return ms_engineOwnsWindow; } // ---------------------------------------------------------------------- /** * Return the full name of the running executable. * * The program name will include the path as well. * * @return The full name of the running executable * @see Os::getShortProgramName() */ const char *Os::getProgramName() { return ms_programName; } // ---------------------------------------------------------------------- /** * Return the short name of the running executable. * * The program name will not include the path, but will just be the file name. * * @return The short name of the running executable * @see Os::getProgramName() */ const char *Os::getShortProgramName() { return ms_shortProgramName; } // ---------------------------------------------------------------------- /** * Return the current working directory when the program was started. * */ const char *Os::getProgramStartupDirectory() { return ms_programStartupDirectory; } // ---------------------------------------------------------------------- /** * Cause Os::abort() to return instead of abort the process. * * This routine should not be called directly by users. * * This routine is provided so that structured exception handling can catch * an exception, call Fatal to run the ExitChain, and rethrow the exception * so that the debugger will catch it. */ void Os::returnFromAbort() { ms_shouldReturnFromAbort = true; } // ---------------------------------------------------------------------- /** * Check if the Os knows the game needs to shut down. * * The Os can decide that the game need to end for a number of reasons, * including closing the application or shutting the machine down. * * @return True if the game should quit, otherwise false */ bool Os::isGameOver() { return ms_gameOver; } // ---------------------------------------------------------------------- /** * Return the number of updates that the have occurred. * * @return This value is updated during the Os::update() routine. */ int Os::getNumberOfUpdates() { return ms_numberOfUpdates; } // ---------------------------------------------------------------------- /** * Indicate whether or not the application was paused. * * @return True if the application was in the background (paused), otherwise false */ bool Os::wasFocusLost() { return ms_wasFocusLost; } // ---------------------------------------------------------------------- /** * Return a flag indicating whether we are running a multiprocessor machine or not. * * @return True if the machine has more than one processor, false if not. */ bool Os::isMultiprocessor() { return ms_processorCount > 1; } // ---------------------------------------------------------------------- /** * Return the number of processors. * * @return The number of processors in the machine. */ int Os::getProcessorCount() { return ms_processorCount; } // ---------------------------------------------------------------------- bool Os::isMainThread() { // if the Os class hasn't been installed, then assume we are the main thread. // otherwise, check to see if our thread id is the main thread id return !ms_installed || (GetCurrentThreadId() == ms_mainThreadId); } // ---------------------------------------------------------------------- /** * Terminate the application because of an error condition. * * This routine is supported for all platforms. * * This routine should not be called directly. The engine and game should use the * FATAL macro to terminate the application because of an error. * * Calling Os::returnFromAbort() will cause the routine to do nothing but return * immediately. * * @see Os::returnFromAbort(), FATAL() */ void Os::abort() { if (!isMainThread()) { ms_threadDied = true; ExitThread(1); } if (!ms_shouldReturnFromAbort) { // let the C runtime deal with the abnormal termination ::abort(); } } // ---------------------------------------------------------------------- void Os::setLostFocusHookFunction(LostFocusHookFunction lostFocusHookFunction) { ms_lostFocusHookFunction = lostFocusHookFunction; } // ---------------------------------------------------------------------- void Os::setIsGdiVisibleHookFunction(IsGdiVisibleHookFunction isGdiVisibleHookFunction) { ms_isGdiVisibleHookFunction = isGdiVisibleHookFunction; } // ---------------------------------------------------------------------- void Os::setQueueCharacterHookFunction(QueueCharacterHookFunction queueCharacterHookFunction) { ms_queueCharacterHookFunction = queueCharacterHookFunction; } // ---------------------------------------------------------------------- void Os::setSetSystemMouseCursorPositionHookFunction(SetSystemMouseCursorPositionHookFunction setSystemMouseCursorPositionHookFunction) { ms_setSystemMouseCursorPositionHookFunction = setSystemMouseCursorPositionHookFunction; } // ---------------------------------------------------------------------- void Os::setSetSystemMouseCursorPositionHookFunction2(SetSystemMouseCursorPositionHookFunction setSystemMouseCursorPositionHookFunction) { ms_setSystemMouseCursorPositionHookFunction2 = setSystemMouseCursorPositionHookFunction; } // ---------------------------------------------------------------------- void Os::setAcquiredFocusHookFunction(AcquiredFocusHookFunction acquiredFocusHookFunction) { ms_acquiredFocusHookFunction = acquiredFocusHookFunction; } // ---------------------------------------------------------------------- void Os::setAcquiredFocusHookFunction2(AcquiredFocusHookFunction acquiredFocusHookFunction) { ms_acquiredFocusHookFunction2 = acquiredFocusHookFunction; } // ---------------------------------------------------------------------- void Os::setGetHardwareMouseCursorEnabled(GetHardwareMouseCursorEnabled getHardwareMouseCursorEnabled) { ms_getHardwareMouseCursorEnabled = getHardwareMouseCursorEnabled; } // ---------------------------------------------------------------------- void Os::setGetOtherAdapterRectsHookFunction(GetOtherAdapterRectsHookFunction getOtherAdapterRectsHookFunction) { ms_getOtherAdapterRectsHookFunction = getOtherAdapterRectsHookFunction; } // ---------------------------------------------------------------------- void Os::setWindowPositionChangedHookFunction(WindowPositionChangedHookFunction windowPositionChangedHookFunction) { ms_windowPositionChangedHookFunction = windowPositionChangedHookFunction; } //----------------------------------------------------------------- void Os::setDisplayModeChangedHookFunction(DisplayModeChangedHookFunction displayModeChangedHookFunction) { ms_displayModeChangedHookFunction = displayModeChangedHookFunction; } //----------------------------------------------------------------- void Os::setInputLanguageChangedHookFunction(InputLanguageChangedHookFunction inputLanguageChangedHookFunction) { ms_inputLanguageChangedHookFunction = inputLanguageChangedHookFunction; } //----------------------------------------------------------------- // rls - In IME mode, the F5 key can cause the IME pad window to open up. // This causes the Japanese player to essentially lock-up because of // context issues. There is probably a better way to handle this, but be // warned: the SWG input system may not work properly. void Os::setIMEHookFunction(IMEHookFunction imeHookFunction) { if (ms_IMEHookFunction) { UnregisterHotKey(ms_window, ms_hotKeyId); } ms_IMEHookFunction = imeHookFunction; // do not install the hotkey fix if the debugger is present. if (ms_IMEHookFunction && !OsNamespace::IsDebuggerPresent()) { RegisterHotKey(ms_window, ms_hotKeyId, 0, VK_F5); } } //----------------------------------------------------------------- void Os::setQueueKeyDownHookFunction(QueueKeyDownHookFunction queueKeyDownHookFunction) { ms_queueKeyDownHookFunction = queueKeyDownHookFunction; } //----------------------------------------------------------------- /** * Create the specified directory and all of it's parents. * @param directory the path to a directory * @return always true currently */ bool Os::createDirectories (const char *directory) { //-- construct list of subdirectories all the way down to root std::stack directoryStack; std::string currentDirectory = directory; static const char path_seps [] = { '\\', '/', 0 }; // build the stack while (!currentDirectory.empty()) { // remove trailing backslash if (currentDirectory[currentDirectory.size()-1] == '\\' || currentDirectory[currentDirectory.size()-1] == '/') IGNORE_RETURN(currentDirectory.erase(currentDirectory.size()-1)); if (currentDirectory[currentDirectory.size()-1] == ':') { // we've hit something like c: break; } if (!currentDirectory.empty()) directoryStack.push(currentDirectory); // now strip off current directory const size_t previousDirIndex = currentDirectory.find_last_of (path_seps); if (static_cast(previousDirIndex) == currentDirectory.npos) break; else IGNORE_RETURN(currentDirectory.erase(previousDirIndex)); } //-- build all directories specified by the initial directory while (!directoryStack.empty()) { // get the directory currentDirectory = directoryStack.top(); directoryStack.pop(); // try to create it (don't pass any security attributes) IGNORE_RETURN (CreateDirectory(currentDirectory.c_str(), NULL)); } return true; } // ---------------------------------------------------------------------- /** * Write out a file. * * The file name and where the file is written is system-dependent. * * @param fileName Name of the file to write * @param data Data buffer to write to the file */ bool Os::writeFile(const char *fileName, const void *data, int length) // Length of the data bufferto write { BOOL result; HANDLE handle; DWORD written; // open the file for writing handle = CreateFile(fileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); // check if it was opened if (handle == INVALID_HANDLE_VALUE) { WARNING (true, ("Os::writeFile unable to create file [%s] for writing.", fileName)); return false; } // attempt to write the data result = WriteFile(handle, data, static_cast(length), &written, NULL); // make sure the data was written okay if (!result || written != static_cast(length)) { WARNING (true, ("Os::writeFile error writing file [%s]. Wrote %d, attempted to write %d.", fileName, written, length)); static_cast(CloseHandle(handle)); return false; } // close the file result = CloseHandle(handle); // make sure the close was sucessful if (!result) { WARNING (true, ("Os::writeFile error closing file [%s].", fileName)); return false; } return true; } // ---------------------------------------------------------------------- /** * Check to see if any child threads have Fataled. * * This routine will */ void Os::checkChildThreads() { FATAL(ms_threadDied, ("child thread died")); } // ---------------------------------------------------------------------- /** * Change the priority of this process. * */ void Os::setProcessPriority(Priority priority) { switch (priority) { case P_low: SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS); break; case P_normal: SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); break; case P_high: SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); break; default: DEBUG_FATAL(true, ("Invalid priority")); break; } } // ---------------------------------------------------------------------- /** * Update the Os subsystem. * * This routine is supported for all platforms. * * For the Win* platforms, this routine will process the windows message pump. */ bool Os::update() { MSG msg; int result; FloatingPointUnit::update(); ms_wasFocusLost = false; ++ms_numberOfUpdates; #if PRODUCTION == 0 if (ms_validateGuardPatterns || ms_validateFreePatterns) { PROFILER_AUTO_BLOCK_DEFINE ("validate heap"); MemoryManager::verify(ms_validateGuardPatterns, ms_validateFreePatterns); } #endif if (ms_processMessagePump) { do { checkChildThreads(); // while there are messages in the queue while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { result = GetMessage(&msg, NULL, 0, 0); if (result < 0) { // error, ignore GetMessage } else // get the message if (result > 0) { static_cast(TranslateMessage(&msg)); static_cast(DispatchMessage(&msg)); } else { // WM_QUIT handled here ms_gameOver = true; return false; } } // may need to reprocess the message queue now } while (handleDebugMenu()); } Clock::update(); return true; } // ---------------------------------------------------------------------- /** * Formats a message error using GetLastError() and FormatMessge(). * * The buffer returned from this function is dynamically allocated to prevent * issues with this routine being called from multiple threads. The caller * must delete the buffer when it is done. * * @return A dynamically allocated buffer containing the error message */ char *Os::getLastError() { char buffer[2048]; const DWORD result = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer), NULL); if (result == 0) return NULL; return DuplicateString(buffer); } // ---------------------------------------------------------------------- bool Os::handleDebugMenu() { #if PRODUCTION == 0 // pop up the menu if it is wanted and GDI is actually visible if (ms_wantPopupDebugMenu && ms_isGdiVisibleHookFunction && ms_isGdiVisibleHookFunction()) { ms_focused = false; // unaquire all the dinput devices if (ms_lostFocusHookFunction) ms_lostFocusHookFunction(); BOOL b; POINT p; // get the upper left corner of the client space in screen coordinates p.x = 0; p.y = 0; b = ClientToScreen(ms_window, &p); DEBUG_FATAL(!b, ("ClientToScreen failed")); typedef std::map Map; Map map; // create the menu HMENU menu = CreatePopupMenu(); const char *lastSection.clear(); HMENU lastSubmenu = NULL; int index = 1; DebugFlags::FlagVector::const_iterator end = DebugFlags::ms_flagsSortedByName.end(); for (DebugFlags::FlagVector::const_iterator i = DebugFlags::ms_flagsSortedByName.begin(); i != end; ++i, ++index) { const DebugFlags::Flag &flag = *i; // check if we are starting a new section if (strcmp(lastSection, flag.section) != 0) { lastSection = flag.section; char buffer[512]; strcpy(buffer, lastSection); char *start = buffer; char *slash = NULL; lastSubmenu = menu; while ((slash = strchr(start, '/')) != NULL) { *slash = '\0'; Map::iterator entry = map.find(buffer); if (entry == map.end()) { const int numberOfItems = GetMenuItemCount(lastSubmenu); int insertLocation = 0; for (insertLocation = 0; insertLocation < numberOfItems && GetSubMenu(lastSubmenu, insertLocation); ++insertLocation) ; HMENU newMenu = CreatePopupMenu(); static_cast(InsertMenu(lastSubmenu, insertLocation, MF_BYPOSITION | MF_POPUP, reinterpret_cast(newMenu), start)); lastSubmenu = newMenu; map.insert(Map::value_type(DuplicateString(buffer), lastSubmenu)); } else lastSubmenu = entry->second; *slash = '/'; start = slash + 1; } const int numberOfItems = GetMenuItemCount(lastSubmenu); int insertLocation = 0; for (insertLocation = 0; insertLocation < numberOfItems && GetSubMenu(lastSubmenu, insertLocation); ++insertLocation) ; HMENU newMenu = CreatePopupMenu(); static_cast(InsertMenu(lastSubmenu, insertLocation, MF_BYPOSITION | MF_POPUP, reinterpret_cast(newMenu), start)); lastSubmenu = newMenu; map.insert(Map::value_type(DuplicateString(buffer), lastSubmenu)); } // add the current flag to the current menu static_cast(AppendMenu(lastSubmenu, MF_ENABLED | MF_STRING | (*flag.variable ? MF_CHECKED : MF_UNCHECKED), index, flag.name)); } { ms_debugKeyIndex = index; Map::iterator entry = map.find("SharedDebug"); DEBUG_FATAL(entry == map.end(), ("Could not find SharedDebug section")); HMENU newMenu = CreatePopupMenu(); static_cast(AppendMenu(entry->second, MF_POPUP, reinterpret_cast(newMenu), "DebugKey")); lastSubmenu = newMenu; DebugKey::FlagVector::const_iterator end = DebugKey::ms_flags.end(); for (DebugKey::FlagVector::const_iterator i = DebugKey::ms_flags.begin(); i != end; ++i, ++index) { // add the current flag to the current menu const DebugKey::Flag &flag = *i; static_cast(AppendMenu(lastSubmenu, MF_ENABLED | MF_STRING | (*flag.variable ? MF_CHECKED : MF_UNCHECKED), index, flag.name)); } } // free the map memory while (!map.empty()) { Map::iterator i = map.begin(); char *value = i->first; map.erase(i); delete [] value; } // pop up the menu at the top corner of the client space index = TrackPopupMenuEx(menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD | TPM_RIGHTBUTTON, p.x, p.y, ms_window, NULL); if (index) { if (index < ms_debugKeyIndex) { bool &value = *DebugFlags::ms_flagsSortedByName[index-1].variable; value = !value; } else { #if PRODUCTION == 0 DebugKey::setCurrentFlag(DebugKey::ms_flags[index - ms_debugKeyIndex].variable); #endif } } DestroyMenu(menu); // don't want the menu anymore ms_wantPopupDebugMenu = false; ms_wasFocusLost = true; ms_focused = true; if (ms_acquiredFocusHookFunction2) ms_acquiredFocusHookFunction2(); return true; } #endif return false; } // ---------------------------------------------------------------------- void Os::enablePopupDebugMenu() { #if PRODUCTION == 0 ms_allowPopupDebugMenu = true; #endif } // ---------------------------------------------------------------------- /** * Request that the popup debug menu be displayed. * * The popup debug menu will only be displayed if the proper config file * switch has been set. */ void Os::requestPopupDebugMenu() { #if PRODUCTION == 0 ms_wantPopupDebugMenu = ms_allowPopupDebugMenu; #endif } bool Os::isNumPadValue(unsigned char asciiChar) { return (asciiChar >= '0' && asciiChar <= '9') || (asciiChar == '/') || (asciiChar == '*') || (asciiChar == '-') || (asciiChar == '+') || (asciiChar == '.'); } // ---------------------------------------------------------------------- /** * Check to see if this is a NumPad keypress to be consumed. Enter is the only one not consumed. */ bool Os::isNumPadChar(unsigned char asciiChar) { BYTE keyboardstate[256]; GetKeyboardState( keyboardstate ); if (isNumPadValue(asciiChar)) { return ( (keyboardstate[VK_NUMPAD1] > 1) || (keyboardstate[VK_NUMPAD2] > 1) || (keyboardstate[VK_NUMPAD3] > 1) || (keyboardstate[VK_NUMPAD4] > 1) || (keyboardstate[VK_NUMPAD5] > 1) || (keyboardstate[VK_NUMPAD6] > 1) || (keyboardstate[VK_NUMPAD7] > 1) || (keyboardstate[VK_NUMPAD8] > 1) || (keyboardstate[VK_NUMPAD9] > 1) || (keyboardstate[VK_NUMPAD0] > 1) || (keyboardstate[VK_DIVIDE] > 1) || (keyboardstate[VK_ADD] > 1) || (keyboardstate[VK_SUBTRACT] > 1) || (keyboardstate[VK_MULTIPLY] > 1) || (keyboardstate[VK_DECIMAL] > 1)); } return false; } // ---------------------------------------------------------------------- /** * Handle window messages. * * This routine will process window messages that are passed into the application from * Windows, likely though the Os::update() routine, but may be from other locations as well. * * @param hwnd Handle of window * @param uMsg Message identifier * @param wParam First message parameter * @param lParam Second message parameter * @see Os::update() */ LRESULT CALLBACK Os::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { #if 0 DEBUG_REPORT_LOG_PRINT(true, ("%08d %08x %08x %08x\n", ms_numberOfUpdates, uMsg, wParam, lParam)); #endif if (ms_IMEHookFunction) { // Let the IME Manager see and possibly consume message if (ms_IMEHookFunction(hwnd, uMsg, wParam, lParam) == 0) { return 0; } } switch (uMsg) { case WM_ERASEBKGND: // won't let windows erase the background return 0; case WM_IME_CHAR: if (ms_queueCharacterHookFunction) { const char ansiChars [2] = { static_cast(HIBYTE( wParam )), //lint !e1924 // c-style case msvc bug static_cast(LOBYTE( wParam )) //lint !e1924 // c-style case msvc bug }; wchar_t u; if (MultiByteToWideChar (CP_ACP, MB_PRECOMPOSED, reinterpret_cast(ansiChars), 2, &u, 1)) ms_queueCharacterHookFunction(0, static_cast(u)); } return 0; case WM_HOTKEY: { if (ms_queueKeyDownHookFunction) { ms_queueKeyDownHookFunction(0, MapVirtualKey(HIWORD(lParam), 0)); } return 0; } case WM_CHAR: // handle typed string characters if (ms_queueCharacterHookFunction) { //-- the extended bit is bit 24 const int extended = (lParam & (1 << 24)) != 0; if (!extended) { int keyCode = (lParam << 8) >> 24; // key code is in bits 16-23 // cp* == ASCII until 0x80, for which you then need to call the following to get the Unicode value // from the cp* value. WM_CHAR always returns the values from the windows codepage (cp) used. For US/Europe // it is cp1252 and for Japan it is cp932. if (wParam >= 0x80) { char cpChar[2]; wchar_t unicodeChar[2]; cpChar[0] = static_cast(wParam); cpChar[1] = '\0'; int result = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, cpChar, sizeof(cpChar), unicodeChar, sizeof(unicodeChar)); if (result > 0) { ms_queueCharacterHookFunction(keyCode, static_cast(unicodeChar[0])); } } else { ms_queueCharacterHookFunction(keyCode, static_cast(wParam)); } } } return 0; case WM_INPUTLANGCHANGE: { if (ms_inputLanguageChangedHookFunction) { ms_inputLanguageChangedHookFunction(); } } return 0; case WM_DESTROY: // if the main window gets destroyed, it's time to quit PostQuitMessage(0); return 0; case WM_SYSCOMMAND: switch (wParam & 0xFFF0) { // hack hack hack hack hack // don't let alt-f4 close the window, but allow clicking on the close X button to close the window case SC_CLOSE: if (GetKeyState(VK_F4) && (GetKeyState(VK_MENU) || GetKeyState(VK_RMENU))) return 0; break; // don't let the monitor get turned off case SC_MONITORPOWER: return 0; // don't allow the screen saver to come on case SC_SCREENSAVE: return 0; // don't allow alt-space to open up the window menu case SC_KEYMENU: return 0; case SC_MOVE: ClipCursor(NULL); ms_focused = false; if (ms_lostFocusHookFunction) ms_lostFocusHookFunction(); ms_wasFocusLost = true; break; default: break; } break; case WM_ENTERSIZEMOVE: if (ms_getOtherAdapterRectsHookFunction) { ms_otherAdapterRects.clear(); (*ms_getOtherAdapterRectsHookFunction)(ms_otherAdapterRects); } break; case WM_EXITSIZEMOVE: ms_focused = true; if (ms_acquiredFocusHookFunction) ms_acquiredFocusHookFunction(); if (ms_acquiredFocusHookFunction2) ms_acquiredFocusHookFunction2(); ms_wasFocusLost = true; if (ms_windowPositionChangedHookFunction) (*ms_windowPositionChangedHookFunction)(); #if PRODUCTION == 0 DebugMonitor::setBehindWindow(ms_window); #endif break; case WM_MOUSEACTIVATE: if (hwnd == ms_window) { if (LOWORD(lParam) == HTCAPTION) ms_clickToMove = true; else if (LOWORD(lParam) == HTCLIENT) { ms_focused = true; if (ms_acquiredFocusHookFunction) ms_acquiredFocusHookFunction(); if (ms_acquiredFocusHookFunction2) ms_acquiredFocusHookFunction2(); } } break; case WM_MOUSEMOVE: if (ms_focused && hwnd == ms_window) { ms_mouseMoveInClient = true; updateMousePosition(LOWORD(lParam), HIWORD(lParam)); } break; case WM_NCMOUSEMOVE: ms_mouseMoveInClient = false; break; case WM_SETCURSOR: if (!ms_mouseMoveInClient || hwnd != ms_window) SetCursor(ms_cursorArrow); else if (!ms_getHardwareMouseCursorEnabled || !ms_getHardwareMouseCursorEnabled()) SetCursor(NULL); break; case WM_NCACTIVATE: #if PRODUCTION == 0 // hack to handle coming back from the debugger cleanly if (wParam) { //allow game-specific systems a chance to respond to focus changes DebugMonitor::setBehindWindow(ms_window); if (ms_lostFocusHookFunction) ms_lostFocusHookFunction(); if (ms_acquiredFocusHookFunction) ms_acquiredFocusHookFunction(); if (ms_acquiredFocusHookFunction2) ms_acquiredFocusHookFunction2(); ms_wasFocusLost = true; ms_focused = true; } #endif break; case WM_ACTIVATE: if (hwnd == ms_window) { if (wParam != WA_INACTIVE) { if (ms_clickToMove) ms_clickToMove = false; ms_mouseMoveInClient = true; ms_focused = true; ms_wasFocusLost = true; if (ms_acquiredFocusHookFunction) ms_acquiredFocusHookFunction(); if (ms_acquiredFocusHookFunction2) ms_acquiredFocusHookFunction2(); } else { ClipCursor(NULL); ms_focused = false; if (ms_lostFocusHookFunction) ms_lostFocusHookFunction(); } } break; case WM_ACTIVATEAPP: if (wParam == FALSE) { ClipCursor(NULL); ms_focused = false; if (ms_lostFocusHookFunction) ms_lostFocusHookFunction(); } break; case WM_DISPLAYCHANGE: if (ms_displayModeChangedHookFunction) ms_displayModeChangedHookFunction(); break; default: break; } return DefWindowProc(hwnd, uMsg, wParam, lParam); } // ---------------------------------------------------------------------- void OsNamespace::updateMousePosition(int x, int y) { if (ms_setSystemMouseCursorPositionHookFunction) ms_setSystemMouseCursorPositionHookFunction(x, y); if (ms_setSystemMouseCursorPositionHookFunction2) ms_setSystemMouseCursorPositionHookFunction2(x, y); } // ---------------------------------------------------------------------- /** * Get an identifier indicating which thread called this function. */ Os::ThreadId Os::getThreadId() { return GetCurrentThreadId(); } // ---------------------------------------------------------------------- /** * Get the actual system time, in seconds since the epoch. * * Do not use this for most game systems, since it does not take into account * clock sku, game loop times, etc. */ time_t Os::getRealSystemTime() { return time(0); } // ---------------------------------------------------------------------- /** * Convert a time in seconds since the epoch to GMT. * */ void Os::convertTimeToGMT(const time_t &convertTime, tm &zulu) { zulu=*gmtime(&convertTime); // gmtime uses a single static tm structure. Yuck! } // ---------------------------------------------------------------------- /** * Convert a tm structure to the time in seconds since the epoch. * */ time_t Os::convertGMTToTime(const tm &zulu) { return mktime(const_cast(&zulu)); } // ---------------------------------------------------------------------- /** * Cause the current thread to suspend for a period of time. Zero delay indicates * to yield the current time slice. */ void Os::sleep(int ms) { ::Sleep(static_cast(ms)); } // ---------------------------------------------------------------------- /** * Assign the given thread a reasonable name (only works for MSDev 6.0 debugger) * Max 9 characters * */ void Os::setThreadName(ThreadId threadID, const char* threadName) { //used to give threads reasonable names in the MSDev debugger, //see http://www.vcdj.com/upload/free/features/vcdj/2001/03mar01/et0103/et0103.asp for more info struct ThreadNameInfo { DWORD dwType; LPCSTR szName; DWORD dwThreadID; DWORD dwFlags; }; ThreadNameInfo info; info.dwType = 0x1000; //must be this value info.szName = threadName; info.dwThreadID = threadID; info.dwFlags = 0; //unused, reserved for future use __try { // use the magic exception number MS picked for this purpose RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD), reinterpret_cast(&info)); } __except (EXCEPTION_CONTINUE_EXECUTION) { } } // ---------------------------------------------------------------------- /** * Given a base directory and a full file pathname, find the relative path * needed to get from the source directory to the target. * * This command is case sensitive. It does not care whether baseDirectory contains * a trailing backslash or not. * * @param baseDirectory the base directory from which a relative path will be constructed. * @param targetPathname the full pathname for the file for which we want to generate a relative path * @param relativePath the string into which the constructed relative path will be returned */ void Os::buildRelativePath(const char *baseDirectory, const char *targetPathname, std::string &relativePath) { NOT_NULL(baseDirectory); DEBUG_FATAL(!targetPathname || !*targetPathname, ("bad targetPathname, must be non-zero length")); const char directorySeparator = '\\'; const char *const backupDirectoryString = "..\\"; std::string workingBaseDirectory(baseDirectory); //-- ensure base directory ends in directory separator if (workingBaseDirectory[workingBaseDirectory.length()-1] != directorySeparator) workingBaseDirectory += directorySeparator; //-- grab the target directory std::string workingTargetDirectory; const char *endOfDirectory = strrchr(targetPathname, static_cast(directorySeparator)); if (endOfDirectory) IGNORE_RETURN(workingTargetDirectory.append(targetPathname, static_cast(endOfDirectory - targetPathname + 1))); //-- find count of character of match between directory strings const size_t workingBaseDirectoryLength = workingBaseDirectory.length(); const size_t workingTargetDirectoryLength = workingTargetDirectory.length(); size_t searchIndex = 0; while ((searchIndex < workingBaseDirectoryLength) && (searchIndex < workingTargetDirectoryLength) && (workingBaseDirectory[searchIndex] == workingTargetDirectory[searchIndex])) ++searchIndex; size_t matchCount = searchIndex; //-- if we match into the middle of a directory, back up until the previous directory if ((matchCount > 0) && (workingBaseDirectory[matchCount - 1] != directorySeparator)) { do { --matchCount; } while ((matchCount > 0) && (workingBaseDirectory[matchCount - 1] != directorySeparator)); } //-- if we have no match between directories, the best path is the full path. if (matchCount < 1) { relativePath = targetPathname; return; } //-- for each directory in base directory not matched, insert a "backup directory" string relativePath.clear(); { for (size_t i = matchCount; i < workingBaseDirectoryLength; ++i) if (workingBaseDirectory[i] == directorySeparator) IGNORE_RETURN(relativePath.append(backupDirectoryString)); } //-- for each directory in the target directory not matched, append to result. // this works out to copying from the match point forward in the target path. IGNORE_RETURN(relativePath.append(targetPathname + matchCount)); } // ---------------------------------------------------------------------- /** * Convert a relative path to an absolute path. */ bool Os::getAbsolutePath(const char *relativePath, char *absolutePath, int absolutePathBufferSize) { if (_fullpath(absolutePath, relativePath, static_cast(absolutePathBufferSize)) == NULL) return false; // convert the slashes to be forwards for (char *convert = absolutePath; *convert; ++convert) if (*convert == '\\') *convert = '/'; return true; } // ---------------------------------------------------------------------- /** * Copy text to the system clipboard. * * This routine can be used if the application wants to make some data easily available for pasting (like crash call stacks). */ bool Os::copyTextToClipboard(const char *text) { if (!OpenClipboard(ms_window)) return false; if (!EmptyClipboard()) return false; // need to convert to cr/lf sequences. yuck. int length = 1; for (const char *t2 = text; *t2; ++t2, ++length) { if (*t2 == '\n') ++length; } HANDLE memoryHandle = GlobalAlloc(GMEM_MOVEABLE, length); if (memoryHandle == NULL) { CloseClipboard(); return FALSE; } // lock the handle and copy the text to the buffer. char *destination = reinterpret_cast(GlobalLock(memoryHandle)); while (*text) { if (*text == '\n') *(destination++) = '\r'; *destination++ = *text++; } if (GlobalUnlock(memoryHandle) != 0 || GetLastError() != NO_ERROR) { IGNORE_RETURN(GlobalFree(memoryHandle)); return false; } if (!SetClipboardData(CF_TEXT, memoryHandle)) return false; if (!CloseClipboard()) return false; return true; } // ---------------------------------------------------------------------- bool Os::getUserName(char *buffer, int &bufferSize) { NOT_NULL(buffer); DWORD windowsBufferSize = static_cast(bufferSize); buffer[0] = '\0'; bool result = GetUserName(buffer, &windowsBufferSize) == TRUE; return result; } //----------------------------------------------------------------------- Os::OsPID_t Os::getProcessId() { return static_cast(GetCurrentProcessId()); } //---------------------------------------------------------------------- bool Os::isFocused() { return ms_focused; } //---------------------------------------------------------------------- bool Os::launchBrowser(std::string const & website) { std::string URL("http://"); if (strncmp(URL.c_str(), website.c_str(),7)!=0) URL+=website; else URL=website; int result = reinterpret_cast(ShellExecute(NULL, "open", URL.c_str(), NULL, NULL, SW_SHOWNORMAL)); return (result > 32); } // ======================================================================