raylib/src/platforms/rcore_desktop_win32.c
Ray dd7a1948f1 WARNING: REDESIGN: REMOVED: utils module, functionality moved to rcore module: logging and file-system #4551
[utils] was created long time ago, when [rcore] contained all the platforms code, the purpose of the file was exposing basic filesystem functionality across modules and also logging mechanism but many things have changed since then and there is no need to keep using this module.

 - Logging system has been move to [rcore] module and macros are exposed through `config.h` to other modules
 - File system functionality has also been centralized in [rcore] module that along the years it was already adding more and more file-system functions, now they are all in the same module
 - Android specific code has been moved to `rcore_android.c`, it had no sense to have specific platform code in `utils`, [rcore] is responsible of all platform code.
2026-01-10 12:13:07 +01:00

2201 lines
85 KiB
C

/**********************************************************************************************
*
* rcore_desktop_win32 - Functions to manage window, graphics device and inputs
*
* PLATFORM: DESKTOP: WIN32
* - Windows (Win32, Win64)
*
* LIMITATIONS:
* - Initial development stage, lot of functionality missing
* - No support for MOUSE_BUTTON_FORWARD/MOUSE_BUTTON_BACK
*
* POSSIBLE IMPROVEMENTS:
* - Improvement 01
* - Improvement 02
*
* CONFIGURATION:
* #define RCORE_PLATFORM_CUSTOM_FLAG
* Custom flag for rcore on target platform -not used-
*
* DEPENDENCIES:
* - Win32 API (windows.h)
*
*
* LICENSE: zlib/libpng
*
* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors
*
* 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.
*
**********************************************************************************************/
// Move windows.h symbols to new names to avoid redefining the same names as raylib
#define CloseWindow CloseWindowWin32
#define Rectangle RectangleWin32
#define ShowCursor ShowCursorWin32
#define DrawTextA DrawTextAWin32
#define DrawTextW DrawTextWin32
#define DrawTextExA DrawTextExAWin32
#define DrawTextExW DrawTextExWin32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef CloseWindow // raylib symbol collision
#undef Rectangle // raylib symbol collision
#undef ShowCursor // raylib symbol collision
#undef LoadImage // raylib symbol collision
#undef DrawText // raylib symbol collision
#undef DrawTextA
#undef DrawTextW
#undef DrawTextEx // raylib symbol collision
#undef DrawTextExA
#undef DrawTextExW
#include <windowsx.h>
#include <shellscalingapi.h>
#include <versionhelpers.h>
#include <malloc.h> // Required for alloca()
#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
#include <GL/gl.h>
#endif
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
// NOTE: appScreenSize is the last screen size requested by the app,
// the backend must keep the client area this size (after DPI scaling is applied)
// when the window isn't fullscreen/maximized/minimized
typedef struct {
HWND hwnd; // Window handler
HDC hdc; // Graphic context handler
HGLRC glContext; // OpenGL context handler
// Software renderer variables
HDC hdcmem; // Memory graphic context handler
HBITMAP hbitmap; // GDI bitmap handler
unsigned int *pixels; // Pointer to pixel data buffer (BGRA format)
unsigned int appScreenWidth;
unsigned int appScreenHeight;
unsigned int desiredFlags;
LARGE_INTEGER timerFrequency;
} PlatformData;
// Define WGL function pointer types (no wglext.h needed)
typedef HGLRC (WINAPI *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int *);
typedef BOOL (WINAPI *PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC, const int *, const FLOAT *, UINT, int *, UINT *);
typedef BOOL (WINAPI *PFNWGLSWAPINTERVALEXTPROC)(int);
typedef const char *(WINAPI *PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC hdc);
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
extern CoreData CORE; // Global CORE state context
static PlatformData platform = { 0 }; // Platform specific data
// Required WGL functions
static PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL;
static PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = NULL;
static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL;
static PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = NULL;
// --------------------------------------------------------------------------------
// This part of the file contains pure functions that never access global state
// This distinction helps keep the backend maintainable as the inputs and outputs
// of every function called in this section can be fully derived from the call-site alone
// --------------------------------------------------------------------------------
// Prevent any code in this part of the file from accessing the global CORE state
#define CORE DONT_USE_CORE_HERE
//----------------------------------------------------------------------------------
// Defines and Macros
//----------------------------------------------------------------------------------
#define A_TO_W_ALLOCA(outWstr, inAnsi) do { \
size_t outLen = AToWLen(inAnsi); \
outWstr = (WCHAR *)alloca(sizeof(WCHAR)*(outLen + 1)); \
AToWCopy(inAnsi, outWstr, outLen); \
outWstr[outLen] = 0; \
} while (0)
#define STYLE_MASK_ALL 0xffffffff
#define STYLE_MASK_READONLY (WS_MINIMIZE | WS_MAXIMIZE)
#define STYLE_MASK_WRITABLE (~STYLE_MASK_READONLY)
#define STYLE_FLAGS_RESIZABLE WS_THICKFRAME
#define STYLE_FLAGS_UNDECORATED_OFF (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)
#define STYLE_FLAGS_UNDECORATED_ON WS_POPUP
#define WINDOW_STYLE_EX 0
#define CLASS_NAME L"raylibWindow"
#define FLAG_MASK_OPTIONAL (FLAG_VSYNC_HINT)
#define FLAG_MASK_REQUIRED ~(FLAG_MASK_OPTIONAL)
// Flags that have no operations to perform during an update
#define FLAG_MASK_NO_UPDATE (FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT)
#define WM_APP_UPDATE_WINDOW_SIZE (WM_APP + 1)
#define WGL_DRAW_TO_WINDOW_ARB 0x2001
#define WGL_ACCELERATION_ARB 0x2003
#define WGL_SUPPORT_OPENGL_ARB 0x2010
#define WGL_DOUBLE_BUFFER_ARB 0x2011
#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_DEPTH_BITS_ARB 0x2022
#define WGL_STENCIL_BITS_ARB 0x2023
#define WGL_TYPE_RGBA_ARB 0x202b
// Context acceleration types
#define WGL_NO_ACCELERATION_ARB 0x2025 // OpenGL 1.1 GDI software rasterizer
#define WGL_GENERIC_ACCELERATION_ARB 0x2026
#define WGL_FULL_ACCELERATION_ARB 0x2027 // OpenGL hardware-accelerated, using GPU-drivers provided by vendor
// WGL_ARB_multisample extension supported
#define WGL_SAMPLE_BUFFERS_ARB 0x2041 // Multisampling: 1 if multisample buffers are supported
#define WGL_SAMPLES_ARB 0x2042 // Multisampling: Number of samples per pixel (4, 8, 16)
// WGL_ARB_framebuffer_sRGB extension supported
#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9 // GL_TRUE if the framebuffer can do sRGB conversion
#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002
#define WGL_CONTEXT_ES_PROFILE_BIT_EXT 0x00000004
#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
// Maximize-minimize request types
typedef enum {
MIZED_NONE,
MIZED_MIN,
MIZED_MAX
} Mized;
// Flag operations
// NOTE: Some ops need to be deferred
typedef struct {
DWORD set;
DWORD clear;
} FlagsOp;
// Monitor info type
typedef struct {
HMONITOR needle;
int index;
int matchIndex;
RECT rect;
} MonitorInfo;
//----------------------------------------------------------------------------------
// Module Internal Functions Declaration
//----------------------------------------------------------------------------------
// Get ASCII to WCHAR length
static size_t AToWLen(const char *ascii)
{
int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, ascii, -1, NULL, 0);
if (sizeNeeded < 0) TRACELOG(LOG_ERROR, "WIN32: Failed to calculate wide length [ERROR: %u]", GetLastError());
return sizeNeeded;
}
// Copy ASCII to WCHAR string
static void AToWCopy(const char *ascii, wchar_t *outPtr, size_t outLen)
{
int size = MultiByteToWideChar(CP_UTF8, 0, ascii, -1, outPtr, (int)outLen);
if (size != outLen) TRACELOG(LOG_WARNING, "WIN32: Failed to convert %i UTF-8 chars to WCHAR, converted %i chars", outLen, size);
}
static bool DecoratedFromStyle(DWORD style)
{
if (style & STYLE_FLAGS_UNDECORATED_ON)
{
if (style & STYLE_FLAGS_UNDECORATED_OFF) TRACELOG(LOG_ERROR, "WIN32: FLAGS: Style 0x%x has both undecorated on/off flags", style);
return false; // Not decorated
}
DWORD masked = (style & STYLE_FLAGS_UNDECORATED_OFF);
if (STYLE_FLAGS_UNDECORATED_OFF != masked) TRACELOG(LOG_ERROR, "WIN32: FLAGS: Style 0x%x is missing flags 0x%x", masked, masked ^ STYLE_FLAGS_UNDECORATED_OFF);
return true; // Decorated
}
// Get window style from required flags
static DWORD MakeWindowStyle(unsigned flags)
{
// Flag is not needed because there are no child windows,
// but supposedly it improves efficiency, plus, windows adds this
// flag automatically anyway so it keeps flags in sync with the OS
DWORD style = WS_CLIPSIBLINGS;
style |= (flags & FLAG_WINDOW_HIDDEN)? 0 : WS_VISIBLE;
style |= (flags & FLAG_WINDOW_RESIZABLE)? STYLE_FLAGS_RESIZABLE : 0;
style |= (flags & FLAG_WINDOW_UNDECORATED)? STYLE_FLAGS_UNDECORATED_ON : STYLE_FLAGS_UNDECORATED_OFF;
// Minimized takes precedence over maximized
int mized = MIZED_NONE;
if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) mized = MIZED_MIN;
if (flags & FLAG_WINDOW_MAXIMIZED) mized = MIZED_MAX;
switch (mized)
{
case MIZED_NONE: break;
case MIZED_MIN: style |= WS_MINIMIZE; break;
case MIZED_MAX: style |= WS_MAXIMIZE; break;
default: break;
}
return style;
}
// Check flags state, enforces that the actual window/platform state is in sync with raylib's flags
static void CheckFlags(const char *context, HWND hwnd, DWORD flags, DWORD expectedStyle, DWORD styleCheckMask)
{
DWORD styleFromFlags = MakeWindowStyle(flags);
if ((styleFromFlags & styleCheckMask) != (expectedStyle & styleCheckMask))
{
TRACELOG(LOG_ERROR, "WIN32: FLAGS: %s: window flags (0x%x) produced style 0x%x which != expected 0x%x (diff=0x%x, mask=0x%x)",
context, flags, styleFromFlags & styleCheckMask, expectedStyle & styleCheckMask,
(styleFromFlags & styleCheckMask) ^ (expectedStyle & styleCheckMask), styleCheckMask);
}
SetLastError(0);
LONG actualStyle = (LONG)GetWindowLongPtrW(hwnd, GWL_STYLE);
if ((actualStyle & styleCheckMask) != (expectedStyle & styleCheckMask))
{
TRACELOG(LOG_ERROR, "WIN32: FLAGS: %s: expected style 0x%x but got 0x%x (diff=0x%x, mask=0x%x, lasterror=%lu)",
context, expectedStyle & styleCheckMask, actualStyle & styleCheckMask,
(expectedStyle & styleCheckMask) ^ (actualStyle & styleCheckMask),
styleCheckMask, GetLastError());
}
if (styleCheckMask & WS_MINIMIZE)
{
bool isIconic = IsIconic(hwnd);
bool styleMinimized = !!(WS_MINIMIZE & actualStyle);
if (isIconic != styleMinimized) TRACELOG(LOG_ERROR, "WIN32: FLAGS: IsIconic(%d) != WS_MINIMIZED(%d)", isIconic, styleMinimized);
}
if (styleCheckMask & WS_MAXIMIZE)
{
WINDOWPLACEMENT placement;
placement.length = sizeof(placement);
if (!GetWindowPlacement(hwnd, &placement))
{
TRACELOG(LOG_ERROR, "WIN32: FLAGS: %s failed, error=%lu", "GetWindowPlacement", GetLastError());
}
bool placementMaximized = (placement.showCmd == SW_SHOWMAXIMIZED);
bool styleMaximized = WS_MAXIMIZE & actualStyle;
if (placementMaximized != styleMaximized)
{
TRACELOG(LOG_ERROR, "WIN32: FLAGS: Maximized state desync, placement maximized=%d (showCmd=%lu) style maximized=%d",
placementMaximized, placement.showCmd, styleMaximized);
}
}
}
// Calculate window size (with borders, title-bar...) from desired client size (framebuffer size)
static SIZE CalcWindowSize(UINT dpi, SIZE clientSize, DWORD style)
{
RECT rect = { 0, 0, clientSize.cx, clientSize.cy };
int result = AdjustWindowRectExForDpi(&rect, style, 0, WINDOW_STYLE_EX, dpi);
if (result == 0) TRACELOG(LOG_ERROR, "WIN32: Failed to adjust window rect [ERROR: %lu]", GetLastError());
return (SIZE){ rect.right - rect.left, rect.bottom - rect.top };
}
// Update window size if required
// NOTE: Returns true if the window size was updated, false otherwise
static bool UpdateWindowSize(int mode, HWND hwnd, int width, int height, unsigned flags)
{
if (flags & FLAG_WINDOW_MINIMIZED) return false;
if (flags & FLAG_WINDOW_MAXIMIZED)
{
CheckFlags("UpdateWindowSize(maximized)", hwnd, flags, MakeWindowStyle(flags), STYLE_MASK_ALL);
return false;
}
if (flags & FLAG_BORDERLESS_WINDOWED_MODE)
{
MONITORINFO info = { 0 };
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
info.cbSize = sizeof(info);
if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "WIN32: Failed to get monitor info [ERROR: %lu]", GetLastError());
RECT windowRect = { 0 };
if (!GetWindowRect(hwnd, &windowRect)) TRACELOG(LOG_ERROR, "WIN32: Failed to get window rect [ERROR: %lu]", GetLastError());
if ((windowRect.left == info.rcMonitor.left) &&
(windowRect.top == info.rcMonitor.top) &&
((windowRect.right - windowRect.left) == (info.rcMonitor.right - info.rcMonitor.left)) &&
((windowRect.bottom - windowRect.top) == (info.rcMonitor.bottom - info.rcMonitor.top))) return false;
if (!SetWindowPos(hwnd, HWND_TOP,
info.rcMonitor.left, info.rcMonitor.top,
info.rcMonitor.right - info.rcMonitor.left,
info.rcMonitor.bottom - info.rcMonitor.top,
SWP_NOOWNERZORDER))
{
TRACELOG(LOG_ERROR, "WIN32: Failed to set window position [ERROR: %lu]", GetLastError());
}
return true;
}
// Get size in pixels from points, considering high-dpi
UINT dpi = GetDpiForWindow(hwnd);
float dpiScale = ((float)dpi)/96.0f;
bool dpiScaling = flags & FLAG_WINDOW_HIGHDPI;
SIZE desiredSize = {
.cx = dpiScaling? (int)((float)width*dpiScale) : width,
.cy = dpiScaling? (int)((float)height*dpiScale) : height
};
// Get client size (framebuffer inside the window)
RECT rect = { 0 };
GetClientRect(hwnd, &rect);
SIZE clientSize = { rect.right, rect.bottom };
// If client size is alread desired size, no need to update
if ((clientSize.cx == desiredSize.cx) || (clientSize.cy == desiredSize.cy)) return false;
TRACELOG(LOG_INFO, "WIN32: Restoring client size from [%dx%d] to [%dx%d] (dpi:%lu dpiScaling:%d app:%ix%i)",
clientSize.cx, clientSize.cy, desiredSize.cx, desiredSize.cy, dpi, dpiScaling, width, height);
// Calculate window size from desired framebuffer size and window flags
SIZE windowSize = CalcWindowSize(dpi, desiredSize, MakeWindowStyle(flags));
POINT windowPos = { 0 };
UINT swpFlags = SWP_NOZORDER | SWP_FRAMECHANGED;
if (mode == 0) // UPDATE_WINDOW_FIRST
{
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
if (!monitor) TRACELOG(LOG_ERROR, "WIN32: Failed to get monitor from window [ERROR: %lu]", GetLastError());
MONITORINFO info = { 0 };
info.cbSize = sizeof(info);
if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "WIN32: Failed to get monitor info [ERROR: %lu]", GetLastError());
#define MAX(a,b) (((a)>(b))? (a):(b))
LONG monitorWidth = info.rcMonitor.right - info.rcMonitor.left;
LONG monitorHeight = info.rcMonitor.bottom - info.rcMonitor.top;
windowPos = (POINT){
MAX(0, (monitorWidth - windowSize.cx)/2),
MAX(0, (monitorHeight - windowSize.cy)/2),
};
}
else swpFlags |= SWP_NOMOVE;
// WARNING: This code must be called after swInit() has been called, after InitPlatform() in [rcore]
//RECT rc = {0, 0, desired.cx, desired.cy};
//AdjustWindowRectEx(&rc, WS_OVERLAPPEDWINDOW, FALSE, 0);
//SetWindowPos(hwnd, NULL, windowPos.x, windowPos.y, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER);
return true;
}
// Verify if we are running in Windows 10 version 1703 (Creators Update)
static BOOL IsWindows10Version1703OrGreaterWin32(void)
{
HMODULE ntdll = LoadLibraryW(L"ntdll.dll");
DWORD (*Verify)(RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG) =
(DWORD (*)(RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG))GetProcAddress(ntdll, "RtlVerifyVersionInfo");
if (!Verify)
{
TRACELOG(LOG_ERROR, "WIN32: Failed to verify Windows version [ERROR: %lu]", GetLastError());
return 0;
}
RTL_OSVERSIONINFOEXW osvi = { 0 };
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osvi.dwMajorVersion = 10;
osvi.dwMinorVersion = 0;
osvi.dwBuildNumber = 15063; // Build 15063 corresponds to Windows 10 version 1703 (Creators Update)
DWORDLONG cond = 0;
VER_SET_CONDITION(cond, VER_MAJORVERSION, VER_GREATER_EQUAL);
VER_SET_CONDITION(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
VER_SET_CONDITION(cond, VER_BUILDNUMBER, VER_GREATER_EQUAL);
return 0 == (*Verify)(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER, cond);
}
// Get OpenGL function pointers
static void *WglGetProcAddress(const char *procname)
{
void *proc = (void *)wglGetProcAddress(procname);
if ((proc == NULL) ||
// NOTE: Some GPU drivers could return following
// invalid sentinel values instead of NULL
(proc == (void *)0x1) ||
(proc == (void *)0x2) ||
(proc == (void *)0x3) ||
(proc == (void *)-1))
{
// TODO: Keep gl module pointer as global platform data?
HMODULE glModule = LoadLibraryW(L"opengl32.dll");
proc = (void *)GetProcAddress(glModule, procname);
//if (proc == NULL) TRACELOG(LOG_ERROR, "GL: GetProcAddress() failed to get %s [%p], error=%u", procname, proc, GetLastError());
//else TRACELOG(LOG_INFO, "GL: Found entry point for %s [%p]", procname, proc);
}
return proc;
}
// Get key from wparam (mapping)
static KeyboardKey GetKeyFromWparam(WPARAM wparam)
{
switch (wparam)
{
/* case VK_LBUTTON: return KEY_; */
/* case VK_RBUTTON: return KEY_; */
/* case VK_CANCEL: return KEY_; */
/* case VK_MBUTTON: return KEY_; */
/* case VK_XBUTTON1: return KEY_; */
/* case VK_XBUTTON2: return KEY_; */
/* case VK_BACK: return KEY_; */
/* case VK_TAB: return KEY_; */
/* case VK_CLEAR: return KEY_; */
case VK_RETURN: return KEY_ENTER;
/* case VK_SHIFT: return KEY_; */
/* case VK_CONTROL: return KEY_; */
/* case VK_MENU: return KEY_; */
/* case VK_PAUSE: return KEY_; */
/* case VK_CAPITAL: return KEY_; */
/* case VK_KANA: return KEY_; */
/* case VK_HANGUL: return KEY_; */
/* case VK_IME_ON: return KEY_; */
/* case VK_JUNJA: return KEY_; */
/* case VK_FINAL: return KEY_; */
/* case VK_HANJA: return KEY_; */
/* case VK_KANJI: return KEY_; */
/* case VK_IME_OFF: return KEY_; */
case VK_ESCAPE: return KEY_ESCAPE;
/* case VK_CONVERT: return KEY_; */
/* case VK_NONCONVERT: return KEY_; */
/* case VK_ACCEPT: return KEY_; */
/* case VK_MODECHANGE: return KEY_; */
case VK_SPACE: return KEY_SPACE;
/* case VK_PRIOR: return KEY_; */
/* case VK_NEXT: return KEY_; */
/* case VK_END: return KEY_; */
/* case VK_HOME: return KEY_; */
case VK_LEFT: return KEY_LEFT;
case VK_UP: return KEY_UP;
case VK_RIGHT: return KEY_RIGHT;
case VK_DOWN: return KEY_DOWN;
/* case VK_SELECT: return KEY_; */
/* case VK_PRINT: return KEY_; */
/* case VK_EXECUTE: return KEY_; */
/* case VK_SNAPSHOT: return KEY_; */
/* case VK_INSERT: return KEY_; */
/* case VK_DELETE: return KEY_; */
/* case VK_HELP: return KEY_; */
case '0': return KEY_ZERO;
case '1': return KEY_ONE;
case '2': return KEY_TWO;
case '3': return KEY_THREE;
case '4': return KEY_FOUR;
case '5': return KEY_FIVE;
case '6': return KEY_SIX;
case '7': return KEY_SEVEN;
case '8': return KEY_EIGHT;
case '9': return KEY_NINE;
/* case 0x3A-40: return KEY_; */
case 'A': return KEY_A;
case 'B': return KEY_B;
case 'C': return KEY_C;
case 'D': return KEY_D;
case 'E': return KEY_E;
case 'F': return KEY_F;
case 'G': return KEY_G;
case 'H': return KEY_H;
case 'I': return KEY_I;
case 'J': return KEY_J;
case 'K': return KEY_K;
case 'L': return KEY_L;
case 'M': return KEY_M;
case 'N': return KEY_N;
case 'O': return KEY_O;
case 'P': return KEY_P;
case 'Q': return KEY_Q;
case 'R': return KEY_R;
case 'S': return KEY_S;
case 'T': return KEY_T;
case 'U': return KEY_U;
case 'V': return KEY_V;
case 'W': return KEY_W;
case 'X': return KEY_X;
case 'Y': return KEY_Y;
case 'Z': return KEY_Z;
/* case VK_LWIN: return KEY_; */
/* case VK_RWIN: return KEY_; */
/* case VK_APPS: return KEY_; */
/* case VK_SLEEP: return KEY_; */
/* case VK_NUMPAD0: return KEY_; */
/* case VK_NUMPAD1: return KEY_; */
/* case VK_NUMPAD2: return KEY_; */
/* case VK_NUMPAD3: return KEY_; */
/* case VK_NUMPAD4: return KEY_; */
/* case VK_NUMPAD5: return KEY_; */
/* case VK_NUMPAD6: return KEY_; */
/* case VK_NUMPAD7: return KEY_; */
/* case VK_NUMPAD8: return KEY_; */
/* case VK_NUMPAD9: return KEY_; */
/* case VK_MULTIPLY: return KEY_; */
/* case VK_ADD: return KEY_; */
/* case VK_SEPARATOR: return KEY_; */
/* case VK_SUBTRACT: return KEY_; */
/* case VK_DECIMAL: return KEY_; */
/* case VK_DIVIDE: return KEY_; */
/* case VK_F1: return KEY_; */
/* case VK_F2: return KEY_; */
/* case VK_F3: return KEY_; */
/* case VK_F4: return KEY_; */
/* case VK_F5: return KEY_; */
/* case VK_F6: return KEY_; */
/* case VK_F7: return KEY_; */
/* case VK_F8: return KEY_; */
/* case VK_F9: return KEY_; */
/* case VK_F10: return KEY_; */
/* case VK_F11: return KEY_; */
/* case VK_F12: return KEY_; */
/* case VK_F13: return KEY_; */
/* case VK_F14: return KEY_; */
/* case VK_F15: return KEY_; */
/* case VK_F16: return KEY_; */
/* case VK_F17: return KEY_; */
/* case VK_F18: return KEY_; */
/* case VK_F19: return KEY_; */
/* case VK_F20: return KEY_; */
/* case VK_F21: return KEY_; */
/* case VK_F22: return KEY_; */
/* case VK_F23: return KEY_; */
/* case VK_F24: return KEY_; */
/* case VK_NUMLOCK: return KEY_; */
/* case VK_SCROLL: return KEY_; */
/* case VK_LSHIFT: return KEY_; */
/* case VK_RSHIFT: return KEY_; */
/* case VK_LCONTROL: return KEY_; */
/* case VK_RCONTROL: return KEY_; */
/* case VK_LMENU: return KEY_; */
/* case VK_RMENU: return KEY_; */
/* case VK_BROWSER_BACK: return KEY_; */
/* case VK_BROWSER_FORWARD: return KEY_; */
/* case VK_BROWSER_REFRESH: return KEY_; */
/* case VK_BROWSER_STOP: return KEY_; */
/* case VK_BROWSER_SEARCH: return KEY_; */
/* case VK_BROWSER_FAVORITES: return KEY_; */
/* case VK_BROWSER_HOME: return KEY_; */
/* case VK_VOLUME_MUTE: return KEY_; */
/* case VK_VOLUME_DOWN: return KEY_; */
/* case VK_VOLUME_UP: return KEY_; */
/* case VK_MEDIA_NEXT_TRACK: return KEY_; */
/* case VK_MEDIA_PREV_TRACK: return KEY_; */
/* case VK_MEDIA_STOP: return KEY_; */
/* case VK_MEDIA_PLAY_PAUSE: return KEY_; */
/* case VK_LAUNCH_MAIL: return KEY_; */
/* case VK_LAUNCH_MEDIA_SELECT: return KEY_; */
/* case VK_LAUNCH_APP1: return KEY_; */
/* case VK_LAUNCH_APP2: return KEY_; */
/* case VK_OEM_1: return KEY_; */
/* case VK_OEM_PLUS: return KEY_; */
/* case VK_OEM_COMMA: return KEY_; */
/* case VK_OEM_MINUS: return KEY_; */
/* case VK_OEM_PERIOD: return KEY_; */
/* case VK_OEM_2: return KEY_; */
/* case VK_OEM_3: return KEY_; */
/* case VK_OEM_4: return KEY_; */
/* case VK_OEM_5: return KEY_; */
/* case VK_OEM_6: return KEY_; */
/* case VK_OEM_7: return KEY_; */
/* case VK_OEM_8: return KEY_; */
/* case VK_OEM_102: return KEY_; */
/* case VK_PROCESSKEY: return KEY_; */
/* case VK_PACKET: return KEY_; */
/* case VK_ATTN: return KEY_; */
/* case VK_CRSEL: return KEY_; */
/* case VK_EXSEL: return KEY_; */
/* case VK_EREOF: return KEY_; */
/* case VK_PLAY: return KEY_; */
/* case VK_ZOOM: return KEY_; */
/* case VK_NONAME: return KEY_; */
/* case VK_PA1: return KEY_; */
/* case VK_OEM_CLEAR: return KEY_; */
default: return KEY_NULL;
}
}
// Get cursor name
static LPCWSTR GetCursorName(int cursor)
{
LPCWSTR name = (LPCWSTR)IDC_ARROW;
switch (cursor)
{
case MOUSE_CURSOR_DEFAULT: name = (LPCWSTR)IDC_ARROW; break;
case MOUSE_CURSOR_ARROW: name = (LPCWSTR)IDC_ARROW; break;
case MOUSE_CURSOR_IBEAM: name = (LPCWSTR)IDC_IBEAM; break;
case MOUSE_CURSOR_CROSSHAIR: name = (LPCWSTR)IDC_CROSS; break;
case MOUSE_CURSOR_POINTING_HAND: name = (LPCWSTR)IDC_HAND; break;
case MOUSE_CURSOR_RESIZE_EW: name = (LPCWSTR)IDC_SIZEWE; break;
case MOUSE_CURSOR_RESIZE_NS: name = (LPCWSTR)IDC_SIZENS; break;
case MOUSE_CURSOR_RESIZE_NWSE: name = (LPCWSTR)IDC_SIZENWSE; break;
case MOUSE_CURSOR_RESIZE_NESW: name = (LPCWSTR)IDC_SIZENESW; break;
case MOUSE_CURSOR_RESIZE_ALL: name = (LPCWSTR)IDC_SIZEALL; break;
case MOUSE_CURSOR_NOT_ALLOWED: name = (LPCWSTR)IDC_NO; break;
default: break;
}
return name;
}
// Count monitors process
// NOTE: Required by GetMonitorCount()
static BOOL CALLBACK CountMonitorsProc(HMONITOR handle, HDC hdc, LPRECT rect, LPARAM lparam)
{
int *count = (int *)lparam;
*count += 1;
// Always return TRUE to continue the loop, otherwise, the caller
// can't distinguish between stopping the loop and an error
return TRUE;
}
// Find monitor process
// NOTE: Required by GetCurrentMonitor()
static BOOL CALLBACK FindMonitorProc(HMONITOR handle, HDC hdc, LPRECT rect, LPARAM lparam)
{
MonitorInfo *monitor = (MonitorInfo *)lparam;
if (handle == monitor->needle)
{
monitor->matchIndex = monitor->index;
monitor->rect = *rect;
}
monitor->index += 1;
// Always return TRUE to continue the loop, otherwise, the caller
// can't distinguish between stopping the loop and an error
return TRUE;
}
// Get style changed required operations flags
// NOTE: Required for deferred operations
static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *style, FlagsOp *deferredFlags)
{
// Check window resizable flag change
bool resizable = (coreWindowFlags & FLAG_WINDOW_RESIZABLE);
bool resizableOld = ((style->styleOld & STYLE_FLAGS_RESIZABLE) != 0);
bool resizableNew = ((style->styleNew & STYLE_FLAGS_RESIZABLE) != 0);
if (resizable != resizableOld) TRACELOG(LOG_ERROR, "WIN32: Expected resizable %u but got %u", resizable, resizableOld);
if (resizableOld != resizableNew)
{
if (resizableNew) deferredFlags->set |= FLAG_WINDOW_RESIZABLE;
else deferredFlags->clear |= FLAG_WINDOW_RESIZABLE;
}
// Check window decorated flag change
bool decorated = (0 == (coreWindowFlags & FLAG_WINDOW_UNDECORATED));
bool decoratedOld = DecoratedFromStyle(style->styleOld);
bool decoratedNew = DecoratedFromStyle(style->styleNew);
if (decorated != decoratedOld) TRACELOG(LOG_ERROR, "WIN32: Expected decorated %u but got %u", decorated, decoratedOld);
if (decoratedOld != decoratedNew)
{
if (decoratedNew) deferredFlags->clear |= FLAG_WINDOW_UNDECORATED;
else deferredFlags->set |= FLAG_WINDOW_UNDECORATED;
}
// Check window hidden flag change
bool hidden = (coreWindowFlags & FLAG_WINDOW_HIDDEN);
bool hiddenOld = ((style->styleOld & WS_VISIBLE) == 0);
bool hiddenNew = ((style->styleNew & WS_VISIBLE) == 0);
if (hidden != hiddenOld) TRACELOG(LOG_ERROR, "WIN32: Expected hidden %u but got %u", hidden, hiddenOld);
if (hiddenOld != hiddenNew)
{
if (hiddenNew) deferredFlags->set |= FLAG_WINDOW_HIDDEN;
else deferredFlags->clear |= FLAG_WINDOW_HIDDEN;
}
}
// Adopt window resize
// NOTE: Call when the window is rezised, returns true
// if the new window size should update the desired app size
static bool AdoptWindowResize(unsigned flags)
{
if (flags & FLAG_WINDOW_MINIMIZED) return false;
if (flags & FLAG_WINDOW_MAXIMIZED) return false;
if (flags & FLAG_FULLSCREEN_MODE) return false;
if (flags & FLAG_BORDERLESS_WINDOWED_MODE) return false;
if (!(flags & FLAG_WINDOW_RESIZABLE)) return false;
return true;
}
// ---------------------------------------------------------------------------------------------
// Here's the end of the "pure function section", the rest of the file can access global state
// ---------------------------------------------------------------------------------------------
// Unlock the ability to use CORE in the rest of the file
#undef CORE
//----------------------------------------------------------------------------------
// Module Internal Functions Declaration
//----------------------------------------------------------------------------------
int InitPlatform(void); // Initialize platform (graphics, inputs and more)
void ClosePlatform(void); // Close platform
// Win32 process messages management function
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
// Win32: Handle inputs functions
static void HandleKey(WPARAM wparam, LPARAM lparam, char state);
static void HandleMouseButton(int button, char state);
static void HandleRawInput(LPARAM lparam);
static void HandleWindowResize(HWND hwnd, int *width, int *height);
static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags);
static unsigned SanitizeFlags(int mode, unsigned flags);
static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height); // Update window flags
// Check if OpenGL extension is available
static bool IsWglExtensionAvailable(HDC hdc, const char *extension);
//----------------------------------------------------------------------------------
// Module Functions Declaration
//----------------------------------------------------------------------------------
// NOTE: Functions declaration is provided by raylib.h
//----------------------------------------------------------------------------------
// Module Functions Definition: Window and Graphics Device
//----------------------------------------------------------------------------------
// Check if application should close
bool WindowShouldClose(void)
{
return CORE.Window.shouldClose;
}
// Toggle fullscreen mode
void ToggleFullscreen(void)
{
TRACELOG(LOG_WARNING, "WIN32: Toggle full screen functionality not implemented");
}
// Toggle borderless windowed mode
void ToggleBorderlessWindowed(void)
{
if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) ClearWindowState(FLAG_BORDERLESS_WINDOWED_MODE);
else SetWindowState(FLAG_BORDERLESS_WINDOWED_MODE);
}
// Set window state: maximized, if resizable
void MaximizeWindow(void)
{
SetWindowState(FLAG_WINDOW_MAXIMIZED);
}
// Set window state: minimized
void MinimizeWindow(void)
{
SetWindowState(FLAG_WINDOW_MINIMIZED);
}
// Restore window from being minimized/maximized
void RestoreWindow(void)
{
if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) &&
(CORE.Window.flags & FLAG_WINDOW_MINIMIZED)) ClearWindowState(FLAG_WINDOW_MINIMIZED);
else ClearWindowState(FLAG_WINDOW_MINIMIZED | FLAG_WINDOW_MAXIMIZED);
}
// Set window configuration state using flags
void SetWindowState(unsigned int flags)
{
platform.desiredFlags = SanitizeFlags(1 /*SANITIZE_FLAGS_NORMAL*/, CORE.Window.flags | flags);
UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight);
}
// Clear window configuration state flags
void ClearWindowState(unsigned int flags)
{
platform.desiredFlags = SanitizeFlags(1 /*SANITIZE_FLAGS_NORMAL*/, CORE.Window.flags & ~flags);
UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight);
}
// Set icon for window
void SetWindowIcon(Image image)
{
if (!platform.hwnd || (image.data == NULL) || (image.width <= 0) || (image.height <= 0)) return;
HDC hdc = GetDC(platform.hwnd);
// Create 32-bit BGRA DIB for color
BITMAPV5HEADER bi = { 0 };
ZeroMemory(&bi, sizeof(bi));
bi.bV5Size = sizeof(bi);
bi.bV5Width = image.width;
bi.bV5Height = -image.height; // Negative = top-down bitmap
bi.bV5Planes = 1;
bi.bV5BitCount = 32;
bi.bV5Compression = BI_BITFIELDS;
bi.bV5RedMask = 0x00FF0000;
bi.bV5GreenMask = 0x0000FF00;
bi.bV5BlueMask = 0x000000FF;
bi.bV5AlphaMask = 0xFF000000;
unsigned char *targetBits = NULL;
HBITMAP hColorBitmap = CreateDIBSection(hdc, (BITMAPINFO *)&bi, DIB_RGB_COLORS, (void **)&targetBits, NULL, 0);
if (!hColorBitmap)
{
ReleaseDC(platform.hwnd, hdc);
return;
}
// Copy RGBA > BGRA (Win32 expects BGRA)
for (int y = 0; y < image.height; y++)
{
for (int x = 0; x < image.width; x++)
{
int i = (y*image.width + x)*4;
targetBits[i + 0] = ((unsigned char *)image.data)[i + 2]; // B
targetBits[i + 1] = ((unsigned char *)image.data)[i + 1]; // G
targetBits[i + 2] = ((unsigned char *)image.data)[i + 0]; // R
targetBits[i + 3] = ((unsigned char *)image.data)[i + 3]; // A
}
}
// Create mask bitmap (1-bit, all opaque)
HBITMAP hMaskBitmap = CreateBitmap(image.width, image.height, 1, 1, NULL);
// Build icon info
ICONINFO ii = { 0 };
ZeroMemory(&ii, sizeof(ii));
ii.fIcon = TRUE;
ii.hbmMask = hMaskBitmap;
ii.hbmColor = hColorBitmap;
HICON hIcon = CreateIconIndirect(&ii);
// Clean up GDI bitmaps (icon keeps copies internally)
DeleteObject(hColorBitmap);
DeleteObject(hMaskBitmap);
ReleaseDC(platform.hwnd, hdc);
if (hIcon)
{
// Set both large and small icons
SendMessage(platform.hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
SendMessage(platform.hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
}
}
// Set icon for window
void SetWindowIcons(Image *images, int count)
{
// TODO: Implement SetWindowIcons()
}
void SetWindowTitle(const char *title)
{
CORE.Window.title = title;
WCHAR *titleWide = NULL;
A_TO_W_ALLOCA(titleWide, CORE.Window.title);
int result = SetWindowTextW(platform.hwnd, titleWide);
if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to set window title [ERROR: %lu]", GetLastError());
}
// Set window position on screen (windowed mode)
void SetWindowPosition(int x, int y)
{
if (platform.hwnd != NULL)
{
RECT rect = { 0 };
if (GetWindowRect(platform.hwnd, &rect))
{
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
// Move the window to the new position (keeping size and z-order)
SetWindowPos(platform.hwnd, NULL, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
}
}
}
// Set monitor for the current window
void SetWindowMonitor(int monitor)
{
TRACELOG(LOG_WARNING, "SetWindowMonitor not implemented");
}
// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE)
void SetWindowMinSize(int width, int height)
{
TRACELOG(LOG_WARNING, "SetWindowMinSize not implemented");
CORE.Window.screenMin.width = width;
CORE.Window.screenMin.height = height;
}
// Set window maximum dimensions (FLAG_WINDOW_RESIZABLE)
void SetWindowMaxSize(int width, int height)
{
TRACELOG(LOG_WARNING, "SetWindowMaxSize not implemented");
CORE.Window.screenMax.width = width;
CORE.Window.screenMax.height = height;
}
// Set window dimensions
void SetWindowSize(int width, int height)
{
TRACELOG(LOG_WARNING, "SetWindowSize not implemented");
}
// Set window opacity, value opacity is between 0.0 and 1.0
void SetWindowOpacity(float opacity)
{
TRACELOG(LOG_WARNING, "SetWindowOpacity not implemented");
}
// Set window focused
void SetWindowFocused(void)
{
TRACELOG(LOG_WARNING, "SetWindowFocused not implemented");
}
// Get native window handle
void *GetWindowHandle(void)
{
return platform.hwnd;
}
int GetMonitorCount(void)
{
int count = 0;
int result = EnumDisplayMonitors(NULL, NULL, CountMonitorsProc, (LPARAM)&count);
if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "EnumDisplayMonitors", GetLastError());
return count;
}
// Get current monitor where window is placed
int GetCurrentMonitor(void)
{
HMONITOR monitor = MonitorFromWindow(platform.hwnd, MONITOR_DEFAULTTOPRIMARY);
if (!monitor) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "MonitorFromWindow", GetLastError());
MonitorInfo info = { 0 };
info.needle = monitor;
info.index = 0;
info.matchIndex = -1;
int result = EnumDisplayMonitors(NULL, NULL, FindMonitorProc, (LPARAM)&info);
if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "EnumDisplayMonitors", GetLastError());
return info.matchIndex;
}
// Get selected monitor position
Vector2 GetMonitorPosition(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorPosition not implemented");
return (Vector2){ 0, 0 };
}
// Get selected monitor width (currently used by monitor)
int GetMonitorWidth(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorWidth not implemented");
return 0;
}
// Get selected monitor height (currently used by monitor)
int GetMonitorHeight(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorHeight not implemented");
return 0;
}
// Get selected monitor physical width in millimetres
int GetMonitorPhysicalWidth(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth not implemented");
return 0;
}
// Get selected monitor physical height in millimetres
int GetMonitorPhysicalHeight(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight not implemented");
return 0;
}
// Get selected monitor refresh rate
int GetMonitorRefreshRate(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorRefreshRate not implemented");
return 0;
}
// Get the human-readable, UTF-8 encoded name of the selected monitor
const char *GetMonitorName(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorName not implemented");
return 0;
}
// Get window position XY on monitor
Vector2 GetWindowPosition(void)
{
TRACELOG(LOG_WARNING, "GetWindowPosition not implemented");
return (Vector2){ 0, 0 };
}
// Get window scale DPI factor for current monitor
Vector2 GetWindowScaleDPI(void)
{
float scale = ((float)GetDpiForWindow(platform.hwnd))/96.0f;
return (Vector2){ scale, scale };
}
// Set clipboard text content
void SetClipboardText(const char *text)
{
TRACELOG(LOG_WARNING, "SetClipboardText not implemented");
}
// Get clipboard text content
const char *GetClipboardText(void)
{
TRACELOG(LOG_WARNING, "GetClipboardText not implemented");
return NULL;
}
// Get clipboard image
Image GetClipboardImage(void)
{
Image image = { 0 };
TRACELOG(LOG_WARNING, "GetClipboardText not implemented");
return image;
}
// Show mouse cursor
void ShowCursor(void)
{
SetCursor(LoadCursorW(NULL, (LPCWSTR)IDC_ARROW));
CORE.Input.Mouse.cursorHidden = false;
}
// Hides mouse cursor
void HideCursor(void)
{
// NOTE: We use SetCursor() instead of ShowCursor() because
// it makes it easy to only hide the cursor while it's inside the client area
SetCursor(NULL);
CORE.Input.Mouse.cursorHidden = true;
}
// Enables cursor (unlock cursor)
void EnableCursor(void)
{
if (CORE.Input.Mouse.cursorLocked)
{
if (!ClipCursor(NULL)) TRACELOG(LOG_WARNING, "WIN32: Failed to clip cursor [ERROR: %lu]", GetLastError());
RAWINPUTDEVICE rid = { 0 };
rid.usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC
rid.usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE
rid.dwFlags = RIDEV_REMOVE; // Add to this window even in background
rid.hwndTarget = NULL;
int result = RegisterRawInputDevices(&rid, 1, sizeof(rid));
if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to register raw input devices [ERROR: %lu]", GetLastError());
ShowCursor();
CORE.Input.Mouse.cursorLocked = false;
}
}
// Disables cursor (lock cursor)
void DisableCursor(void)
{
if (!CORE.Input.Mouse.cursorLocked)
{
RAWINPUTDEVICE rid = { 0 };
rid.usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC
rid.usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE
rid.dwFlags = RIDEV_INPUTSINK; // Add to this window even in background
rid.hwndTarget = platform.hwnd;
int result = RegisterRawInputDevices(&rid, 1, sizeof(rid));
if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to register raw input devices [ERROR: %lu]", GetLastError());
RECT clientRect = { 0 };
if (!GetClientRect(platform.hwnd, &clientRect)) TRACELOG(LOG_WARNING, "WIN32: Failed to get client rectangle [ERROR: %lu]", GetLastError());
POINT topleft = { clientRect.left, clientRect.top };
if (!ClientToScreen(platform.hwnd, &topleft)) TRACELOG(LOG_WARNING, "WIN32: Failed to get client to screen size [ERROR: %lu]", GetLastError());
LONG width = clientRect.right - clientRect.left;
LONG height = clientRect.bottom - clientRect.top;
TRACELOG(LOG_INFO, "WIN32: Clip cursor client rect: [%d,%d %d,%d], top-left: (%d,%d)",
clientRect.left, clientRect.top, clientRect.right, clientRect.bottom, topleft.x, topleft.y);
LONG centerX = topleft.x + width/2;
LONG centerY = topleft.y + height/2;
RECT clipRect = { centerX, centerY, centerX + 1, centerY + 1 };
if (!ClipCursor(&clipRect)) TRACELOG(LOG_WARNING, "WIN32: Failed to clip cursor [ERROR: %lu]", GetLastError());
CORE.Input.Mouse.previousPosition = (Vector2){ 0, 0 };
CORE.Input.Mouse.currentPosition = (Vector2){ 0, 0 };
HideCursor();
CORE.Input.Mouse.cursorLocked = true;
}
}
// Swap back buffer with front buffer (screen drawing)
void SwapScreenBuffer(void)
{
if (!platform.hdc) abort();
#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
// Update framebuffer
rlCopyFramebuffer(0, 0, CORE.Window.render.width, CORE.Window.render.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, platform.pixels);
// Force redraw
InvalidateRect(platform.hwnd, NULL, FALSE);
UpdateWindow(platform.hwnd);
#else
if (!SwapBuffers(platform.hdc)) TRACELOG(LOG_ERROR, "WIN32: Failed to swap buffers [ERROR: %lu]", GetLastError());
if (!ValidateRect(platform.hwnd, NULL)) TRACELOG(LOG_ERROR, "WIN32: Failed to validate screen rect [ERROR: %lu]", GetLastError());
#endif
}
//----------------------------------------------------------------------------------
// Module Functions Definition: Misc
//----------------------------------------------------------------------------------
// Get elapsed time measure in seconds
double GetTime(void)
{
LARGE_INTEGER now = 0;
QueryPerformanceCounter(&now);
return (double)(now.QuadPart - CORE.Time.base)/(double)platform.timerFrequency.QuadPart;
}
// Open URL with default system browser (if available)
// NOTE: This function is only safe to use if you control the URL given
// A user could craft a malicious string performing another action
// Only call this function yourself not with user input or make sure to check the string yourself
// REF: https://github.com/raysan5/raylib/issues/686
void OpenURL(const char *url)
{
// Security check to (partially) avoid malicious code on target platform
if (strchr(url, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'] character");
else
{
char *cmd = (char *)RL_CALLOC(strlen(url) + 32, sizeof(char));
sprintf(cmd, "explorer \"%s\"", url);
int result = system(cmd);
if (result == -1) TRACELOG(LOG_WARNING, "OpenURL() child process could not be created");
RL_FREE(cmd);
}
}
//----------------------------------------------------------------------------------
// Module Functions Definition: Inputs
//----------------------------------------------------------------------------------
// Set internal gamepad mappings
int SetGamepadMappings(const char *mappings)
{
TRACELOG(LOG_WARNING, "SetGamepadMappings not implemented");
return -1;
}
// Set gamepad vibration
void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration)
{
TRACELOG(LOG_WARNING, "SetGamepadVibration not implemented");
}
// Set mouse position XY
void SetMousePosition(int x, int y)
{
if (!CORE.Input.Mouse.cursorLocked)
{
CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y };
CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
TRACELOG(LOG_WARNING, "SetMousePosition not implemented");
}
else TRACELOG(LOG_WARNING, "INPUT: MOUSE: Cursor not enabled");
}
// Set mouse cursor
void SetMouseCursor(int cursor)
{
LPCWSTR cursorName = GetCursorName(cursor);
HCURSOR hcursor = LoadCursorW(NULL, cursorName);
if (!hcursor) TRACELOG(LOG_ERROR, "WIN32: Failed to load requested cursor [ERROR: %lu]", GetLastError());
SetCursor(hcursor);
CORE.Input.Mouse.cursorHidden = false;
}
// Get physical key name
const char *GetKeyName(int key)
{
TRACELOG(LOG_WARNING, "GetKeyName not implemented");
return NULL;
}
// Register all input events
void PollInputEvents(void)
{
// Reset keys/chars pressed registered
CORE.Input.Keyboard.keyPressedQueueCount = 0;
CORE.Input.Keyboard.charPressedQueueCount = 0;
// Reset key repeats
for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0;
// Reset last gamepad button/axis registered state
CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN
//CORE.Input.Gamepad.axisCount = 0;
// Register previous touch states
for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i];
// Reset touch positions
// TODO: It resets on target platform the mouse position and not filled again until a move-event,
// so, if mouse is not moved it returns a (0, 0) position... this behaviour should be reviewed!
//for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 };
memcpy(CORE.Input.Keyboard.previousKeyState, CORE.Input.Keyboard.currentKeyState, sizeof(CORE.Input.Keyboard.previousKeyState));
memset(CORE.Input.Keyboard.keyRepeatInFrame, 0, sizeof(CORE.Input.Keyboard.keyRepeatInFrame));
// Register previous mouse wheel state
CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove;
CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f };
// Register previous mouse position
CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
// Process windows messages
MSG msg = { 0 };
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
//----------------------------------------------------------------------------------
// Module Internal Functions Definition
//----------------------------------------------------------------------------------
// Initialize modern OpenGL context
// NOTE: We need to create a dummy context first to query required extensions
HGLRC InitOpenGL(HWND hwnd, HDC hdc)
{
// First, create a dummy context to get WGL extensions
PIXELFORMATDESCRIPTOR pixelFormatDesc = {
.nSize = sizeof(PIXELFORMATDESCRIPTOR),
.nVersion = 1,
.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
.iPixelType = PFD_TYPE_RGBA,
.cColorBits = 32,
.cAlphaBits = 8,
.cDepthBits = 24,
.iLayerType = PFD_MAIN_PLANE
};
int pixelFormat = ChoosePixelFormat(hdc, &pixelFormatDesc);
SetPixelFormat(hdc, pixelFormat, &pixelFormatDesc);
//int pixelFormat = ChoosePixelFormat(platform.hdc, &pixelFormatDesc);
//if (!pixelFormat) { TRACELOG(LOG_ERROR, "%s failed, error=%lu", "ChoosePixelFormat", GetLastError()); return -1; }
//if (!SetPixelFormat(platform.hdc, pixelFormat, &pixelFormatDesc)) { TRACELOG(LOG_ERROR, "%s failed, error=%lu", "SetPixelFormat", GetLastError()); return -1; }
HGLRC tempContext = wglCreateContext(hdc);
//if (!tempContext) { TRACELOG(LOG_ERROR, "%s failed, error=%lu", "wglCreateContext", GetLastError()); return -1; }
BOOL result = wglMakeCurrent(hdc, tempContext);
//if (!result) { TRACELOG(LOG_ERROR, "%s failed, error=%lu", "wglMakeCurrent", GetLastError()); return -1; }
// Load WGL extension entry points
wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress("wglGetExtensionsStringARB");
// Setup modern pixel format if extension is available
if (wglChoosePixelFormatARB)
{
int pixelFormatAttribs[] = {
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
WGL_COLOR_BITS_ARB, 32,
//WGL_RED_BITS_ARB, 8,
//WGL_GREEN_BITS_ARB, 8,
//WGL_BLUE_BITS_ARB, 8,
//WGL_ALPHA_BITS_ARB, 8,
WGL_DEPTH_BITS_ARB, 24,
WGL_STENCIL_BITS_ARB, 8,
0 // Terminator
};
int format = 0;
UINT numFormats = 0;
if (wglChoosePixelFormatARB(hdc, pixelFormatAttribs, NULL, 1, &format, &numFormats) && (numFormats > 0))
{
PIXELFORMATDESCRIPTOR newPixelFormatDescriptor = { 0 };
DescribePixelFormat(hdc, format, sizeof(newPixelFormatDescriptor), &newPixelFormatDescriptor);
SetPixelFormat(hdc, format, &newPixelFormatDescriptor);
}
}
// Create real modern OpenGL context (3.3 core)
HGLRC realContext = NULL;
if (wglCreateContextAttribsARB)
{
int glContextVersionMajor = 1;
int glContextVersionMinor = 1;
int glContextProfile = WGL_CONTEXT_CORE_PROFILE_BIT_ARB;
if (rlGetVersion() == RL_OPENGL_21) // Request OpenGL 2.1 context
{
glContextVersionMajor = 2;
glContextVersionMinor = 1;
}
else if (rlGetVersion() == RL_OPENGL_33) // Request OpenGL 3.3 context
{
glContextVersionMajor = 3;
glContextVersionMinor = 3;
}
else if (rlGetVersion() == RL_OPENGL_43) // Request OpenGL 4.3 context
{
glContextVersionMajor = 4;
glContextVersionMinor = 3;
}
else if (rlGetVersion() == RL_OPENGL_ES_20) // Request OpenGL ES 2.0 context
{
if (IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es_profile") ||
IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es2_profile"))
{
glContextVersionMajor = 2;
glContextVersionMinor = 0;
glContextProfile = WGL_CONTEXT_ES_PROFILE_BIT_EXT;
}
else TRACELOG(LOG_WARNING, "GL: OpenGL ES context not supported by GPU");
}
else if (rlGetVersion() == RL_OPENGL_ES_30) // Request OpenGL ES 3.0 context
{
if (IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es_profile") ||
IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es2_profile"))
{
glContextVersionMajor = 3;
glContextVersionMinor = 0;
glContextProfile = WGL_CONTEXT_ES_PROFILE_BIT_EXT;
}
else TRACELOG(LOG_WARNING, "GL: OpenGL ES context not supported by GPU");
}
int contextAttribs[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, glContextVersionMajor,
WGL_CONTEXT_MINOR_VERSION_ARB, glContextVersionMinor,
WGL_CONTEXT_PROFILE_MASK_ARB, glContextProfile, // WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, WGL_CONTEXT_ES_PROFILE_BIT_EXT (if supported)
//WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB [glDebugMessageCallback()]
0 // Terminator
};
// NOTE: We are not sharing context resources so, second parameters is NULL
realContext = wglCreateContextAttribsARB(hdc, NULL, contextAttribs);
// Check for error context creation errors
// ERROR_INVALID_VERSION_ARB (0x2095)
// ERROR_INVALID_PROFILE_ARB (0x2096)
if (realContext == NULL) TRACELOG(LOG_ERROR, "GL: Error creating requested context: %lu", GetLastError());
}
// Cleanup dummy temp context
wglMakeCurrent(NULL, NULL);
wglDeleteContext(tempContext);
// Activate real context
if (realContext) wglMakeCurrent(hdc, realContext);
// Once we got a real modern OpenGL context,
// we can load required extensions (function pointers)
rlLoadExtensions(WglGetProcAddress);
return realContext;
}
// Initialize platform: graphics, inputs and more
int InitPlatform(void)
{
int result = 0;
platform.appScreenWidth = CORE.Window.screen.width;
platform.appScreenHeight = CORE.Window.screen.height;
platform.desiredFlags = SanitizeFlags(0 /*SANITIZE_FLAGS_FIRST*/, CORE.Window.flags);
// NOTE: From this point CORE.Window.flags should always reflect the actual state of the window
CORE.Window.flags = FLAG_WINDOW_HIDDEN | (platform.desiredFlags & FLAG_MASK_NO_UPDATE);
/*
// TODO: Review SetProcessDpiAwarenessContext()
// NOTE: SetProcessDpiAwarenessContext() requires Windows 10, version 1703 and shcore.lib linkage
if (IsWindows10Version1703OrGreaterWin32())
{
TRACELOG(LOG_INFO, "DpiAware: >=Win10Creators");
if (!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
TRACELOG(LOG_ERROR, "%s failed, error %u", "SetProcessDpiAwarenessContext", GetLastError());
}
else
{
TRACELOG(LOG_INFO, "DpiAware: <Win10Creators");
HRESULT hr = SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
if (hr < 0) TRACELOG(LOG_ERROR, "%s failed, hresult=0x%lx", "SetProcessDpiAwareness", (DWORD)hr);
}
*/
HINSTANCE hInstance = GetModuleHandleW(0);
// Define window class
WNDCLASSEXW windowClass = {
.cbSize = sizeof(WNDCLASSEXW),
.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
.lpfnWndProc = WndProc, // Custom procedure assigned
.cbWndExtra = sizeof(LONG_PTR), // extra space for the Tuple object ptr
.hInstance = hInstance,
.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW), // TODO: Audit if we want to set this since we're implementing WM_SETCURSOR
.lpszClassName = CLASS_NAME // Class name: L"raylibWindow"
};
// Load user-provided icon if available
// NOTE: raylib resource file defaults to GLFW_ICON id, so looking for same identifier
windowClass.hIcon = LoadImageW(hInstance, L"GLFW_ICON", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
if (!windowClass.hIcon) windowClass.hIcon = LoadImageW(NULL, (LPCWSTR)IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
// Register window class
result = (int)RegisterClassExW(&windowClass);
if (result == 0) TRACELOG(LOG_ERROR, "WIN32: Failed to register window class [ERROR: %lu]", GetLastError());
// Get primary monitor info
POINT primaryTopLeft = { 0 };
HMONITOR monitor = MonitorFromPoint(primaryTopLeft, MONITOR_DEFAULTTOPRIMARY);
if (monitor != NULL)
{
MONITORINFO info = { 0 };
info.cbSize = sizeof(info);
result = (int)GetMonitorInfoW(monitor, &info);
if (result == 0) TRACELOG(LOG_WARNING, "WIN32: DISPLAY: Failed to get monitor info [ERROR: %u]", GetLastError());
else
{
CORE.Window.display.width = info.rcMonitor.right - info.rcMonitor.left;
CORE.Window.display.height = info.rcMonitor.bottom - info.rcMonitor.top;
}
}
else TRACELOG(LOG_WARNING, "WIN32: DISPLAY: Failed to get primary monitor from point [ERROR: %u]", GetLastError());
// Adjust the window rectangle so the *client area* matches desired size
// NOTE: Window width/height includes borders and title-bar
DWORD style = WS_OVERLAPPEDWINDOW;
RECT rect = { 0, 0, platform.appScreenWidth, platform.appScreenHeight };
AdjustWindowRect(&rect, style, FALSE);
//AdjustWindowRectEx(&rect, WS_OVERLAPPEDWINDOW, FALSE, WINDOW_STYLE_EX);
//AdjustWindowRectExForDpi(&rect, style, FALSE, WINDOW_STYLE_EX, dpi);
int windowWidth = rect.right - rect.left;
int windowHeight = rect.bottom - rect.top;
// Create window
// NOTE: Title string needs to be converted to WCHAR
WCHAR *titleWide = NULL;
A_TO_W_ALLOCA(titleWide, CORE.Window.title);
// Create window and get handle
platform.hwnd = CreateWindowExW(
WINDOW_STYLE_EX,
CLASS_NAME,
titleWide,
MakeWindowStyle(CORE.Window.flags), // WS_OVERLAPPEDWINDOW | WS_VISIBLE
CW_USEDEFAULT, CW_USEDEFAULT,
windowWidth, windowHeight, // TODO: Window size [width, height], needs to be updated?
NULL, NULL,
GetModuleHandleW(NULL), NULL);
if (!platform.hwnd)
{
TRACELOG(LOG_ERROR, "WIN32: WINDOW: Failed to create window [ERROR: %lu]", GetLastError());
return -1;
}
// Get handle to device drawing context
// NOTE: Windows GDI object that represents a drawing surface
platform.hdc = GetDC(platform.hwnd);
if (rlGetVersion() == RL_OPENGL_11_SOFTWARE) // Using software renderer
{
//ShowWindow(platform.hwnd, SW_SHOWDEFAULT); //SW_SHOWNORMAL
// Initialize software framebuffer
BITMAPINFO bmi = { 0 };
ZeroMemory(&bmi, sizeof(bmi));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = platform.appScreenWidth;
bmi.bmiHeader.biHeight = -(int)(platform.appScreenHeight); // Top-down bitmap
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32; // 32-bit BGRA
bmi.bmiHeader.biCompression = BI_RGB;
platform.hdcmem = CreateCompatibleDC(platform.hdc);
platform.hbitmap = CreateDIBSection(
platform.hdcmem, &bmi, DIB_RGB_COLORS,
(void **)&platform.pixels, NULL, 0);
SelectObject(platform.hdcmem, platform.hbitmap);
//ReleaseDC(platform.hwnd, platform.hdc); // Required?
}
else
{
// Init hardware-accelerated OpenGL modern context
platform.glContext = InitOpenGL(platform.hwnd, platform.hdc);
}
CORE.Window.ready = true;
// Update flags (in case of deferred state change required)
UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight);
CORE.Window.render.width = CORE.Window.screen.width;
CORE.Window.render.height = CORE.Window.screen.height;
CORE.Window.currentFbo.width = CORE.Window.render.width;
CORE.Window.currentFbo.height = CORE.Window.render.height;
TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully");
TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height);
TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height);
TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height);
TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y);
if (rlGetVersion() == RL_OPENGL_11_SOFTWARE) // Using software renderer
{
TRACELOG(LOG_INFO, "GL: OpenGL device information:");
TRACELOG(LOG_INFO, " > Vendor: %s", "raylib");
TRACELOG(LOG_INFO, " > Renderer: %s", "rlsw - OpenGL 1.1 Software Renderer");
TRACELOG(LOG_INFO, " > Version: %s", "1.0");
TRACELOG(LOG_INFO, " > GLSL: %s", "NOT SUPPORTED");
}
// Initialize timming system
//----------------------------------------------------------------------------
LARGE_INTEGER time = { 0 };
QueryPerformanceCounter(&time);
QueryPerformanceFrequency(&platform.timerFrequency);
CORE.Time.base = time.QuadPart;
InitTimer();
//----------------------------------------------------------------------------
// Initialize storage system
//----------------------------------------------------------------------------
CORE.Storage.basePath = GetWorkingDirectory();
//----------------------------------------------------------------------------
TRACELOG(LOG_INFO, "PLATFORM: DESKTOP: WIN32: Initialized successfully");
return 0;
}
// Close platform
void ClosePlatform(void)
{
if (platform.hwnd)
{
int result = DestroyWindow(platform.hwnd);
if (result == 0) TRACELOG(LOG_WARNING, "WIN32: WINDOW: Failed on window destroy [ERROR: %u]", GetLastError());
platform.hwnd = NULL;
}
}
// Window procedure, message processing callback
// NOTE: All window event messages are processed here
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
LRESULT result = 0;
// Sanity check
DWORD mask = STYLE_MASK_ALL;
if (platform.hwnd == hwnd)
{
if (msg == WM_WINDOWPOSCHANGING) mask &= ~(WS_MINIMIZE | WS_MAXIMIZE);
CheckFlags("WndProc", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), mask);
}
FlagsOp flagsOp = { 0 };
FlagsOp *deferredFlags = &flagsOp;
// Message processing
//------------------------------------------------------------------------------------
switch (msg)
{
case WM_CREATE:
{
// WARNING: Not recommended to do OpenGL intialization at this point
} break;
//case WM_ACTIVATE
case WM_DESTROY:
{
// Clean up for window destruction
if (rlGetVersion() == RL_OPENGL_11_SOFTWARE) // Using software renderer
{
if (platform.hdcmem)
{
DeleteDC(platform.hdcmem);
platform.hdcmem = NULL;
}
if (platform.hbitmap)
{
DeleteObject(platform.hbitmap); // Clears platform.pixels data
platform.hbitmap = NULL;
platform.pixels = NULL; // NOTE: Pointer invalid after DeleteObject()
}
}
else // OpenGL hardware renderer
{
wglMakeCurrent(platform.hdc, NULL);
if (platform.glContext)
{
if (!wglDeleteContext(platform.glContext)) abort();
platform.glContext = NULL;
}
}
if (platform.hdc)
{
if (!ReleaseDC(hwnd, platform.hdc)) abort();
platform.hdc = NULL;
}
PostQuitMessage(0);
} break;
case WM_CLOSE: CORE.Window.shouldClose = true; break; // Window close button [x], ALT+F4
//case WM_QUIT: // Application closing, not related to window
case WM_KILLFOCUS:
{
memset(CORE.Input.Keyboard.previousKeyState, 0, sizeof(CORE.Input.Keyboard.previousKeyState));
memset(CORE.Input.Keyboard.currentKeyState, 0, sizeof(CORE.Input.Keyboard.currentKeyState));
} break;
case WM_SIZING:
{
if (CORE.Window.flags & FLAG_WINDOW_RESIZABLE)
{
// TODO: Enforce min/max size
}
else TRACELOG(LOG_WARNING, "WIN32: WINDOW: Trying to resize a non-resizable window");
result = TRUE;
} break;
case WM_STYLECHANGING:
{
if (wparam == GWL_STYLE)
{
STYLESTRUCT *ss = (STYLESTRUCT *)lparam;
GetStyleChangeFlagOps(CORE.Window.flags, ss, deferredFlags);
UINT dpi = GetDpiForWindow(hwnd);
// Get client size (framebuffer inside the window)
RECT rect = { 0 };
GetClientRect(hwnd, &rect);
SIZE clientSize = { rect.right, rect.bottom };
SIZE oldSize = CalcWindowSize(dpi, clientSize, ss->styleOld);
SIZE newSize = CalcWindowSize(dpi, clientSize, ss->styleNew);
if (oldSize.cx != newSize.cx || oldSize.cy != newSize.cy)
{
TRACELOG(LOG_INFO, "WIN32: WINDOW: Resize from style change [%dx%d] to [%dx%d]", oldSize.cx, oldSize.cy, newSize.cx, newSize.cy);
if (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED)
{
// looks like windows will automatically "unminimize" a window
// if a style changes modifies it's size
TRACELOG(LOG_INFO, "WIN32: WINDOW: Style change modifed window size, removing maximized flag");
deferredFlags->clear |= FLAG_WINDOW_MAXIMIZED;
}
}
}
} break;
case WM_WINDOWPOSCHANGING:
{
WINDOWPOS *pos = (WINDOWPOS *)lparam;
if (pos->flags & SWP_SHOWWINDOW) deferredFlags->clear |= FLAG_WINDOW_HIDDEN;
else if (pos->flags & SWP_HIDEWINDOW) deferredFlags->set |= FLAG_WINDOW_HIDDEN;
Mized mized = MIZED_NONE;
bool isIconic = IsIconic(hwnd);
bool styleMinimized = !!(WS_MINIMIZE & GetWindowLongPtrW(hwnd, GWL_STYLE));
if (isIconic != styleMinimized) TRACELOG(LOG_WARNING, "WIN32: IsIconic state different from WS_MINIMIZED state");
if (isIconic) mized = MIZED_MIN;
else
{
WINDOWPLACEMENT placement;
placement.length = sizeof(placement);
if (!GetWindowPlacement(hwnd, &placement)) TRACELOG(LOG_ERROR, "WIN32: WINDOW: FAiled to get monitor placement [ERROR: %lu]", GetLastError());
if (placement.showCmd == SW_SHOWMAXIMIZED) mized = MIZED_MAX;
}
switch (mized)
{
case MIZED_NONE:
{
deferredFlags->clear |= (FLAG_WINDOW_MINIMIZED | FLAG_WINDOW_MAXIMIZED);
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
MONITORINFO info;
info.cbSize = sizeof(info);
if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "WIN32: MONITOR: Failed to get monitor info [ERROR: %lu]", GetLastError());
if ((pos->x == info.rcMonitor.left) &&
(pos->y == info.rcMonitor.top) &&
(pos->cx == (info.rcMonitor.right - info.rcMonitor.left)) &&
(pos->cy == (info.rcMonitor.bottom - info.rcMonitor.top))) deferredFlags->set |= FLAG_BORDERLESS_WINDOWED_MODE;
else deferredFlags->clear |= FLAG_BORDERLESS_WINDOWED_MODE;
} break;
case MIZED_MIN:
{
// !!! NOTE !!! Do not update the maximized/borderless
// flags because when hwnd is minimized it temporarily overrides
// the maximized state/flag which gets restored on SW_RESTORE
deferredFlags->set |= FLAG_WINDOW_MINIMIZED;
} break;
case MIZED_MAX:
{
deferredFlags->clear |= FLAG_WINDOW_MINIMIZED;
deferredFlags->set |= FLAG_WINDOW_MAXIMIZED;
} break;
default: break;
}
} break;
case WM_SIZE:
{
// WARNING: Don't trust the docs, they say you won't get this message if you don't call DefWindowProc
// in response to WM_WINDOWPOSCHANGED but looks like when a window is created you'll get this
// message without getting WM_WINDOWPOSCHANGED
HandleWindowResize(hwnd, &platform.appScreenWidth, &platform.appScreenHeight);
} break;
//case WM_MOVE
case WM_WINDOWPOSCHANGED:
{
WINDOWPOS *pos = (WINDOWPOS*)lparam;
if (!(pos->flags & SWP_NOSIZE)) HandleWindowResize(hwnd, &platform.appScreenWidth, &platform.appScreenHeight);
} break;
case WM_GETDPISCALEDSIZE:
{
SIZE *inoutSize = (SIZE *)lparam;
UINT newDpi = (UINT)wparam; // TODO: WARNING: Converting from WPARAM = UINT_PTR
// for any of these other cases, we might want to post a window
// resize event after the dpi changes?
if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) return TRUE;
if (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) return TRUE;
if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) return TRUE;
float dpiScale = ((float)newDpi)/96.0f;
bool dpiScaling = CORE.Window.flags & FLAG_WINDOW_HIGHDPI;
// Get size in pixels from points
SIZE desired = {
.cx = dpiScaling? (int)((float)platform.appScreenWidth*dpiScale) : platform.appScreenWidth,
.cy = dpiScaling? (int)((float)platform.appScreenHeight*dpiScale) : platform.appScreenHeight
};
inoutSize->cx = desired.cx;
inoutSize->cy = desired.cy;
result = TRUE;
} break;
case WM_DPICHANGED:
{
// Get current dpi scale factor
float scalex = HIWORD(wParam)/96.0f;
float scaley = LOWORD(wParam)/96.0f;
RECT *suggestedRect = (RECT *)lparam;
// Never set the window size to anything other than the suggested rect here
// Doing so can cause a window to stutter between monitors when transitioning between them
int result = (int)SetWindowPos(hwnd, NULL,
suggestedRect->left, suggestedRect->top,
suggestedRect->right - suggestedRect->left,
suggestedRect->bottom - suggestedRect->top,
SWP_NOZORDER | SWP_NOACTIVATE);
if (result == 0) TRACELOG(LOG_ERROR, "Failed to set window position [ERROR: %lu]", GetLastError());
// TODO: Update screen data, render size, screen scaling, viewport...
} break;
case WM_SETCURSOR:
{
// Called when mouse moves, enters/leaves window...
if (LOWORD(lparam) == HTCLIENT)
{
SetCursor(CORE.Input.Mouse.cursorHidden? NULL : LoadCursorW(NULL, (LPCWSTR)IDC_ARROW));
return 0;
}
result = DefWindowProc(hwnd, msg, wparam, lparam);
} break;
case WM_PAINT:
{
if (rlGetVersion() == RL_OPENGL_11_SOFTWARE) // Using software renderer
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
// Blit from memory DC to window DC
BitBlt(hdc, 0, 0, platform.appScreenWidth, platform.appScreenHeight, platform.hdcmem, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
}
}
case WM_INPUT:
{
//HandleRawInput(lparam);
} break;
case WM_MOUSEMOVE:
{
if (!CORE.Input.Mouse.cursorLocked)
{
CORE.Input.Mouse.currentPosition.x = (float)GET_X_LPARAM(lparam);
CORE.Input.Mouse.currentPosition.y = (float)GET_Y_LPARAM(lparam);
CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition;
}
} break;
case WM_KEYDOWN: HandleKey(wparam, lparam, 1); break;
case WM_KEYUP: HandleKey(wparam, lparam, 0); break;
case WM_LBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_LEFT, 1); break;
case WM_LBUTTONUP : HandleMouseButton(MOUSE_BUTTON_LEFT, 0); break;
case WM_RBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_RIGHT, 1); break;
case WM_RBUTTONUP : HandleMouseButton(MOUSE_BUTTON_RIGHT, 0); break;
case WM_MBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_MIDDLE, 1); break;
case WM_MBUTTONUP : HandleMouseButton(MOUSE_BUTTON_MIDDLE, 0); break;
case WM_XBUTTONDOWN:
{
switch (HIWORD(wparam))
{
case XBUTTON1: HandleMouseButton(MOUSE_BUTTON_SIDE, 1); break;
case XBUTTON2: HandleMouseButton(MOUSE_BUTTON_EXTRA, 1); break;
default: TRACELOG(LOG_WARNING, "TODO: handle ex mouse button DOWN wparam=%u", HIWORD(wparam)); break;
}
} break;
case WM_XBUTTONUP:
{
switch (HIWORD(wparam))
{
case XBUTTON1: HandleMouseButton(MOUSE_BUTTON_SIDE, 0); break;
case XBUTTON2: HandleMouseButton(MOUSE_BUTTON_EXTRA, 0); break;
default: TRACELOG(LOG_WARNING, "TODO: handle ex mouse button UP wparam=%u", HIWORD(wparam)); break;
}
} break;
case WM_MOUSEWHEEL: CORE.Input.Mouse.currentWheelMove.y = ((float)GET_WHEEL_DELTA_WPARAM(wparam))/WHEEL_DELTA; break;
case WM_MOUSEHWHEEL: CORE.Input.Mouse.currentWheelMove.x = ((float)GET_WHEEL_DELTA_WPARAM(wparam))/WHEEL_DELTA; break;
case WM_APP_UPDATE_WINDOW_SIZE:
{
//UpdateWindowSize(UPDATE_WINDOW_NORMAL, hwnd, platform.appScreenWidth, platform.appScreenHeight, CORE.Window.flags);
} break;
default: result = DefWindowProcW(hwnd, msg, wparam, lparam); // Message passed directly for execution (default behaviour)
}
//------------------------------------------------------------------------------------
// Sanity check for flags
if (platform.hwnd == hwnd) CheckFlags("After WndProc", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), mask);
// Operations to execute after the above check
if (flagsOp.set & flagsOp.clear) TRACELOG(LOG_WARNING, "WIN32: FLAGS: Flags 0x%x were both set and cleared", flagsOp.set & flagsOp.clear);
DWORD save = CORE.Window.flags;
CORE.Window.flags |= flagsOp.set;
CORE.Window.flags &= ~flagsOp.clear;
if (save != CORE.Window.flags) TRACELOG(LOG_DEBUG, "WIN32: FLAGS: Current deferred flags: 0x%x > 0x%x (diff 0x%x)", save, CORE.Window.flags, save ^ CORE.Window.flags);
return result;
}
// Handle keyboard input event
static void HandleKey(WPARAM wparam, LPARAM lparam, char state)
{
KeyboardKey key = GetKeyFromWparam(wparam);
// TODO: Use scancode?
//BYTE scancode = lparam >> 16;
//TRACELOG(LOG_INFO, "KEY key=%d vk=%lu scan=%u = %u", key, wparam, scancode, state);
if (key != KEY_NULL)
{
CORE.Input.Keyboard.currentKeyState[key] = state;
if ((key == KEY_ESCAPE) && (state == 1)) CORE.Window.shouldClose = true;
}
else TRACELOG(LOG_WARNING, "INPUT: Unknown (or currently unhandled) virtual keycode %d (0x%x)", wparam, wparam);
// TODO: Add key to the queue as well?
}
// Handle mouse button input event
static void HandleMouseButton(int button, char state)
{
// Register current mouse button state
CORE.Input.Mouse.currentButtonState[button] = state;
CORE.Input.Touch.currentTouchState[button] = state;
}
// Handle raw input event
static void HandleRawInput(LPARAM lparam)
{
RAWINPUT input = { 0 };
UINT inputSize = sizeof(input);
UINT size = GetRawInputData((HRAWINPUT)lparam, RID_INPUT, &input, &inputSize, sizeof(RAWINPUTHEADER));
if (size == (UINT)-1) TRACELOG(LOG_ERROR, "WIN32: Failed to get raw input data [ERROR: %lu]", GetLastError());
if (input.header.dwType != RIM_TYPEMOUSE) TRACELOG(LOG_ERROR, "WIN32: Unexpected WM_INPUT type %lu", input.header.dwType);
if (input.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) TRACELOG(LOG_ERROR, "TODO: handle absolute mouse inputs!");
if (input.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) TRACELOG(LOG_ERROR, "TODO: handle virtual desktop mouse inputs!");
// Trick to keep the mouse position at 0,0 and instead move
// the previous position so we can still get a proper mouse delta
//CORE.Input.Mouse.previousPosition.x -= input.data.mouse.lLastX;
//CORE.Input.Mouse.previousPosition.y -= input.data.mouse.lLastY;
//if (CORE.Input.Mouse.currentPosition.x != 0) abort();
//if (CORE.Input.Mouse.currentPosition.y != 0) abort();
}
// Handle window resizing event
static void HandleWindowResize(HWND hwnd, int *width, int *height)
{
if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) return;
// Get client size (framebuffer inside the window)
RECT rect = { 0 };
GetClientRect(hwnd, &rect);
SIZE clientSize = { rect.right, rect.bottom };
// TODO: Update framebuffer on resize
CORE.Window.currentFbo.width = (int)clientSize.cx;
CORE.Window.currentFbo.height = (int)clientSize.cy;
//SetupViewport(0, 0, clientSize.cx, clientSize.cy);
SetupViewport(clientSize.cx, clientSize.cy);
CORE.Window.resizedLastFrame = true;
float dpiScale = ((float)GetDpiForWindow(hwnd))/96.0f;
bool highdpi = !!(CORE.Window.flags & FLAG_WINDOW_HIGHDPI);
unsigned int screenWidth = highdpi? (unsigned int)(((float)clientSize.cx)/dpiScale) : clientSize.cx;
unsigned int screenHeight = highdpi? (unsigned int)(((float)clientSize.cy)/dpiScale) : clientSize.cy;
CORE.Window.screen.width = screenWidth;
CORE.Window.screen.height = screenHeight;
if (AdoptWindowResize(CORE.Window.flags))
{
TRACELOG(LOG_DEBUG, "WIN32: WINDOW: Updating app size to [%ix%i] from window resize", screenWidth, screenHeight);
*width = screenWidth;
*height = screenHeight;
}
CORE.Window.screenScale = MatrixScale( (float)CORE.Window.render.width/CORE.Window.screen.width,
(float)CORE.Window.render.height/CORE.Window.screen.height, 1.0f);
}
// Update window style
static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags)
{
DWORD current = STYLE_MASK_WRITABLE & MakeWindowStyle(CORE.Window.flags);
DWORD desired = STYLE_MASK_WRITABLE & MakeWindowStyle(desiredFlags);
if (current != desired)
{
SetLastError(0);
DWORD previous = STYLE_MASK_WRITABLE & SetWindowLongPtrW(hwnd, GWL_STYLE, desired);
if (previous != current)
{
TRACELOG(LOG_ERROR, "WIN32: WINDOW: SetWindowLongPtr() returned writable flags 0x%x but expected 0x%x (diff=0x%x, error=%lu)",
previous, current, previous ^ current, GetLastError());
}
CheckFlags("UpdateWindowStyle", hwnd, desiredFlags, desired, STYLE_MASK_WRITABLE);
}
// Minimized takes precedence over maximized
Mized currentMized = MIZED_NONE;
Mized desiredMized = MIZED_NONE;
if (CORE.Window.flags & WS_MINIMIZE) currentMized = MIZED_MIN;
else if (CORE.Window.flags & WS_MAXIMIZE) currentMized = MIZED_MAX;
if (desiredFlags & WS_MINIMIZE) currentMized = MIZED_MIN;
else if (desiredFlags & WS_MAXIMIZE) currentMized = MIZED_MAX;
if (currentMized != desiredMized)
{
switch (desiredMized)
{
case MIZED_NONE: ShowWindow(hwnd, SW_RESTORE); break;
case MIZED_MIN: ShowWindow(hwnd, SW_MINIMIZE); break;
case MIZED_MAX: ShowWindow(hwnd, SW_MAXIMIZE); break;
}
}
}
// Sanitize flags
static unsigned SanitizeFlags(int mode, unsigned flags)
{
if ((flags & FLAG_WINDOW_MAXIMIZED) && (flags & FLAG_BORDERLESS_WINDOWED_MODE))
{
TRACELOG(LOG_WARNING, "WIN32: WINDOW: Borderless windows mode overriding maximized window flag");
flags &= ~FLAG_WINDOW_MAXIMIZED;
}
if (mode == 1)
{
if ((flags & FLAG_MSAA_4X_HINT) && (!(CORE.Window.flags & FLAG_MSAA_4X_HINT)))
{
TRACELOG(LOG_WARNING, "WIN32: WINDOW: MSAA can only be configured before window initialization");
flags &= ~FLAG_MSAA_4X_HINT;
}
}
return flags;
}
// All window state changes from raylib flags go through this function. It performs
// whatever operations are needed to update the window state to match the desired flags
// In most cases this function should not update CORE.Window.flags directly, instead,
// the window itself should update CORE.Window.flags in response to actual state changes
// This means that CORE.Window.flags should always represent the actual state of the
// window. This function will continue to perform these update operations so long as
// the state continues to change
//
// This design takes care of many odd corner cases. For example, if you want to restore
// a window that was previously maximized AND minimized and you want to remove both these
// flags, you actually need to call ShowWindow with SW_RESTORE twice. Another example is
// if you have a maximized window, if the undecorated flag is modified then we'd need to
// update the window style, but updating the style would mean the window size would change
// causing the window to lose its Maximized state which would mean we'd need to update the
// window size and then update the window style a second time to restore that maximized
// state. This implementation is able to handle any/all of these special situations with a
// retry loop that continues until we either reach the desired state or the state stops changing
static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height)
{
// Flags that just apply immediately without needing any operations
CORE.Window.flags |= (desiredFlags & FLAG_MASK_NO_UPDATE);
int vsync = (CORE.Window.flags & FLAG_VSYNC_HINT)? 1 : 0;
if (wglSwapIntervalEXT)
{
(*wglSwapIntervalEXT)(vsync);
if (vsync) CORE.Window.flags |= FLAG_VSYNC_HINT;
else CORE.Window.flags &= ~FLAG_VSYNC_HINT;
}
// TODO: Review all this code...
DWORD previousStyle = 0;
for (unsigned attempt = 1; ; attempt++)
{
CheckFlags("UpdateFlags", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), STYLE_MASK_ALL);
bool windowSizeUpdated = false;
if (MakeWindowStyle(CORE.Window.flags) == MakeWindowStyle(desiredFlags))
{
windowSizeUpdated = UpdateWindowSize(1, hwnd, width, height, desiredFlags);
if ((FLAG_MASK_REQUIRED & desiredFlags) == (FLAG_MASK_REQUIRED & CORE.Window.flags)) break;
}
if ((attempt > 1) && (previousStyle == MakeWindowStyle(CORE.Window.flags)) && !windowSizeUpdated)
{
TRACELOG(LOG_ERROR, "WIN32: WINDOW: UpdateFlags() failed after %u attempt(s) wanted 0x%x but is 0x%x (diff=0x%x)",
attempt, desiredFlags, CORE.Window.flags, desiredFlags ^ CORE.Window.flags);
}
previousStyle = MakeWindowStyle(CORE.Window.flags);
UpdateWindowStyle(hwnd, desiredFlags);
}
}
// Check if OpenGL extension is available
static bool IsWglExtensionAvailable(HDC hdc, const char *extension)
{
bool result = false;
if (wglGetExtensionsStringARB != NULL)
{
const char *extList = wglGetExtensionsStringARB(hdc);
if (extList != NULL)
{
// Simple substring search (could use strtok or strstr)
if (strstr(extList, extension) != NULL) result = true;
}
}
return result;
}