/* 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/UTFUtilities.h" #include "wglshell/WGLShell.h" #include "wglshell/WGLShellCallbacks.h" #include "wglshell/WGLTarget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct WGLShellTimer { double interval; double nextFireTime; bool repeat; 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 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, lastMouseY; static int ignoreDeltaX = 0, ignoreDeltaY = 0; static bool initialIgnoreMouseMotion; static unsigned int ignoreNextMouseUp; 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 coalesceMouseMotionEvents; static double lastDrawTime; static bool initialized; static char * windowTitle; static bool documentEdited; static bool sendActivationClicks = false; 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(int x, int y) { ignoreDeltaX = x - lastMouseX; ignoreDeltaY = y - lastMouseY; POINT point = {x, y}; ClientToScreen(window, &point); SetCursorPos(point.x, point.y); } static void processMouseMovement(int x, int y) { if (cursorHiddenUntilMouseMoves) { cursorHiddenUntilMouseMoves = false; if (!mouseDeltaMode) { ShowCursor(true); } } int reportedX, reportedY, reportedDeltaX, reportedDeltaY; if (mouseDeltaMode) { reportedX = restoreMouseX; reportedY = restoreMouseY; reportedDeltaX = x - lastMouseX; reportedDeltaY = y - lastMouseY; } else { reportedX = x; reportedY = y; reportedDeltaX = x - lastMouseX; reportedDeltaY = y - lastMouseY; 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 int getWindowCenterX() { RECT rect; GetClientRect(window, &rect); return (rect.right - rect.left) / 2; } static int getWindowCenterY() { RECT rect; GetClientRect(window, &rect); return (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 bool processMouseMoveMessage(LPARAM lParam) { int x, y; x = GET_X_LPARAM(lParam); y = GET_Y_LPARAM(lParam); if (ignoreDeltaX != 0 || ignoreDeltaY != 0) { x -= ignoreDeltaX; y -= ignoreDeltaY; ignoreDeltaX = ignoreDeltaY = 0; } if (x != lastMouseX || y != lastMouseY) { processMouseMovement(x, y); if (mouseDeltaMode) { warpPointerAndIgnoreEvent(getWindowCenterX(), getWindowCenterY()); } } else if (mouseDeltaMode) { ignoreDeltaX = getWindowCenterX() - lastMouseX; ignoreDeltaY = getWindowCenterY() - lastMouseY; } return true; } 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].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 (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 && currentTime >= timers[timerIndex].nextFireTime) { timers[timerIndex].callback(timers[timerIndex].id, timers[timerIndex].context); if (timers[timerIndex].repeat) { timers[timerIndex].nextFireTime += 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 (wglSwapIntervalEXT == NULL && !extensionsChecked) { if (strstr((char *) glGetString(GL_EXTENSIONS), "WGL_EXT_swap_control") && strstr(wglGetExtensionsStringEXT(), "WGL_EXT_swap_control")) { wglSwapIntervalEXT = (WGLFunction_wglSwapIntervalEXT) wglGetProcAddress("wglSwapIntervalEXT"); } extensionsChecked = true; } if (wglSwapIntervalEXT != NULL) { wglSwapIntervalEXT(sync ? 1 : 0); } } #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(getWindowCenterX(), getWindowCenterY()); } 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(getWindowCenterX(), getWindowCenterY()); } } 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(widePath) / sizeof(widePath[0])); widePath[sizeof(widePath) / sizeof(widePath[0]) - 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) { BOOL success; struct monitorEnumProcGetIndexContext context = {NULL, 0, false}; RECT windowRect; GetWindowRect(window, &windowRect); context.hMonitor = MonitorFromRect(&windowRect, MONITOR_DEFAULTTONEAREST); success = EnumDisplayMonitors(NULL, NULL, monitorEnumProcGetIndex, (LPARAM) &context); 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, bool repeat, void (* callback)(unsigned int timerID, void * context), void * context) { timers = realloc(timers, (timerCount + 1) * sizeof(struct WGLShellTimer)); timers[timerCount].interval = interval; timers[timerCount].nextFireTime = Shell_getCurrentTime() + interval; timers[timerCount].repeat = repeat; 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; ShowCursor(false); } } void Shell_getMousePosition(float * outX, float * outY) { if (outX != NULL) { *outX = lastMouseX; } if (outY != NULL) { *outY = lastMouseY; } } #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; warpPointerAndIgnoreEvent(getWindowCenterX(), getWindowCenterY()); initialIgnoreMouseMotion = true; ShowCursor(false); mouseDeltaMode = true; } else if (mouseDeltaMode && !deltaMode) { initialIgnoreMouseMotion = false; warpPointerAndIgnoreEvent(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) { 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); 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 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) { fprintf(stderr, "Warning: Filter string too long; can't fit into OPENFILENAME.lpstrFilter\n"); 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) { fprintf(stderr, "Warning: Filter string too long; can't fit into OPENFILENAME.lpstrFilter\n"); return; } strncpy(filterString + filterStringLength, extensionString, sizeof(filterString) + filterStringLength); filterStringLength += extensionStringLength; } filterStringLength++; } char allFilesString[] = "All files\0*.*"; if (filterStringLength + sizeof(allFilesString) >= sizeof(filterString) - 2) { fprintf(stderr, "Warning: Filter string too long; can't fit into OPENFILENAME.lpstrFilter\n"); return; } memcpy(filterString + filterStringLength, allFilesString, sizeof(allFilesString)); openFileName->lpstrFilter = filterString; } } bool Shell_openFileDialog(const char * title, const char * defaultDirectory, const char * defaultFileName, const char ** outFilePath, 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) { cursorHiddenUntilMouseMoves = false; ShowCursor(true); } BOOL success = GetOpenFileName(&openFileName); if (cursorHiddenByHide || mouseDeltaMode) { ShowCursor(false); } updateMouseState(); updateModifierKeys(); if (success) { *outFilePath = returnedFilePath; } return success; } bool Shell_openFileDialogMultiple(const char * title, const char * defaultDirectory, unsigned int defaultFileNameCount, const char ** defaultFileNames, unsigned int * outFilePathCount, const char *** outFilePaths, 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) { cursorHiddenUntilMouseMoves = false; ShowCursor(true); } BOOL success = GetOpenFileName(&openFileName); 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; *outFilePaths = (const char **) returnedFilePaths; } return success; } bool Shell_chooseFolderDialog(const char * title, const char * defaultDirectory, const char ** outFilePath) { 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) { cursorHiddenUntilMouseMoves = false; ShowCursor(true); } LPITEMIDLIST list = SHBrowseForFolder(&browseInfo); if (cursorHiddenByHide || mouseDeltaMode) { ShowCursor(false); } updateMouseState(); updateModifierKeys(); if (list != NULL && SHGetPathFromIDList(list, returnedFilePath)) { *outFilePath = returnedFilePath; success = true; } CoTaskMemFree(list); return success; } bool Shell_saveFileDialog(const char * title, const char * defaultDirectory, const char * defaultFileName, const char ** outFilePath, 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) { cursorHiddenUntilMouseMoves = false; ShowCursor(true); } BOOL success = GetSaveFileName(&openFileName); if (cursorHiddenByHide || mouseDeltaMode) { ShowCursor(false); } updateMouseState(); updateModifierKeys(); if (defaultFileName != NULL) { free(openFileName.lpstrFileTitle); } if (success) { *outFilePath = returnedFilePath; } return success; } 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; } 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; } 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) { case 13: return KEY_CODE_NUMPAD_ENTER; case 162: return KEY_CODE_RIGHT_CONTROL; case 164: return KEY_CODE_RIGHT_ALT; case 144: return KEY_CODE_NUM_LOCK; case 191: return KEY_CODE_NUMPAD_SLASH; case 45: return KEY_CODE_INSERT; case 36: return KEY_CODE_HOME; case 33: return KEY_CODE_PAGE_UP; case 46: return KEY_CODE_FORWARD_DELETE; case 35: return KEY_CODE_END; case 34: return KEY_CODE_PAGE_DOWN; case 37: return KEY_CODE_LEFT_ARROW; case 38: return KEY_CODE_UP_ARROW; case 39: return KEY_CODE_RIGHT_ARROW; case 40: return KEY_CODE_DOWN_ARROW; case 12: return KEY_CODE_NUMPAD_EQUAL; case 106: return KEY_CODE_PRINT_SCREEN; case 241: return KEY_CODE_LEFT_GUI; case 234: return KEY_CODE_RIGHT_GUI; case 249: return KEY_CODE_MENU; #ifdef DEBUG default: fprintf(stderr, "Warning: Unknown extended key code 0x%X\n", vk); #endif } return 0; } switch (vk) { case 27: return KEY_CODE_ESCAPE; case 112: return KEY_CODE_F1; case 113: return KEY_CODE_F2; case 114: return KEY_CODE_F3; case 115: return KEY_CODE_F4; case 116: return KEY_CODE_F5; case 117: return KEY_CODE_F6; case 118: return KEY_CODE_F7; case 119: return KEY_CODE_F8; case 120: return KEY_CODE_F9; case 121: return KEY_CODE_F10; case 122: return KEY_CODE_F11; case 123: return KEY_CODE_F12; case 145: return KEY_CODE_SCROLL_LOCK; case 144: return KEY_CODE_PAUSE; case 192: return KEY_CODE_BACKTICK; case 49: return KEY_CODE_1; case 50: return KEY_CODE_2; case 51: return KEY_CODE_3; case 52: return KEY_CODE_4; case 53: return KEY_CODE_5; case 54: return KEY_CODE_6; case 55: return KEY_CODE_7; case 56: return KEY_CODE_8; case 57: return KEY_CODE_9; case 48: return KEY_CODE_0; case 189: return KEY_CODE_MINUS; case 187: return KEY_CODE_EQUAL; case 8: return KEY_CODE_BACKSPACE; case 9: return KEY_CODE_TAB; case 81: return KEY_CODE_Q; case 87: return KEY_CODE_W; case 69: return KEY_CODE_E; case 82: return KEY_CODE_R; case 84: return KEY_CODE_T; case 89: return KEY_CODE_Y; case 85: return KEY_CODE_U; case 73: return KEY_CODE_I; case 79: return KEY_CODE_O; case 80: return KEY_CODE_P; case 219: return KEY_CODE_OPEN_BRACKET; case 221: return KEY_CODE_CLOSE_BRACKET; case 220: return KEY_CODE_BACKSLASH; case 20: return KEY_CODE_CAPS_LOCK; case 65: return KEY_CODE_A; case 83: return KEY_CODE_S; case 68: return KEY_CODE_D; case 70: return KEY_CODE_F; case 71: return KEY_CODE_G; case 72: return KEY_CODE_H; case 74: return KEY_CODE_J; case 75: return KEY_CODE_K; case 76: return KEY_CODE_L; case 186: return KEY_CODE_SEMICOLON; case 222: return KEY_CODE_SINGLE_QUOTE; case 13: return KEY_CODE_ENTER; case 160: return KEY_CODE_LEFT_SHIFT; case 90: return KEY_CODE_Z; case 88: return KEY_CODE_X; case 67: return KEY_CODE_C; case 86: return KEY_CODE_V; case 66: return KEY_CODE_B; case 78: return KEY_CODE_N; case 77: return KEY_CODE_M; case 188: return KEY_CODE_COMMA; case 190: return KEY_CODE_PERIOD; case 191: return KEY_CODE_SLASH; case 161: return KEY_CODE_RIGHT_SHIFT; case 162: return KEY_CODE_LEFT_CONTROL; case 164: return KEY_CODE_LEFT_ALT; case 241: return KEY_CODE_LEFT_GUI; case 32: return KEY_CODE_SPACE; case 234: return KEY_CODE_RIGHT_GUI; case 249: return KEY_CODE_MENU; case 45: return KEY_CODE_NUMPAD_0; case 36: return KEY_CODE_NUMPAD_7; case 33: return KEY_CODE_NUMPAD_9; case 46: return KEY_CODE_NUMPAD_PERIOD; case 35: return KEY_CODE_NUMPAD_1; case 34: return KEY_CODE_NUMPAD_3; case 37: return KEY_CODE_NUMPAD_4; case 38: return KEY_CODE_NUMPAD_8; case 39: return KEY_CODE_NUMPAD_6; case 40: return KEY_CODE_NUMPAD_2; case 12: return KEY_CODE_NUMPAD_5; case 106: return KEY_CODE_NUMPAD_ASTERISK; case 109: return KEY_CODE_NUMPAD_MINUS; case 107: return KEY_CODE_NUMPAD_PLUS; #ifdef DEBUG default: fprintf(stderr, "Warning: Unknown key code 0x%X\n", vk); #endif } return 0; } #ifndef MAPVK_VSC_TO_VK_EX #define MAPVK_VSC_TO_VK_EX 3 #endif static unsigned int lParamToShellKeyCode(LPARAM lParam) { static HKL layout; if (layout == NULL) { layout = LoadKeyboardLayout("00000409", 0); } return windowsVKToShellKeyCode(MapVirtualKeyEx(lParam >> 16 & 0x7F, MAPVK_VSC_TO_VK_EX, layout), lParam); } static void processMouseDown(unsigned int buttonNumber, LPARAM lParam) { if (buttonMask == 0) { SetCapture(window); } buttonMask |= 1 << buttonNumber; if (initialized && mouseDownCallback != NULL) { mouseDownCallback(buttonNumber, buttonMask, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), modifiers, Shell_getCurrentTime()); } } static void processMouseUp(unsigned int buttonNumber, LPARAM lParam) { if (ignoreNextMouseUp & (1 << buttonNumber)) { ignoreNextMouseUp &= ~(1 << buttonNumber); return; } buttonMask &= ~(1 << buttonNumber); if (initialized && mouseUpCallback != NULL) { mouseUpCallback(buttonNumber, buttonMask, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), modifiers, Shell_getCurrentTime()); } if (buttonMask == 0) { ReleaseCapture(); } } static LRESULT CALLBACK windowProc(HWND window, UINT message, WPARAM wParam, LPARAM 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 (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_SIZING: case WM_SIZE: { 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; } return 0; } case WM_DISPLAYCHANGE: updatePrimaryDisplayIndex(); return 0; case WM_LBUTTONDOWN: processMouseDown(0, lParam); return 0; case WM_LBUTTONUP: processMouseUp(0, lParam); return 0; case WM_RBUTTONDOWN: processMouseDown(1, lParam); return 0; case WM_RBUTTONUP: processMouseUp(1, lParam); return 0; case WM_MBUTTONDOWN: processMouseDown(2, lParam); return 0; case WM_MBUTTONUP: processMouseUp(2, lParam); return 0; case WM_MOUSEMOVE: { TRACKMOUSEEVENT trackMouseEvent = {sizeof(trackMouseEvent), TME_LEAVE, window, 0}; TrackMouseEvent(&trackMouseEvent); processMouseMoveMessage(lParam); return 0; } case WM_MOUSELEAVE: if (mouseLeaveCallback != NULL) { mouseLeaveCallback(modifiers, Shell_getCurrentTime()); } break; case WM_MOUSEWHEEL: 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, modifiers, Shell_getCurrentTime()); } } break; case WM_KEYDOWN: case WM_SYSKEYDOWN: { MSG msg; unsigned int charCode = 0, keyCode; updateModifierKeys(); if (PeekMessage(&msg, window, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE) && msg.message == WM_CHAR) { charCode = msg.wParam; } keyCode = lParamToShellKeyCode(lParam); if (keyCode == KEY_CODE_F4 && ((modifiers & MODIFIER_ALT_BIT) || (modifiers & MODIFIER_CONTROL_BIT))) { if (!initialized || confirmQuitCallback == NULL || confirmQuitCallback(Shell_getCurrentTime())) { exit(EXIT_SUCCESS); } } if (keyCode != 0 && initialized && keyDownCallback != NULL) { 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) { if (!(modifiers & MODIFIER_SHIFT_BIT)) { modifiers |= MODIFIER_SHIFT_BIT; if (initialized && keyModifiersChangedCallback != NULL) { keyModifiersChangedCallback(modifiers, lastModifiers, Shell_getCurrentTime()); } } } else if (keyCode == KEY_CODE_LEFT_CONTROL || keyCode == KEY_CODE_RIGHT_CONTROL) { if (!(modifiers & MODIFIER_CONTROL_BIT)) { modifiers |= MODIFIER_CONTROL_BIT; if (initialized && keyModifiersChangedCallback != NULL) { keyModifiersChangedCallback(modifiers, lastModifiers, Shell_getCurrentTime()); } } } else if (keyCode == KEY_CODE_LEFT_ALT || keyCode == KEY_CODE_RIGHT_ALT) { if (!(modifiers & MODIFIER_ALT_BIT)) { modifiers |= MODIFIER_ALT_BIT; if (initialized && keyModifiersChangedCallback != NULL) { keyModifiersChangedCallback(modifiers, lastModifiers, Shell_getCurrentTime()); } } } return 0; } 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: { unsigned int keyCode; updateModifierKeys(); keyCode = lParamToShellKeyCode(lParam); if (keyCode != 0 && initialized && keyUpCallback != NULL) { keyUpCallback(keyCode, modifiers, Shell_getCurrentTime()); } unsigned int lastModifiers = modifiers; if (keyCode == KEY_CODE_LEFT_SHIFT || keyCode == KEY_CODE_RIGHT_SHIFT) { if (modifiers & MODIFIER_SHIFT_BIT) { modifiers &= ~MODIFIER_SHIFT_BIT; if (initialized && keyModifiersChangedCallback != NULL) { keyModifiersChangedCallback(modifiers, lastModifiers, Shell_getCurrentTime()); } } } else if (keyCode == KEY_CODE_LEFT_CONTROL || keyCode == KEY_CODE_RIGHT_CONTROL) { if (modifiers & MODIFIER_CONTROL_BIT) { modifiers &= ~MODIFIER_CONTROL_BIT; if (initialized && keyModifiersChangedCallback != NULL) { keyModifiersChangedCallback(modifiers, lastModifiers, Shell_getCurrentTime()); } } } else if (keyCode == KEY_CODE_LEFT_ALT || keyCode == KEY_CODE_RIGHT_ALT) { if (modifiers & MODIFIER_ALT_BIT) { modifiers &= ~MODIFIER_ALT_BIT; if (initialized && keyModifiersChangedCallback != NULL) { keyModifiersChangedCallback(modifiers, lastModifiers, Shell_getCurrentTime()); } } } return 0; } case WM_ACTIVATEAPP: if (wParam) { if (initialized && foregroundedCallback != NULL) { foregroundedCallback(Shell_getCurrentTime()); } } else { if (initialized && backgroundedCallback != NULL) { backgroundedCallback(Shell_getCurrentTime()); } } 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_USER: return 0; } 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); if (window == NULL) { fprintf(stderr, "CreateWindow failed! GetLastError(): %ld\n", GetLastError()); 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 = "Change configuration->windowTitle in WGLTarget_configure()"; 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); if (result == 0) { fprintf(stderr, "RegisterClassEx failed! GetLastError(): %ld\n", GetLastError()); 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); 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); int format = ChoosePixelFormat(displayContext, &pixelFormat); if (format == 0) { fprintf(stderr, "ChoosePixelFormat failed! GetLastError(): %ld\n", GetLastError()); exit(EXIT_FAILURE); } if (!SetPixelFormat(displayContext, format, &pixelFormat)) { fprintf(stderr, "SetPixelFormat failed! GetLastError(): %ld\n", GetLastError()); exit(EXIT_FAILURE); } glContext = wglCreateContext(displayContext); if (glContext == NULL) { fprintf(stderr, "wglCreateContext failed! GetLastError(): %ld\n", GetLastError()); exit(EXIT_FAILURE); } wglMakeCurrent(displayContext, glContext); wglGetExtensionsStringEXT = (WGLFunction_wglGetExtensionsStringEXT) wglGetProcAddress("wglGetExtensionsStringEXT"); if (strstr(wglGetExtensionsStringEXT(), "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"); if (wglChoosePixelFormatARB != NULL && wglChoosePixelFormatARB(displayContext, attributes, NULL, 1, &format, &formatCount) && formatCount > 0) { wglMakeCurrent(NULL, NULL); wglDeleteContext(glContext); ReleaseDC(window, displayContext); DestroyWindow(window); window = createShellWindow(windowTitle, windowRect, instance); displayContext = GetDC(window); if (displayContext == NULL) { fprintf(stderr, "GetDC failed! GetLastError(): %ld\n", GetLastError()); exit(EXIT_FAILURE); } if (!SetPixelFormat(displayContext, format, &pixelFormat)) { fprintf(stderr, "SetPixelFormat failed! GetLastError(): %ld\n", GetLastError()); exit(EXIT_FAILURE); } glContext = wglCreateContext(displayContext); if (glContext == NULL) { fprintf(stderr, "wglCreateContext failed! GetLastError(): %ld\n", GetLastError()); exit(EXIT_FAILURE); } wglMakeCurrent(displayContext, glContext); } } setVSync(vsyncWindow); ShowWindow(window, command); 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; }