Files
src-1.2/engine/shared/library/sharedFoundation/src/win32/Os.cpp
T

1634 lines
46 KiB
C++
Executable File

// ======================================================================
//
// 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 <stack>
#include <map>
#include <string>
#include <vector>
// ======================================================================
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<RECT> 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<HBRUSH>(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<int>(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<LPBYTE> (&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<std::string> 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<int>(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<DWORD>(length), &written, NULL);
// make sure the data was written okay
if (!result || written != static_cast<DWORD>(length))
{
WARNING (true, ("Os::writeFile error writing file [%s]. Wrote %d, attempted to write %d.", fileName, written, length));
static_cast<void>(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<void>(TranslateMessage(&msg));
static_cast<void>(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<char *, HMENU, StringCompare> 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<void>(InsertMenu(lastSubmenu, insertLocation, MF_BYPOSITION | MF_POPUP, reinterpret_cast<UINT>(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<void>(InsertMenu(lastSubmenu, insertLocation, MF_BYPOSITION | MF_POPUP, reinterpret_cast<UINT>(newMenu), start));
lastSubmenu = newMenu;
map.insert(Map::value_type(DuplicateString(buffer), lastSubmenu));
}
// add the current flag to the current menu
static_cast<void>(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<void>(AppendMenu(entry->second, MF_POPUP, reinterpret_cast<UINT>(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<void>(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<char>(HIBYTE( wParam )), //lint !e1924 // c-style case msvc bug
static_cast<char>(LOBYTE( wParam )) //lint !e1924 // c-style case msvc bug
};
wchar_t u;
if (MultiByteToWideChar (CP_ACP, MB_PRECOMPOSED, reinterpret_cast<const char *>(ansiChars), 2, &u, 1))
ms_queueCharacterHookFunction(0, static_cast<int>(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<char>(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<int>(unicodeChar[0]));
}
}
else
{
ms_queueCharacterHookFunction(keyCode, static_cast<int>(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<tm*>(&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<DWORD>(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<DWORD *>(&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<int>(directorySeparator));
if (endOfDirectory)
IGNORE_RETURN(workingTargetDirectory.append(targetPathname, static_cast<size_t>(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<size_t>(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<char *>(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<DWORD>(bufferSize);
buffer[0] = '\0';
bool result = GetUserName(buffer, &windowsBufferSize) == TRUE;
return result;
}
//-----------------------------------------------------------------------
Os::OsPID_t Os::getProcessId()
{
return static_cast<OsPID_t>(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<int>(ShellExecute(NULL, "open", URL.c_str(), NULL, NULL, SW_SHOWNORMAL));
return (result > 32);
}
// ======================================================================