/* Copyright (c) 2017 Alex Diener This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Alex Diener alex@ludobloom.com */ #define _WIN32_WINNT 0x0501 #include #include #include #include "shell/Shell.h" #include "shell/ShellBatteryInfo.h" #include "shell/ShellCallbacks.h" #include "shell/ShellKeyCodes.h" #include "shell/ShellThreads.h" #include "utilities/Log.h" #include "utilities/UTFUtilities.h" #include "wglshell/WGLShell.h" #include "wglshell/WGLShellCallbacks.h" #include "wglshell/WGLTarget.h" #include "stem_core.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct WGLShellTimer { double interval; double nextFireTime; unsigned int flags; bool canceled; unsigned int id; void (* callback)(unsigned int timerID, void * context); void * context; }; struct threadFuncInvocation { int (* threadFunction)(void * context); void * context; }; #define VSYNC_DEFAULT_WINDOW true #define VSYNC_DEFAULT_FULLSCREEN true #define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 #define WGL_DRAW_TO_WINDOW_ARB 0x2001 #define WGL_DRAW_TO_BITMAP_ARB 0x2002 #define WGL_ACCELERATION_ARB 0x2003 #define WGL_NEED_PALETTE_ARB 0x2004 #define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005 #define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006 #define WGL_SWAP_METHOD_ARB 0x2007 #define WGL_NUMBER_OVERLAYS_ARB 0x2008 #define WGL_NUMBER_UNDERLAYS_ARB 0x2009 #define WGL_TRANSPARENT_ARB 0x200A #define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037 #define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038 #define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039 #define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A #define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B #define WGL_SHARE_DEPTH_ARB 0x200C #define WGL_SHARE_STENCIL_ARB 0x200D #define WGL_SHARE_ACCUM_ARB 0x200E #define WGL_SUPPORT_GDI_ARB 0x200F #define WGL_SUPPORT_OPENGL_ARB 0x2010 #define WGL_DOUBLE_BUFFER_ARB 0x2011 #define WGL_STEREO_ARB 0x2012 #define WGL_PIXEL_TYPE_ARB 0x2013 #define WGL_COLOR_BITS_ARB 0x2014 #define WGL_RED_BITS_ARB 0x2015 #define WGL_RED_SHIFT_ARB 0x2016 #define WGL_GREEN_BITS_ARB 0x2017 #define WGL_GREEN_SHIFT_ARB 0x2018 #define WGL_BLUE_BITS_ARB 0x2019 #define WGL_BLUE_SHIFT_ARB 0x201A #define WGL_ALPHA_BITS_ARB 0x201B #define WGL_ALPHA_SHIFT_ARB 0x201C #define WGL_ACCUM_BITS_ARB 0x201D #define WGL_ACCUM_RED_BITS_ARB 0x201E #define WGL_ACCUM_GREEN_BITS_ARB 0x201F #define WGL_ACCUM_BLUE_BITS_ARB 0x2020 #define WGL_ACCUM_ALPHA_BITS_ARB 0x2021 #define WGL_DEPTH_BITS_ARB 0x2022 #define WGL_STENCIL_BITS_ARB 0x2023 #define WGL_AUX_BUFFERS_ARB 0x2024 #define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 #define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 #define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 #define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 #define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 #define WGL_TYPE_RGBA_ARB 0x202B #define WGL_SAMPLE_BUFFERS_ARB 0x2041 #define WGL_SAMPLES_ARB 0x2042 #define WM_INPUT_DEVICE_CHANGE 0x00FE #define DEVICE_CHANGE_TIMER_ID_1 1200 #define DEVICE_CHANGE_TIMER_INTERVAL_1 300 #define DEVICE_CHANGE_TIMER_ID_2 1201 #define DEVICE_CHANGE_TIMER_INTERVAL_2 2000 #define SHELL_KEY_CODE_MAX 0xE7 #define MOUSE_BUTTON_COUNT_MAX 3 #ifndef MAPVK_VSC_TO_VK_EX #define MAPVK_VSC_TO_VK_EX 3 #endif #ifndef MAPVK_VK_TO_VSC_EX #define MAPVK_VK_TO_VSC_EX 4 #endif typedef BOOL (WINAPI * WGLFunction_wglChoosePixelFormatARB)(HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); typedef const char * (WINAPI * WGLFunction_wglGetExtensionsStringEXT)(void); typedef BOOL (WINAPI * WGLFunction_wglSwapIntervalEXT)(int interval); static HWND window; static unsigned int buttonMask; static unsigned int modifiers; static unsigned int primaryDisplayIndex; static bool isFullScreen; static bool cursorHiddenByHide; static bool cursorHiddenUntilMouseMoves; static bool mouseDeltaMode; static int restoreMouseX, restoreMouseY; static int lastMouseX = INT_MIN, lastMouseY; static int ignoreDeltaX, ignoreDeltaY; static DWORD ignoreMouseMoveEventsBeforeTime; static unsigned int ignoreNextMouseUp; static float cursorUnhideThreshold = 0.0f; static float mouseDeltaXSinceHide, mouseDeltaYSinceHide; static double mainLoopTargetInterval; static bool vsyncWindow = VSYNC_DEFAULT_WINDOW, vsyncFullscreen = VSYNC_DEFAULT_FULLSCREEN; static unsigned int nextTimerID; static size_t timerCount; static struct WGLShellTimer * timers; static bool redisplayNeeded; static HDC displayContext; static HGLRC glContext; static bool windowShown = false; static int lastWidth, lastHeight; static RECT oldWindowRect; static DWORD oldWindowStyle; static int accumulatedWheelDelta; static bool backgrounded; static bool coalesceMouseMotionEvents; static double lastDrawTime; static bool initialized; static char * windowTitle; static bool documentEdited; static bool runningDialog; static bool sendActivationClicks = false; static bool keysDown[SHELL_KEY_CODE_MAX + 1]; static void (* keyboardDisconnectCallback)(void); static void (* usbConnectionChangeCallback)(void); static ShellMutex callbackQueueHeadMutex; static volatile unsigned int queuedCallbackHead, queuedCallbackTail; #define QUEUED_CALLBACK_MAX 500 static struct { void (* callback)(void * context); void * context; } queuedCallbacks[QUEUED_CALLBACK_MAX]; #define CUSTOM_CURSOR_ID_OFFSET 1000 static unsigned int customCursorCount; static struct { unsigned int representationCount; struct { float scaleFactor; HCURSOR cursor; } * representations; } * customCursors; #define WINDOW_STYLE (WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SIZEBOX | WS_OVERLAPPEDWINDOW) static WGLFunction_wglGetExtensionsStringEXT wglGetExtensionsStringEXT; static void warpPointerAndIgnoreEvent(POINT warpPosition) { ignoreDeltaX = warpPosition.x - lastMouseX; ignoreDeltaY = warpPosition.y - lastMouseY; ClientToScreen(window, &warpPosition); SetCursorPos(warpPosition.x, warpPosition.y); } static void processMouseMovement(int x, int y) { int reportedDeltaX = x - lastMouseX; int reportedDeltaY = y - lastMouseY; if (cursorHiddenUntilMouseMoves) { mouseDeltaXSinceHide += reportedDeltaX; mouseDeltaYSinceHide += reportedDeltaY; if (fabs(mouseDeltaXSinceHide) >= cursorUnhideThreshold || fabs(mouseDeltaYSinceHide) >= cursorUnhideThreshold) { cursorHiddenUntilMouseMoves = false; if (!mouseDeltaMode) { ShowCursor(true); } if (cursorUnhiddenCallback != NULL) { cursorUnhiddenCallback(true, Shell_getCurrentTime()); } } } int reportedX, reportedY; if (mouseDeltaMode) { reportedX = restoreMouseX; reportedY = restoreMouseY; } else { reportedX = x; reportedY = y; lastMouseX = x; lastMouseY = y; } if (buttonMask != 0) { if (initialized && mouseDraggedCallback != NULL) { mouseDraggedCallback(buttonMask, reportedX, reportedY, reportedDeltaX, reportedDeltaY, modifiers, Shell_getCurrentTime()); } } else { if (initialized && mouseMovedCallback != NULL) { mouseMovedCallback(reportedX, reportedY, reportedDeltaX, reportedDeltaY, modifiers, Shell_getCurrentTime()); } } } static POINT getWindowCenter(void) { RECT rect; GetClientRect(window, &rect); return (POINT) {(rect.right - rect.left) / 2, (rect.bottom - rect.top) / 2}; } static unsigned int displayIndexToMonitorIndex(unsigned int displayIndex) { if (displayIndex == 0) { return primaryDisplayIndex; } if (displayIndex <= primaryDisplayIndex) { return displayIndex - 1; } return displayIndex; } static unsigned int monitorIndexToDisplayIndex(unsigned int monitorIndex) { if (monitorIndex == primaryDisplayIndex) { return 0; } if (monitorIndex < primaryDisplayIndex) { return monitorIndex + 1; } return monitorIndex; } static void processMouseMoveMessage(LPARAM lParam) { TRACKMOUSEEVENT trackMouseEvent = {sizeof(trackMouseEvent), TME_LEAVE, window, 0}; TrackMouseEvent(&trackMouseEvent); int x = GET_X_LPARAM(lParam); int y = GET_Y_LPARAM(lParam); lastMouseX += ignoreDeltaX; lastMouseY += ignoreDeltaY; ignoreDeltaX = ignoreDeltaY = 0; if (x != lastMouseX || y != lastMouseY) { if (lastMouseX == INT_MIN) { lastMouseX = x; lastMouseY = y; } processMouseMovement(x, y); if (mouseDeltaMode) { warpPointerAndIgnoreEvent(getWindowCenter()); } } else if (mouseDeltaMode) { POINT windowCenter = getWindowCenter(); ignoreDeltaX = windowCenter.x - lastMouseX; ignoreDeltaY = windowCenter.y - lastMouseY; } } static void sleepEventLoop(double currentTime, bool coordinateWithTimers) { DWORD milliseconds = INFINITE; if (coordinateWithTimers) { double closestNextFireTime = 1000.0; for (unsigned int timerIndex = 0; timerIndex < timerCount; timerIndex++) { if (!timers[timerIndex].canceled && (!backgrounded || (timers[timerIndex].flags & TIMER_FLAG_RUN_IN_BACKGROUND)) && (!runningDialog || (timers[timerIndex].flags & TIMER_FLAG_RUN_DURING_FILE_DIALOGS)) && timers[timerIndex].nextFireTime - currentTime < closestNextFireTime) { closestNextFireTime = timers[timerIndex].nextFireTime - currentTime; } } if (closestNextFireTime < 0.0) { closestNextFireTime = 0.0; } milliseconds = closestNextFireTime * 1000; } MsgWaitForMultipleObjects(0, NULL, FALSE, milliseconds, QS_ALLINPUT); } void Shell_mainLoop(void) { callbackQueueHeadMutex = Shell_createMutex(); double loopStartTime = Shell_getCurrentTime(); for (;;) { MSG message, lastMouseMoveMessage; lastMouseMoveMessage.message = 0; while (PeekMessage(&message, NULL, 0, 0, PM_NOREMOVE)) { if (!GetMessage(&message, NULL, 0, 0)) { return; } if (message.message == WM_MOUSEMOVE && message.time < ignoreMouseMoveEventsBeforeTime) { continue; } if (coalesceMouseMotionEvents && message.message == WM_MOUSEMOVE) { lastMouseMoveMessage = message; } else { if (lastMouseMoveMessage.message == WM_MOUSEMOVE) { processMouseMoveMessage(lastMouseMoveMessage.lParam); lastMouseMoveMessage.message = 0; } TranslateMessage(&message); DispatchMessage(&message); } } if (lastMouseMoveMessage.message == WM_MOUSEMOVE) { processMouseMoveMessage(lastMouseMoveMessage.lParam); } double currentTime = Shell_getCurrentTime(); for (unsigned int timerIndex = 0; timerIndex < timerCount; timerIndex++) { if (!timers[timerIndex].canceled && (!backgrounded || (timers[timerIndex].flags & TIMER_FLAG_RUN_IN_BACKGROUND)) && (!runningDialog || (timers[timerIndex].flags & TIMER_FLAG_RUN_DURING_FILE_DIALOGS)) && currentTime >= timers[timerIndex].nextFireTime) { timers[timerIndex].callback(timers[timerIndex].id, timers[timerIndex].context); if (timers[timerIndex].flags & TIMER_FLAG_REPEAT) { //timers[timerIndex].nextFireTime += timers[timerIndex].interval; // Similar to above line, but ensures that next fire time is in the future timers[timerIndex].nextFireTime += ceil((currentTime - timers[timerIndex].nextFireTime) / timers[timerIndex].interval) * timers[timerIndex].interval; } else { timers[timerIndex].canceled = true; } } if (timers[timerIndex].canceled) { timerCount--; for (unsigned int timerIndex2 = timerIndex; timerIndex2 < timerCount; timerIndex2++) { timers[timerIndex2] = timers[timerIndex2 + 1]; } timerIndex--; } } while (queuedCallbackTail != queuedCallbackHead) { queuedCallbacks[queuedCallbackTail].callback(queuedCallbacks[queuedCallbackTail].context); queuedCallbackTail = (queuedCallbackTail + 1) % QUEUED_CALLBACK_MAX; } if (redisplayNeeded) { currentTime = Shell_getCurrentTime(); if (mainLoopTargetInterval > 0.0) { timeBeginPeriod(1); double timeAllocationUsed = fmod(currentTime - loopStartTime, mainLoopTargetInterval); useconds_t sleepTime = (mainLoopTargetInterval - timeAllocationUsed) * 1000000; usleep(sleepTime); timeEndPeriod(1); } redisplayNeeded = false; RedrawWindow(window, NULL, NULL, RDW_NOERASE | RDW_INTERNALPAINT | RDW_INVALIDATE | RDW_UPDATENOW); } if (!redisplayNeeded) { sleepEventLoop(Shell_getCurrentTime(), timerCount > 0); } } } void Shell_redisplay(void) { if (!redisplayNeeded) { lastDrawTime = Shell_getCurrentTime(); } redisplayNeeded = true; } bool Shell_needsDisplay(void) { return redisplayNeeded; } bool Shell_isFullScreen(void) { return isFullScreen; } void Shell_setDisplaySleepEnabled(bool enabled) { SetThreadExecutionState(enabled ? ES_CONTINUOUS : ES_CONTINUOUS | ES_DISPLAY_REQUIRED); } void Shell_setDrawThrottleRate(double targetInterval) { mainLoopTargetInterval = targetInterval; } static void setVSync(bool sync) { static bool extensionsChecked; static WGLFunction_wglSwapIntervalEXT wglSwapIntervalEXT; if (!extensionsChecked) { if (wglGetExtensionsStringEXT != NULL) { const GLubyte * extensionsString = glGetString(GL_EXTENSIONS); LOG(0, "glGetString(GL_EXTENSIONS): %p", extensionsString); const char * wglExtensionsString = wglGetExtensionsStringEXT(); LOG(0, "wglGetExtensionsStringEXT(): %p", wglExtensionsString); if (strstr((const char *) extensionsString, "WGL_EXT_swap_control") && strstr(wglExtensionsString, "WGL_EXT_swap_control")) { wglSwapIntervalEXT = (WGLFunction_wglSwapIntervalEXT) wglGetProcAddress("wglSwapIntervalEXT"); LOG(0, "wglGetProcAddress(\"wglSwapIntervalEXT\"): %p", wglSwapIntervalEXT); } } extensionsChecked = true; } if (wglSwapIntervalEXT != NULL) { #ifdef LOG_ENABLED BOOL success = #endif wglSwapIntervalEXT(sync ? 1 : 0); LOG(0, "wglSwapIntervalEXT(%d): %s", sync ? 1 : 0, success ? "true" : "false"); } } #ifndef MONITOR_DEFAULTTONEAREST #define MONITOR_DEFAULTTONEAREST 2 #endif WINUSERAPI HMONITOR WINAPI MonitorFromRect(LPCRECT,DWORD); struct monitorEnumProcGetMonitorAtIndexContext { unsigned int targetMonitorIndex; unsigned int monitorIndex; HMONITOR hMonitor; }; static BOOL CALLBACK monitorEnumProcGetMonitorAtIndex(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { struct monitorEnumProcGetMonitorAtIndexContext * context = (struct monitorEnumProcGetMonitorAtIndexContext *) dwData; if (context->monitorIndex++ == context->targetMonitorIndex) { context->hMonitor = hMonitor; } return true; } bool Shell_enterFullScreen(unsigned int displayIndex, bool captureDisplay) { if (isFullScreen) { return true; } GetWindowRect(window, &oldWindowRect); HMONITOR monitor = NULL; struct monitorEnumProcGetMonitorAtIndexContext context = {displayIndexToMonitorIndex(displayIndex), 0, NULL}; if (EnumDisplayMonitors(NULL, NULL, monitorEnumProcGetMonitorAtIndex, (LPARAM) &context)) { monitor = context.hMonitor; } else { monitor = MonitorFromRect(&oldWindowRect, MONITOR_DEFAULTTONEAREST); } if (monitor == NULL) { return false; } MONITORINFO monitorInfo; monitorInfo.cbSize = sizeof(monitorInfo); GetMonitorInfo(monitor, &monitorInfo); DWORD windowStyle; oldWindowStyle = windowStyle = GetWindowLong(window, GWL_STYLE); if (captureDisplay) { windowStyle &= ~WS_OVERLAPPEDWINDOW; windowStyle |= WS_POPUP; } else { windowStyle &= ~(WS_CAPTION | WS_THICKFRAME); } SetWindowLong(window, GWL_STYLE, windowStyle); RECT screenRect = monitorInfo.rcMonitor; SetWindowPos(window, HWND_TOP, screenRect.left, screenRect.top, screenRect.right - screenRect.left, screenRect.bottom - screenRect.top, SWP_NOZORDER | SWP_FRAMECHANGED); isFullScreen = true; setVSync(vsyncFullscreen); if (mouseDeltaMode) { warpPointerAndIgnoreEvent(getWindowCenter()); } return true; } void Shell_exitFullScreen(void) { if (!isFullScreen) { return; } SetWindowLong(window, GWL_STYLE, oldWindowStyle); SetWindowPos(window, HWND_TOP, oldWindowRect.left, oldWindowRect.top, oldWindowRect.right - oldWindowRect.left, oldWindowRect.bottom - oldWindowRect.top, SWP_NOZORDER | SWP_FRAMECHANGED); isFullScreen = false; setVSync(vsyncWindow); if (mouseDeltaMode) { warpPointerAndIgnoreEvent(getWindowCenter()); } } double Shell_getCurrentTime(void) { static LARGE_INTEGER frequency; LARGE_INTEGER currentTime; if (frequency.QuadPart == 0) { QueryPerformanceFrequency(&frequency); } QueryPerformanceCounter(¤tTime); return (double) currentTime.QuadPart / frequency.QuadPart; } #define RESOURCE_PATH_MAX 4096 const char * Shell_getResourcePath(void) { static char * resourcePath; if (resourcePath == NULL) { wchar_t widePath[RESOURCE_PATH_MAX / 4]; GetModuleFileNameW(NULL, widePath, sizeof_count(widePath)); widePath[sizeof_count(widePath) - 1] = '\0'; resourcePath = malloc(RESOURCE_PATH_MAX); WideCharToMultiByte(CP_UTF8, 0, widePath, -1, resourcePath, RESOURCE_PATH_MAX, NULL, NULL); int charIndex; for (charIndex = strlen(resourcePath) - 1; charIndex > 0; charIndex--) { if (resourcePath[charIndex] == '\\') { charIndex++; break; } } strncpy(resourcePath + charIndex, "Resources", RESOURCE_PATH_MAX - charIndex); } return resourcePath; } const char * Shell_getSupportPath(const char * subdirectory) { static char supportPath[PATH_MAX]; if (subdirectory == NULL) { strncpy(supportPath, getenv("APPDATA"), PATH_MAX); } else { snprintf(supportPath, PATH_MAX, "%s/%s", getenv("APPDATA"), subdirectory); } _mkdir(supportPath); return supportPath; } void Shell_getMainScreenSize(unsigned int * outWidth, unsigned int * outHeight) { if (outWidth != NULL) { *outWidth = GetSystemMetrics(SM_CXSCREEN); } if (outHeight != NULL) { *outHeight = GetSystemMetrics(SM_CYSCREEN); } } unsigned int Shell_getDisplayCount(void) { return GetSystemMetrics(SM_CMONITORS); } struct monitorEnumProcGetIndexContext { HMONITOR hMonitor; unsigned int monitorIndex; bool found; }; static BOOL CALLBACK monitorEnumProcGetIndex(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { struct monitorEnumProcGetIndexContext * context = (struct monitorEnumProcGetIndexContext *) dwData; if (!context->found) { if (hMonitor == context->hMonitor) { context->found = true; } else { context->monitorIndex++; } } return true; } unsigned int Shell_getDisplayIndexFromWindow(void) { RECT windowRect; BOOL success = GetWindowRect(window, &windowRect); LOG(0, "GetWindowRect(): %s, {%ld, %ld, %ld, %ld}", success ? "true" : "false", windowRect.left, windowRect.top, windowRect.right, windowRect.bottom); struct monitorEnumProcGetIndexContext context = {NULL, 0, false}; context.hMonitor = MonitorFromRect(&windowRect, MONITOR_DEFAULTTONEAREST); LOG(0, "MonitorFromRect(): %p", context.hMonitor); success = EnumDisplayMonitors(NULL, NULL, monitorEnumProcGetIndex, (LPARAM) &context); LOG(0, "EnumDisplayMonitors(): %s, %s, %u", success ? "true" : "false", context.found ? "true" : "false", context.monitorIndex); if (success && context.found) { return monitorIndexToDisplayIndex(context.monitorIndex); } return 0; } struct monitorEnumProcGetBoundsContext { unsigned int targetMonitorIndex; unsigned int monitorIndex; bool found; RECT bounds; }; static BOOL CALLBACK monitorEnumProcGetBounds(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { struct monitorEnumProcGetBoundsContext * context = (struct monitorEnumProcGetBoundsContext *) dwData; if (context->monitorIndex++ == context->targetMonitorIndex) { context->bounds = *lprcMonitor; context->found = true; } return true; } void Shell_getDisplayBounds(unsigned int displayIndex, int * outOffsetX, int * outOffsetY, unsigned int * outWidth, unsigned int * outHeight) { struct monitorEnumProcGetBoundsContext context = {displayIndexToMonitorIndex(displayIndex), 0, false, {0, 0, 0, 0}}; BOOL success = EnumDisplayMonitors(NULL, NULL, monitorEnumProcGetBounds, (LPARAM) &context); if (success && context.found) { if (outOffsetX != NULL) { *outOffsetX = context.bounds.left; } if (outOffsetY != NULL) { *outOffsetY = context.bounds.top; } if (outWidth != NULL) { *outWidth = context.bounds.right - context.bounds.left; } if (outHeight != NULL) { *outHeight = context.bounds.bottom - context.bounds.top; } } } double Shell_getDisplayRefreshRate(unsigned int displayIndex) { // TODO: displayIndex DEVMODE devmode; if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devmode)) { if (devmode.dmDisplayFrequency <= 1) { return 0; } return devmode.dmDisplayFrequency; } return 0; } float Shell_getDisplayScaleFactor(unsigned int displayIndex) { // TODO return 1.0f; } float Shell_getDisplayDPI(unsigned int displayIndex) { // TODO: displayIndex DEVMODE devmode; if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devmode)) { if (devmode.dmYResolution <= 1) { return 1; } return devmode.dmYResolution; } return 1; } static BOOL CALLBACK monitorEnumProcGetWorkArea(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { struct monitorEnumProcGetBoundsContext * context = (struct monitorEnumProcGetBoundsContext *) dwData; if (context->monitorIndex++ == context->targetMonitorIndex) { MONITORINFO monitorInfo; monitorInfo.cbSize = sizeof(monitorInfo); if (GetMonitorInfo(hMonitor, &monitorInfo)) { context->bounds = monitorInfo.rcWork; context->found = true; } } return true; } void Shell_getSafeWindowRect(unsigned int displayIndex, int * outOffsetX, int * outOffsetY, unsigned int * outWidth, unsigned int * outHeight) { struct monitorEnumProcGetBoundsContext context = {displayIndexToMonitorIndex(displayIndex), 0, false, {0, 0, 0, 0}}; BOOL success = EnumDisplayMonitors(NULL, NULL, monitorEnumProcGetWorkArea, (LPARAM) &context); if (success && context.found) { RECT adjustedRect = context.bounds; AdjustWindowRect(&adjustedRect, WINDOW_STYLE, false); context.bounds.left -= adjustedRect.left - context.bounds.left; context.bounds.top -= adjustedRect.top - context.bounds.top; context.bounds.right -= adjustedRect.right - context.bounds.right; context.bounds.bottom -= adjustedRect.bottom - context.bounds.bottom; if (outOffsetX != NULL) { *outOffsetX = context.bounds.left; } if (outOffsetY != NULL) { *outOffsetY = context.bounds.top; } if (outWidth != NULL) { *outWidth = context.bounds.right - context.bounds.left; } if (outHeight != NULL) { *outHeight = context.bounds.bottom - context.bounds.top; } } } unsigned int Shell_setTimer(double interval, unsigned int flags, void (* callback)(unsigned int timerID, void * context), void * context) { timers = realloc(timers, (timerCount + 1) * sizeof(*timers)); timers[timerCount].interval = interval; timers[timerCount].nextFireTime = Shell_getCurrentTime() + interval; timers[timerCount].flags = flags; timers[timerCount].canceled = false; timers[timerCount].id = nextTimerID++; timers[timerCount].callback = callback; timers[timerCount].context = context; PostMessage(window, WM_USER, 0, 0); // Wake up run loop in case it's blocked on GetMessage return timers[timerCount++].id; } void Shell_cancelTimer(unsigned int timerID) { unsigned int timerIndex; for (timerIndex = 0; timerIndex < timerCount; timerIndex++) { if (timers[timerIndex].id == timerID) { timers[timerIndex].canceled = true; break; } } } void Shell_setCursorVisible(bool visible) { if (visible && cursorHiddenByHide) { cursorHiddenByHide = false; if (!mouseDeltaMode) { ShowCursor(true); } } else if (!visible && !cursorHiddenByHide) { cursorHiddenByHide = true; ShowCursor(false); } } void Shell_hideCursorUntilMouseMoves(void) { if (!cursorHiddenUntilMouseMoves) { cursorHiddenUntilMouseMoves = true; mouseDeltaXSinceHide = 0.0f; mouseDeltaYSinceHide = 0.0f; ShowCursor(false); } } void Shell_getMousePosition(float * outX, float * outY) { if (outX != NULL) { *outX = lastMouseX; } if (outY != NULL) { *outY = lastMouseY; } } void Shell_setMousePosition(float x, float y, bool sendEvent) { POINT warpPosition = {x, y}; if (!sendEvent) { ignoreDeltaX = warpPosition.x - lastMouseX; ignoreDeltaY = warpPosition.y - lastMouseY; } ClientToScreen(window, &warpPosition); SetCursorPos(warpPosition.x, warpPosition.y); } void Shell_setUnhideCursorMovementThreshold(float threshold) { cursorUnhideThreshold = threshold; } #ifndef GCL_HCURSOR #define GCL_HCURSOR -12 #endif void Shell_setCursor(ShellCursorID cursorID) { HCURSOR cursor = NULL; if (cursorID >= CUSTOM_CURSOR_ID_OFFSET) { cursorID -= CUSTOM_CURSOR_ID_OFFSET; if ((unsigned int) cursorID < customCursorCount) { float scaleFactor = Shell_getDisplayScaleFactor(Shell_getDisplayIndexFromWindow()); unsigned int bestRepresentationIndex = 0; float bestScaleFactor = 0.0f; for (unsigned int representationIndex = 0; representationIndex < customCursors[cursorID].representationCount; representationIndex++) { if (customCursors[cursorID].representations[representationIndex].scaleFactor > bestScaleFactor && customCursors[cursorID].representations[representationIndex].scaleFactor <= scaleFactor) { bestRepresentationIndex = representationIndex; bestScaleFactor = customCursors[cursorID].representations[representationIndex].scaleFactor; } } cursor = customCursors[cursorID].representations[bestRepresentationIndex].cursor; } } else { switch (cursorID) { case ShellCursor_arrow: cursor = LoadCursor(NULL, IDC_ARROW); break; case ShellCursor_iBeam: cursor = LoadCursor(NULL, IDC_IBEAM); break; case ShellCursor_crosshair: cursor = LoadCursor(NULL, IDC_CROSS); break; case ShellCursor_hand: cursor = LoadCursor(NULL, IDC_HAND); break; case ShellCursor_wait: cursor = LoadCursor(NULL, IDC_WAIT); break; case WGLShellCursor_appStarting: cursor = LoadCursor(NULL, IDC_APPSTARTING); break; case WGLShellCursor_no: cursor = LoadCursor(NULL, IDC_NO); break; case WGLShellCursor_sizeAll: cursor = LoadCursor(NULL, IDC_SIZEALL); break; case WGLShellCursor_sizeNESW: cursor = LoadCursor(NULL, IDC_SIZENESW); break; case WGLShellCursor_sizeNS: cursor = LoadCursor(NULL, IDC_SIZENS); break; case WGLShellCursor_sizeNWSE: cursor = LoadCursor(NULL, IDC_SIZENWSE); break; case WGLShellCursor_sizeWE: cursor = LoadCursor(NULL, IDC_SIZEWE); break; case WGLShellCursor_upArrow: cursor = LoadCursor(NULL, IDC_UPARROW); break; case WGLShellCursor_help: cursor = LoadCursor(NULL, IDC_HELP); break; } } if (cursor != NULL) { SetClassLongPtr(window, GCL_HCURSOR, (LONG_PTR) cursor); SetCursor(cursor); } } ShellCursorID Shell_registerCustomCursor(unsigned int representationCount, struct ShellCustomCursorImage * representations, unsigned int hotspotX, unsigned int hotspotY) { customCursors = realloc(customCursors, (customCursorCount + 1) * sizeof(*customCursors)); customCursors[customCursorCount].representationCount = representationCount; customCursors[customCursorCount].representations = malloc(customCursors[customCursorCount].representationCount * sizeof(*customCursors[customCursorCount].representations)); for (unsigned int representationIndex = 0; representationIndex < representationCount; representationIndex++) { ICONINFO iconInfo; memset(&iconInfo, 0, sizeof(iconInfo)); iconInfo.fIcon = FALSE; iconInfo.xHotspot = hotspotX * representations[representationIndex].scaleFactor; iconInfo.yHotspot = hotspotY * representations[representationIndex].scaleFactor; iconInfo.hbmColor = CreateBitmap(representations[representationIndex].width, representations[representationIndex].height, 1, 32, representations[representationIndex].pixels); unsigned int maskBitsSize = (representations[representationIndex].width + 31) / 32 * 32 / 8 * representations[representationIndex].height; uint8_t * maskBits = malloc(maskBitsSize); memset(maskBits, 0xFF, maskBitsSize); iconInfo.hbmMask = CreateBitmap(representations[representationIndex].width, representations[representationIndex].height, 1, 1, maskBits); free(maskBits); customCursors[customCursorCount].representations[representationIndex].scaleFactor = representations[representationIndex].scaleFactor; customCursors[customCursorCount].representations[representationIndex].cursor = CreateIconIndirect(&iconInfo); DeleteObject(iconInfo.hbmColor); DeleteObject(iconInfo.hbmMask); } return CUSTOM_CURSOR_ID_OFFSET + customCursorCount++; } void Shell_setMouseDeltaMode(bool deltaMode) { if (!mouseDeltaMode && deltaMode) { restoreMouseX = lastMouseX; restoreMouseY = lastMouseY; mouseDeltaMode = true; ignoreMouseMoveEventsBeforeTime = GetTickCount() + 2; warpPointerAndIgnoreEvent(getWindowCenter()); ShowCursor(false); } else if (mouseDeltaMode && !deltaMode) { warpPointerAndIgnoreEvent((POINT) {restoreMouseX, restoreMouseY}); mouseDeltaMode = false; if (!cursorHiddenByHide && !cursorHiddenUntilMouseMoves) { ShowCursor(true); } } } bool Shell_getMouseDeltaMode(void) { return mouseDeltaMode; } void Shell_openURL(const char * url) { ShellExecute(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL); } enum ShellBatteryState Shell_getBatteryState(void) { SYSTEM_POWER_STATUS powerStatus; SYSTEM_POWER_CAPABILITIES powerCapabilities; if (GetPwrCapabilities(&powerCapabilities) && !powerCapabilities.SystemBatteriesPresent) { return ShellBatteryState_notBatteryPowered; } if (GetSystemPowerStatus(&powerStatus)) { if (powerStatus.ACLineStatus == 0) { return ShellBatteryState_unplugged; } if (powerStatus.ACLineStatus == 1) { if (powerStatus.BatteryFlag & 0x80) { return ShellBatteryState_batteryMissing; } if (powerStatus.BatteryFlag & 0x08) { return ShellBatteryState_charging; } return ShellBatteryState_full; } } return ShellBatteryState_unknown; } float Shell_getBatteryLevel(void) { SYSTEM_POWER_STATUS powerStatus; if (GetSystemPowerStatus(&powerStatus) && powerStatus.BatteryLifePercent <= 100) { return powerStatus.BatteryLifePercent / 100.0f; } return -1.0f; } static DWORD WINAPI callThreadFunc(LPVOID context) { struct threadFuncInvocation * invocation = context; int (* threadFunction)(void * context); void * threadContext; threadFunction = invocation->threadFunction; threadContext = invocation->context; free(invocation); return (DWORD) threadFunction(threadContext); } ShellThread Shell_createThread(int (* threadFunction)(void * context), void * context) { HANDLE thread; struct threadFuncInvocation * invocation; invocation = malloc(sizeof(struct threadFuncInvocation)); invocation->threadFunction = threadFunction; invocation->context = context; thread = CreateThread(NULL, 0, callThreadFunc, invocation, 0, NULL); return thread; } void Shell_detachThread(ShellThread thread) { CloseHandle(thread); } int Shell_joinThread(ShellThread thread) { DWORD status; DWORD returnValue = -1; status = WaitForSingleObject(thread, INFINITE); if (status != WAIT_FAILED) { GetExitCodeThread(thread, &returnValue); return returnValue; } return status; } void Shell_exitThread(int statusCode) { ExitThread(statusCode); } ShellThread Shell_getCurrentThread(void) { HANDLE duplicate = NULL; if (DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &duplicate, 0, true, DUPLICATE_SAME_ACCESS)) { CloseHandle(duplicate); } return duplicate; } ShellMutex Shell_createMutex(void) { return CreateMutex(NULL, false, NULL); } void Shell_disposeMutex(ShellMutex mutex) { CloseHandle(mutex); } void Shell_lockMutex(ShellMutex mutex) { WaitForSingleObject(mutex, INFINITE); } bool Shell_tryLockMutex(ShellMutex mutex) { return WaitForSingleObject(mutex, 0) != WAIT_FAILED; } void Shell_unlockMutex(ShellMutex mutex) { ReleaseMutex(mutex); } #define SEMAPHORE_MAX 32767 ShellSemaphore Shell_createSemaphore(unsigned int value) { return CreateSemaphore(NULL, value > SEMAPHORE_MAX ? SEMAPHORE_MAX : value, SEMAPHORE_MAX, NULL); } void Shell_disposeSemaphore(ShellSemaphore semaphore) { CloseHandle(semaphore); } void Shell_postSemaphore(ShellSemaphore semaphore) { ReleaseSemaphore(semaphore, 1, NULL); } void Shell_waitSemaphore(ShellSemaphore semaphore) { WaitForSingleObject(semaphore, INFINITE); } bool Shell_tryWaitSemaphore(ShellSemaphore semaphore) { return WaitForSingleObject(semaphore, 0) != WAIT_TIMEOUT; } void Shell_executeOnMainThread(void (* callback)(void * context), void * context) { Shell_lockMutex(callbackQueueHeadMutex); if ((queuedCallbackHead + 1) % QUEUED_CALLBACK_MAX == queuedCallbackTail) { #ifdef DEBUG fprintf(stderr, "Error: Shell_executeOnMainThread() called when the maximum number of callbacks (%u) is already queued. Couldn't add to queue.\n", QUEUED_CALLBACK_MAX); #endif Shell_unlockMutex(callbackQueueHeadMutex); return; } queuedCallbacks[queuedCallbackHead].callback = callback; queuedCallbacks[queuedCallbackHead].context = context; queuedCallbackHead = (queuedCallbackHead + 1) % QUEUED_CALLBACK_MAX; Shell_unlockMutex(callbackQueueHeadMutex); PostMessage(window, WM_USER, 0, 0); } void Shell_setVSync(bool sync, bool fullscreen) { if (fullscreen) { vsyncFullscreen = sync; if (isFullScreen) { setVSync(sync); } } else { vsyncWindow = sync; if (!isFullScreen) { setVSync(sync); } } } static void updateMouseState(void) { POINT cursorPos; GetCursorPos(&cursorPos); ScreenToClient(window, &cursorPos); bool left = !!(GetAsyncKeyState(VK_LBUTTON) & 0x8000); if (left && !(buttonMask & (1 << 0))) { ignoreNextMouseUp |= 1 << 0; } else if (!left && (buttonMask & (1 << 0))) { buttonMask &= ~(1 << 0); if (initialized && mouseUpCallback != NULL) { mouseUpCallback(0, buttonMask, cursorPos.x, cursorPos.y, modifiers, Shell_getCurrentTime()); } } bool right = !!(GetAsyncKeyState(VK_RBUTTON) & 0x8000); if (right && !(buttonMask & (1 << 1))) { ignoreNextMouseUp |= 1 << 1; } else if (!right && (buttonMask & (1 << 1))) { buttonMask &= ~(1 << 1); if (initialized && mouseUpCallback != NULL) { mouseUpCallback(1, buttonMask, cursorPos.x, cursorPos.y, modifiers, Shell_getCurrentTime()); } } bool middle = !!(GetAsyncKeyState(VK_MBUTTON) & 0x8000); if (middle && !(buttonMask & (1 << 2))) { ignoreNextMouseUp |= 1 << 2; } else if (!middle && (buttonMask & (1 << 2))) { buttonMask &= ~(1 << 2); if (initialized && mouseUpCallback != NULL) { mouseUpCallback(2, buttonMask, cursorPos.x, cursorPos.y, modifiers, Shell_getCurrentTime()); } } if (cursorPos.x != lastMouseX || cursorPos.y != lastMouseY) { processMouseMovement(cursorPos.x, cursorPos.y); } } static void updateModifierKeys(void) { unsigned int lastModifiers = modifiers; bool capsLock = GetKeyState(VK_CAPITAL) & 0x01; if (capsLock && !(modifiers & MODIFIER_CAPS_LOCK_BIT)) { modifiers |= MODIFIER_CAPS_LOCK_BIT; } else if (!capsLock && (modifiers & MODIFIER_CAPS_LOCK_BIT)) { modifiers &= ~MODIFIER_CAPS_LOCK_BIT; } bool shift = !!(GetKeyState(VK_SHIFT) & 0x8000); if (shift && !(modifiers & MODIFIER_SHIFT_BIT)) { modifiers |= MODIFIER_SHIFT_BIT; } else if (!shift && (modifiers & MODIFIER_SHIFT_BIT)) { modifiers &= ~MODIFIER_SHIFT_BIT; } bool control = !!(GetKeyState(VK_CONTROL) & 0x8000); if (control && !(modifiers & MODIFIER_CONTROL_BIT)) { modifiers |= MODIFIER_CONTROL_BIT; } else if (!control && (modifiers & MODIFIER_CONTROL_BIT)) { modifiers &= ~MODIFIER_CONTROL_BIT; } bool alt = !!(GetKeyState(VK_MENU) & 0x8000); if (alt && !(modifiers & MODIFIER_ALT_BIT)) { modifiers |= MODIFIER_ALT_BIT; } else if (!alt && (modifiers & MODIFIER_ALT_BIT)) { modifiers &= ~MODIFIER_ALT_BIT; } if (modifiers != lastModifiers) { if (initialized && keyModifiersChangedCallback != NULL) { keyModifiersChangedCallback(modifiers, lastModifiers, Shell_getCurrentTime()); } } } static void releaseAllKeys(void) { if (keyUpCallback != NULL) { double currentTime = Shell_getCurrentTime(); for (unsigned int keyCode = 0; keyCode <= SHELL_KEY_CODE_MAX; keyCode++) { if (keysDown[keyCode]) { keysDown[keyCode] = false; keyUpCallback(keyCode, modifiers, currentTime); } } } unsigned int lastModifiers = modifiers; modifiers = 0; if (modifiers != lastModifiers && keyModifiersChangedCallback != NULL) { keyModifiersChangedCallback(modifiers, lastModifiers, Shell_getCurrentTime()); } } static void releaseAllMouseButtons(void) { POINT cursorPos; GetCursorPos(&cursorPos); ScreenToClient(window, &cursorPos); for (unsigned int buttonIndex = 0; buttonIndex < MOUSE_BUTTON_COUNT_MAX; buttonIndex++) { if (buttonMask & (1 << buttonIndex)) { buttonMask &= ~(1 << buttonIndex); if (mouseUpCallback != NULL) { mouseUpCallback(buttonIndex, buttonMask, cursorPos.x, cursorPos.y, modifiers, Shell_getCurrentTime()); } } } } static void addFileDialogFilters(OPENFILENAME * openFileName, unsigned int filterCount, struct ShellFileDialogFilter * filters) { if (filterCount > 0) { static char filterString[1024]; memset(filterString, 0, sizeof(filterString)); size_t filterStringLength = 0; for (unsigned int filterIndex = 0; filterIndex < filterCount; filterIndex++) { size_t filterNameLength = strlen(filters[filterIndex].filterName); if (filterStringLength + filterNameLength >= sizeof(filterString) - 2) { #ifdef DEBUG fprintf(stderr, "Warning: Filter string too long; can't fit into OPENFILENAME.lpstrFilter\n"); #endif return; } strncpy(filterString + filterStringLength, filters[filterIndex].filterName, sizeof(filterString) - filterStringLength); filterStringLength += filterNameLength + 1; for (unsigned int extensionIndex = 0; extensionIndex < filters[filterIndex].fileExtensionCount; extensionIndex++) { char extensionString[64]; snprintf(extensionString, sizeof(extensionString), "%s*.%s", extensionIndex > 0 ? ";" : "", filters[filterIndex].fileExtensions[extensionIndex]); extensionString[sizeof(extensionString) - 1] = 0; size_t extensionStringLength = strlen(extensionString); if (filterStringLength + extensionStringLength >= sizeof(filterString) - 2) { #ifdef DEBUG fprintf(stderr, "Warning: Filter string too long; can't fit into OPENFILENAME.lpstrFilter\n"); #endif return; } strncpy(filterString + filterStringLength, extensionString, sizeof(filterString) - filterStringLength); filterStringLength += extensionStringLength; } filterStringLength++; } char allFilesString[] = "All files\0*.*"; if (filterStringLength + sizeof(allFilesString) >= sizeof(filterString) - 2) { #ifdef DEBUG fprintf(stderr, "Warning: Filter string too long; can't fit into OPENFILENAME.lpstrFilter\n"); #endif return; } memcpy(filterString + filterStringLength, allFilesString, sizeof(allFilesString)); openFileName->lpstrFilter = filterString; } } const char * Shell_openFileDialog(const char * title, const char * defaultDirectory, const char * defaultFileName, unsigned int filterCount, struct ShellFileDialogFilter * filters) { OPENFILENAME openFileName; static char * returnedFilePath; if (returnedFilePath == NULL) { returnedFilePath = malloc(MAX_PATH); } ZeroMemory(&openFileName, sizeof(openFileName)); openFileName.lStructSize = sizeof(openFileName); openFileName.hwndOwner = window; openFileName.lpstrTitle = title; openFileName.lpstrFile = returnedFilePath; openFileName.lpstrFile[0] = '\0'; openFileName.nMaxFile = MAX_PATH; openFileName.lpstrInitialDir = defaultDirectory; openFileName.Flags |= OFN_NOCHANGEDIR | OFN_FILEMUSTEXIST; addFileDialogFilters(&openFileName, filterCount, filters); if (mouseDeltaMode || cursorHiddenByHide || cursorHiddenUntilMouseMoves) { ShowCursor(true); if (cursorHiddenUntilMouseMoves) { cursorHiddenUntilMouseMoves = false; if (cursorUnhiddenCallback != NULL) { cursorUnhiddenCallback(true, Shell_getCurrentTime()); } } } runningDialog = true; BOOL success = GetOpenFileName(&openFileName); runningDialog = false; if (cursorHiddenByHide || mouseDeltaMode) { ShowCursor(false); } updateMouseState(); updateModifierKeys(); if (!success) { return NULL; } return returnedFilePath; } const char ** Shell_openFileDialogMultiple(const char * title, const char * defaultDirectory, unsigned int defaultFileNameCount, const char ** defaultFileNames, unsigned int * outFilePathCount, unsigned int filterCount, struct ShellFileDialogFilter * filters) { OPENFILENAME openFileName; static unsigned int returnedFilePathCount; static char ** returnedFilePaths; char filePath[8192]; ZeroMemory(&openFileName, sizeof(openFileName)); openFileName.lStructSize = sizeof(openFileName); openFileName.hwndOwner = window; openFileName.lpstrTitle = title; openFileName.lpstrFile = filePath; openFileName.lpstrFile[0] = '\0'; openFileName.nMaxFile = sizeof(filePath); openFileName.lpstrInitialDir = defaultDirectory; openFileName.Flags |= OFN_NOCHANGEDIR | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_EXPLORER; addFileDialogFilters(&openFileName, filterCount, filters); if (mouseDeltaMode || cursorHiddenByHide || cursorHiddenUntilMouseMoves) { ShowCursor(true); if (cursorHiddenUntilMouseMoves) { cursorHiddenUntilMouseMoves = false; if (cursorUnhiddenCallback != NULL) { cursorUnhiddenCallback(true, Shell_getCurrentTime()); } } } runningDialog = true; BOOL success = GetOpenFileName(&openFileName); runningDialog = false; if (cursorHiddenByHide || mouseDeltaMode) { ShowCursor(false); } updateMouseState(); updateModifierKeys(); if (success) { for (unsigned int filePathIndex = 0; filePathIndex < returnedFilePathCount; filePathIndex++) { free(returnedFilePaths[filePathIndex]); } free(returnedFilePaths); returnedFilePathCount = 0; for (unsigned int charIndex = 0; charIndex < sizeof(filePath) && filePath[charIndex] != 0; charIndex += strlen(filePath + charIndex) + 1) { returnedFilePathCount++; } returnedFilePaths = malloc(returnedFilePathCount * sizeof(*returnedFilePaths)); if (returnedFilePathCount == 1) { returnedFilePaths[0] = strdup(filePath); } else if (returnedFilePathCount > 1) { unsigned int nameLength = 0, basePathLength = strlen(filePath); returnedFilePathCount = 0; for (unsigned int charIndex = basePathLength + 1; charIndex < sizeof(filePath) && filePath[charIndex] != 0; charIndex += nameLength + 1) { nameLength = strlen(filePath + charIndex); returnedFilePaths[returnedFilePathCount] = malloc(basePathLength + 1 + nameLength + 1); memcpy(returnedFilePaths[returnedFilePathCount], filePath, basePathLength); returnedFilePaths[returnedFilePathCount][basePathLength] = '\\'; memcpy(returnedFilePaths[returnedFilePathCount] + basePathLength + 1, filePath + charIndex, nameLength); returnedFilePaths[returnedFilePathCount][basePathLength + 1 + nameLength] = 0; returnedFilePathCount++; } } *outFilePathCount = returnedFilePathCount; return (const char **) returnedFilePaths; } return false; } const char * Shell_chooseFolderDialog(const char * title, const char * defaultDirectory) { BROWSEINFO browseInfo; bool success = false; static char * returnedFilePath; if (returnedFilePath == NULL) { returnedFilePath = malloc(MAX_PATH); } ZeroMemory(&browseInfo, sizeof(browseInfo)); browseInfo.hwndOwner = window; browseInfo.lpszTitle = title; browseInfo.pszDisplayName = returnedFilePath; browseInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI; if (mouseDeltaMode || cursorHiddenByHide || cursorHiddenUntilMouseMoves) { ShowCursor(true); if (cursorHiddenUntilMouseMoves) { cursorHiddenUntilMouseMoves = false; if (cursorUnhiddenCallback != NULL) { cursorUnhiddenCallback(true, Shell_getCurrentTime()); } } } runningDialog = true; LPITEMIDLIST list = SHBrowseForFolder(&browseInfo); runningDialog = false; if (cursorHiddenByHide || mouseDeltaMode) { ShowCursor(false); } updateMouseState(); updateModifierKeys(); if (list != NULL && SHGetPathFromIDList(list, returnedFilePath)) { success = true; } CoTaskMemFree(list); if (!success) { return NULL; } return returnedFilePath; } const char * Shell_saveFileDialog(const char * title, const char * defaultDirectory, const char * defaultFileName, unsigned int filterCount, struct ShellFileDialogFilter * filters) { OPENFILENAME openFileName; static char * returnedFilePath; if (returnedFilePath == NULL) { returnedFilePath = malloc(MAX_PATH); } ZeroMemory(&openFileName, sizeof(openFileName)); openFileName.lStructSize = sizeof(openFileName); openFileName.hwndOwner = window; openFileName.lpstrTitle = title; if (defaultFileName == NULL) { returnedFilePath[0] = '\0'; } else { strncpy(returnedFilePath, defaultFileName, MAX_PATH); } openFileName.lpstrFile = returnedFilePath; openFileName.nMaxFile = MAX_PATH; openFileName.lpstrInitialDir = defaultDirectory; openFileName.Flags |= OFN_NOCHANGEDIR; addFileDialogFilters(&openFileName, filterCount, filters); if (mouseDeltaMode || cursorHiddenByHide || cursorHiddenUntilMouseMoves) { ShowCursor(true); if (cursorHiddenUntilMouseMoves) { cursorHiddenUntilMouseMoves = false; if (cursorUnhiddenCallback != NULL) { cursorUnhiddenCallback(true, Shell_getCurrentTime()); } } } runningDialog = true; BOOL success = GetSaveFileName(&openFileName); runningDialog = false; if (cursorHiddenByHide || mouseDeltaMode) { ShowCursor(false); } updateMouseState(); updateModifierKeys(); if (defaultFileName != NULL) { free(openFileName.lpstrFileTitle); } if (!success) { return NULL; } return returnedFilePath; } bool Shell_copyTextToClipboard(const char * text) { if (!OpenClipboard(window)) { return false; } EmptyClipboard(); if (text != NULL) { size_t utf8Length = strlen(text); size_t utf16Length = utf8StringUTF16Length((const uint8_t *) text, utf8Length); uint16_t utf16String[utf16Length + 1]; utf8StringToUTF16StringExtended((const uint8_t *) text, utf8Length, utf16String, NULL); HGLOBAL clipboardData = GlobalAlloc(GMEM_MOVEABLE, sizeof(utf16String)); char * clipboardDataLocked = GlobalLock(clipboardData); memcpy(clipboardDataLocked, utf16String, sizeof(utf16String)); GlobalUnlock(clipboardData); SetClipboardData(CF_UNICODETEXT, clipboardData); } CloseClipboard(); return true; } const char * Shell_getClipboardText(void) { static char * lastClipboardContents; if (!IsClipboardFormatAvailable(CF_UNICODETEXT) || !OpenClipboard(window)) { return NULL; } if (lastClipboardContents != NULL) { free(lastClipboardContents); lastClipboardContents = NULL; } HGLOBAL clipboardData = GetClipboardData(CF_UNICODETEXT); if (clipboardData != NULL) { const uint16_t * clipboardDataLocked = GlobalLock(clipboardData); if (clipboardDataLocked != NULL) { lastClipboardContents = (char *) utf16StringToUTF8String(clipboardDataLocked, utf16StringLength(clipboardDataLocked)); GlobalUnlock(clipboardData); } } CloseClipboard(); return lastClipboardContents; } unsigned int Shell_getModifierKeys(void) { return modifiers; } unsigned int Shell_getButtonMask(void) { return buttonMask; } static HKL getUSKeyboardLayout(void) { static HKL layout; if (layout == NULL) { layout = LoadKeyboardLayout("00000409", 0); } return layout; } static UINT shellKeyCodeToWin32KeyCode(unsigned int keyCode, bool * outIsExtended) { switch (keyCode) { #define KEY_CODE_PAIR_EXTENDED(shell_key_code, win32_virtual_key_code) \ case shell_key_code: \ *outIsExtended = true; \ return win32_virtual_key_code; #define KEY_CODE_PAIR(shell_key_code, win32_virtual_key_code) \ case shell_key_code: \ *outIsExtended = false; \ return win32_virtual_key_code; #include "wglshell/WGLShell_keyCodes.h" #undef KEY_CODE_PAIR_EXTENDED #undef KEY_CODE_PAIR } return 0; } unsigned int Shell_getKeyLabel(unsigned int keyCode, unsigned int modifiers) { bool extended = false; UINT virtualKeyCode = shellKeyCodeToWin32KeyCode(keyCode, &extended); UINT scanCode = MapVirtualKeyEx(virtualKeyCode, MAPVK_VK_TO_VSC_EX, getUSKeyboardLayout()); if (keyCode == KEY_CODE_NUMPAD_PERIOD) { virtualKeyCode = 0x6E; // VK_DECIMAL } else { virtualKeyCode = MapVirtualKeyEx(scanCode, MAPVK_VSC_TO_VK_EX, GetKeyboardLayout(0)); } BYTE keyState[256]; memset(keyState, 0, sizeof(keyState)); if (modifiers & MODIFIER_SHIFT_BIT) { keyState[VK_SHIFT] = 0x80; } if (modifiers & MODIFIER_CAPS_LOCK_BIT) { keyState[VK_CAPITAL] = 0x81; } if (modifiers & MODIFIER_ALTGR_BIT) { keyState[VK_CONTROL] = 0x80; keyState[VK_MENU] = 0x81; keyState[VK_LCONTROL] = 0x81; keyState[VK_RMENU] = 0x81; } wchar_t buffer[16]; int result = ToUnicode(virtualKeyCode, scanCode, keyState, buffer, sizeof_count(buffer), 0x4); if (result > 0) { return utf32CodepointAtUTF16Index(buffer, result, 0); } return 0; } const char * Shell_getKeyboardLayoutName(void) { char keyboardLayoutName[KL_NAMELENGTH]; if (GetKeyboardLayoutName(keyboardLayoutName)) { HKEY registryKey; LSTATUS status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts", 0, KEY_READ, ®istryKey); if (status == ERROR_SUCCESS) { static char layoutText[128]; DWORD bufferSize = sizeof(layoutText); status = RegGetValue(registryKey, keyboardLayoutName, "Layout Text", RRF_RT_REG_SZ, NULL, layoutText, &bufferSize); RegCloseKey(registryKey); if (status == ERROR_SUCCESS) { return layoutText; } } } return ""; } void Shell_systemBeep(void) { MessageBeep(MB_OK); } static void updateWindowTitle(void) { if (window != NULL) { if (documentEdited) { char adornedTitle[256]; snprintf(adornedTitle, sizeof(adornedTitle), "*%s", windowTitle); SetWindowText(window, adornedTitle); } else { SetWindowText(window, windowTitle); } } } void Shell_setWindowTitle(const char * title) { free(windowTitle); windowTitle = strdup(title); updateWindowTitle(); } void Shell_setWindowDocumentEdited(bool edited) { documentEdited = edited; updateWindowTitle(); } void Shell_moveWindow(int x, int y) { if (window != NULL) { RECT windowRect; GetClientRect(window, &windowRect); int offsetX = x - windowRect.left, offsetY = y - windowRect.top; windowRect.left += offsetX; windowRect.top += offsetY; windowRect.right += offsetX; windowRect.bottom += offsetY; AdjustWindowRect(&windowRect, WINDOW_STYLE, false); MoveWindow(window, windowRect.left, windowRect.top, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, false); } } void Shell_resizeWindow(unsigned int width, unsigned int height) { if (window != NULL) { RECT windowRect, windowRect2; GetClientRect(window, &windowRect); windowRect.right = windowRect.left + width; windowRect.bottom = windowRect.top + height; AdjustWindowRect(&windowRect, WINDOW_STYLE, false); GetWindowRect(window, &windowRect2); windowRect.right += windowRect2.left - windowRect.left; windowRect.bottom += windowRect2.top - windowRect.top; windowRect.left = windowRect2.left; windowRect.top = windowRect2.top; MoveWindow(window, windowRect.left, windowRect.top, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, true); } } void Shell_moveResizeWindow(int x, int y, unsigned int width, unsigned int height) { if (window != NULL) { RECT windowRect; GetClientRect(window, &windowRect); windowRect.left = x; windowRect.top = y; windowRect.right = windowRect.left + width; windowRect.bottom = windowRect.top + height; AdjustWindowRect(&windowRect, WINDOW_STYLE, false); MoveWindow(window, windowRect.left, windowRect.top, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, true); } } void Shell_msleep(unsigned int milliseconds) { Sleep(milliseconds); } void Shell_preciseSleep(double seconds) { timeBeginPeriod(1); usleep(seconds * 1000000); timeEndPeriod(1); } void WGLShell_redirectStdoutToFile(const char * path) { freopen(path, "a", stdout); setvbuf(stdout, NULL, _IONBF, 0); } void WGLShell_redirectStderrToFile(const char * path) { freopen(path, "a", stderr); setvbuf(stderr, NULL, _IONBF, 0); } void WGLShell_coalesceMouseMotionEvents(bool coalesce) { coalesceMouseMotionEvents = coalesce; } void WGLShell_sendActivationClicks(bool send) { sendActivationClicks = send; } void WGLShell_keyboardDisconnectFunc(void (* callback)(void)) { keyboardDisconnectCallback = callback; } void WGLShell_usbConnectionChangeFunc(void (* callback)(void)) { usbConnectionChangeCallback = callback; } static BOOL CALLBACK monitorEnumProcGetPrimaryDisplayIndex(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { unsigned int * monitorIndex = (unsigned int *) dwData; MONITORINFO monitorInfo; monitorInfo.cbSize = sizeof(monitorInfo); if (GetMonitorInfo(hMonitor, &monitorInfo) && (monitorInfo.dwFlags & MONITORINFOF_PRIMARY)) { primaryDisplayIndex = *monitorIndex; return false; } ++*monitorIndex; return true; } static void updatePrimaryDisplayIndex(void) { primaryDisplayIndex = 0; unsigned int monitorIndex = 0; EnumDisplayMonitors(NULL, NULL, monitorEnumProcGetPrimaryDisplayIndex, (LPARAM) &monitorIndex); } static unsigned int windowsVKToShellKeyCode(UINT vk, LPARAM lParam) { bool extended = lParam & 0x1000000; if (extended) { switch (vk) { #define KEY_CODE_PAIR_EXTENDED(shell_key_code, win32_virtual_key_code) \ case win32_virtual_key_code: \ return shell_key_code; #define KEY_CODE_PAIR(shell_key_code, win32_virtual_key_code) #include "wglshell/WGLShell_keyCodes.h" #undef KEY_CODE_PAIR_EXTENDED #undef KEY_CODE_PAIR default: #ifdef DEBUG fprintf(stderr, "Warning: Unknown extended key code 0x%X\n", vk); #endif break; } return 0; } switch (vk) { #define KEY_CODE_PAIR_EXTENDED(shell_key_code, win32_virtual_key_code) #define KEY_CODE_PAIR(shell_key_code, win32_virtual_key_code) \ case win32_virtual_key_code: \ return shell_key_code; #include "wglshell/WGLShell_keyCodes.h" #undef KEY_CODE_PAIR_EXTENDED #undef KEY_CODE_PAIR default: #ifdef DEBUG fprintf(stderr, "Warning: Unknown key code 0x%X\n", vk); #endif break; } return 0; } static unsigned int lParamToShellKeyCode(LPARAM lParam) { return windowsVKToShellKeyCode(MapVirtualKeyEx(lParam >> 16 & 0x7F, MAPVK_VSC_TO_VK_EX, getUSKeyboardLayout()), lParam); } static void processMouseDown(unsigned int buttonNumber, LPARAM lParam) { double timestamp = Shell_getCurrentTime(); if (cursorHiddenUntilMouseMoves) { ShowCursor(true); cursorHiddenUntilMouseMoves = false; if (cursorUnhiddenCallback != NULL) { cursorUnhiddenCallback(false, timestamp); } } if (buttonMask == 0) { SetCapture(window); } buttonMask |= 1 << buttonNumber; if (initialized && mouseDownCallback != NULL) { float reportedX, reportedY; if (mouseDeltaMode) { reportedX = restoreMouseX; reportedY = restoreMouseY; } else { reportedX = GET_X_LPARAM(lParam); reportedY = GET_Y_LPARAM(lParam); } mouseDownCallback(buttonNumber, buttonMask, reportedX, reportedY, modifiers, timestamp); } } static void processMouseUp(unsigned int buttonNumber, LPARAM lParam) { if (ignoreNextMouseUp & (1 << buttonNumber)) { ignoreNextMouseUp &= ~(1 << buttonNumber); return; } buttonMask &= ~(1 << buttonNumber); if (initialized && mouseUpCallback != NULL) { float reportedX, reportedY; if (mouseDeltaMode) { reportedX = restoreMouseX; reportedY = restoreMouseY; } else { reportedX = GET_X_LPARAM(lParam); reportedY = GET_Y_LPARAM(lParam); } mouseUpCallback(buttonNumber, buttonMask, reportedX, reportedY, modifiers, Shell_getCurrentTime()); } if (buttonMask == 0) { ReleaseCapture(); } } static LRESULT CALLBACK windowProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam) { if (!initialized) { LOG(0, "Window message during init: %d, 0x%lX, 0x%lX\n", message, (unsigned long) wParam, (unsigned long) lParam); } switch (message) { case WM_PAINT: { double currentTime = Shell_getCurrentTime(); #ifdef DEBUG GLenum error; #endif ValidateRect(window, NULL); if (lastDrawTime == 0.0) { lastDrawTime = currentTime; } if (initialized && drawCallback != NULL) { drawCallback(currentTime, currentTime - lastDrawTime); } lastDrawTime = currentTime; #ifdef DEBUG while ((error = glGetError()) != GL_NO_ERROR) { fprintf(stderr, "GL error: 0x%X\n", error); } #endif SwapBuffers(displayContext); return 0; } case WM_ERASEBKGND: return 1; case WM_SIZING: case WM_SIZE: if (initialized) { RECT rect; GetClientRect(window, &rect); if ((int) (rect.right - rect.left) != lastWidth || (int) (rect.bottom - rect.top) != lastHeight) { lastWidth = rect.right - rect.left; lastHeight = rect.bottom - rect.top; if (resizeCallback != NULL) { resizeCallback(lastWidth, lastHeight, Shell_getCurrentTime()); } redisplayNeeded = true; } if (message == WM_SIZING) { return TRUE; } return 0; } break; case WM_DISPLAYCHANGE: updatePrimaryDisplayIndex(); return 0; case WM_LBUTTONDOWN: if (initialized) { processMouseDown(0, lParam); return 0; } break; case WM_LBUTTONUP: if (initialized) { processMouseUp(0, lParam); return 0; } break; case WM_RBUTTONDOWN: if (initialized) { processMouseDown(1, lParam); return 0; } break; case WM_RBUTTONUP: if (initialized) { processMouseUp(1, lParam); return 0; } break; case WM_MBUTTONDOWN: if (initialized) { processMouseDown(2, lParam); return 0; } break; case WM_MBUTTONUP: if (initialized) { processMouseUp(2, lParam); return 0; } break; case WM_XBUTTONDOWN: if (initialized) { processMouseDown(2 + GET_XBUTTON_WPARAM(wParam), lParam); return 0; } break; case WM_XBUTTONUP: if (initialized) { processMouseUp(2 + GET_XBUTTON_WPARAM(wParam), lParam); return 0; } break; case WM_MOUSEMOVE: if (initialized && !coalesceMouseMotionEvents) { processMouseMoveMessage(lParam); return 0; } break; case WM_MOUSELEAVE: if (initialized && mouseLeaveCallback != NULL) { mouseLeaveCallback(modifiers, Shell_getCurrentTime()); return 0; } break; case WM_MOUSEWHEEL: if (initialized) { accumulatedWheelDelta += GET_WHEEL_DELTA_WPARAM(wParam); if (accumulatedWheelDelta / WHEEL_DELTA != 0) { int deltaX = 0; int deltaY = accumulatedWheelDelta / WHEEL_DELTA; accumulatedWheelDelta -= deltaY * WHEEL_DELTA; if (initialized && scrollWheelCallback != NULL) { POINT position = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; ScreenToClient(window, &position); scrollWheelCallback(position.x, position.y, -deltaX, -deltaY, buttonMask, modifiers, Shell_getCurrentTime()); } } return 0; } break; case WM_KEYDOWN: case WM_SYSKEYDOWN: if (initialized) { updateModifierKeys(); unsigned int charCode = 0; MSG msg; if (PeekMessage(&msg, window, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE) && msg.message == WM_CHAR) { charCode = msg.wParam; } unsigned int keyCode = lParamToShellKeyCode(lParam); if (keyCode == KEY_CODE_F4 && ((modifiers & MODIFIER_ANY_ALT_BIT) || (modifiers & MODIFIER_CONTROL_BIT))) { if (!initialized || confirmQuitCallback == NULL || confirmQuitCallback(Shell_getCurrentTime())) { exit(EXIT_SUCCESS); } } if (keyCode != 0 && initialized && keyDownCallback != NULL) { if (keyCode <= SHELL_KEY_CODE_MAX) { keysDown[keyCode] = true; } keyDownCallback(charCode, keyCode, modifiers, !!(HIWORD(lParam) & KF_REPEAT), Shell_getCurrentTime()); } unsigned int lastModifiers = modifiers; if (keyCode == KEY_CODE_LEFT_SHIFT || keyCode == KEY_CODE_RIGHT_SHIFT) { modifiers |= MODIFIER_SHIFT_BIT; } else if (keyCode == KEY_CODE_LEFT_CONTROL || keyCode == KEY_CODE_RIGHT_CONTROL) { modifiers |= MODIFIER_CONTROL_BIT; } else if (keyCode == KEY_CODE_LEFT_ALT) { modifiers |= MODIFIER_ALT_BIT; } else if (keyCode == KEY_CODE_RIGHT_ALT) { modifiers |= MODIFIER_ALTGR_BIT; } if (initialized && modifiers != lastModifiers && keyModifiersChangedCallback != NULL) { keyModifiersChangedCallback(modifiers, lastModifiers, Shell_getCurrentTime()); } return 0; } break; case WM_SYSCHAR: // Must trap WM_SYSCHAR to prevent beeping from alt+key presses which are handled by WM_SYSKEYDOWN return 0; case WM_KEYUP: case WM_SYSKEYUP: if (initialized) { updateModifierKeys(); unsigned int keyCode = lParamToShellKeyCode(lParam); if (keyCode != 0 && initialized && keyUpCallback != NULL) { if (keyCode <= SHELL_KEY_CODE_MAX) { keysDown[keyCode] = false; } keyUpCallback(keyCode, modifiers, Shell_getCurrentTime()); } unsigned int lastModifiers = modifiers; if (keyCode == KEY_CODE_LEFT_SHIFT || keyCode == KEY_CODE_RIGHT_SHIFT) { modifiers &= ~MODIFIER_SHIFT_BIT; } else if (keyCode == KEY_CODE_LEFT_CONTROL || keyCode == KEY_CODE_RIGHT_CONTROL) { modifiers &= ~MODIFIER_CONTROL_BIT; } else if (keyCode == KEY_CODE_LEFT_ALT) { modifiers &= ~MODIFIER_ALT_BIT; } else if (keyCode == KEY_CODE_RIGHT_ALT) { modifiers &= ~MODIFIER_ALTGR_BIT; } if (initialized && modifiers != lastModifiers && keyModifiersChangedCallback != NULL) { keyModifiersChangedCallback(modifiers, lastModifiers, Shell_getCurrentTime()); } return 0; } break; case WM_ACTIVATEAPP: if (initialized) { if (wParam) { backgrounded = false; updateMouseState(); updateModifierKeys(); if (foregroundedCallback != NULL) { foregroundedCallback(Shell_getCurrentTime()); } } else { backgrounded = true; releaseAllKeys(); releaseAllMouseButtons(); if (backgroundedCallback != NULL) { backgroundedCallback(Shell_getCurrentTime()); } } return 0; } break; case WM_MOUSEACTIVATE: if (sendActivationClicks) { return MA_ACTIVATE; } return MA_ACTIVATEANDEAT; case WM_CLOSE: if (initialized && confirmQuitCallback != NULL && !confirmQuitCallback(Shell_getCurrentTime())) { ShowWindow(window, SW_SHOWNORMAL); return 0; } break; case WM_DESTROY: if (windowShown) { PostQuitMessage(0); } return 0; case WM_DEVICECHANGE: if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVNODES_CHANGED) { SetTimer(window, DEVICE_CHANGE_TIMER_ID_1, DEVICE_CHANGE_TIMER_INTERVAL_1, NULL); SetTimer(window, DEVICE_CHANGE_TIMER_ID_2, DEVICE_CHANGE_TIMER_INTERVAL_2, NULL); break; } return 0; case WM_TIMER: if (wParam == DEVICE_CHANGE_TIMER_ID_1 || wParam == DEVICE_CHANGE_TIMER_ID_2) { KillTimer(window, wParam); if (usbConnectionChangeCallback != NULL) { usbConnectionChangeCallback(); } return 0; } break; case WM_INPUT_DEVICE_CHANGE: if (initialized && wParam == 2) { // GIDC_REMOVAL if (keyboardDisconnectCallback != NULL) { keyboardDisconnectCallback(); } // Send key up events for all keys currently down, because a disconnected keyboard isn't going to do that releaseAllKeys(); return 0; } break; case WM_INPUTLANGCHANGE: if (initialized && keyboardLayoutChangedCallback != NULL) { keyboardLayoutChangedCallback(Shell_getCurrentTime()); return 0; } break; case WM_USER: return 0; } if (!initialized) { LOG(0, "Calling DefWindowProc"); } return DefWindowProc(window, message, wParam, lParam); } static HWND createShellWindow(const char * windowTitle, RECT windowRect, HINSTANCE instance) { HWND window = CreateWindow("wglshell", windowTitle, WINDOW_STYLE, windowRect.left, windowRect.top, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, NULL, NULL, instance, NULL); LOG(0, "CreateWindow: %p", window); if (window == NULL) { #ifdef DEBUG fprintf(stderr, "CreateWindow failed! GetLastError(): %ld\n", GetLastError()); #endif exit(EXIT_FAILURE); } return window; } static void destroyGLContext() { wglDeleteContext(glContext); } int WINAPI WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLine, int command) { updatePrimaryDisplayIndex(); setlocale(LC_ALL, "C"); struct WGLShellConfiguration configuration; configuration.windowX = 20; configuration.windowY = 50; configuration.windowWidth = 800; configuration.windowHeight = 600; configuration.windowTitle = "Window"; configuration.iconResource = 101; configuration.useGLCoreProfile = false; configuration.displayMode.doubleBuffer = true; configuration.displayMode.colorBits = 24; configuration.displayMode.alphaBits = 8; configuration.displayMode.depthBuffer = false; configuration.displayMode.depthBits = 24; configuration.displayMode.stencilBuffer = false; configuration.displayMode.stencilBits = 8; configuration.displayMode.accumBuffer = false; configuration.displayMode.accumBits = 32; configuration.displayMode.multisample = false; configuration.displayMode.sampleBuffers = 1; configuration.displayMode.samples = 4; int argc = 0; LPWSTR * argvW = CommandLineToArgvW(GetCommandLineW(), &argc); const char ** argv = malloc(argc * sizeof(*argv)); for (int argIndex = 0; argIndex < argc; argIndex++) { size_t lengthUTF8 = WideCharToMultiByte(CP_UTF8, 0, argvW[argIndex], -1, NULL, 0, NULL, NULL); char * arg = malloc(lengthUTF8 + 1); WideCharToMultiByte(CP_UTF8, 0, argvW[argIndex], -1, arg, lengthUTF8 + 1, NULL, NULL); argv[argIndex] = arg; } LocalFree(argvW); WGLTarget_configure(instance, prevInstance, commandLine, command, argc, argv, &configuration); WNDCLASSEX windowClass; windowClass.cbSize = sizeof(windowClass); windowClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW; windowClass.lpfnWndProc = windowProc; windowClass.cbClsExtra = 0; windowClass.cbWndExtra = 0; windowClass.hInstance = instance; windowClass.hIcon = LoadIcon(instance, MAKEINTRESOURCE(configuration.iconResource)); windowClass.hCursor = LoadCursor(NULL, IDC_ARROW); windowClass.hbrBackground = NULL; windowClass.lpszMenuName = NULL; windowClass.lpszClassName = "wglshell"; windowClass.hIconSm = NULL; ATOM result = RegisterClassEx(&windowClass); LOG(0, "RegisterClassEx: %d", result); if (result == 0) { #ifdef DEBUG fprintf(stderr, "RegisterClassEx failed! GetLastError(): %ld\n", GetLastError()); #endif exit(EXIT_FAILURE); } RECT windowRect; windowRect.left = configuration.windowX; windowRect.top = configuration.windowY; windowRect.right = windowRect.left + configuration.windowWidth; windowRect.bottom = windowRect.top + configuration.windowHeight; AdjustWindowRect(&windowRect, WINDOW_STYLE, false); LOG(0, "AdjustWindowRect: %ld, %ld, %ld, %ld", windowRect.left, windowRect.top, windowRect.right, windowRect.bottom); windowTitle = strdup(configuration.windowTitle); window = createShellWindow(windowTitle, windowRect, instance); PIXELFORMATDESCRIPTOR pixelFormat = { sizeof(PIXELFORMATDESCRIPTOR), // nSize 1, // nVersion PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL, // dwFlags PFD_TYPE_RGBA, // iPixelType 32, // cColorBits 0, 0, 0, 0, 0, 0, // c(Red|Green|Blue)(Shift|Bits) 0, // cAlphaBits 0, // cAlphaShift 0, // cAccumBits 0, 0, 0, 0, // cAccum(Red|Green|Blue|Alpha)Bits 24, // cDepthBits 8, // cStencilBits 0, // cAuxBuffers PFD_MAIN_PLANE, // iLayerType 0, // bReserved 0, 0, 0 // dw(Layer|Visible|Damage)Mask }; if (configuration.displayMode.doubleBuffer) { pixelFormat.dwFlags |= PFD_DOUBLEBUFFER; } pixelFormat.cColorBits = configuration.displayMode.colorBits; pixelFormat.cAlphaBits = configuration.displayMode.alphaBits; pixelFormat.cDepthBits = configuration.displayMode.depthBits; pixelFormat.cStencilBits = configuration.displayMode.stencilBits; displayContext = GetDC(window); LOG(0, "GetDC: %p", displayContext); int format = ChoosePixelFormat(displayContext, &pixelFormat); LOG(0, "ChoosePixelFormat: %d", format); if (format == 0) { #ifdef DEBUG fprintf(stderr, "ChoosePixelFormat failed! GetLastError(): %ld\n", GetLastError()); #endif exit(EXIT_FAILURE); } BOOL success = SetPixelFormat(displayContext, format, &pixelFormat); LOG(0, "SetPixelFormat: %s", success ? "true" : "false"); if (!success) { #ifdef DEBUG fprintf(stderr, "SetPixelFormat failed! GetLastError(): %ld\n", GetLastError()); #endif exit(EXIT_FAILURE); } glContext = wglCreateContext(displayContext); LOG(0, "wglCreateContext: %p", glContext); if (glContext == NULL) { #ifdef DEBUG fprintf(stderr, "wglCreateContext failed! GetLastError(): %ld\n", GetLastError()); #endif exit(EXIT_FAILURE); } success = wglMakeCurrent(displayContext, glContext); LOG(0, "wglMakeCurrent: %s", success ? "true" : "false"); wglGetExtensionsStringEXT = (WGLFunction_wglGetExtensionsStringEXT) wglGetProcAddress("wglGetExtensionsStringEXT"); LOG(0, "wglGetProcAddress(\"wglGetExtensionsStringEXT\"): %p", wglGetExtensionsStringEXT); if (wglGetExtensionsStringEXT != NULL) { const char * wglExtensionsString = wglGetExtensionsStringEXT(); LOG(2, "wglGetExtensionsStringEXT(): %s", wglExtensionsString); if (wglExtensionsString != NULL && strstr(wglExtensionsString, "WGL_ARB_pixel_format")) { int attributes[27]; unsigned int attributeCount = 0; UINT formatCount; attributes[attributeCount++] = WGL_DRAW_TO_WINDOW_ARB; attributes[attributeCount++] = GL_TRUE; attributes[attributeCount++] = WGL_SUPPORT_OPENGL_ARB; attributes[attributeCount++] = GL_TRUE; if (configuration.useGLCoreProfile) { attributes[attributeCount++] = WGL_CONTEXT_MAJOR_VERSION_ARB; attributes[attributeCount++] = 3; attributes[attributeCount++] = WGL_CONTEXT_MINOR_VERSION_ARB; attributes[attributeCount++] = 2; attributes[attributeCount++] = WGL_CONTEXT_PROFILE_MASK_ARB; attributes[attributeCount++] = WGL_CONTEXT_CORE_PROFILE_BIT_ARB; } if (configuration.displayMode.doubleBuffer) { attributes[attributeCount++] = WGL_DOUBLE_BUFFER_ARB; attributes[attributeCount++] = GL_TRUE; } attributes[attributeCount++] = WGL_PIXEL_TYPE_ARB; attributes[attributeCount++] = WGL_TYPE_RGBA_ARB; attributes[attributeCount++] = WGL_COLOR_BITS_ARB; attributes[attributeCount++] = configuration.displayMode.colorBits; if (configuration.displayMode.depthBuffer) { attributes[attributeCount++] = WGL_DEPTH_BITS_ARB; attributes[attributeCount++] = configuration.displayMode.depthBits; } if (configuration.displayMode.stencilBuffer) { attributes[attributeCount++] = WGL_STENCIL_BITS_ARB; attributes[attributeCount++] = configuration.displayMode.stencilBits; } if (configuration.displayMode.accumBuffer) { attributes[attributeCount++] = WGL_ACCUM_BITS_ARB; attributes[attributeCount++] = configuration.displayMode.accumBits; } if (configuration.displayMode.multisample) { attributes[attributeCount++] = WGL_SAMPLE_BUFFERS_ARB; attributes[attributeCount++] = configuration.displayMode.sampleBuffers; attributes[attributeCount++] = WGL_SAMPLES_ARB; attributes[attributeCount++] = configuration.displayMode.samples; } attributes[attributeCount++] = 0; WGLFunction_wglChoosePixelFormatARB wglChoosePixelFormatARB = (WGLFunction_wglChoosePixelFormatARB) wglGetProcAddress("wglChoosePixelFormatARB"); LOG(0, "wglGetProcAddress(\"wglChoosePixelFormatARB\"): %p", wglChoosePixelFormatARB); if (wglChoosePixelFormatARB != NULL && wglChoosePixelFormatARB(displayContext, attributes, NULL, 1, &format, &formatCount) && formatCount > 0) { LOG(0, "wglChoosePixelFormatARB()"); success = wglMakeCurrent(NULL, NULL); LOG(0, "wglMakeCurrent(): %s", success ? "true" : "false"); success = wglDeleteContext(glContext); LOG(0, "wglDeleteContext(): %s", success ? "true" : "false"); ReleaseDC(window, displayContext); LOG(0, "ReleaseDC()"); DestroyWindow(window); LOG(0, "DestroyWindow()"); window = createShellWindow(windowTitle, windowRect, instance); displayContext = GetDC(window); LOG(0, "GetDC(): %p", displayContext); if (displayContext == NULL) { #ifdef DEBUG fprintf(stderr, "GetDC failed! GetLastError(): %ld\n", GetLastError()); #endif exit(EXIT_FAILURE); } success = SetPixelFormat(displayContext, format, &pixelFormat); LOG(0, "SetPixelFormat: %s", success ? "true" : "false"); if (!success) { #ifdef DEBUG fprintf(stderr, "SetPixelFormat failed! GetLastError(): %ld\n", GetLastError()); #endif exit(EXIT_FAILURE); } glContext = wglCreateContext(displayContext); LOG(0, "wglCreateContext: %p", glContext); if (glContext == NULL) { #ifdef DEBUG fprintf(stderr, "wglCreateContext failed! GetLastError(): %ld\n", GetLastError()); #endif exit(EXIT_FAILURE); } success = wglMakeCurrent(displayContext, glContext); LOG(0, "wglMakeCurrent(): %s", success ? "true" : "false"); } } } RAWINPUTDEVICE rawInputDevice = { .usUsagePage = 0x01, // HID_USAGE_PAGE_GENERIC .usUsage = 0x06, // HID_USAGE_GENERIC_KEYBOARD .dwFlags = 0x00002000, // RIDEV_DEVNOTIFY .hwndTarget = window }; success = RegisterRawInputDevices(&rawInputDevice, 1, sizeof(rawInputDevice)); LOG(0, "RegisterRawInputDevices(): %s", success ? "true" : "false"); if (!success) { LOG(1, "Warning: RegisterRawInputDevices failed; GetLastError(): %ld\n", GetLastError()); } setVSync(vsyncWindow); LOG(0, "Calling ShowWindow(%d)", command); success = ShowWindow(window, command); LOG(0, "ShowWindow(): %s", success ? "true" : "false"); windowShown = true; if ((int) configuration.windowWidth != lastWidth && (int) configuration.windowHeight != lastHeight) { lastWidth = configuration.windowWidth; lastHeight = configuration.windowHeight; if (resizeCallback != NULL) { resizeCallback(configuration.windowWidth, configuration.windowHeight, Shell_getCurrentTime()); } } atexit(destroyGLContext); initialized = true; Target_init(); return EXIT_SUCCESS; }