From 0ffc8c517f15680392328c9af1be085190177b99 Mon Sep 17 00:00:00 2001 From: Pivok Date: Sat, 17 May 2025 12:32:17 +0200 Subject: [PATCH 001/242] Pbr example fix --- examples/shaders/shaders_basic_pbr.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/examples/shaders/shaders_basic_pbr.c b/examples/shaders/shaders_basic_pbr.c index e595ef9ca..b375c4fa1 100644 --- a/examples/shaders/shaders_basic_pbr.c +++ b/examples/shaders/shaders_basic_pbr.c @@ -125,6 +125,8 @@ int main() SetShaderValue(shader, GetShaderLocation(shader, "ambient"), &ambientIntensity, SHADER_UNIFORM_FLOAT); // Get location for shader parameters that can be modified in real time + int metallicValueLoc = GetShaderLocation(shader, "metallicValue"); + int roughnessValueLoc = GetShaderLocation(shader, "roughnessValue"); int emissiveIntensityLoc = GetShaderLocation(shader, "emissivePower"); int emissiveColorLoc = GetShaderLocation(shader, "emissiveColor"); int textureTilingLoc = GetShaderLocation(shader, "tiling"); @@ -141,7 +143,7 @@ int main() // Setup materials[0].maps default parameters car.materials[0].maps[MATERIAL_MAP_ALBEDO].color = WHITE; - car.materials[0].maps[MATERIAL_MAP_METALNESS].value = 0.0f; + car.materials[0].maps[MATERIAL_MAP_METALNESS].value = 1.0f; car.materials[0].maps[MATERIAL_MAP_ROUGHNESS].value = 0.0f; car.materials[0].maps[MATERIAL_MAP_OCCLUSION].value = 1.0f; car.materials[0].maps[MATERIAL_MAP_EMISSION].color = (Color){ 255, 162, 0, 255 }; @@ -163,8 +165,8 @@ int main() floor.materials[0].shader = shader; floor.materials[0].maps[MATERIAL_MAP_ALBEDO].color = WHITE; - floor.materials[0].maps[MATERIAL_MAP_METALNESS].value = 0.0f; - floor.materials[0].maps[MATERIAL_MAP_ROUGHNESS].value = 0.0f; + floor.materials[0].maps[MATERIAL_MAP_METALNESS].value = 0.8f; + floor.materials[0].maps[MATERIAL_MAP_ROUGHNESS].value = 0.1f; floor.materials[0].maps[MATERIAL_MAP_OCCLUSION].value = 1.0f; floor.materials[0].maps[MATERIAL_MAP_EMISSION].color = BLACK; @@ -228,6 +230,10 @@ int main() SetShaderValue(shader, textureTilingLoc, &floorTextureTiling, SHADER_UNIFORM_VEC2); Vector4 floorEmissiveColor = ColorNormalize(floor.materials[0].maps[MATERIAL_MAP_EMISSION].color); SetShaderValue(shader, emissiveColorLoc, &floorEmissiveColor, SHADER_UNIFORM_VEC4); + + // Set floor metallic and roughness values + SetShaderValue(shader, metallicValueLoc, &floor.materials[0].maps[MATERIAL_MAP_METALNESS].value, SHADER_UNIFORM_FLOAT); + SetShaderValue(shader, roughnessValueLoc, &floor.materials[0].maps[MATERIAL_MAP_ROUGHNESS].value, SHADER_UNIFORM_FLOAT); DrawModel(floor, (Vector3){ 0.0f, 0.0f, 0.0f }, 5.0f, WHITE); // Draw floor model @@ -237,6 +243,10 @@ int main() SetShaderValue(shader, emissiveColorLoc, &carEmissiveColor, SHADER_UNIFORM_VEC4); float emissiveIntensity = 0.01f; SetShaderValue(shader, emissiveIntensityLoc, &emissiveIntensity, SHADER_UNIFORM_FLOAT); + + // Set old car metallic and roughness values + SetShaderValue(shader, metallicValueLoc, &car.materials[0].maps[MATERIAL_MAP_METALNESS].value, SHADER_UNIFORM_FLOAT); + SetShaderValue(shader, roughnessValueLoc, &car.materials[0].maps[MATERIAL_MAP_ROUGHNESS].value, SHADER_UNIFORM_FLOAT); DrawModel(car, (Vector3){ 0.0f, 0.0f, 0.0f }, 0.25f, WHITE); // Draw car model From 5da2d1011848d277d7848567dd17650ef341b6f2 Mon Sep 17 00:00:00 2001 From: Padmadev D <128023777+padmadevd@users.noreply.github.com> Date: Sun, 18 May 2025 18:53:28 +0530 Subject: [PATCH 002/242] Update rcore_android.c Bug Fix Update Code to Ignore Hovering Inputs Completely --- src/platforms/rcore_android.c | 95 +++++++++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 20 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index ddf7802ba..7f88b7363 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -246,6 +246,17 @@ static const KeyboardKey mapKeycode[KEYCODE_MAP_SIZE] = { KEY_KP_EQUAL // AKEYCODE_NUMPAD_EQUALS }; +// Store data for both Hover and Touch events +// Used to ignore Hover events which are interpreted as Touch events +static struct { + + int32_t pointCount; // Number of touch points active + int32_t pointId[MAX_TOUCH_POINTS]; // Point identifiers + Vector2 position[MAX_TOUCH_POINTS]; // Touch position on screen + + int32_t hoverPoints[MAX_TOUCH_POINTS]; // Hover Points +}touchRaw; + //---------------------------------------------------------------------------------- // Module Internal Functions Declaration //---------------------------------------------------------------------------------- @@ -801,6 +812,11 @@ int InitPlatform(void) } } + touchRaw.pointCount = 0; + for(int i = 0; i < MAX_TOUCH_POINTS; i++){ + touchRaw.hoverPoints[i] = -1; + } + return 0; } @@ -1269,25 +1285,78 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) } // Register touch points count - CORE.Input.Touch.pointCount = AMotionEvent_getPointerCount(event); + touchRaw.pointCount = AMotionEvent_getPointerCount(event); - for (int i = 0; (i < CORE.Input.Touch.pointCount) && (i < MAX_TOUCH_POINTS); i++) + for (int i = 0; (i < touchRaw.pointCount) && (i < MAX_TOUCH_POINTS); i++) { // Register touch points id - CORE.Input.Touch.pointId[i] = AMotionEvent_getPointerId(event, i); + touchRaw.pointId[i] = AMotionEvent_getPointerId(event, i); // Register touch points position - CORE.Input.Touch.position[i] = (Vector2){ AMotionEvent_getX(event, i), AMotionEvent_getY(event, i) }; + touchRaw.position[i] = (Vector2){ AMotionEvent_getX(event, i), AMotionEvent_getY(event, i) }; // Normalize CORE.Input.Touch.position[i] for CORE.Window.screen.width and CORE.Window.screen.height float widthRatio = (float)(CORE.Window.screen.width + CORE.Window.renderOffset.x)/(float)CORE.Window.display.width; float heightRatio = (float)(CORE.Window.screen.height + CORE.Window.renderOffset.y)/(float)CORE.Window.display.height; - CORE.Input.Touch.position[i].x = CORE.Input.Touch.position[i].x*widthRatio - (float)CORE.Window.renderOffset.x/2; - CORE.Input.Touch.position[i].y = CORE.Input.Touch.position[i].y*heightRatio - (float)CORE.Window.renderOffset.y/2; + touchRaw.position[i].x = touchRaw.position[i].x*widthRatio - (float)CORE.Window.renderOffset.x/2; + touchRaw.position[i].y = touchRaw.position[i].y*heightRatio - (float)CORE.Window.renderOffset.y/2; } int32_t action = AMotionEvent_getAction(event); unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; + int32_t pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + + if(flags == AMOTION_EVENT_ACTION_HOVER_ENTER){ + // The new pointer is hover + // So add it to hoverPoints + for(int i = 0; i < MAX_TOUCH_POINTS; i++){ + if(touchRaw.hoverPoints[i] == -1){ + touchRaw.hoverPoints[i] = touchRaw.pointId[pointerIndex]; + break; + } + } + } + + if (flags == AMOTION_EVENT_ACTION_POINTER_UP || flags == AMOTION_EVENT_ACTION_UP || flags == AMOTION_EVENT_ACTION_HOVER_EXIT) + { + // One of the touchpoints is released, remove it from touch point arrays + if(flags == AMOTION_EVENT_ACTION_HOVER_EXIT){ + // If the touchPoint is hover, remove it from hoverPoints + for(int i = 0; i < MAX_TOUCH_POINTS; i++){ + if(touchRaw.hoverPoints[i] == touchRaw.pointId[pointerIndex]){ + touchRaw.hoverPoints[i] = -1; + break; + } + } + } + for (int i = pointerIndex; (i < touchRaw.pointCount - 1) && (i < MAX_TOUCH_POINTS-1); i++) + { + touchRaw.pointId[i] = touchRaw.pointId[i+1]; + touchRaw.position[i] = touchRaw.position[i+1]; + } + touchRaw.pointCount--; + } + + int pointCount = 0; + for (int i = 0; (i < touchRaw.pointCount) && (i < MAX_TOUCH_POINTS); i++) + { + // If the touchPoint is hover, Ignore it + bool hover = false; + for(int j = 0; j < MAX_TOUCH_POINTS; j++){ + // Check if the touchPoint is in hoverPointers + if(touchRaw.hoverPoints[j] == touchRaw.pointId[i]){ + hover = true; + break; + } + } + if(hover) + continue; + + CORE.Input.Touch.pointId[pointCount] = touchRaw.pointId[i]; + CORE.Input.Touch.position[pointCount] = touchRaw.position[i]; + pointCount++; + } + CORE.Input.Touch.pointCount = pointCount; #if defined(SUPPORT_GESTURES_SYSTEM) GestureEvent gestureEvent = { 0 }; @@ -1312,20 +1381,6 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) ProcessGestureEvent(gestureEvent); #endif - int32_t pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; - - if (flags == AMOTION_EVENT_ACTION_POINTER_UP || flags == AMOTION_EVENT_ACTION_UP) - { - // One of the touchpoints is released, remove it from touch point arrays - for (int i = pointerIndex; (i < CORE.Input.Touch.pointCount - 1) && (i < MAX_TOUCH_POINTS); i++) - { - CORE.Input.Touch.pointId[i] = CORE.Input.Touch.pointId[i+1]; - CORE.Input.Touch.position[i] = CORE.Input.Touch.position[i+1]; - } - - CORE.Input.Touch.pointCount--; - } - // When all touchpoints are tapped and released really quickly, this event is generated if (flags == AMOTION_EVENT_ACTION_CANCEL) CORE.Input.Touch.pointCount = 0; From 21e711b13f6de679bbc9df54ee0c40f6a28dd793 Mon Sep 17 00:00:00 2001 From: Colby Newman Date: Sun, 18 May 2025 19:35:21 -0400 Subject: [PATCH 003/242] Fix typo in mesh animNormals --- src/rmodels.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rmodels.c b/src/rmodels.c index d927f4099..e5af19bf5 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -1433,7 +1433,7 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) else rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.vertices); rlEnableStatePointer(GL_TEXTURE_COORD_ARRAY, mesh.texcoords); - if (mesh.normals) rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.animNormalss); + if (mesh.normals) rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.animNormals); else rlEnableStatePointer(GL_NORMAL_ARRAY, mesh.normals); rlEnableStatePointer(GL_COLOR_ARRAY, mesh.colors); From b6daa48a9cad5d1be62401131cf8390e4331bad5 Mon Sep 17 00:00:00 2001 From: Padmadev D <128023777+padmadevd@users.noreply.github.com> Date: Mon, 19 May 2025 15:09:58 +0530 Subject: [PATCH 004/242] Update rcore_android.c corrected coding conventions. --- src/platforms/rcore_android.c | 59 +++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 7f88b7363..03cb9741c 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -70,6 +70,16 @@ typedef struct { EGLConfig config; // Graphic config } PlatformData; +typedef struct { + // Store data for both Hover and Touch events + // Used to ignore Hover events which are interpreted as Touch events + int32_t pointCount; // Number of touch points active + int32_t pointId[MAX_TOUCH_POINTS]; // Point identifiers + Vector2 position[MAX_TOUCH_POINTS]; // Touch position on screen + + int32_t hoverPoints[MAX_TOUCH_POINTS]; // Hover Points +} TouchRaw; + //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- @@ -246,16 +256,7 @@ static const KeyboardKey mapKeycode[KEYCODE_MAP_SIZE] = { KEY_KP_EQUAL // AKEYCODE_NUMPAD_EQUALS }; -// Store data for both Hover and Touch events -// Used to ignore Hover events which are interpreted as Touch events -static struct { - - int32_t pointCount; // Number of touch points active - int32_t pointId[MAX_TOUCH_POINTS]; // Point identifiers - Vector2 position[MAX_TOUCH_POINTS]; // Touch position on screen - - int32_t hoverPoints[MAX_TOUCH_POINTS]; // Hover Points -}touchRaw; +static TouchRaw touchRaw = { 0 }; //---------------------------------------------------------------------------------- // Module Internal Functions Declaration @@ -812,10 +813,7 @@ int InitPlatform(void) } } - touchRaw.pointCount = 0; - for(int i = 0; i < MAX_TOUCH_POINTS; i++){ - touchRaw.hoverPoints[i] = -1; - } + for (int i = 0; i < MAX_TOUCH_POINTS; i++) touchRaw.hoverPoints[i] = -1; return 0; } @@ -1306,30 +1304,36 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; int32_t pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; - if(flags == AMOTION_EVENT_ACTION_HOVER_ENTER){ + if (flags == AMOTION_EVENT_ACTION_HOVER_ENTER) + { // The new pointer is hover // So add it to hoverPoints - for(int i = 0; i < MAX_TOUCH_POINTS; i++){ - if(touchRaw.hoverPoints[i] == -1){ + for (int i = 0; i < MAX_TOUCH_POINTS; i++) + { + if (touchRaw.hoverPoints[i] == -1) + { touchRaw.hoverPoints[i] = touchRaw.pointId[pointerIndex]; break; } } } - if (flags == AMOTION_EVENT_ACTION_POINTER_UP || flags == AMOTION_EVENT_ACTION_UP || flags == AMOTION_EVENT_ACTION_HOVER_EXIT) + if ((flags == AMOTION_EVENT_ACTION_POINTER_UP) || (flags == AMOTION_EVENT_ACTION_UP) || (flags == AMOTION_EVENT_ACTION_HOVER_EXIT)) { // One of the touchpoints is released, remove it from touch point arrays - if(flags == AMOTION_EVENT_ACTION_HOVER_EXIT){ + if (flags == AMOTION_EVENT_ACTION_HOVER_EXIT) + { // If the touchPoint is hover, remove it from hoverPoints - for(int i = 0; i < MAX_TOUCH_POINTS; i++){ - if(touchRaw.hoverPoints[i] == touchRaw.pointId[pointerIndex]){ + for (int i = 0; i < MAX_TOUCH_POINTS; i++) + { + if (touchRaw.hoverPoints[i] == touchRaw.pointId[pointerIndex]) + { touchRaw.hoverPoints[i] = -1; break; } } } - for (int i = pointerIndex; (i < touchRaw.pointCount - 1) && (i < MAX_TOUCH_POINTS-1); i++) + for (int i = pointerIndex; (i < touchRaw.pointCount - 1) && (i < MAX_TOUCH_POINTS - 1); i++) { touchRaw.pointId[i] = touchRaw.pointId[i+1]; touchRaw.position[i] = touchRaw.position[i+1]; @@ -1339,18 +1343,19 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) int pointCount = 0; for (int i = 0; (i < touchRaw.pointCount) && (i < MAX_TOUCH_POINTS); i++) - { + { // If the touchPoint is hover, Ignore it bool hover = false; - for(int j = 0; j < MAX_TOUCH_POINTS; j++){ + for (int j = 0; j < MAX_TOUCH_POINTS; j++) + { // Check if the touchPoint is in hoverPointers - if(touchRaw.hoverPoints[j] == touchRaw.pointId[i]){ + if (touchRaw.hoverPoints[j] == touchRaw.pointId[i]) + { hover = true; break; } } - if(hover) - continue; + if (hover) continue; CORE.Input.Touch.pointId[pointCount] = touchRaw.pointId[i]; CORE.Input.Touch.position[pointCount] = touchRaw.position[i]; From 21f0fe2a73eca20c3a9724149bf3f1cee29c2bca Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 21 May 2025 19:06:04 +0200 Subject: [PATCH 005/242] Removed some spaces --- src/rlgl.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index bdbdc9824..d595f4606 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -1748,7 +1748,6 @@ void rlTextureParameters(unsigned int id, int param, int value) #endif } else glTexParameteri(GL_TEXTURE_2D, param, value); - } break; case RL_TEXTURE_MAG_FILTER: case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_2D, param, value); break; @@ -1793,7 +1792,6 @@ void rlCubemapParameters(unsigned int id, int param, int value) else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); } else glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); - } break; case RL_TEXTURE_MAG_FILTER: case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); break; @@ -2112,14 +2110,12 @@ void rlSetBlendMode(int mode) { // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactors() glBlendFunc(RLGL.State.glBlendSrcFactor, RLGL.State.glBlendDstFactor); glBlendEquation(RLGL.State.glBlendEquation); - } break; case RL_BLEND_CUSTOM_SEPARATE: { // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactorsSeparate() glBlendFuncSeparate(RLGL.State.glBlendSrcFactorRGB, RLGL.State.glBlendDestFactorRGB, RLGL.State.glBlendSrcFactorAlpha, RLGL.State.glBlendDestFactorAlpha); glBlendEquationSeparate(RLGL.State.glBlendEquationRGB, RLGL.State.glBlendEquationAlpha); - } break; default: break; } @@ -3747,19 +3743,16 @@ void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_2D, texId, mipLevel); else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_RENDERBUFFER, texId); else if (texType >= RL_ATTACHMENT_CUBEMAP_POSITIVE_X) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_CUBE_MAP_POSITIVE_X + texType, texId, mipLevel); - } break; case RL_ATTACHMENT_DEPTH: { if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, texId); - } break; case RL_ATTACHMENT_STENCIL: { if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, texId); - } break; default: break; } From afb52b19a40dde040e4b01624017f2ead54d3c6d Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 22 May 2025 17:06:55 +0200 Subject: [PATCH 006/242] WARNING: REDESIGNED: `EncodeDataBase64()`, NULL terminated string returned Note that returned output size considers the NULL terminator as an additional byte. --- src/raylib.h | 2 +- src/rcore.c | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/raylib.h b/src/raylib.h index 0cffa4272..73cf931be 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1153,7 +1153,7 @@ RLAPI long GetFileModTime(const char *fileName); // Get file mo // Compression/Encoding functionality RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize); // Compress data (DEFLATE algorithm), memory must be MemFree() RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // Decompress data (DEFLATE algorithm), memory must be MemFree() -RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string, memory must be MemFree() +RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string (includes NULL terminator), memory must be MemFree() RLAPI unsigned char *DecodeDataBase64(const char *data, int *outputSize); // Decode Base64 string data, memory must be MemFree() RLAPI unsigned int ComputeCRC32(unsigned char *data, int dataSize); // Compute CRC32 hash code RLAPI unsigned int *ComputeMD5(unsigned char *data, int dataSize); // Compute MD5 hash code, returns static int[4] (16 bytes) diff --git a/src/rcore.c b/src/rcore.c index 207e112a4..8d2142a05 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2539,6 +2539,7 @@ unsigned char *DecompressData(const unsigned char *compData, int compDataSize, i } // Encode data to Base64 string +// NOTE: Returned string includes NULL terminator, considered on outputSize char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize) { static const unsigned char base64encodeTable[] = { @@ -2549,7 +2550,7 @@ char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize) static const int modTable[] = { 0, 2, 1 }; - *outputSize = 4*((dataSize + 2)/3); + *outputSize = 4*((dataSize + 2)/3) + 1; // Adding +1 for NULL terminator char *encodedData = (char *)RL_MALLOC(*outputSize); @@ -2571,6 +2572,8 @@ char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize) for (int i = 0; i < modTable[dataSize%3]; i++) encodedData[*outputSize - 1 - i] = '='; // Padding character + encodedData[*outputSize - 1] = '\0'; + return encodedData; } From 8d9c1cecb7f53aef720e2ee0d1558ffc39fa7eef Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 May 2025 15:07:11 +0000 Subject: [PATCH 007/242] Update raylib_api.* by CI --- parser/output/raylib_api.json | 2 +- parser/output/raylib_api.lua | 2 +- parser/output/raylib_api.txt | 2 +- parser/output/raylib_api.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index e73e18fba..c9b314207 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -4684,7 +4684,7 @@ }, { "name": "EncodeDataBase64", - "description": "Encode data to Base64 string, memory must be MemFree()", + "description": "Encode data to Base64 string (includes NULL terminator), memory must be MemFree()", "returnType": "char *", "params": [ { diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index fa92cdea7..18ef44303 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -4198,7 +4198,7 @@ return { }, { name = "EncodeDataBase64", - description = "Encode data to Base64 string, memory must be MemFree()", + description = "Encode data to Base64 string (includes NULL terminator), memory must be MemFree()", returnType = "char *", params = { {type = "const unsigned char *", name = "data"}, diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 4aa682d78..dd7b21c86 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -1787,7 +1787,7 @@ Function 147: DecompressData() (3 input parameters) Function 148: EncodeDataBase64() (3 input parameters) Name: EncodeDataBase64 Return type: char * - Description: Encode data to Base64 string, memory must be MemFree() + Description: Encode data to Base64 string (includes NULL terminator), memory must be MemFree() Param[1]: data (type: const unsigned char *) Param[2]: dataSize (type: int) Param[3]: outputSize (type: int *) diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index c88a20bd1..18ef07eb2 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -1123,7 +1123,7 @@ - + From 5ddd13b775fa3622f63917bc856ed1d3d35a9ef7 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 28 May 2025 17:18:02 +0200 Subject: [PATCH 008/242] REVIEWED: Hexadecimal formatting to be consistent --- src/rcore.c | 64 ++++++++++++++++++++++++------------------------- src/rmodels.c | 6 ++--- src/rtext.c | 2 +- src/rtextures.c | 26 ++++++++++---------- 4 files changed, 48 insertions(+), 50 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 8d2142a05..bd0e8399b 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2639,38 +2639,38 @@ unsigned char *DecodeDataBase64(const char *data, int *outputSize) unsigned int ComputeCRC32(unsigned char *data, int dataSize) { static unsigned int crcTable[256] = { - 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, - 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, - 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, - 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, - 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, - 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, - 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, - 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, - 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, - 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, - 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, - 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, - 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, - 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, - 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, - 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, - 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, - 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, - 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, - 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, - 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, - 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, - 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, - 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, - 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, - 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, - 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, - 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, - 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, - 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, - 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, - 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; unsigned int crc = ~0u; diff --git a/src/rmodels.c b/src/rmodels.c index e5af19bf5..5f3fd8993 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -6623,11 +6623,11 @@ static Model LoadM3D(const char *fileName) // Without vertex color (full transparency), we use the default color if (model.meshes[k].colors != NULL) { - if (m3d->vertex[m3d->face[i].vertex[0]].color & 0xFF000000) + if (m3d->vertex[m3d->face[i].vertex[0]].color & 0xff000000) memcpy(&model.meshes[k].colors[l*12 + 0], &m3d->vertex[m3d->face[i].vertex[0]].color, 4); - if (m3d->vertex[m3d->face[i].vertex[1]].color & 0xFF000000) + if (m3d->vertex[m3d->face[i].vertex[1]].color & 0xff000000) memcpy(&model.meshes[k].colors[l*12 + 4], &m3d->vertex[m3d->face[i].vertex[1]].color, 4); - if (m3d->vertex[m3d->face[i].vertex[2]].color & 0xFF000000) + if (m3d->vertex[m3d->face[i].vertex[2]].color & 0xff000000) memcpy(&model.meshes[k].colors[l*12 + 8], &m3d->vertex[m3d->face[i].vertex[2]].color, 4); } diff --git a/src/rtext.c b/src/rtext.c index 70dcfc38c..ee498b119 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -253,7 +253,7 @@ extern void LoadFontDefault(void) } else { - ((unsigned char *)imFont.data)[(i + j)*sizeof(short)] = 0xFF; + ((unsigned char *)imFont.data)[(i + j)*sizeof(short)] = 0xff; ((unsigned char *)imFont.data)[(i + j)*sizeof(short) + 1] = 0x00; } } diff --git a/src/rtextures.c b/src/rtextures.c index 2f5aaa871..7a404a6de 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -5148,10 +5148,10 @@ Color GetColor(unsigned int hexValue) { Color color; - color.r = (unsigned char)(hexValue >> 24) & 0xFF; - color.g = (unsigned char)(hexValue >> 16) & 0xFF; - color.b = (unsigned char)(hexValue >> 8) & 0xFF; - color.a = (unsigned char)hexValue & 0xFF; + color.r = (unsigned char)(hexValue >> 24) & 0xff; + color.g = (unsigned char)(hexValue >> 16) & 0xff; + color.b = (unsigned char)(hexValue >> 8) & 0xff; + color.a = (unsigned char)hexValue & 0xff; return color; } @@ -5391,17 +5391,16 @@ static float HalfToFloat(unsigned short x) { float result = 0.0f; - union - { + union { float fm; unsigned int ui; } uni; - const unsigned int e = (x & 0x7C00) >> 10; // Exponent - const unsigned int m = (x & 0x03FF) << 13; // Mantissa + const unsigned int e = (x & 0x7c00) >> 10; // Exponent + const unsigned int m = (x & 0x03cc) << 13; // Mantissa uni.fm = (float)m; const unsigned int v = uni.ui >> 23; // Evil log2 bit hack to count leading zeros in denormalized format - uni.ui = (x & 0x8000) << 16 | (e != 0)*((e + 112) << 23 | m) | ((e == 0)&(m != 0))*((v - 37) << 23 | ((m << (150 - v)) & 0x007FE000)); // sign : normalized : denormalized + uni.ui = (x & 0x8000) << 16 | (e != 0)*((e + 112) << 23 | m) | ((e == 0)&(m != 0))*((v - 37) << 23 | ((m << (150 - v)) & 0x007fe000)); // sign : normalized : denormalized result = uni.fm; @@ -5413,18 +5412,17 @@ static unsigned short FloatToHalf(float x) { unsigned short result = 0; - union - { + union { float fm; unsigned int ui; } uni; uni.fm = x; const unsigned int b = uni.ui + 0x00001000; // Round-to-nearest-even: add last bit after truncated mantissa - const unsigned int e = (b & 0x7F800000) >> 23; // Exponent - const unsigned int m = b & 0x007FFFFF; // Mantissa; in line below: 0x007FF000 = 0x00800000-0x00001000 = decimal indicator flag - initial rounding + const unsigned int e = (b & 0x7f800000) >> 23; // Exponent + const unsigned int m = b & 0x007fffff; // Mantissa; in line below: 0x007ff000 = 0x00800000-0x00001000 = decimal indicator flag - initial rounding - result = (b & 0x80000000) >> 16 | (e > 112)*((((e - 112) << 10) & 0x7C00) | m >> 13) | ((e < 113) & (e > 101))*((((0x007FF000 + m) >> (125 - e)) + 1) >> 1) | (e > 143)*0x7FFF; // sign : normalized : denormalized : saturate + result = (b & 0x80000000) >> 16 | (e > 112)*((((e - 112) << 10) & 0x7c00) | m >> 13) | ((e < 113) & (e > 101))*((((0x007ff000 + m) >> (125 - e)) + 1) >> 1) | (e > 143)*0x7fff; // sign : normalized : denormalized : saturate return result; } From d7148f5f9d7a6f091adcc0d6444ef3f2c1040007 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 28 May 2025 17:19:19 +0200 Subject: [PATCH 009/242] REDESIGNED: Base64 encoding/decoding functions Found some issues with output size when padding required, just re-implemented both functions from scratch. --- src/raylib.h | 2 +- src/rcore.c | 151 ++++++++++++++++++++++++++++----------------------- 2 files changed, 83 insertions(+), 70 deletions(-) diff --git a/src/raylib.h b/src/raylib.h index 73cf931be..563156525 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1154,7 +1154,7 @@ RLAPI long GetFileModTime(const char *fileName); // Get file mo RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize); // Compress data (DEFLATE algorithm), memory must be MemFree() RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // Decompress data (DEFLATE algorithm), memory must be MemFree() RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string (includes NULL terminator), memory must be MemFree() -RLAPI unsigned char *DecodeDataBase64(const char *data, int *outputSize); // Decode Base64 string data, memory must be MemFree() +RLAPI unsigned char *DecodeDataBase64(const char *text, int *outputSize); // Decode Base64 string (expected NULL terminated), memory must be MemFree() RLAPI unsigned int ComputeCRC32(unsigned char *data, int dataSize); // Compute CRC32 hash code RLAPI unsigned int *ComputeMD5(unsigned char *data, int dataSize); // Compute MD5 hash code, returns static int[4] (16 bytes) RLAPI unsigned int *ComputeSHA1(unsigned char *data, int dataSize); // Compute SHA1 hash code, returns static int[5] (20 bytes) diff --git a/src/rcore.c b/src/rcore.c index bd0e8399b..5f81b7099 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2542,96 +2542,109 @@ unsigned char *DecompressData(const unsigned char *compData, int compDataSize, i // NOTE: Returned string includes NULL terminator, considered on outputSize char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize) { - static const unsigned char base64encodeTable[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' - }; + // Base64 conversion table from RFC 4648 [0..63] + // NOTE: They represent 64 values (6 bits), to encode 3 bytes of data into 4 "sixtets" (6bit characters) + static const char base64EncodeTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - static const int modTable[] = { 0, 2, 1 }; + // Compute expected size and padding + int paddedSize = dataSize; + while (paddedSize%3 != 0) paddedSize++; // Padding bytes to round 4*(dataSize/3) to 4 bytes + int estimatedOutputSize = 4*(paddedSize/3); + int padding = paddedSize - dataSize; - *outputSize = 4*((dataSize + 2)/3) + 1; // Adding +1 for NULL terminator + // Adding null terminator to string + estimatedOutputSize += 1; - char *encodedData = (char *)RL_MALLOC(*outputSize); + // Load some memory to store encoded string + char *encodedData = (char *)RL_CALLOC(estimatedOutputSize, 1); + if (encodedData == NULL) return NULL; - if (encodedData == NULL) return NULL; // Security check - - for (int i = 0, j = 0; i < dataSize;) + int outputCount = 0; + for (int i = 0; i < dataSize;) { - unsigned int octetA = (i < dataSize)? (unsigned char)data[i++] : 0; - unsigned int octetB = (i < dataSize)? (unsigned char)data[i++] : 0; - unsigned int octetC = (i < dataSize)? (unsigned char)data[i++] : 0; + unsigned int octetA = 0; + unsigned int octetB = 0; + unsigned int octetC = 0; + unsigned int octetPack = 0; - unsigned int triple = (octetA << 0x10) + (octetB << 0x08) + octetC; + octetA = data[i]; // Generates 2 sextets + octetB = ((i + 1) < dataSize)? data[i + 1] : 0; // Generates 3 sextets + octetC = ((i + 2) < dataSize)? data[i + 2] : 0; // Generates 4 sextets - encodedData[j++] = base64encodeTable[(triple >> 3*6) & 0x3F]; - encodedData[j++] = base64encodeTable[(triple >> 2*6) & 0x3F]; - encodedData[j++] = base64encodeTable[(triple >> 1*6) & 0x3F]; - encodedData[j++] = base64encodeTable[(triple >> 0*6) & 0x3F]; + octetPack = (octetA << 16) | (octetB << 8) | octetC; + + encodedData[outputCount + 0] = (unsigned char)(base64EncodeTable[(octetPack >> 18) & 0x3f]); + encodedData[outputCount + 1] = (unsigned char)(base64EncodeTable[(octetPack >> 12) & 0x3f]); + encodedData[outputCount + 2] = (unsigned char)(base64EncodeTable[(octetPack >> 6) & 0x3f]); + encodedData[outputCount + 3] = (unsigned char)(base64EncodeTable[octetPack & 0x3f]); + outputCount += 4; + i += 3; } + + // Add required padding bytes + for (int p = 0; p < padding; p++) encodedData[outputCount - p - 1] = '='; - for (int i = 0; i < modTable[dataSize%3]; i++) encodedData[*outputSize - 1 - i] = '='; // Padding character + // Add null terminator to string + encodedData[outputCount] = '\0'; + outputCount++; - encodedData[*outputSize - 1] = '\0'; + if (outputCount != estimatedOutputSize) TRACELOG(LOG_WARNING, "BASE64: Output size differs from estimation"); + *outputSize = estimatedOutputSize; return encodedData; } -// Decode Base64 string data -unsigned char *DecodeDataBase64(const char *data, int *outputSize) +// Decode Base64 string (expected NULL terminated) +unsigned char *DecodeDataBase64(const char *text, int *outputSize) { - static const unsigned char base64decodeTable[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, - 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + // Base64 decode table + // NOTE: Following ASCII order [0..255] assigning the expected sixtet value to + // every character in the corresponding ASCII position + static const unsigned char base64DecodeTable[256] = { + ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, ['F'] = 5, ['G'] = 6, ['H'] = 7, + ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15, + ['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, ['Y'] = 24, ['Z'] = 25, + ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31, ['g'] = 32, ['h'] = 33, + ['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, ['o'] = 40, ['p'] = 41, + ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47, ['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51, + ['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55, ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, + ['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63 }; - // Get output size of Base64 input data - int outSize = 0; - for (int i = 0; data[4*i] != 0; i++) + // Compute expected size and padding + int dataSize = (int)strlen(text); // WARNING: Expecting NULL terminated strings! + int ending = dataSize - 1; + int padding = 0; + while (text[ending] == '=') { padding++; ending--; } + int estimatedOutputSize = 3*(dataSize/4) - padding; + int maxOutputSize = 3*(dataSize/4); + + // Load some memory to store decoded data + // NOTE: Allocated enough size to include padding + unsigned char *decodedData = (unsigned char *)RL_CALLOC(maxOutputSize, 1); + if (decodedData == NULL) return NULL; + + int outputCount = 0; + for (int i = 0, j = 0; i < dataSize;) { - if (data[4*i + 3] == '=') - { - if (data[4*i + 2] == '=') outSize += 1; - else outSize += 2; - } - else outSize += 3; + // Every 4 sixtets must generate 3 octets + unsigned int sixtetA = base64DecodeTable[(unsigned char)text[i]]; + unsigned int sixtetB = base64DecodeTable[(unsigned char)text[i + 1]]; + unsigned int sixtetC = ((unsigned char)text[i + 2] != '=')? base64DecodeTable[(unsigned char)text[i + 2]] : 0; + unsigned int sixtetD = ((unsigned char)text[i + 3] != '=')? base64DecodeTable[(unsigned char)text[i + 3]] : 0; + + unsigned int octetPack = (sixtetA << 18) | (sixtetB << 12) | (sixtetC << 6) | sixtetD; + + decodedData[outputCount + 0] = (octetPack >> 16) & 0xff; + decodedData[outputCount + 1] = (octetPack >> 8) & 0xff; + decodedData[outputCount + 2] = octetPack & 0xff; + outputCount += 3; + i += 4; } - // Allocate memory to store decoded Base64 data - unsigned char *decodedData = (unsigned char *)RL_MALLOC(outSize); + if (estimatedOutputSize != (outputCount - padding)) TRACELOG(LOG_WARNING, "BASE64: Decoded size differs from estimation"); - for (int i = 0; i < outSize/3; i++) - { - unsigned char a = base64decodeTable[(int)data[4*i]]; - unsigned char b = base64decodeTable[(int)data[4*i + 1]]; - unsigned char c = base64decodeTable[(int)data[4*i + 2]]; - unsigned char d = base64decodeTable[(int)data[4*i + 3]]; - - decodedData[3*i] = (a << 2) | (b >> 4); - decodedData[3*i + 1] = (b << 4) | (c >> 2); - decodedData[3*i + 2] = (c << 6) | d; - } - - if (outSize%3 == 1) - { - int n = outSize/3; - unsigned char a = base64decodeTable[(int)data[4*n]]; - unsigned char b = base64decodeTable[(int)data[4*n + 1]]; - decodedData[outSize - 1] = (a << 2) | (b >> 4); - } - else if (outSize%3 == 2) - { - int n = outSize/3; - unsigned char a = base64decodeTable[(int)data[4*n]]; - unsigned char b = base64decodeTable[(int)data[4*n + 1]]; - unsigned char c = base64decodeTable[(int)data[4*n + 2]]; - decodedData[outSize - 2] = (a << 2) | (b >> 4); - decodedData[outSize - 1] = (b << 4) | (c >> 2); - } - - *outputSize = outSize; + *outputSize = estimatedOutputSize; return decodedData; } From 2d952d8e94f7b91cbc4be875e67a192055f9d05f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 28 May 2025 15:19:32 +0000 Subject: [PATCH 010/242] Update raylib_api.* by CI --- parser/output/raylib_api.json | 4 ++-- parser/output/raylib_api.lua | 4 ++-- parser/output/raylib_api.txt | 4 ++-- parser/output/raylib_api.xml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index c9b314207..72a3477a4 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -4703,12 +4703,12 @@ }, { "name": "DecodeDataBase64", - "description": "Decode Base64 string data, memory must be MemFree()", + "description": "Decode Base64 string (expected NULL terminated), memory must be MemFree()", "returnType": "unsigned char *", "params": [ { "type": "const char *", - "name": "data" + "name": "text" }, { "type": "int *", diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 18ef44303..662d45a78 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -4208,10 +4208,10 @@ return { }, { name = "DecodeDataBase64", - description = "Decode Base64 string data, memory must be MemFree()", + description = "Decode Base64 string (expected NULL terminated), memory must be MemFree()", returnType = "unsigned char *", params = { - {type = "const char *", name = "data"}, + {type = "const char *", name = "text"}, {type = "int *", name = "outputSize"} } }, diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index dd7b21c86..c399c5291 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -1794,8 +1794,8 @@ Function 148: EncodeDataBase64() (3 input parameters) Function 149: DecodeDataBase64() (2 input parameters) Name: DecodeDataBase64 Return type: unsigned char * - Description: Decode Base64 string data, memory must be MemFree() - Param[1]: data (type: const char *) + Description: Decode Base64 string (expected NULL terminated), memory must be MemFree() + Param[1]: text (type: const char *) Param[2]: outputSize (type: int *) Function 150: ComputeCRC32() (2 input parameters) Name: ComputeCRC32 diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 18ef07eb2..511d5d121 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -1128,8 +1128,8 @@ - - + + From f7fe8b88cbed5bba641588f5750f83ffca0d271d Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Wed, 28 May 2025 17:46:32 -0400 Subject: [PATCH 011/242] add EmscriptenKeyboardCallback to consume key events --- src/platforms/rcore_web.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index bf5a6ba5a..1c61052a9 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -137,6 +137,7 @@ static EM_BOOL EmscriptenFocusCallback(int eventType, const EmscriptenFocusEvent static EM_BOOL EmscriptenVisibilityChangeCallback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData); // Emscripten input callback events +static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyboardEvent, void *userData); static EM_BOOL EmscriptenMouseMoveCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); static EM_BOOL EmscriptenPointerlockCallback(int eventType, const EmscriptenPointerlockChangeEvent *pointerlockChangeEvent, void *userData); @@ -1362,9 +1363,11 @@ int InitPlatform(void) // Trigger this once to get initial window sizing EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); - // Support keyboard events -> Not used, GLFW.JS takes care of that - // emscripten_set_keypress_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback); - // emscripten_set_keydown_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback); + // Support keyboard events + // NOTE: used only to consume keyboard events. GLFW.JS takes care of + // the actual input. + emscripten_set_keypress_callback(GetCanvasId(), NULL, 1, EmscriptenKeyboardCallback); + emscripten_set_keydown_callback(GetCanvasId(), NULL, 1, EmscriptenKeyboardCallback); // Support mouse events emscripten_set_click_callback(GetCanvasId(), NULL, 1, EmscriptenMouseCallback); @@ -1620,6 +1623,14 @@ static void MouseCursorPosCallback(GLFWwindow *window, double x, double y) #endif } +static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyboardEvent, void *userData) +{ + // NOTE: handled by GLFW, this is only to consume the keyboard events so we + // make use of F-keys and other shortcuts without triggering browser + // functions. + return 1; // The event was consumed by the callback handler +} + static EM_BOOL EmscriptenMouseMoveCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) { // To emulate the GLFW_RAW_MOUSE_MOTION property. From 299f5350a4c07518a9bd52d6f98e5479d7a24bc7 Mon Sep 17 00:00:00 2001 From: M374LX Date: Wed, 28 May 2025 19:49:57 -0300 Subject: [PATCH 012/242] Remove unused variable --- src/rcore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rcore.c b/src/rcore.c index 5f81b7099..cccd2310e 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2625,7 +2625,7 @@ unsigned char *DecodeDataBase64(const char *text, int *outputSize) if (decodedData == NULL) return NULL; int outputCount = 0; - for (int i = 0, j = 0; i < dataSize;) + for (int i = 0; i < dataSize;) { // Every 4 sixtets must generate 3 octets unsigned int sixtetA = base64DecodeTable[(unsigned char)text[i]]; From c0cf57f8f02faa9403f5a9ad333ef19fabc6b44f Mon Sep 17 00:00:00 2001 From: M374LX Date: Wed, 28 May 2025 22:38:16 -0300 Subject: [PATCH 013/242] RGFW backend: add missing Right Control key --- src/platforms/rcore_desktop_rgfw.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 92e3def05..1c4f606a7 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -175,6 +175,7 @@ static const unsigned short keyMappingRGFW[] = { [RGFW_superL] = KEY_LEFT_SUPER, #ifndef RGFW_MACOS [RGFW_shiftR] = KEY_RIGHT_SHIFT, + [RGFW_controlR] = KEY_RIGHT_CONTROL, [RGFW_altR] = KEY_RIGHT_ALT, #endif [RGFW_space] = KEY_SPACE, From 341bfb22cc80fae266c8d5f29c5fed272abc3d32 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 29 May 2025 12:25:00 +0200 Subject: [PATCH 014/242] REVIEWED: `MAX_GAMEPAD_AXEX` for consistency #4960 --- src/config.h | 2 +- src/rcore.c | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/config.h b/src/config.h index ca54fa8cd..58f092ed1 100644 --- a/src/config.h +++ b/src/config.h @@ -104,7 +104,7 @@ #define MAX_KEYBOARD_KEYS 512 // Maximum number of keyboard keys supported #define MAX_MOUSE_BUTTONS 8 // Maximum number of mouse buttons supported #define MAX_GAMEPADS 4 // Maximum number of gamepads supported -#define MAX_GAMEPAD_AXIS 8 // Maximum number of axis supported (per gamepad) +#define MAX_GAMEPAD_AXES 8 // Maximum number of axis supported (per gamepad) #define MAX_GAMEPAD_BUTTONS 32 // Maximum number of buttons supported (per gamepad) #define MAX_GAMEPAD_VIBRATION_TIME 2.0f // Maximum vibration time in seconds #define MAX_TOUCH_POINTS 8 // Maximum number of touch points supported diff --git a/src/rcore.c b/src/rcore.c index cccd2310e..bfa0cc036 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -237,8 +237,8 @@ __declspec(dllimport) unsigned int __stdcall timeEndPeriod(unsigned int uPeriod) #ifndef MAX_GAMEPAD_NAME_LENGTH #define MAX_GAMEPAD_NAME_LENGTH 128 // Maximum number of characters of gamepad name (byte size) #endif -#ifndef MAX_GAMEPAD_AXIS - #define MAX_GAMEPAD_AXIS 8 // Maximum number of axis supported (per gamepad) +#ifndef MAX_GAMEPAD_AXES + #define MAX_GAMEPAD_AXES 8 // Maximum number of axis supported (per gamepad) #endif #ifndef MAX_GAMEPAD_BUTTONS #define MAX_GAMEPAD_BUTTONS 32 // Maximum number of buttons supported (per gamepad) @@ -359,7 +359,7 @@ typedef struct CoreData { char name[MAX_GAMEPADS][MAX_GAMEPAD_NAME_LENGTH]; // Gamepad name holder char currentButtonState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Current gamepad buttons state char previousButtonState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Previous gamepad buttons state - float axisState[MAX_GAMEPADS][MAX_GAMEPAD_AXIS]; // Gamepad axis state + float axisState[MAX_GAMEPADS][MAX_GAMEPAD_AXES]; // Gamepad axes state } Gamepad; } Input; @@ -3368,11 +3368,11 @@ int GetGamepadAxisCount(int gamepad) // Get axis movement vector for a gamepad float GetGamepadAxisMovement(int gamepad, int axis) { - float value = (axis == GAMEPAD_AXIS_LEFT_TRIGGER || axis == GAMEPAD_AXIS_RIGHT_TRIGGER)? -1.0f : 0.0f; + float value = ((axis == GAMEPAD_AXIS_LEFT_TRIGGER) || (axis == GAMEPAD_AXIS_RIGHT_TRIGGER))? -1.0f : 0.0f; - if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (axis < MAX_GAMEPAD_AXIS)) + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (axis < MAX_GAMEPAD_AXES)) { - float movement = value < 0.0f ? CORE.Input.Gamepad.axisState[gamepad][axis] : fabsf(CORE.Input.Gamepad.axisState[gamepad][axis]); + float movement = (value < 0.0f)? CORE.Input.Gamepad.axisState[gamepad][axis] : fabsf(CORE.Input.Gamepad.axisState[gamepad][axis]); if (movement > value) value = CORE.Input.Gamepad.axisState[gamepad][axis]; } @@ -4050,10 +4050,10 @@ static void RecordAutomationEvent(void) if (currentEventList->count == currentEventList->capacity) return; // Security check } - for (int axis = 0; axis < MAX_GAMEPAD_AXIS; axis++) + for (int axis = 0; axis < MAX_GAMEPAD_AXES; axis++) { // Event type: INPUT_GAMEPAD_AXIS_MOTION - float defaultMovement = (axis == GAMEPAD_AXIS_LEFT_TRIGGER || axis == GAMEPAD_AXIS_RIGHT_TRIGGER)? -1.0f : 0.0f; + float defaultMovement = ((axis == GAMEPAD_AXIS_LEFT_TRIGGER) || (axis == GAMEPAD_AXIS_RIGHT_TRIGGER))? -1.0f : 0.0f; if (GetGamepadAxisMovement(gamepad, axis) != defaultMovement) { currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; From 913c236487712ea6281b96990c14dea08979e727 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 29 May 2025 12:51:08 +0200 Subject: [PATCH 015/242] REVIEWED: `MAX_GAMEPAD_AXES` --- src/platforms/rcore_drm.c | 4 ++-- src/platforms/rcore_web.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 74af3d453..ac56e0f9e 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -124,7 +124,7 @@ typedef struct { // Gamepad data int gamepadStreamFd[MAX_GAMEPADS]; // Gamepad device file descriptor - int gamepadAbsAxisRange[MAX_GAMEPADS][MAX_GAMEPAD_AXIS][2]; // [0] = min, [1] = range value of the axis + int gamepadAbsAxisRange[MAX_GAMEPADS][MAX_GAMEPAD_AXES][2]; // [0] = min, [1] = range value of the axis int gamepadAbsAxisMap[MAX_GAMEPADS][ABS_CNT]; // Maps the axes gamepads from the evdev api to a sequential one int gamepadCount; // The number of gamepads registered } PlatformData; @@ -1681,7 +1681,7 @@ static void PollGamepadEvents(void) TRACELOG(LOG_DEBUG, "INPUT: Gamepad %2i: Axis: %2i Value: %i", i, axisRaylib, event.value); - if (axisRaylib < MAX_GAMEPAD_AXIS) + if (axisRaylib < MAX_GAMEPAD_AXES) { int min = platform.gamepadAbsAxisRange[i][event.code][0]; int range = platform.gamepadAbsAxisRange[i][event.code][1]; diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index bf5a6ba5a..f83e3159e 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -1081,7 +1081,7 @@ void PollInputEvents(void) } // Register axis data for every connected gamepad - for (int j = 0; (j < gamepadState.numAxes) && (j < MAX_GAMEPAD_AXIS); j++) + for (int j = 0; (j < gamepadState.numAxes) && (j < MAX_GAMEPAD_AXES); j++) { CORE.Input.Gamepad.axisState[i][j] = gamepadState.axis[j]; } From 6d5aedbd3816ae9fa2309a7f3835f7e9a2a469e1 Mon Sep 17 00:00:00 2001 From: Meowster <142757105+meowstr@users.noreply.github.com> Date: Thu, 29 May 2025 07:05:49 -0400 Subject: [PATCH 016/242] Add DrawEllipseV and DrawEllipseLinesV --- examples/shapes/shapes_basic_shapes.c | 2 ++ src/raylib.h | 2 ++ src/rshapes.c | 22 +++++++++++++++++----- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/examples/shapes/shapes_basic_shapes.c b/examples/shapes/shapes_basic_shapes.c index bd1688453..0302a2ef5 100644 --- a/examples/shapes/shapes_basic_shapes.c +++ b/examples/shapes/shapes_basic_shapes.c @@ -52,6 +52,8 @@ int main(void) DrawCircle(screenWidth/5, 120, 35, DARKBLUE); DrawCircleGradient(screenWidth/5, 220, 60, GREEN, SKYBLUE); DrawCircleLines(screenWidth/5, 340, 80, DARKBLUE); + DrawEllipse(screenWidth/5, 120, 25, 20, YELLOW); + DrawEllipseLines(screenWidth/5, 120, 30, 25, YELLOW); // Rectangle shapes and lines DrawRectangle(screenWidth/4*2 - 60, 100, 120, 60, RED); diff --git a/src/raylib.h b/src/raylib.h index 563156525..c2f2c4797 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1264,7 +1264,9 @@ RLAPI void DrawCircleV(Vector2 center, float radius, Color color); RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline RLAPI void DrawCircleLinesV(Vector2 center, float radius, Color color); // Draw circle outline (Vector version) RLAPI void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse +RLAPI void DrawEllipseV(Vector2 center, float radiusH, float radiusV, Color color); // Draw ellipse (Vector version) RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse outline +RLAPI void DrawEllipseLinesV(Vector2 center, float radiusH, float radiusV, Color color); // Draw ellipse outline (Vector version) RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring RLAPI void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring outline RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle diff --git a/src/rshapes.c b/src/rshapes.c index c739f4162..7fa3223cf 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -470,27 +470,39 @@ void DrawCircleLinesV(Vector2 center, float radius, Color color) // Draw ellipse void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color) +{ + DrawEllipseV((Vector2){ (float)centerX, (float)centerY }, radiusH, radiusV, color); +} + +// Draw ellipse (Vector version) +void DrawEllipseV(Vector2 center, float radiusH, float radiusV, Color color) { rlBegin(RL_TRIANGLES); for (int i = 0; i < 360; i += 10) { rlColor4ub(color.r, color.g, color.b, color.a); - rlVertex2f((float)centerX, (float)centerY); - rlVertex2f((float)centerX + cosf(DEG2RAD*(i + 10))*radiusH, (float)centerY + sinf(DEG2RAD*(i + 10))*radiusV); - rlVertex2f((float)centerX + cosf(DEG2RAD*i)*radiusH, (float)centerY + sinf(DEG2RAD*i)*radiusV); + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + cosf(DEG2RAD*(i + 10))*radiusH, center.y + sinf(DEG2RAD*(i + 10))*radiusV); + rlVertex2f(center.x + cosf(DEG2RAD*i)*radiusH, center.y + sinf(DEG2RAD*i)*radiusV); } rlEnd(); } // Draw ellipse outline void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color) +{ + DrawEllipseLinesV((Vector2){ (float)centerX, (float)centerY }, radiusH, radiusV, color); +} + +// Draw ellipse outline +void DrawEllipseLinesV(Vector2 center, float radiusH, float radiusV, Color color) { rlBegin(RL_LINES); for (int i = 0; i < 360; i += 10) { rlColor4ub(color.r, color.g, color.b, color.a); - rlVertex2f(centerX + cosf(DEG2RAD*(i + 10))*radiusH, centerY + sinf(DEG2RAD*(i + 10))*radiusV); - rlVertex2f(centerX + cosf(DEG2RAD*i)*radiusH, centerY + sinf(DEG2RAD*i)*radiusV); + rlVertex2f(center.x + cosf(DEG2RAD*(i + 10))*radiusH, center.y + sinf(DEG2RAD*(i + 10))*radiusV); + rlVertex2f(center.x + cosf(DEG2RAD*i)*radiusH, center.y + sinf(DEG2RAD*i)*radiusV); } rlEnd(); } From 16f398b464acdf5b44e873a141d89bb8262fb4e5 Mon Sep 17 00:00:00 2001 From: M374LX Date: Thu, 29 May 2025 15:01:01 -0300 Subject: [PATCH 017/242] Update comment (gamepad axes) --- src/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.h b/src/config.h index 58f092ed1..dc6cc5893 100644 --- a/src/config.h +++ b/src/config.h @@ -104,7 +104,7 @@ #define MAX_KEYBOARD_KEYS 512 // Maximum number of keyboard keys supported #define MAX_MOUSE_BUTTONS 8 // Maximum number of mouse buttons supported #define MAX_GAMEPADS 4 // Maximum number of gamepads supported -#define MAX_GAMEPAD_AXES 8 // Maximum number of axis supported (per gamepad) +#define MAX_GAMEPAD_AXES 8 // Maximum number of axes supported (per gamepad) #define MAX_GAMEPAD_BUTTONS 32 // Maximum number of buttons supported (per gamepad) #define MAX_GAMEPAD_VIBRATION_TIME 2.0f // Maximum vibration time in seconds #define MAX_TOUCH_POINTS 8 // Maximum number of touch points supported From a9525bfbc26f9b94ad69b0202a0e198f828cf384 Mon Sep 17 00:00:00 2001 From: M374LX Date: Thu, 29 May 2025 16:03:29 -0300 Subject: [PATCH 018/242] Update RGFW to 1.7 --- src/external/RGFW.h | 20938 ++++++++++++++------------- src/platforms/rcore_desktop_rgfw.c | 20 +- 2 files changed, 10627 insertions(+), 10331 deletions(-) diff --git a/src/external/RGFW.h b/src/external/RGFW.h index 40349bd98..f121ef630 100644 --- a/src/external/RGFW.h +++ b/src/external/RGFW.h @@ -1,10321 +1,10617 @@ -/* -* -* RGFW 1.6.5-dev -* -* Copyright (C) 2022-25 ColleagueRiley -* -* libpng license -* -* 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. -* -* -*/ - -/* - (MAKE SURE RGFW_IMPLEMENTATION is in exactly one header or you use -D RGFW_IMPLEMENTATION) - #define RGFW_IMPLEMENTATION - makes it so source code is included with header -*/ - -/* - #define RGFW_IMPLEMENTATION - (required) makes it so the source code is included - #define RGFW_DEBUG - (optional) makes it so RGFW prints debug messages and errors when they're found - #define RGFW_OSMESA - (optional) use OSmesa as backend (instead of system's opengl api + regular opengl) - #define RGFW_BUFFER - (optional) just draw directly to (RGFW) window pixel buffer that is drawn to screen (the buffer is in the RGBA format) - #define RGFW_EGL - (optional) use EGL for loading an OpenGL context (instead of the system's opengl api) - #define RGFW_OPENGL_ES1 - (optional) use EGL to load and use Opengl ES (version 1) for backend rendering (instead of the system's opengl api) - This version doesn't work for desktops (I'm pretty sure) - #define RGFW_OPENGL_ES2 - (optional) use OpenGL ES (version 2) - #define RGFW_OPENGL_ES3 - (optional) use OpenGL ES (version 3) - #define RGFW_DIRECTX - (optional) use directX for the rendering backend (rather than opengl) (windows only, defaults to opengl for unix) - #define RGFW_WEBGPU - (optional) use webGPU for rendering (Web ONLY) - #define RGFW_NO_API - (optional) don't use any rendering API (no opengl, no vulkan, no directX) - - #define RGFW_LINK_EGL (optional) (windows only) if EGL is being used, if EGL functions should be defined dymanically (using GetProcAddress) - #define RGFW_X11 (optional) (unix only) if X11 should be used. This option is turned on by default by unix systems except for MacOS - #define RGFW_WAYLAND (optional) (unix only) use Wayland. (This can be used with X11) - #define RGFW_NO_X11 (optional) (unix only) don't fallback to X11 when using Wayland - #define RGFW_NO_LOAD_WGL (optional) (windows only) if WGL should be loaded dynamically during runtime - #define RGFW_NO_X11_CURSOR (optional) (unix only) don't use XCursor - #define RGFW_NO_X11_CURSOR_PRELOAD (optional) (unix only) use XCursor, but don't link it in code, (you'll have to link it with -lXcursor) - #define RGFW_NO_LOAD_WINMM (optional) (windows only) use winmm (timeBeginPeriod), but don't link it in code, (you'll have to link it with -lwinmm) - #define RGFW_NO_WINMM (optional) (windows only) don't use winmm - #define RGFW_NO_IOKIT (optional) (macOS) don't use IOKit - #define RGFW_NO_UNIX_CLOCK (optional) (unix) don't link unix clock functions - #define RGFW_NO_DWM (windows only) - do not use or link dwmapi - #define RGFW_USE_XDL (optional) (X11) if XDL (XLib Dynamic Loader) should be used to load X11 dynamically during runtime (must include XDL.h along with RGFW) - #define RGFW_COCOA_GRAPHICS_SWITCHING - (optional) (cocoa) use automatic graphics switching (allow the system to choose to use GPU or iGPU) - #define RGFW_COCOA_FRAME_NAME (optional) (cocoa) set frame name - #define RGFW_NO_DPI - do not calculate DPI (no XRM nor libShcore included) - #define RGFW_BUFFER_BGR - use the BGR format for bufffers instead of RGB, saves processing time - - #define RGFW_ALLOC x - choose the default allocation function (defaults to standard malloc) - #define RGFW_FREE x - choose the default deallocation function (defaults to standard free) - #define RGFW_USERPTR x - choose the default userptr sent to the malloc call, (NULL by default) - - #define RGFW_EXPORT - use when building RGFW - #define RGFW_IMPORT - use when linking with RGFW (not as a single-header) - - #define RGFW_USE_INT - force the use c-types rather than stdint.h (for systems that might not have stdint.h (msvc)) - #define RGFW_bool x - choose what type to use for bool, by default u32 is used -*/ - -/* -Example to get you started : - -linux : gcc main.c -lX11 -lXrandr -lGL -windows : gcc main.c -lopengl32 -lgdi32 -macos : gcc main.c -framework Cocoa -framework CoreVideo -framework OpenGL -framework IOKit - -#define RGFW_IMPLEMENTATION -#include "RGFW.h" - -u8 icon[4 * 3 * 3] = {0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF}; - -int main() { - RGFW_window* win = RGFW_createWindow("name", RGFW_RECT(100, 100, 500, 500), (u64)0); - - RGFW_window_setIcon(win, icon, RGFW_AREA(3, 3), 4); - - for (;;) { - RGFW_window_checkEvent(win); // NOTE: checking events outside of a while loop may cause input lag - if (win->event.type == RGFW_quit || RGFW_isPressed(win, RGFW_escape)) - break; - - RGFW_window_swapBuffers(win); - - glClearColor(1, 1, 1, 1); - glClear(GL_COLOR_BUFFER_BIT); - } - - RGFW_window_close(win); -} - - compiling : - - if you wish to compile the library all you have to do is create a new file with this in it - - rgfw.c - #define RGFW_IMPLEMENTATION - #include "RGFW.h" - - You may also want to add - `#define RGFW_EXPORT` when compiling and - `#define RGFW_IMPORT`when linking RGFW on it's own: - this reduces inline functions and prevents bloat in the object file - - then you can use gcc (or whatever compile you wish to use) to compile the library into object file - - ex. gcc -c RGFW.c -fPIC - - after you compile the library into an object file, you can also turn the object file into an static or shared library - - (commands ar and gcc can be replaced with whatever equivalent your system uses) - - static : ar rcs RGFW.a RGFW.o - shared : - windows: - gcc -shared RGFW.o -lopengl32 -lgdi32 -o RGFW.dll - linux: - gcc -shared RGFW.o -lX11 -lGL -lXrandr -o RGFW.so - macos: - gcc -shared RGFW.o -framework CoreVideo -framework Cocoa -framework OpenGL -framework IOKit -*/ - - - -/* - Credits : - EimaMei/Sacode : Much of the code for creating windows using winapi, Wrote the Silicon library, helped with MacOS Support, siliapp.h -> referencing - - stb - This project is heavily inspired by the stb single header files - - GLFW: - certain parts of winapi and X11 are very poorly documented, - GLFW's source code was referenced and used throughout the project. - - contributors : (feel free to put yourself here if you contribute) - krisvers -> code review - EimaMei (SaCode) -> code review - Code-Nycticebus -> bug fixes - Rob Rohan -> X11 bugs and missing features, MacOS/Cocoa fixing memory issues/bugs - AICDG (@THISISAGOODNAME) -> vulkan support (example) - @Easymode -> support, testing/debugging, bug fixes and reviews - Joshua Rowe (omnisci3nce) - bug fix, review (macOS) - @lesleyrs -> bug fix, review (OpenGL) - Nick Porcino (meshula) - testing, organization, review (MacOS, examples) - @DarekParodia -> code review (X11) (C++) -*/ - -#if _MSC_VER - #pragma comment(lib, "gdi32") - #pragma comment(lib, "shell32") - #pragma comment(lib, "opengl32") - #pragma comment(lib, "winmm") - #pragma comment(lib, "user32") -#endif - -#ifndef RGFW_USERPTR - #define RGFW_USERPTR NULL -#endif - -#ifndef RGFW_UNUSED - #define RGFW_UNUSED(x) (void)(x) -#endif - -#ifndef RGFW_ROUND - #define RGFW_ROUND(x) (int)((x) >= 0 ? (x) + 0.5f : (x) - 0.5f) -#endif - -#ifndef RGFW_ALLOC - #include - - #ifndef __USE_POSIX199309 - #define __USE_POSIX199309 - #endif - - #define RGFW_ALLOC malloc - #define RGFW_FREE free -#endif - -#ifndef RGFW_ASSERT - #include - #define RGFW_ASSERT assert -#endif - -#ifndef RGFW_MEMCPY - #include - - #define RGFW_MEMCPY(dist, src, len) memcpy(dist, src, len) - #define RGFW_STRNCMP(s1, s2, max) strncmp(s1, s2, max) - #define RGFW_STRNCPY(dist, src, len) strncpy(dist, src, len) - #define RGFW_STRSTR(str, substr) strstr(str, substr) - //required for X11 XDnD - #define RGFW_STRTOL(str, endptr, base) strtol(str, endptr, base) -#else -#undef _INC_STRING -#endif - -#if !_MSC_VER - #ifndef inline - #ifndef __APPLE__ - #define inline __inline - #endif - #endif -#endif - -#ifdef RGFW_WIN95 /* for windows 95 testing (not that it really works) */ - #define RGFW_NO_MONITOR - #define RGFW_NO_PASSTHROUGH -#endif - -#if defined(RGFW_EXPORT) || defined(RGFW_IMPORT) - #if defined(_WIN32) - #if defined(__TINYC__) && (defined(RGFW_EXPORT) || defined(RGFW_IMPORT)) - #define __declspec(x) __attribute__((x)) - #endif - - #if defined(RGFW_EXPORT) - #define RGFWDEF __declspec(dllexport) - #else - #define RGFWDEF __declspec(dllimport) - #endif - #else - #if defined(RGFW_EXPORT) - #define RGFWDEF __attribute__((visibility("default"))) - #endif - #endif -#endif - -#ifndef RGFWDEF - #define RGFWDEF inline -#endif - -#ifndef RGFW_ENUM - #define RGFW_ENUM(type, name) type name; enum -#endif - - -#if defined(__cplusplus) && !defined(__EMSCRIPTEN__) - #ifdef __clang__ - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wnullability-completeness" - #endif - extern "C" { -#endif - - /* makes sure the header file part is only defined once by default */ -#ifndef RGFW_HEADER - -#define RGFW_HEADER - -#include -#if !defined(u8) - #ifdef RGFW_USE_INT /* optional for any system that might not have stdint.h */ - typedef unsigned char u8; - typedef signed char i8; - typedef unsigned short u16; - typedef signed short i16; - typedef unsigned long int u32; - typedef signed long int i32; - typedef unsigned long long u64; - typedef signed long long i64; - #else /* use stdint standard types instead of c ""standard"" types */ - #include - - typedef uint8_t u8; - typedef int8_t i8; - typedef uint16_t u16; - typedef int16_t i16; - typedef uint32_t u32; - typedef int32_t i32; - typedef uint64_t u64; - typedef int64_t i64; - #endif - #define u8 u8 -#endif - -#if !defined(RGFW_bool) /* RGFW bool type */ - typedef u8 RGFW_bool; - #define RGFW_bool u8 -#endif - -#define RGFW_BOOL(x) ((x) ? RGFW_TRUE : RGFW_FALSE) /* force an value to be 0 or 1 */ -#define RGFW_TRUE 1 -#define RGFW_FALSE 0 - -/* these OS macros look better & are standardized */ -/* plus it helps with cross-compiling */ - -#ifdef __EMSCRIPTEN__ - #define RGFW_WASM - - #if !defined(RGFW_NO_API) && !defined(RGFW_WEBGPU) - #define RGFW_OPENGL - #endif - - #ifdef RGFW_EGL - #undef RGFW_EGL - #endif - - #include - #include - - #ifdef RGFW_WEBGPU - #include - #endif -#endif - -#if defined(RGFW_X11) && defined(__APPLE__) && !defined(RGFW_CUSTOM_BACKEND) - #define RGFW_MACOS_X11 - #define RGFW_UNIX - #undef __APPLE__ -#endif - -#if defined(_WIN32) && !defined(RGFW_UNIX) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) /* (if you're using X11 on windows some how) */ - #define RGFW_WINDOWS - /* make sure the correct architecture is defined */ - #if defined(_WIN64) - #define _AMD64_ - #undef _X86_ - #else - #undef _AMD64_ - #ifndef _X86_ - #define _X86_ - #endif - #endif - - #ifndef RGFW_NO_XINPUT - #ifdef __MINGW32__ /* try to find the right header */ - #include - #else - #include - #endif - #endif -#elif defined(RGFW_WAYLAND) - #define RGFW_DEBUG // wayland will be in debug mode by default for now - #if !defined(RGFW_NO_API) && (!defined(RGFW_BUFFER) || defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) - #define RGFW_EGL - #define RGFW_OPENGL - #define RGFW_UNIX - #include - #endif - - #include -#endif -#if !defined(RGFW_NO_X11) && !defined(RGFW_NO_X11) && (defined(__unix__) || defined(RGFW_MACOS_X11) || defined(RGFW_X11)) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) - #define RGFW_MACOS_X11 - #define RGFW_X11 - #define RGFW_UNIX - #include -#elif defined(__APPLE__) && !defined(RGFW_MACOS_X11) && !defined(RGFW_X11) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) - #define RGFW_MACOS -#endif - -#if (defined(RGFW_OPENGL_ES1) || defined(RGFW_OPENGL_ES2) || defined(RGFW_OPENGL_ES3)) && !defined(RGFW_EGL) - #define RGFW_EGL -#endif - -#if !defined(RGFW_OSMESA) && !defined(RGFW_EGL) && !defined(RGFW_OPENGL) && !defined(RGFW_DIRECTX) && !defined(RGFW_BUFFER) && !defined(RGFW_NO_API) - #define RGFW_OPENGL -#endif - -#ifdef RGFW_EGL - #include -#elif defined(RGFW_OSMESA) - #ifdef RGFW_WINDOWS - #define OEMRESOURCE - #include - #define GLAPIENTRY APIENTRY - #define GLAPI WINGDIAPI - #endif - - #ifndef __APPLE__ - #include - #else - #include - #endif -#endif - -#if defined(RGFW_OPENGL) && defined(RGFW_X11) - #ifndef GLX_MESA_swap_control - #define GLX_MESA_swap_control - #endif - #include /* GLX defs, xlib.h, gl.h */ -#endif - -#define RGFW_COCOA_FRAME_NAME NULL - -/*! (unix) Toggle use of wayland. This will be on by default if you use `RGFW_WAYLAND` (if you don't use RGFW_WAYLAND, you don't expose WAYLAND functions) - this is mostly used to allow you to force the use of XWayland -*/ -RGFWDEF void RGFW_useWayland(RGFW_bool wayland); -/* - regular RGFW stuff -*/ - -#define RGFW_key u8 - -typedef RGFW_ENUM(u8, RGFW_eventType) { - /*! event codes */ - RGFW_eventNone = 0, /*!< no event has been sent */ - RGFW_keyPressed, /* a key has been pressed */ - RGFW_keyReleased, /*!< a key has been released */ - /*! key event note - the code of the key pressed is stored in - RGFW_event.key - !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! - - while a string version is stored in - RGFW_event.KeyString - - RGFW_event.keyMod holds the current keyMod - this means if CapsLock, NumLock are active or not - */ - RGFW_mouseButtonPressed, /*!< a mouse button has been pressed (left,middle,right) */ - RGFW_mouseButtonReleased, /*!< a mouse button has been released (left,middle,right) */ - RGFW_mousePosChanged, /*!< the position of the mouse has been changed */ - /*! mouse event note - the x and y of the mouse can be found in the vector, RGFW_event.point - - RGFW_event.button holds which mouse button was pressed - */ - RGFW_gamepadConnected, /*!< a gamepad was connected */ - RGFW_gamepadDisconnected, /*!< a gamepad was disconnected */ - RGFW_gamepadButtonPressed, /*!< a gamepad button was pressed */ - RGFW_gamepadButtonReleased, /*!< a gamepad button was released */ - RGFW_gamepadAxisMove, /*!< an axis of a gamepad was moved */ - /*! gamepad event note - RGFW_event.gamepad holds which gamepad was altered, if any - RGFW_event.button holds which gamepad button was pressed - - RGFW_event.axis holds the data of all the axises - RGFW_event.axisesCount says how many axises there are - */ - RGFW_windowMoved, /*!< the window was moved (by the user) */ - RGFW_windowResized, /*!< the window was resized (by the user), [on WASM this means the browser was resized] */ - RGFW_focusIn, /*!< window is in focus now */ - RGFW_focusOut, /*!< window is out of focus now */ - RGFW_mouseEnter, /* mouse entered the window */ - RGFW_mouseLeave, /* mouse left the window */ - RGFW_windowRefresh, /* The window content needs to be refreshed */ - - /* attribs change event note - The event data is sent straight to the window structure - with win->r.x, win->r.y, win->r.w and win->r.h - */ - RGFW_quit, /*!< the user clicked the quit button */ - RGFW_DND, /*!< a file has been dropped into the window */ - RGFW_DNDInit, /*!< the start of a dnd event, when the place where the file drop is known */ - /* dnd data note - The x and y coords of the drop are stored in the vector RGFW_event.point - - RGFW_event.droppedFilesCount holds how many files were dropped - - This is also the size of the array which stores all the dropped file string, - RGFW_event.droppedFiles - */ - RGFW_windowMaximized, /*!< the window was maximized */ - RGFW_windowMinimized, /*!< the window was minimized */ - RGFW_windowRestored, /*!< the window was restored */ -}; - -/*! mouse button codes (RGFW_event.button) */ -typedef RGFW_ENUM(u8, RGFW_mouseButton) { - RGFW_mouseLeft = 0, /*!< left mouse button is pressed */ - RGFW_mouseMiddle, /*!< mouse-wheel-button is pressed */ - RGFW_mouseRight, /*!< right mouse button is pressed */ - RGFW_mouseScrollUp, /*!< mouse wheel is scrolling up */ - RGFW_mouseScrollDown, /*!< mouse wheel is scrolling down */ - RGFW_mouseMisc1, RGFW_mouseMisc2, RGFW_mouseMisc3, RGFW_mouseMisc4, RGFW_mouseMisc5, - RGFW_mouseFinal -}; - -#ifndef RGFW_MAX_PATH -#define RGFW_MAX_PATH 260 /* max length of a path (for dnd) */ -#endif -#ifndef RGFW_MAX_DROPS -#define RGFW_MAX_DROPS 260 /* max items you can drop at once */ -#endif - -#define RGFW_BIT(x) (1L << x) - -/* for RGFW_event.lockstate */ -typedef RGFW_ENUM(u8, RGFW_keymod) { - RGFW_modCapsLock = RGFW_BIT(0), - RGFW_modNumLock = RGFW_BIT(1), - RGFW_modControl = RGFW_BIT(2), - RGFW_modAlt = RGFW_BIT(3), - RGFW_modShift = RGFW_BIT(4), - RGFW_modSuper = RGFW_BIT(5), - RGFW_modScrollLock = RGFW_BIT(6) -}; - -/*! gamepad button codes (based on xbox/playstation), you may need to change these values per controller */ -typedef RGFW_ENUM(u8, RGFW_gamepadCodes) { - RGFW_gamepadNone = 0, /*!< or PS X button */ - RGFW_gamepadA, /*!< or PS X button */ - RGFW_gamepadB, /*!< or PS circle button */ - RGFW_gamepadY, /*!< or PS triangle button */ - RGFW_gamepadX, /*!< or PS square button */ - RGFW_gamepadStart, /*!< start button */ - RGFW_gamepadSelect, /*!< select button */ - RGFW_gamepadHome, /*!< home button */ - RGFW_gamepadUp, /*!< dpad up */ - RGFW_gamepadDown, /*!< dpad down */ - RGFW_gamepadLeft, /*!< dpad left */ - RGFW_gamepadRight, /*!< dpad right */ - RGFW_gamepadL1, /*!< left bump */ - RGFW_gamepadL2, /*!< left trigger */ - RGFW_gamepadR1, /*!< right bumper */ - RGFW_gamepadR2, /*!< right trigger */ - RGFW_gamepadL3, /* left thumb stick */ - RGFW_gamepadR3, /*!< right thumb stick */ - RGFW_gamepadFinal -}; - -/*! basic vector type, if there's not already a point/vector type of choice */ -#ifndef RGFW_point - typedef struct { i32 x, y; } RGFW_point; -#endif - -/*! basic rect type, if there's not already a rect type of choice */ -#ifndef RGFW_rect - typedef struct { i32 x, y, w, h; } RGFW_rect; -#endif - -/*! basic area type, if there's not already a area type of choice */ -#ifndef RGFW_area - typedef struct { u32 w, h; } RGFW_area; -#endif - -#define RGFW_POINT(x, y) (RGFW_point){(i32)(x), (i32)(y)} -#define RGFW_RECT(x, y, w, h) (RGFW_rect){(i32)(x), (i32)(y), (i32)(w), (i32)(h)} -#define RGFW_AREA(w, h) (RGFW_area){(u32)(w), (u32)(h)} - -#ifndef RGFW_NO_MONITOR - /* monitor mode data | can be changed by the user (with functions)*/ - typedef struct RGFW_monitorMode { - RGFW_area area; /*!< monitor workarea size */ - u32 refreshRate; /*!< monitor refresh rate */ - u8 red, blue, green; - } RGFW_monitorMode; - - /*! structure for monitor data */ - typedef struct RGFW_monitor { - i32 x, y; /*!< x - y of the monitor workarea */ - char name[128]; /*!< monitor name */ - float scaleX, scaleY; /*!< monitor content scale */ - float pixelRatio; /*!< pixel ratio for monitor (1.0 for regular, 2.0 for hiDPI) */ - float physW, physH; /*!< monitor physical size in inches */ - - RGFW_monitorMode mode; - } RGFW_monitor; - - /*! get an array of all the monitors (max 6) */ - RGFWDEF RGFW_monitor* RGFW_getMonitors(void); - /*! get the primary monitor */ - RGFWDEF RGFW_monitor RGFW_getPrimaryMonitor(void); - - typedef RGFW_ENUM(u8, RGFW_modeRequest) { - RGFW_monitorScale = RGFW_BIT(0), /*!< scale the monitor size */ - RGFW_monitorRefresh = RGFW_BIT(1), /*!< change the refresh rate */ - RGFW_monitorRGB = RGFW_BIT(2), /*!< change the monitor RGB bits size */ - RGFW_monitorAll = RGFW_monitorScale | RGFW_monitorRefresh | RGFW_monitorRGB - }; - - /*! request a specific mode */ - RGFWDEF RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request); - /*! check if 2 monitor modes are the same */ - RGFWDEF RGFW_bool RGFW_monitorModeCompare(RGFW_monitorMode mon, RGFW_monitorMode mon2, RGFW_modeRequest request); -#endif - -/* RGFW mouse loading */ -typedef void RGFW_mouse; - -/*!< loads mouse icon from bitmap (similar to RGFW_window_setIcon). Icon NOT resized by default */ -RGFWDEF RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels); -/*!< frees RGFW_mouse data */ -RGFWDEF void RGFW_freeMouse(RGFW_mouse* mouse); - -/* NOTE: some parts of the data can represent different things based on the event (read comments in RGFW_event struct) */ -/*! Event structure for checking/getting events */ -typedef struct RGFW_event { - RGFW_eventType type; /*!< which event has been sent?*/ - RGFW_point point; /*!< mouse x, y of event (or drop point) */ - RGFW_point vector; /*!< raw mouse movement */ - - RGFW_key key; /*!< the physical key of the event, refers to where key is physically !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! */ - u8 keyChar; /*!< mapped key char of the event */ - - RGFW_bool repeat; /*!< key press event repeated (the key is being held) */ - RGFW_keymod keyMod; - - u8 button; /* !< which mouse (or gamepad) button was pressed */ - double scroll; /*!< the raw mouse scroll value */ - - u16 gamepad; /*! which gamepad this event applies to (if applicable to any) */ - u8 axisesCount; /*!< number of axises */ - - u8 whichAxis; /* which axis was effected */ - RGFW_point axis[4]; /*!< x, y of axises (-100 to 100) */ - - /*! drag and drop data */ - /* 260 max paths with a max length of 260 */ - char** droppedFiles; /*!< dropped files */ - size_t droppedFilesCount; /*!< house many files were dropped */ - - void* _win; /*!< the window this event applies too (for event queue events) */ -} RGFW_event; - -/*! source data for the window (used by the APIs) */ -#ifdef RGFW_WINDOWS -typedef struct RGFW_window_src { - HWND window; /*!< source window */ - HDC hdc; /*!< source HDC */ - u32 hOffset; /*!< height offset for window */ - HICON hIconSmall, hIconBig; /*!< source window icons */ - #if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) - HGLRC ctx; /*!< source graphics context */ - #elif defined(RGFW_OSMESA) - OSMesaContext ctx; - #elif defined(RGFW_EGL) - EGLSurface EGL_surface; - EGLDisplay EGL_display; - EGLContext EGL_context; - #endif - - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - HDC hdcMem; - HBITMAP bitmap; - u8* bitmapBits; - #endif - RGFW_area maxSize, minSize, aspectRatio; /*!< for setting max/min resize (RGFW_WINDOWS) */ -} RGFW_window_src; -#elif defined(RGFW_UNIX) -typedef struct RGFW_window_src { -#if defined(RGFW_X11) - Display* display; /*!< source display */ - Window window; /*!< source window */ - #if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) - GLXContext ctx; /*!< source graphics context */ - #elif defined(RGFW_OSMESA) - OSMesaContext ctx; - #elif defined(RGFW_EGL) - EGLSurface EGL_surface; - EGLDisplay EGL_display; - EGLContext EGL_context; - #endif - - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - XImage* bitmap; - #endif - GC gc; - char* clipboard; /* for writing to the clipboard selection */ - size_t clipboard_len; -#endif /* RGFW_X11 */ -#if defined(RGFW_WAYLAND) - struct wl_display* wl_display; - struct wl_surface* surface; - struct wl_buffer* wl_buffer; - struct wl_keyboard* keyboard; - - struct wl_compositor* compositor; - struct xdg_surface* xdg_surface; - struct xdg_toplevel* xdg_toplevel; - struct zxdg_toplevel_decoration_v1* decoration; - struct xdg_wm_base* xdg_wm_base; - struct wl_shm* shm; - struct wl_seat *seat; - u8* buffer; - #if defined(RGFW_EGL) - struct wl_egl_window* eglWindow; - #endif - #if defined(RGFW_EGL) && !defined(RGFW_X11) - EGLSurface EGL_surface; - EGLDisplay EGL_display; - EGLContext EGL_context; - #elif defined(RGFW_OSMESA) && !defined(RGFW_X11) - OSMesaContext ctx; - #endif -#endif /* RGFW_WAYLAND */ -} RGFW_window_src; -#endif /* RGFW_UNIX */ -#if defined(RGFW_MACOS) -typedef struct RGFW_window_src { - void* window; -#if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) - void* ctx; /*!< source graphics context */ -#elif defined(RGFW_OSMESA) - OSMesaContext ctx; -#elif defined(RGFW_EGL) - EGLSurface EGL_surface; - EGLDisplay EGL_display; - EGLContext EGL_context; -#endif - - void* view; /* apple viewpoint thingy */ - -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - void* bitmap; /*!< API's bitmap for storing or managing */ - void* image; -#endif -} RGFW_window_src; -#elif defined(RGFW_WASM) -typedef struct RGFW_window_src { - #ifdef RGFW_WEBGPU - WGPUInstance ctx; - WGPUDevice device; - WGPUQueue queue; - #else - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx; - #endif -} RGFW_window_src; -#endif - -/*! Optional arguments for making a windows */ -typedef RGFW_ENUM(u32, RGFW_windowFlags) { - RGFW_windowNoInitAPI = RGFW_BIT(0), /* do NOT init an API (mostly for bindings. you should use `#define RGFW_NO_API` in C) */ - RGFW_windowNoBorder = RGFW_BIT(1), /*!< the window doesn't have a border */ - RGFW_windowNoResize = RGFW_BIT(2), /*!< the window cannot be resized by the user */ - RGFW_windowAllowDND = RGFW_BIT(3), /*!< the window supports drag and drop */ - RGFW_windowHideMouse = RGFW_BIT(4), /*! the window should hide the mouse (can be toggled later on using `RGFW_window_mouseShow`) */ - RGFW_windowFullscreen = RGFW_BIT(5), /*!< the window is fullscreen by default */ - RGFW_windowTransparent = RGFW_BIT(6), /*!< the window is transparent (only properly works on X11 and MacOS, although it's meant for for windows) */ - RGFW_windowCenter = RGFW_BIT(7), /*! center the window on the screen */ - RGFW_windowOpenglSoftware = RGFW_BIT(8), /*! use OpenGL software rendering */ - RGFW_windowCocoaCHDirToRes = RGFW_BIT(9), /*! (cocoa only), change directory to resource folder */ - RGFW_windowScaleToMonitor = RGFW_BIT(10), /*! scale the window to the screen */ - RGFW_windowHide = RGFW_BIT(11), /*! the window is hidden */ - RGFW_windowMaximize = RGFW_BIT(12), - RGFW_windowCenterCursor = RGFW_BIT(13), - RGFW_windowFloating = RGFW_BIT(14), /*!< create a floating window */ - RGFW_windowFreeOnClose = RGFW_BIT(15), /*!< free (RGFW_window_close) the RGFW_window struct when the window is closed (by the end user) */ - RGFW_windowFocusOnShow = RGFW_BIT(16), /*!< focus the window when it's shown */ - RGFW_windowMinimize = RGFW_BIT(17), /*!< focus the window when it's shown */ - RGFW_windowFocus = RGFW_BIT(18), /*!< if the window is in focus */ - RGFW_windowedFullscreen = RGFW_windowNoBorder | RGFW_windowMaximize, -}; - -typedef struct RGFW_window { - RGFW_window_src src; /*!< src window data */ - -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - u8* buffer; /*!< buffer for non-GPU systems (OSMesa, basic software rendering) */ - /* when rendering using RGFW_BUFFER, the buffer is in the RGBA format */ - RGFW_area bufferSize; -#endif - void* userPtr; /* ptr for usr data */ - - RGFW_event event; /*!< current event */ - - RGFW_rect r; /*!< the x, y, w and h of the struct */ - - RGFW_point _lastMousePoint; /*!< last cusor point (for raw mouse data) */ - - u32 _flags; /*!< windows flags (for RGFW to check) */ - RGFW_rect _oldRect; /*!< rect before fullscreen */ -} RGFW_window; /*!< window structure for managing the window */ - -#if defined(RGFW_X11) || defined(RGFW_MACOS) - typedef u64 RGFW_thread; /*!< thread type unix */ -#else - typedef void* RGFW_thread; /*!< thread type for windows */ -#endif - -/*! scale monitor to window size */ -RGFWDEF RGFW_bool RGFW_monitor_scaleToWindow(RGFW_monitor mon, RGFW_window* win); - -/** * @defgroup Window_management -* @{ */ - - -/*! - * the class name for X11 and WinAPI. apps with the same class will be grouped by the WM - * by default the class name will == the root window's name -*/ -RGFWDEF void RGFW_setClassName(const char* name); -RGFWDEF void RGFW_setXInstName(const char* name); /*!< X11 instance name (window name will by used by default) */ - -/*! (cocoa only) change directory to resource folder */ -RGFWDEF void RGFW_moveToMacOSResourceDir(void); - -/* NOTE: (windows) if the executable has an icon resource named RGFW_ICON, it will be set as the initial icon for the window */ - -RGFWDEF RGFW_window* RGFW_createWindow( - const char* name, /* name of the window */ - RGFW_rect rect, /* rect of window */ - RGFW_windowFlags flags /* extra arguments ((u32)0 means no flags used)*/ -); /*!< function to create a window and struct */ - -RGFWDEF RGFW_window* RGFW_createWindowPtr( - const char* name, /* name of the window */ - RGFW_rect rect, /* rect of window */ - RGFW_windowFlags flags, /* extra arguments (NULL / (u32)0 means no flags used) */ - RGFW_window* win /* ptr to the window struct you want to use */ -); /*!< function to create a window (without allocating a window struct) */ - -RGFWDEF void RGFW_window_initBuffer(RGFW_window* win); -RGFWDEF void RGFW_window_initBufferSize(RGFW_window* win, RGFW_area area); -RGFWDEF void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area); - -/*! set the window flags (will undo flags if they don't match the old ones) */ -RGFWDEF void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags); - -/*! get the size of the screen to an area struct */ -RGFWDEF RGFW_area RGFW_getScreenSize(void); - - -/*! - this function checks an *individual* event (and updates window structure attributes) - this means, using this function without a while loop may cause event lag - - ex. - - while (RGFW_window_checkEvent(win) != NULL) [this keeps checking events until it reaches the last one] - - this function is optional if you choose to use event callbacks, - although you still need some way to tell RGFW to process events eg. `RGFW_window_checkEvents` -*/ - -RGFWDEF RGFW_event* RGFW_window_checkEvent(RGFW_window* win); /*!< check current event (returns a pointer to win->event or NULL if there is no event)*/ - -/*! - for RGFW_window_eventWait and RGFW_window_checkEvents - waitMS -> Allows the function to keep checking for events even after `RGFW_window_checkEvent == NULL` - if waitMS == 0, the loop will not wait for events - if waitMS > 0, the loop will wait that many miliseconds after there are no more events until it returns - if waitMS == -1 or waitMS == the max size of an unsigned 32-bit int, the loop will not return until it gets another event -*/ -typedef RGFW_ENUM(u32, RGFW_eventWait) { - RGFW_eventNoWait = 0, - RGFW_eventWaitNext = 0xFFFFFFFF -}; - -/*! sleep until RGFW gets an event or the timer ends (defined by OS) */ -RGFWDEF void RGFW_window_eventWait(RGFW_window* win, u32 waitMS); - -/*! - check all the events until there are none left. - This should only be used if you're using callbacks only -*/ -RGFWDEF void RGFW_window_checkEvents(RGFW_window* win, u32 waitMS); - -/*! - tell RGFW_window_eventWait to stop waiting (to be ran from another thread) -*/ -RGFWDEF void RGFW_stopCheckEvents(void); - -/*! window managment functions */ -RGFWDEF void RGFW_window_close(RGFW_window* win); /*!< close the window and free leftover data */ - -/*! move a window to a given point */ -RGFWDEF void RGFW_window_move(RGFW_window* win, - RGFW_point v /*!< new pos */ -); - -#ifndef RGFW_NO_MONITOR - /*! move window to a specific monitor */ - RGFWDEF void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor m /* monitor */); -#endif - -/*! resize window to a current size/area */ -RGFWDEF void RGFW_window_resize(RGFW_window* win, /*!< source window */ - RGFW_area a /*!< new size */ -); - -/*! set window aspect ratio */ -RGFWDEF void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a); -/*! set the minimum dimensions of a window */ -RGFWDEF void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a); -/*! set the maximum dimensions of a window */ -RGFWDEF void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a); - -RGFWDEF void RGFW_window_focus(RGFW_window* win); /*!< sets the focus to this window */ -RGFWDEF RGFW_bool RGFW_window_isInFocus(RGFW_window* win); /*!< checks the focus to this window */ -RGFWDEF void RGFW_window_raise(RGFW_window* win); /*!< raise the window (to the top) */ -RGFWDEF void RGFW_window_maximize(RGFW_window* win); /*!< maximize the window */ -RGFWDEF void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen); /*!< turn fullscreen on / off for a window */ -RGFWDEF void RGFW_window_center(RGFW_window* win); /*!< center the window */ -RGFWDEF void RGFW_window_minimize(RGFW_window* win); /*!< minimize the window (in taskbar (per OS))*/ -RGFWDEF void RGFW_window_restore(RGFW_window* win); /*!< restore the window from minimized (per OS)*/ -RGFWDEF void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating); /*!< make the window a floating window */ -RGFWDEF void RGFW_window_setOpacity(RGFW_window* win, u8 opacity); /*!< sets the opacity of a window */ - -/*! if the window should have a border or not (borderless) based on bool value of `border` */ -RGFWDEF void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border); -RGFWDEF RGFW_bool RGFW_window_borderless(RGFW_window* win); - -/*! turn on / off dnd (RGFW_windowAllowDND stil must be passed to the window)*/ -RGFWDEF void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow); -/*! check if DND is allowed */ -RGFWDEF RGFW_bool RGFW_window_allowsDND(RGFW_window* win); - - -#ifndef RGFW_NO_PASSTHROUGH - /*! turn on / off mouse passthrough */ - RGFWDEF void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough); -#endif - -/*! rename window to a given string */ -RGFWDEF void RGFW_window_setName(RGFW_window* win, - const char* name -); - -RGFWDEF RGFW_bool RGFW_window_setIcon(RGFW_window* win, /*!< source window */ - u8* icon /*!< icon bitmap */, - RGFW_area a /*!< width and height of the bitmap */, - i32 channels /*!< how many channels the bitmap has (rgb : 3, rgba : 4) */ -); /*!< image MAY be resized by default, set both the taskbar and window icon */ - -typedef RGFW_ENUM(u8, RGFW_icon) { - RGFW_iconTaskbar = RGFW_BIT(0), - RGFW_iconWindow = RGFW_BIT(1), - RGFW_iconBoth = RGFW_iconTaskbar | RGFW_iconWindow -}; -RGFWDEF RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* icon, RGFW_area a, i32 channels, u8 type); - -/*!< sets mouse to RGFW_mouse icon (loaded from a bitmap struct) */ -RGFWDEF void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse); - -/*!< sets the mouse to a standard API cursor (based on RGFW_MOUSE, as seen at the end of the RGFW_HEADER part of this file) */ -RGFWDEF RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse); - -RGFWDEF RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win); /*!< sets the mouse to the default mouse icon */ -/* - Locks cursor at the center of the window - win->event.point becomes raw mouse movement data - - this is useful for a 3D camera -*/ -RGFWDEF void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area); -/*! stop holding the mouse and let it move freely */ -RGFWDEF void RGFW_window_mouseUnhold(RGFW_window* win); - -/*! hide the window */ -RGFWDEF void RGFW_window_hide(RGFW_window* win); -/*! show the window */ -RGFWDEF void RGFW_window_show(RGFW_window* win); - -/* - makes it so `RGFW_window_shouldClose` returns true - by setting the window event.type to RGFW_quit -*/ -RGFWDEF void RGFW_window_setShouldClose(RGFW_window* win); - -/*! where the mouse is on the screen */ -RGFWDEF RGFW_point RGFW_getGlobalMousePoint(void); - -/*! where the mouse is on the window */ -RGFWDEF RGFW_point RGFW_window_getMousePoint(RGFW_window* win); - -/*! show the mouse or hide the mouse */ -RGFWDEF void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show); -/*! if the mouse is hidden */ -RGFWDEF RGFW_bool RGFW_window_mouseHidden(RGFW_window* win); -/*! move the mouse to a given point */ -RGFWDEF void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v); - -/*! if the window should close (RGFW_close was sent or escape was pressed) */ -RGFWDEF RGFW_bool RGFW_window_shouldClose(RGFW_window* win); -/*! if the window is fullscreen */ -RGFWDEF RGFW_bool RGFW_window_isFullscreen(RGFW_window* win); -/*! if the window is hidden */ -RGFWDEF RGFW_bool RGFW_window_isHidden(RGFW_window* win); -/*! if the window is minimized */ -RGFWDEF RGFW_bool RGFW_window_isMinimized(RGFW_window* win); -/*! if the window is maximized */ -RGFWDEF RGFW_bool RGFW_window_isMaximized(RGFW_window* win); -/*! if the window is floating */ -RGFWDEF RGFW_bool RGFW_window_isFloating(RGFW_window* win); - -/** @} */ - -/** * @defgroup Monitor -* @{ */ - -#ifndef RGFW_NO_MONITOR -/* - scale the window to the monitor. - This is run by default if the user uses the arg `RGFW_scaleToMonitor` during window creation -*/ -RGFWDEF void RGFW_window_scaleToMonitor(RGFW_window* win); -/*! get the struct of the window's monitor */ -RGFWDEF RGFW_monitor RGFW_window_getMonitor(RGFW_window* win); -#endif - -/** @} */ - -/** * @defgroup Input -* @{ */ - -/*! if window == NULL, it checks if the key is pressed globally. Otherwise, it checks only if the key is pressed while the window in focus. */ -RGFWDEF RGFW_bool RGFW_isPressed(RGFW_window* win, RGFW_key key); /*!< if key is pressed (key code)*/ - -RGFWDEF RGFW_bool RGFW_wasPressed(RGFW_window* win, RGFW_key key); /*!< if key was pressed (checks previous state only) (key code) */ - -RGFWDEF RGFW_bool RGFW_isHeld(RGFW_window* win, RGFW_key key); /*!< if key is held (key code) */ -RGFWDEF RGFW_bool RGFW_isReleased(RGFW_window* win, RGFW_key key); /*!< if key is released (key code) */ - -/* if a key is pressed and then released, pretty much the same as RGFW_isReleased */ -RGFWDEF RGFW_bool RGFW_isClicked(RGFW_window* win, RGFW_key key /*!< key code */); - -/*! if a mouse button is pressed */ -RGFWDEF RGFW_bool RGFW_isMousePressed(RGFW_window* win, RGFW_mouseButton button /*!< mouse button code */ ); -/*! if a mouse button is held */ -RGFWDEF RGFW_bool RGFW_isMouseHeld(RGFW_window* win, RGFW_mouseButton button /*!< mouse button code */ ); -/*! if a mouse button was released */ -RGFWDEF RGFW_bool RGFW_isMouseReleased(RGFW_window* win, RGFW_mouseButton button /*!< mouse button code */ ); -/*! if a mouse button was pressed (checks previous state only) */ -RGFWDEF RGFW_bool RGFW_wasMousePressed(RGFW_window* win, RGFW_mouseButton button /*!< mouse button code */ ); -/** @} */ - -/** * @defgroup Clipboard -* @{ */ -typedef ptrdiff_t RGFW_ssize_t; - -RGFWDEF const char* RGFW_readClipboard(size_t* size); /*!< read clipboard data */ -/*! read clipboard data or send a NULL str to just get the length of the clipboard data */ -RGFWDEF RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity); -RGFWDEF void RGFW_writeClipboard(const char* text, u32 textLen); /*!< write text to the clipboard */ -/** @} */ - - - -/** * @defgroup error handling -* @{ */ -typedef RGFW_ENUM(u8, RGFW_debugType) { - RGFW_typeError = 0, RGFW_typeWarning, RGFW_typeInfo -}; - -typedef RGFW_ENUM(u8, RGFW_errorCode) { - RGFW_noError = 0, /*!< no error */ - RGFW_errOpenglContext, RGFW_errEGLContext, /*!< error with the OpenGL context */ - RGFW_errWayland, - RGFW_errDirectXContext, - RGFW_errIOKit, - RGFW_errClipboard, - RGFW_errFailedFuncLoad, - RGFW_errBuffer, - RGFW_infoMonitor, RGFW_infoWindow, RGFW_infoBuffer, - RGFW_warningWayland, RGFW_warningOpenGL -}; - -typedef struct RGFW_debugContext { RGFW_window* win; RGFW_monitor monitor; u32 srcError; } RGFW_debugContext; -#define RGFW_DEBUG_CTX(win, err) (RGFW_debugContext){win, (RGFW_monitor){}, err} -#define RGFW_DEBUG_CTX_MON(monitor) (RGFW_debugContext){RGFW_root, monitor, 0} - -typedef void (* RGFW_debugfunc)(RGFW_debugType type, RGFW_errorCode err, RGFW_debugContext ctx, const char* msg); -RGFWDEF RGFW_debugfunc RGFW_setDebugCallback(RGFW_debugfunc func); -RGFWDEF void RGFW_sendDebugInfo(RGFW_debugType type, RGFW_errorCode err, RGFW_debugContext ctx, const char* msg); -/** @} */ - -/** - - - event callbacks. - These are completely optional, so you can use the normal - RGFW_checkEvent() method if you prefer that - -* @defgroup Callbacks -* @{ -*/ - -/*! RGFW_windowMoved, the window and its new rect value */ -typedef void (* RGFW_windowmovefunc)(RGFW_window* win, RGFW_rect r); -/*! RGFW_windowResized, the window and its new rect value */ -typedef void (* RGFW_windowresizefunc)(RGFW_window* win, RGFW_rect r); -/*! RGFW_quit, the window that was closed */ -typedef void (* RGFW_windowquitfunc)(RGFW_window* win); -/*! RGFW_focusIn / RGFW_focusOut, the window who's focus has changed and if its in focus */ -typedef void (* RGFW_focusfunc)(RGFW_window* win, RGFW_bool inFocus); -/*! RGFW_mouseEnter / RGFW_mouseLeave, the window that changed, the point of the mouse (enter only) and if the mouse has entered */ -typedef void (* RGFW_mouseNotifyfunc)(RGFW_window* win, RGFW_point point, RGFW_bool status); -/*! RGFW_mousePosChanged, the window that the move happened on, and the new point of the mouse */ -typedef void (* RGFW_mouseposfunc)(RGFW_window* win, RGFW_point point, RGFW_point vector); -/*! RGFW_DNDInit, the window, the point of the drop on the windows */ -typedef void (* RGFW_dndInitfunc)(RGFW_window* win, RGFW_point point); -/*! RGFW_windowRefresh, the window that needs to be refreshed */ -typedef void (* RGFW_windowrefreshfunc)(RGFW_window* win); -/*! RGFW_keyPressed / RGFW_keyReleased, the window that got the event, the mapped key, the physical key, the string version, the state of the mod keys, if it was a press (else it's a release) */ -typedef void (* RGFW_keyfunc)(RGFW_window* win, u8 key, char keyChar, RGFW_keymod keyMod, RGFW_bool pressed); -/*! RGFW_mouseButtonPressed / RGFW_mouseButtonReleased, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ -typedef void (* RGFW_mousebuttonfunc)(RGFW_window* win, RGFW_mouseButton button, double scroll, RGFW_bool pressed); -/*! RGFW_gamepadButtonPressed, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ -typedef void (* RGFW_gamepadButtonfunc)(RGFW_window* win, u16 gamepad, u8 button, RGFW_bool pressed); -/*! RGFW_gamepadAxisMove, the window that got the event, the gamepad in question, the axis values and the axis count */ -typedef void (* RGFW_gamepadAxisfunc)(RGFW_window* win, u16 gamepad, RGFW_point axis[2], u8 axisesCount, u8 whichAxis); -/*! RGFW_gamepadConnected / RGFW_gamepadDisconnected, the window that got the event, the gamepad in question, if the controller was connected (else it was disconnected) */ -typedef void (* RGFW_gamepadfunc)(RGFW_window* win, u16 gamepad, RGFW_bool connected); -/*! RGFW_dnd, the window that had the drop, the drop data and the number of files dropped */ -typedef void (* RGFW_dndfunc)(RGFW_window* win, char** droppedFiles, u32 droppedFilesCount); - -/*! set callback for a window move event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_windowmovefunc RGFW_setWindowMoveCallback(RGFW_windowmovefunc func); -/*! set callback for a window resize event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_windowresizefunc RGFW_setWindowResizeCallback(RGFW_windowresizefunc func); -/*! set callback for a window quit event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_windowquitfunc RGFW_setWindowQuitCallback(RGFW_windowquitfunc func); -/*! set callback for a mouse move event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_mouseposfunc RGFW_setMousePosCallback(RGFW_mouseposfunc func); -/*! set callback for a window refresh event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_windowrefreshfunc RGFW_setWindowRefreshCallback(RGFW_windowrefreshfunc func); -/*! set callback for a window focus change event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_focusfunc RGFW_setFocusCallback(RGFW_focusfunc func); -/*! set callback for a mouse notify event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_mouseNotifyfunc RGFW_setMouseNotifyCallBack(RGFW_mouseNotifyfunc func); -/*! set callback for a drop event event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_dndfunc RGFW_setDndCallback(RGFW_dndfunc func); -/*! set callback for a start of a drop event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_dndInitfunc RGFW_setDndInitCallback(RGFW_dndInitfunc func); -/*! set callback for a key (press / release) event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_keyfunc RGFW_setKeyCallback(RGFW_keyfunc func); -/*! set callback for a mouse button (press / release) event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_mousebuttonfunc RGFW_setMouseButtonCallback(RGFW_mousebuttonfunc func); -/*! set callback for a controller button (press / release) event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_gamepadButtonfunc RGFW_setgamepadButtonCallback(RGFW_gamepadButtonfunc func); -/*! set callback for a gamepad axis move event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_gamepadAxisfunc RGFW_setgamepadAxisCallback(RGFW_gamepadAxisfunc func); -/*! set callback for when a controller is connected or disconnected. Returns the previous callback function (if it was set) */ -RGFWDEF RGFW_gamepadfunc RGFW_setGamepadCallback(RGFW_gamepadfunc func); -/*! set call back for when window is maximized. Returns the previous callback function (if it was set) */ -RGFWDEF RGFW_windowresizefunc RGFW_setWindowMaximizedCallback(RGFW_windowresizefunc func); -/*! set call back for when window is minimized. Returns the previous callback function (if it was set) */ -RGFWDEF RGFW_windowresizefunc RGFW_setWindowMinimizedCallback(RGFW_windowresizefunc func); -/*! set call back for when window is restored. Returns the previous callback function (if it was set) */ -RGFWDEF RGFW_windowresizefunc RGFW_setWindowRestoredCallback(RGFW_windowresizefunc func); - -/** @} */ - -/** * @defgroup Threads -* @{ */ - -#ifndef RGFW_NO_THREADS -/*! threading functions */ - -/*! NOTE! (for X11/linux) : if you define a window in a thread, it must be run after the original thread's window is created or else there will be a memory error */ -/* - I'd suggest you use sili's threading functions instead - if you're going to use sili - which is a good idea generally -*/ - -#if defined(__unix__) || defined(__APPLE__) || defined(RGFW_WASM) || defined(RGFW_CUSTOM_BACKEND) - typedef void* (* RGFW_threadFunc_ptr)(void*); -#else - typedef DWORD (__stdcall *RGFW_threadFunc_ptr) (LPVOID lpThreadParameter); -#endif - -RGFWDEF RGFW_thread RGFW_createThread(RGFW_threadFunc_ptr ptr, void* args); /*!< create a thread */ -RGFWDEF void RGFW_cancelThread(RGFW_thread thread); /*!< cancels a thread */ -RGFWDEF void RGFW_joinThread(RGFW_thread thread); /*!< join thread to current thread */ -RGFWDEF void RGFW_setThreadPriority(RGFW_thread thread, u8 priority); /*!< sets the priority priority */ -#endif - -/** @} */ - -/** * @defgroup gamepad -* @{ */ - -typedef RGFW_ENUM(u8, RGFW_gamepadType) { - RGFW_gamepadMicrosoft = 0, RGFW_gamepadSony, RGFW_gamepadNintendo, RGFW_gamepadLogitech, RGFW_gamepadUnknown -}; - -/*! gamepad count starts at 0*/ -RGFWDEF u32 RGFW_isPressedGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button); -RGFWDEF u32 RGFW_isReleasedGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button); -RGFWDEF u32 RGFW_isHeldGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button); -RGFWDEF u32 RGFW_wasPressedGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button); -RGFWDEF RGFW_point RGFW_getGamepadAxis(RGFW_window* win, u16 controller, u16 whichAxis); -RGFWDEF const char* RGFW_getGamepadName(RGFW_window* win, u16 controller); -RGFWDEF size_t RGFW_getGamepadCount(RGFW_window* win); -RGFWDEF RGFW_gamepadType RGFW_getGamepadType(RGFW_window* win, u16 controller); - -/** @} */ - -/** * @defgroup graphics_API -* @{ */ - -/*!< make the window the current opengl drawing context - - NOTE: - if you want to switch the graphics context's thread, - you have to run RGFW_window_makeCurrent(NULL); on the old thread - then RGFW_window_makeCurrent(valid_window) on the new thread -*/ -RGFWDEF void RGFW_window_makeCurrent(RGFW_window* win); - -/* supports openGL, directX, OSMesa, EGL and software rendering */ -RGFWDEF void RGFW_window_swapBuffers(RGFW_window* win); /*!< swap the rendering buffer */ -RGFWDEF void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval); - -RGFWDEF void RGFW_window_setGPURender(RGFW_window* win, RGFW_bool set); -RGFWDEF void RGFW_window_setCPURender(RGFW_window* win, RGFW_bool set); - -/*! native API functions */ -#if defined(RGFW_OPENGL) || defined(RGFW_EGL) -/*! OpenGL init hints */ -typedef RGFW_ENUM(u8, RGFW_glHints) { - RGFW_glStencil = 0, /*!< set stencil buffer bit size (8 by default) */ - RGFW_glSamples, /*!< set number of sampiling buffers (4 by default) */ - RGFW_glStereo, /*!< use GL_STEREO (GL_FALSE by default) */ - RGFW_glAuxBuffers, /*!< number of aux buffers (0 by default) */ - RGFW_glDoubleBuffer, /*!< request double buffering */ - RGFW_glRed, RGFW_glGreen, RGFW_glBlue, RGFW_glAlpha, /*!< set RGBA bit sizes */ - RGFW_glDepth, - RGFW_glAccumRed, RGFW_glAccumGreen, RGFW_glAccumBlue,RGFW_glAccumAlpha, /*!< set accumulated RGBA bit sizes */ - RGFW_glSRGB, /*!< request sRGA */ - RGFW_glRobustness, /*!< request a robust context */ - RGFW_glDebug, /*!< request opengl debugging */ - RGFW_glNoError, /*!< request no opengl errors */ - RGFW_glReleaseBehavior, - RGFW_glProfile, - RGFW_glMajor, RGFW_glMinor, - RGFW_glFinalHint, /*!< the final hint (not for setting) */ - RGFW_releaseFlush = 0, RGFW_glReleaseNone, /* RGFW_glReleaseBehavior options */ - RGFW_glCore = 0, RGFW_glCompatibility /*!< RGFW_glProfile options */ -}; -RGFWDEF void RGFW_setGLHint(RGFW_glHints hint, i32 value); -RGFWDEF void* RGFW_getProcAddress(const char* procname); /*!< get native opengl proc address */ -RGFWDEF void RGFW_window_makeCurrent_OpenGL(RGFW_window* win); /*!< to be called by RGFW_window_makeCurrent */ -void* RGFW_getCurrent_OpenGL(void); /*!< get the current context (OpenGL backend (GLX) (WGL) (EGL) (cocoa) (webgl))*/ -#elif defined(RGFW_VULKAN) - #if defined(RGFW_X11) - #define VK_USE_PLATFORM_XLIB_KHR - #define RGFW_VK_SURFACE "VK_KHR_xlib_surface" - #elif defined(RGFW_WINDOWS) - #define VK_USE_PLATFORM_WIN32_KHR - #define OEMRESOURCE - #define RGFW_VK_SURFACE "VK_KHR_win32_surface" - #elif defined(RGFW_MACOS) && !defined(RGFW_MACOS_X11) - #define VK_USE_PLATFORM_MACOS_MVK - #define RGFW_VK_SURFACE "VK_MVK_macos_surface" - #elif defined(RGFW_WAYLAND) - #define VK_USE_PLATFORM_WAYLAND_KHR - #define RGFW_VK_SURFACE "VK_KHR_wayland_surface" - #else - #define RGFW_VK_SURFACE NULL - #endif - -#include - -RGFWDEF VkResult RGFW_window_createVKSurface(RGFW_window* win, VkInstance instance, VkSurfaceKHR* surface); -#endif - -/** @} */ - -/** * @defgroup Supporting -* @{ */ -RGFWDEF double RGFW_getTime(void); /*!< get time in seconds since RGFW_setTime, which ran when the first window is open */ -RGFWDEF u64 RGFW_getTimeNS(void); /*!< get time in nanoseconds RGFW_setTime, which ran when the first window is open */ -RGFWDEF void RGFW_sleep(u64 milisecond); /*!< sleep for a set time */ -RGFWDEF void RGFW_setTime(double time); /*!< set timer in seconds */ -RGFWDEF u64 RGFW_getTimerValue(void); /*!< get API timer value */ -RGFWDEF u64 RGFW_getTimerFreq(void); /*!< get API time freq */ - -/*< updates fps / sets fps to cap (must by ran manually by the user at the end of a frame), returns current fps */ -RGFWDEF u32 RGFW_checkFPS(double startTime, u32 frameCount, u32 fpsCap); - -/*!< change which window is the root window */ -RGFWDEF void RGFW_setRootWindow(RGFW_window* win); -RGFWDEF RGFW_window* RGFW_getRootWindow(void); - -/*! standard event queue, used for injecting events and returning source API callback events like any other queue check */ -/* these are all used internally by RGFW */ -void RGFW_eventQueuePush(RGFW_event event); -RGFW_event* RGFW_eventQueuePop(RGFW_window* win); - -/*! - key codes and mouse icon enums -*/ -#undef RGFW_key -typedef RGFW_ENUM(u8, RGFW_key) { - RGFW_keyNULL = 0, - RGFW_escape = '\033', - RGFW_backtick = '`', - RGFW_0 = '0', - RGFW_1 = '1', - RGFW_2 = '2', - RGFW_3 = '3', - RGFW_4 = '4', - RGFW_5 = '5', - RGFW_6 = '6', - RGFW_7 = '7', - RGFW_8 = '8', - RGFW_9 = '9', - - RGFW_minus = '-', - RGFW_equals = '=', - RGFW_backSpace = '\b', - RGFW_tab = '\t', - RGFW_space = ' ', - - RGFW_a = 'a', - RGFW_b = 'b', - RGFW_c = 'c', - RGFW_d = 'd', - RGFW_e = 'e', - RGFW_f = 'f', - RGFW_g = 'g', - RGFW_h = 'h', - RGFW_i = 'i', - RGFW_j = 'j', - RGFW_k = 'k', - RGFW_l = 'l', - RGFW_m = 'm', - RGFW_n = 'n', - RGFW_o = 'o', - RGFW_p = 'p', - RGFW_q = 'q', - RGFW_r = 'r', - RGFW_s = 's', - RGFW_t = 't', - RGFW_u = 'u', - RGFW_v = 'v', - RGFW_w = 'w', - RGFW_x = 'x', - RGFW_y = 'y', - RGFW_z = 'z', - - RGFW_period = '.', - RGFW_comma = ',', - RGFW_slash = '/', - RGFW_bracket = '{', - RGFW_closeBracket = '}', - RGFW_semicolon = ';', - RGFW_apostrophe = '\'', - RGFW_backSlash = '\\', - RGFW_return = '\n', - - RGFW_delete = '\177', /* 127 */ - - RGFW_F1, - RGFW_F2, - RGFW_F3, - RGFW_F4, - RGFW_F5, - RGFW_F6, - RGFW_F7, - RGFW_F8, - RGFW_F9, - RGFW_F10, - RGFW_F11, - RGFW_F12, - - RGFW_capsLock, - RGFW_shiftL, - RGFW_controlL, - RGFW_altL, - RGFW_superL, - RGFW_shiftR, - RGFW_controlR, - RGFW_altR, - RGFW_superR, - RGFW_up, - RGFW_down, - RGFW_left, - RGFW_right, - - RGFW_insert, - RGFW_end, - RGFW_home, - RGFW_pageUp, - RGFW_pageDown, - - RGFW_numLock, - RGFW_KP_Slash, - RGFW_multiply, - RGFW_KP_Minus, - RGFW_KP_1, - RGFW_KP_2, - RGFW_KP_3, - RGFW_KP_4, - RGFW_KP_5, - RGFW_KP_6, - RGFW_KP_7, - RGFW_KP_8, - RGFW_KP_9, - RGFW_KP_0, - RGFW_KP_Period, - RGFW_KP_Return, - RGFW_scrollLock, - RGFW_keyLast -}; - -RGFWDEF u32 RGFW_apiKeyToRGFW(u32 keycode); - -typedef RGFW_ENUM(u8, RGFW_mouseIcons) { - RGFW_mouseNormal = 0, - RGFW_mouseArrow, - RGFW_mouseIbeam, - RGFW_mouseCrosshair, - RGFW_mousePointingHand, - RGFW_mouseResizeEW, - RGFW_mouseResizeNS, - RGFW_mouseResizeNWSE, - RGFW_mouseResizeNESW, - RGFW_mouseResizeAll, - RGFW_mouseNotAllowed, -}; - -/** @} */ - -#endif /* RGFW_HEADER */ -#if defined(RGFW_X11) || defined(RGFW_WAYLAND) - #define RGFW_OS_BASED_VALUE(l, w, m, h) l -#elif defined(RGFW_WINDOWS) - #define RGFW_OS_BASED_VALUE(l, w, m, h) w -#elif defined(RGFW_MACOS) - #define RGFW_OS_BASED_VALUE(l, w, m, h) m -#elif defined(RGFW_WASM) - #define RGFW_OS_BASED_VALUE(l, w, m, h) h -#endif - - -#ifdef RGFW_IMPLEMENTATION -RGFW_bool RGFW_useWaylandBool = 1; - -#ifdef RGFW_DEBUG -#include -#endif - -char* RGFW_clipboard_data; -void RGFW_clipboard_switch(char* newstr) { - if (RGFW_clipboard_data != NULL) - RGFW_FREE(RGFW_clipboard_data); - RGFW_clipboard_data = newstr; -} - -#define RGFW_CHECK_CLIPBOARD() \ - if (size <= 0 && RGFW_clipboard_data != NULL) \ - return (const char*)RGFW_clipboard_data; \ - else if (size <= 0) \ - return "\0"; - -const char* RGFW_readClipboard(size_t* len) { - RGFW_ssize_t size = RGFW_readClipboardPtr(NULL, 0); - RGFW_CHECK_CLIPBOARD(); - char* str = (char*)RGFW_ALLOC(size); - size = RGFW_readClipboardPtr(str, size); - RGFW_CHECK_CLIPBOARD(); - - if (len != NULL) *len = size; - - RGFW_clipboard_switch(str); - return (const char*)str; -} - -RGFW_debugfunc RGFW_debugCallback = NULL; -RGFW_debugfunc RGFW_setDebugCallback(RGFW_debugfunc func) { - RGFW_debugfunc RGFW_debugCallbackPrev = RGFW_debugCallback; - RGFW_debugCallback = func; - return RGFW_debugCallbackPrev; -} - -void RGFW_sendDebugInfo(RGFW_debugType type, RGFW_errorCode err, RGFW_debugContext ctx, const char* msg) { - if (RGFW_debugCallback) RGFW_debugCallback(type, err, ctx, msg); - #ifdef RGFW_DEBUG - switch (type) { - case RGFW_typeInfo: printf("RGFW INFO (%i %i): %s", type, err, msg); break; - case RGFW_typeError: printf("RGFW DEBUG (%i %i): %s", type, err, msg); break; - case RGFW_typeWarning: printf("RGFW WARNING (%i %i): %s", type, err, msg); break; - default: break; - } - - switch (err) { - #ifdef RGFW_BUFFER - case RGFW_errBuffer: case RGFW_infoBuffer: printf(" buffer size: %i %i\n", ctx.win->bufferSize.w, ctx.win->bufferSize.h); - #endif - case RGFW_infoMonitor: printf(": scale (%s):\n rect: {%i, %i, %i, %i}\n physical size:%f %f\n scale: %f %f\n pixelRatio: %f\n refreshRate: %i\n depth: %i\n", ctx.monitor.name, ctx.monitor.x, ctx.monitor.y, ctx.monitor.mode.area.w, ctx.monitor.mode.area.h, ctx.monitor.physW, ctx.monitor.physH, ctx.monitor.scaleX, ctx.monitor.scaleY, ctx.monitor.pixelRatio, ctx.monitor.mode.refreshRate, ctx.monitor.mode.red + ctx.monitor.mode.green + ctx.monitor.mode.blue); break; - case RGFW_infoWindow: printf(" with rect of {%i, %i, %i, %i} \n", ctx.win->r.x, ctx.win->r.y,ctx. win->r.w, ctx.win->r.h); break; - case RGFW_errDirectXContext: printf(" srcError %i\n", ctx.srcError); break; - default: printf("\n"); - } - #endif -} - -u32 RGFW_timerOffset = 0; -void RGFW_setTime(double time) { - RGFW_timerOffset = RGFW_getTimerValue() - (u64)(time * RGFW_getTimerFreq()); -} - -double RGFW_getTime(void) { - return (double) ((RGFW_getTimerValue() - RGFW_timerOffset) / (double) RGFW_getTimerFreq()); -} - -u64 RGFW_getTimeNS(void) { - return (u64)(((RGFW_getTimerValue() - RGFW_timerOffset) * 1e9) / RGFW_getTimerFreq()); -} - -/* -RGFW_IMPLEMENTATION starts with generic RGFW defines - -This is the start of keycode data - - Why not use macros instead of the numbers itself? - Windows -> Not all scancodes keys are macros - Linux -> Only symcodes are values, (XK_0 - XK_1, XK_a - XK_z) are larger than 0xFF00, I can't find any way to work with them without making the array an unreasonable size - MacOS -> windows and linux already don't have keycodes as macros, so there's no point -*/ - - - -/* - the c++ compiler doesn't support setting up an array like, - we'll have to do it during runtime using a function & this messy setup -*/ - -#ifndef RGFW_CUSTOM_BACKEND - -#ifndef __cplusplus -#define RGFW_NEXT , -#define RGFW_MAP -#else -#define RGFW_NEXT ; -#define RGFW_MAP RGFW_keycodes -#endif - -u8 RGFW_keycodes [RGFW_OS_BASED_VALUE(136, 0x15C + 1, 128, DOM_VK_WIN_OEM_CLEAR + 1)] = { -#ifdef __cplusplus - 0 -}; -void RGFW_init_keys(void) { -#endif - RGFW_MAP [RGFW_OS_BASED_VALUE(49, 0x029, 50, DOM_VK_BACK_QUOTE)] = RGFW_backtick RGFW_NEXT - - RGFW_MAP [RGFW_OS_BASED_VALUE(19, 0x00B, 29, DOM_VK_0)] = RGFW_0 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(10, 0x002, 18, DOM_VK_1)] = RGFW_1 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(11, 0x003, 19, DOM_VK_2)] = RGFW_2 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(12, 0x004, 20, DOM_VK_3)] = RGFW_3 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(13, 0x005, 21, DOM_VK_4)] = RGFW_4 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(14, 0x006, 23, DOM_VK_5)] = RGFW_5 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(15, 0x007, 22, DOM_VK_6)] = RGFW_6 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(16, 0x008, 26, DOM_VK_7)] = RGFW_7 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(17, 0x009, 28, DOM_VK_8)] = RGFW_8 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(18, 0x00A, 25, DOM_VK_9)] = RGFW_9, - RGFW_MAP [RGFW_OS_BASED_VALUE(65, 0x039, 49, DOM_VK_SPACE)] = RGFW_space, - RGFW_MAP [RGFW_OS_BASED_VALUE(38, 0x01E, 0, DOM_VK_A)] = RGFW_a RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(56, 0x030, 11, DOM_VK_B)] = RGFW_b RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(54, 0x02E, 8, DOM_VK_C)] = RGFW_c RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(40, 0x020, 2, DOM_VK_D)] = RGFW_d RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(26, 0x012, 14, DOM_VK_E)] = RGFW_e RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(41, 0x021, 3, DOM_VK_F)] = RGFW_f RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(42, 0x022, 5, DOM_VK_G)] = RGFW_g RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(43, 0x023, 4, DOM_VK_H)] = RGFW_h RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(31, 0x017, 34, DOM_VK_I)] = RGFW_i RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(44, 0x024, 38, DOM_VK_J)] = RGFW_j RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(45, 0x025, 40, DOM_VK_K)] = RGFW_k RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(46, 0x026, 37, DOM_VK_L)] = RGFW_l RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(58, 0x032, 46, DOM_VK_M)] = RGFW_m RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(57, 0x031, 45, DOM_VK_N)] = RGFW_n RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(32, 0x018, 31, DOM_VK_O)] = RGFW_o RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(33, 0x019, 35, DOM_VK_P)] = RGFW_p RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(24, 0x010, 12, DOM_VK_Q)] = RGFW_q RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(27, 0x013, 15, DOM_VK_R)] = RGFW_r RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(39, 0x01F, 1, DOM_VK_S)] = RGFW_s RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(28, 0x014, 17, DOM_VK_T)] = RGFW_t RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(30, 0x016, 32, DOM_VK_U)] = RGFW_u RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(55, 0x02F, 9, DOM_VK_V)] = RGFW_v RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(25, 0x011, 13, DOM_VK_W)] = RGFW_w RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(53, 0x02D, 7, DOM_VK_X)] = RGFW_x RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(29, 0x015, 16, DOM_VK_Y)] = RGFW_y RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(52, 0x02C, 6, DOM_VK_Z)] = RGFW_z, - RGFW_MAP [RGFW_OS_BASED_VALUE(60, 0x034, 47, DOM_VK_PERIOD)] = RGFW_period RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(59, 0x033, 43, DOM_VK_COMMA)] = RGFW_comma RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(61, 0x035, 44, DOM_VK_SLASH)] = RGFW_slash RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(34, 0x01A, 33, DOM_VK_OPEN_BRACKET)] = RGFW_bracket RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(35, 0x01B, 30, DOM_VK_CLOSE_BRACKET)] = RGFW_closeBracket RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(47, 0x027, 41, DOM_VK_SEMICOLON)] = RGFW_semicolon RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(48, 0x028, 39, DOM_VK_QUOTE)] = RGFW_apostrophe RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(51, 0x02B, 42, DOM_VK_BACK_SLASH)] = RGFW_backSlash, - RGFW_MAP [RGFW_OS_BASED_VALUE(36, 0x01C, 36, DOM_VK_RETURN)] = RGFW_return RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(119, 0x153, 118, DOM_VK_DELETE)] = RGFW_delete RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(77, 0x145, 72, DOM_VK_NUM_LOCK)] = RGFW_numLock RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(106, 0x135, 82, DOM_VK_DIVIDE)] = RGFW_KP_Slash RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(63, 0x037, 76, DOM_VK_MULTIPLY)] = RGFW_multiply RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(82, 0x04A, 67, DOM_VK_SUBTRACT)] = RGFW_KP_Minus RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(87, 0x04F, 84, DOM_VK_NUMPAD1)] = RGFW_KP_1 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(88, 0x050, 85, DOM_VK_NUMPAD2)] = RGFW_KP_2 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(89, 0x051, 86, DOM_VK_NUMPAD3)] = RGFW_KP_3 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(83, 0x04B, 87, DOM_VK_NUMPAD4)] = RGFW_KP_4 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(84, 0x04C, 88, DOM_VK_NUMPAD5)] = RGFW_KP_5 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(85, 0x04D, 89, DOM_VK_NUMPAD6)] = RGFW_KP_6 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(79, 0x047, 90, DOM_VK_NUMPAD7)] = RGFW_KP_7 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(80, 0x048, 92, DOM_VK_NUMPAD8)] = RGFW_KP_8 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(81, 0x049, 93, DOM_VK_NUMPAD9)] = RGFW_KP_9 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(90, 0x052, 83, DOM_VK_NUMPAD0)] = RGFW_KP_0 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(91, 0x053, 65, DOM_VK_DECIMAL)] = RGFW_KP_Period RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(104, 0x11C, 77, 0)] = RGFW_KP_Return, - RGFW_MAP [RGFW_OS_BASED_VALUE(20, 0x00C, 27, DOM_VK_HYPHEN_MINUS)] = RGFW_minus RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(21, 0x00D, 24, DOM_VK_EQUALS)] = RGFW_equals RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(22, 0x00E, 51, DOM_VK_BACK_SPACE)] = RGFW_backSpace RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(23, 0x00F, 48, DOM_VK_TAB)] = RGFW_tab RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(66, 0x03A, 57, DOM_VK_CAPS_LOCK)] = RGFW_capsLock RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(50, 0x02A, 56, DOM_VK_SHIFT)] = RGFW_shiftL RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(37, 0x01D, 59, DOM_VK_CONTROL)] = RGFW_controlL RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(64, 0x038, 58, DOM_VK_ALT)] = RGFW_altL RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(133, 0x15B, 55, DOM_VK_WIN)] = RGFW_superL, - #if !defined(RGFW_MACOS) && !defined(RGFW_WASM) - RGFW_MAP [RGFW_OS_BASED_VALUE(105, 0x11D, 59, 0)] = RGFW_controlR RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(135, 0x15C, 55, 0)] = RGFW_superR, - RGFW_MAP [RGFW_OS_BASED_VALUE(62, 0x036, 56, 0)] = RGFW_shiftR RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(108, 0x138, 58, 0)] = RGFW_altR, - #endif - RGFW_MAP [RGFW_OS_BASED_VALUE(67, 0x03B, 127, DOM_VK_F1)] = RGFW_F1 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(68, 0x03C, 121, DOM_VK_F2)] = RGFW_F2 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(69, 0x03D, 100, DOM_VK_F3)] = RGFW_F3 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(70, 0x03E, 119, DOM_VK_F4)] = RGFW_F4 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(71, 0x03F, 97, DOM_VK_F5)] = RGFW_F5 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(72, 0x040, 98, DOM_VK_F6)] = RGFW_F6 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(73, 0x041, 99, DOM_VK_F7)] = RGFW_F7 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(74, 0x042, 101, DOM_VK_F8)] = RGFW_F8 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(75, 0x043, 102, DOM_VK_F9)] = RGFW_F9 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(76, 0x044, 110, DOM_VK_F10)] = RGFW_F10 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(95, 0x057, 104, DOM_VK_F11)] = RGFW_F11 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(96, 0x058, 112, DOM_VK_F12)] = RGFW_F12 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(111, 0x148, 126, DOM_VK_UP)] = RGFW_up RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(116, 0x150, 125, DOM_VK_DOWN)] = RGFW_down RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(113, 0x14B, 123, DOM_VK_LEFT)] = RGFW_left RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(114, 0x14D, 124, DOM_VK_RIGHT)] = RGFW_right RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(118, 0x152, 115, DOM_VK_INSERT)] = RGFW_insert RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(115, 0x14F, 120, DOM_VK_END)] = RGFW_end RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(112, 0x149, 117, DOM_VK_PAGE_UP)] = RGFW_pageUp RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(117, 0x151, 122, DOM_VK_PAGE_DOWN)] = RGFW_pageDown RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(9, 0x001, 53, DOM_VK_ESCAPE)] = RGFW_escape RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(110, 0x147, 116, DOM_VK_HOME)] = RGFW_home RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(78, 0x046, 107, DOM_VK_SCROLL_LOCK)] = RGFW_scrollLock RGFW_NEXT -#ifndef __cplusplus -}; -#else -} -#endif - -#undef RGFW_NEXT -#undef RGFW_MAP - -u32 RGFW_apiKeyToRGFW(u32 keycode) { - #ifdef __cplusplus - if (RGFW_keycodes[RGFW_OS_BASED_VALUE(49, 0x029, 50, DOM_VK_BACK_QUOTE)] != RGFW_backtick) { - RGFW_init_keys(); - } - #endif - - /* make sure the key isn't out of bounds */ - if (keycode > sizeof(RGFW_keycodes) / sizeof(u8)) - return 0; - - return RGFW_keycodes[keycode]; -} -#endif - -typedef struct { - RGFW_bool current : 1; - RGFW_bool prev : 1; -} RGFW_keyState; - -RGFW_keyState RGFW_keyboard[RGFW_keyLast] = { {0, 0} }; - -RGFWDEF void RGFW_resetKey(void); -void RGFW_resetKey(void) { - size_t len = RGFW_keyLast; /*!< last_key == length */ - - size_t i; /*!< reset each previous state */ - for (i = 0; i < len; i++) - RGFW_keyboard[i].prev = 0; -} - -/* - this is the end of keycode data -*/ - -/* gamepad data */ -RGFW_keyState RGFW_gamepadPressed[4][18]; /*!< if a key is currently pressed or not (per gamepad) */ -RGFW_point RGFW_gamepadAxes[4][4]; /*!< if a key is currently pressed or not (per gamepad) */ - -RGFW_gamepadType RGFW_gamepads_type[4]; /*!< if a key is currently pressed or not (per gamepad) */ -i32 RGFW_gamepads[4] = {0, 0, 0, 0}; /*!< limit of 4 gamepads at a time */ -char RGFW_gamepads_name[4][128]; /*!< gamepad names */ -u16 RGFW_gamepadCount = 0; /*!< the actual amount of gamepads */ - -#define RGFW_MAX_EVENTS 20 -RGFW_event RGFW_events[RGFW_MAX_EVENTS]; -size_t RGFW_eventLen = 0; -i32 RGFW_eventIndex = 0; -void RGFW_eventQueuePush(RGFW_event event) { - if (RGFW_eventLen >= RGFW_MAX_EVENTS) return; - RGFW_events[RGFW_eventLen] = event; - RGFW_eventLen++; -} - -RGFW_event* RGFW_eventQueuePop(RGFW_window* win) { - if (RGFW_eventLen == 0) return NULL; - - RGFW_event* ev = &RGFW_events[RGFW_eventIndex]; - - RGFW_eventLen--; - if (RGFW_eventLen) - RGFW_eventIndex++; - else - RGFW_eventIndex = 0; - - if (ev->_win != win && ev->_win != NULL) { - RGFW_eventQueuePush(*ev); - return NULL; - } - - ev->droppedFiles = win->event.droppedFiles; - return ev; -} - -RGFW_event* RGFW_window_checkEventCore(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - if (win->event.type == 0 && RGFW_eventLen == 0) - RGFW_resetKey(); - - if (win->event.type == RGFW_quit) { - if (win->_flags & RGFW_windowFreeOnClose) { - RGFW_window_close(win); - return (RGFW_event*)-1; - } - - return &win->event; - } - - if (win->event.type != RGFW_DNDInit) win->event.type = 0; - - /* check queued events */ - RGFW_event* ev = RGFW_eventQueuePop(win); - if (ev != NULL) win->event = *ev; - else return NULL; - - return &win->event; -} - -/* - event callback defines start here -*/ - - -/* - These exist to avoid the - if (func == NULL) check - for (allegedly) better performance -*/ -void RGFW_windowmovefuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } -void RGFW_windowresizefuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } -void RGFW_windowquitfuncEMPTY(RGFW_window* win) { RGFW_UNUSED(win); } -void RGFW_focusfuncEMPTY(RGFW_window* win, RGFW_bool inFocus) {RGFW_UNUSED(win); RGFW_UNUSED(inFocus);} -void RGFW_mouseNotifyfuncEMPTY(RGFW_window* win, RGFW_point point, RGFW_bool status) {RGFW_UNUSED(win); RGFW_UNUSED(point); RGFW_UNUSED(status);} -void RGFW_mouseposfuncEMPTY(RGFW_window* win, RGFW_point point, RGFW_point vector) {RGFW_UNUSED(win); RGFW_UNUSED(point); RGFW_UNUSED(vector);} -void RGFW_dndInitfuncEMPTY(RGFW_window* win, RGFW_point point) {RGFW_UNUSED(win); RGFW_UNUSED(point);} -void RGFW_windowrefreshfuncEMPTY(RGFW_window* win) {RGFW_UNUSED(win); } -void RGFW_keyfuncEMPTY(RGFW_window* win, RGFW_key key, char keyChar, RGFW_keymod keyMod, RGFW_bool pressed) {RGFW_UNUSED(win); RGFW_UNUSED(key); RGFW_UNUSED(keyChar); RGFW_UNUSED(keyMod); RGFW_UNUSED(pressed);} -void RGFW_mousebuttonfuncEMPTY(RGFW_window* win, RGFW_mouseButton button, double scroll, RGFW_bool pressed) {RGFW_UNUSED(win); RGFW_UNUSED(button); RGFW_UNUSED(scroll); RGFW_UNUSED(pressed);} -void RGFW_gamepadButtonfuncEMPTY(RGFW_window* win, u16 gamepad, u8 button, RGFW_bool pressed){RGFW_UNUSED(win); RGFW_UNUSED(gamepad); RGFW_UNUSED(button); RGFW_UNUSED(pressed); } -void RGFW_gamepadAxisfuncEMPTY(RGFW_window* win, u16 gamepad, RGFW_point axis[2], u8 axisesCount, u8 whichAxis){RGFW_UNUSED(win); RGFW_UNUSED(gamepad); RGFW_UNUSED(axis); RGFW_UNUSED(axisesCount); RGFW_UNUSED(whichAxis); } -void RGFW_gamepadfuncEMPTY(RGFW_window* win, u16 gamepad, RGFW_bool connected) {RGFW_UNUSED(win); RGFW_UNUSED(gamepad); RGFW_UNUSED(connected);} -void RGFW_dndfuncEMPTY(RGFW_window* win, char** droppedFiles, u32 droppedFilesCount) {RGFW_UNUSED(win); RGFW_UNUSED(droppedFiles); RGFW_UNUSED(droppedFilesCount);} - -RGFW_windowmovefunc RGFW_windowMoveCallback = RGFW_windowmovefuncEMPTY; -RGFW_windowresizefunc RGFW_windowResizeCallback = RGFW_windowresizefuncEMPTY; -RGFW_windowresizefunc RGFW_windowMaximizedCallback = RGFW_windowresizefuncEMPTY; -RGFW_windowresizefunc RGFW_windowMinimizedCallback = RGFW_windowresizefuncEMPTY; -RGFW_windowresizefunc RGFW_windowRestoredCallback = RGFW_windowresizefuncEMPTY; -RGFW_windowquitfunc RGFW_windowQuitCallback = RGFW_windowquitfuncEMPTY; -RGFW_mouseposfunc RGFW_mousePosCallback = RGFW_mouseposfuncEMPTY; -RGFW_windowrefreshfunc RGFW_windowRefreshCallback = RGFW_windowrefreshfuncEMPTY; -RGFW_focusfunc RGFW_focusCallback = RGFW_focusfuncEMPTY; -RGFW_mouseNotifyfunc RGFW_mouseNotifyCallBack = RGFW_mouseNotifyfuncEMPTY; -RGFW_dndfunc RGFW_dndCallback = RGFW_dndfuncEMPTY; -RGFW_dndInitfunc RGFW_dndInitCallback = RGFW_dndInitfuncEMPTY; -RGFW_keyfunc RGFW_keyCallback = RGFW_keyfuncEMPTY; -RGFW_mousebuttonfunc RGFW_mouseButtonCallback = RGFW_mousebuttonfuncEMPTY; -RGFW_gamepadButtonfunc RGFW_gamepadButtonCallback = RGFW_gamepadButtonfuncEMPTY; -RGFW_gamepadAxisfunc RGFW_gamepadAxisCallback = RGFW_gamepadAxisfuncEMPTY; -RGFW_gamepadfunc RGFW_gamepadCallback = RGFW_gamepadfuncEMPTY; - -void RGFW_window_checkEvents(RGFW_window* win, u32 waitMS) { - RGFW_window_eventWait(win, waitMS); - - while (RGFW_window_checkEvent(win) != NULL && RGFW_window_shouldClose(win) == 0) { - if (win->event.type == RGFW_quit) return; - } - - #ifdef RGFW_WASM /* WASM needs to run the sleep function for asyncify */ - RGFW_sleep(0); - #endif -} - -RGFW_windowmovefunc RGFW_setWindowMoveCallback(RGFW_windowmovefunc func) { - RGFW_windowmovefunc prev = (RGFW_windowMoveCallback == RGFW_windowmovefuncEMPTY) ? NULL : RGFW_windowMoveCallback; - RGFW_windowMoveCallback = func; - return prev; -} -RGFW_windowresizefunc RGFW_setWindowResizeCallback(RGFW_windowresizefunc func) { - RGFW_windowresizefunc prev = (RGFW_windowResizeCallback == RGFW_windowresizefuncEMPTY) ? NULL : RGFW_windowResizeCallback; - RGFW_windowResizeCallback = func; - return prev; -} -RGFW_windowresizefunc RGFW_setWindowMaximizedCallback(RGFW_windowresizefunc func) { - RGFW_windowresizefunc prev = (RGFW_windowMaximizedCallback == RGFW_windowresizefuncEMPTY) ? NULL : RGFW_windowMaximizedCallback; - RGFW_windowMaximizedCallback = func; - return prev; -} -RGFW_windowresizefunc RGFW_setWindowMinimizedCallback(RGFW_windowresizefunc func) { - RGFW_windowresizefunc prev = (RGFW_windowMinimizedCallback == RGFW_windowresizefuncEMPTY) ? NULL : RGFW_windowMinimizedCallback; - RGFW_windowMinimizedCallback = func; - return prev; -} -RGFW_windowresizefunc RGFW_setWindowRestoredCallback(RGFW_windowresizefunc func) { - RGFW_windowresizefunc prev = (RGFW_windowRestoredCallback == RGFW_windowresizefuncEMPTY) ? NULL : RGFW_windowRestoredCallback; - RGFW_windowRestoredCallback = func; - return prev; -} -RGFW_windowquitfunc RGFW_setWindowQuitCallback(RGFW_windowquitfunc func) { - RGFW_windowquitfunc prev = (RGFW_windowQuitCallback == RGFW_windowquitfuncEMPTY) ? NULL : RGFW_windowQuitCallback; - RGFW_windowQuitCallback = func; - return prev; -} - -RGFW_mouseposfunc RGFW_setMousePosCallback(RGFW_mouseposfunc func) { - RGFW_mouseposfunc prev = (RGFW_mousePosCallback == RGFW_mouseposfuncEMPTY) ? NULL : RGFW_mousePosCallback; - RGFW_mousePosCallback = func; - return prev; -} -RGFW_windowrefreshfunc RGFW_setWindowRefreshCallback(RGFW_windowrefreshfunc func) { - RGFW_windowrefreshfunc prev = (RGFW_windowRefreshCallback == RGFW_windowrefreshfuncEMPTY) ? NULL : RGFW_windowRefreshCallback; - RGFW_windowRefreshCallback = func; - return prev; -} -RGFW_focusfunc RGFW_setFocusCallback(RGFW_focusfunc func) { - RGFW_focusfunc prev = (RGFW_focusCallback == RGFW_focusfuncEMPTY) ? NULL : RGFW_focusCallback; - RGFW_focusCallback = func; - return prev; -} - -RGFW_mouseNotifyfunc RGFW_setMouseNotifyCallBack(RGFW_mouseNotifyfunc func) { - RGFW_mouseNotifyfunc prev = (RGFW_mouseNotifyCallBack == RGFW_mouseNotifyfuncEMPTY) ? NULL : RGFW_mouseNotifyCallBack; - RGFW_mouseNotifyCallBack = func; - return prev; -} -RGFW_dndfunc RGFW_setDndCallback(RGFW_dndfunc func) { - RGFW_dndfunc prev = (RGFW_dndCallback == RGFW_dndfuncEMPTY) ? NULL : RGFW_dndCallback; - RGFW_dndCallback = func; - return prev; -} -RGFW_dndInitfunc RGFW_setDndInitCallback(RGFW_dndInitfunc func) { - RGFW_dndInitfunc prev = (RGFW_dndInitCallback == RGFW_dndInitfuncEMPTY) ? NULL : RGFW_dndInitCallback; - RGFW_dndInitCallback = func; - return prev; -} -RGFW_keyfunc RGFW_setKeyCallback(RGFW_keyfunc func) { - RGFW_keyfunc prev = (RGFW_keyCallback == RGFW_keyfuncEMPTY) ? NULL : RGFW_keyCallback; - RGFW_keyCallback = func; - return prev; -} -RGFW_mousebuttonfunc RGFW_setMouseButtonCallback(RGFW_mousebuttonfunc func) { - RGFW_mousebuttonfunc prev = (RGFW_mouseButtonCallback == RGFW_mousebuttonfuncEMPTY) ? NULL : RGFW_mouseButtonCallback; - RGFW_mouseButtonCallback = func; - return prev; -} -RGFW_gamepadButtonfunc RGFW_setgamepadButtonCallback(RGFW_gamepadButtonfunc func) { - RGFW_gamepadButtonfunc prev = (RGFW_gamepadButtonCallback == RGFW_gamepadButtonfuncEMPTY) ? NULL : RGFW_gamepadButtonCallback; - RGFW_gamepadButtonCallback = func; - return prev; -} -RGFW_gamepadAxisfunc RGFW_setgamepadAxisCallback(RGFW_gamepadAxisfunc func) { - RGFW_gamepadAxisfunc prev = (RGFW_gamepadAxisCallback == RGFW_gamepadAxisfuncEMPTY) ? NULL : RGFW_gamepadAxisCallback; - RGFW_gamepadAxisCallback = func; - return prev; -} -RGFW_gamepadfunc RGFW_setGamepadCallback(RGFW_gamepadfunc func) { - RGFW_gamepadfunc prev = (RGFW_gamepadCallback == RGFW_gamepadfuncEMPTY) ? NULL : RGFW_gamepadCallback; - RGFW_gamepadCallback = func; - return prev; -} - -void RGFW_window_checkMode(RGFW_window* win) { - if (RGFW_window_isMinimized(win)) { - win->_flags |= RGFW_windowMinimize; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMinimized, ._win = win}); - RGFW_windowMinimizedCallback(win, win->r); - } else if (RGFW_window_isMaximized(win)) { - win->_flags |= RGFW_windowMaximize; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMaximized, ._win = win}); - RGFW_windowMaximizedCallback(win, win->r); - } else if (((win->_flags & RGFW_windowMinimize) && !RGFW_window_isMaximized(win)) || - (win->_flags & RGFW_windowMaximize && !RGFW_window_isMaximized(win))) { - win->_flags &= ~RGFW_windowMinimize; - if (RGFW_window_isMaximized(win) == RGFW_FALSE) win->_flags &= ~RGFW_windowMaximize; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowRestored, ._win = win}); - RGFW_windowRestoredCallback(win, win->r); - } -} - -/* -no more event call back defines -*/ - -#define SET_ATTRIB(a, v) { \ - RGFW_ASSERT(((size_t) index + 1) < sizeof(attribs) / sizeof(attribs[0])); \ - attribs[index++] = a; \ - attribs[index++] = v; \ -} - -#define RGFW_EVENT_PASSED RGFW_BIT(24) /* if a queued event was passed */ -#define RGFW_NO_GPU_RENDER RGFW_BIT(25) /* don't render (using the GPU based API) */ -#define RGFW_NO_CPU_RENDER RGFW_BIT(26) /* don't render (using the CPU based buffer rendering) */ -#define RGFW_HOLD_MOUSE RGFW_BIT(27) /*!< hold the moues still */ -#define RGFW_MOUSE_LEFT RGFW_BIT(28) /* if mouse left the window */ -#define RGFW_WINDOW_ALLOC RGFW_BIT(29) /* if window was allocated by RGFW */ -#define RGFW_BUFFER_ALLOC RGFW_BIT(30) /* if window.buffer was allocated by RGFW */ -#define RGFW_WINDOW_INIT RGFW_BIT(31) /* if window.buffer was allocated by RGFW */ -#define RGFW_INTERNAL_FLAGS (RGFW_EVENT_PASSED | RGFW_NO_GPU_RENDER | RGFW_NO_CPU_RENDER | RGFW_HOLD_MOUSE | RGFW_MOUSE_LEFT | RGFW_WINDOW_ALLOC | RGFW_BUFFER_ALLOC | RGFW_windowFocus) - - -RGFW_window* RGFW_createWindow(const char* name, RGFW_rect rect, RGFW_windowFlags flags) { - RGFW_window* win = (RGFW_window*)RGFW_ALLOC(sizeof(RGFW_window)); - win->_flags = RGFW_WINDOW_ALLOC; - return RGFW_createWindowPtr(name, rect, flags, win); -} - -#if defined(RGFW_USE_XDL) && defined(RGFW_X11) - #define XDL_IMPLEMENTATION - #include "XDL.h" -#endif - -RGFWDEF void RGFW_window_basic_init(RGFW_window* win, RGFW_rect rect, RGFW_windowFlags flags); -#if defined(RGFW_X11) || defined(RGFW_WINDOWS) -RGFW_mouse* RGFW_hiddenMouse = NULL; -#endif - -RGFW_window* RGFW_root = NULL; -void RGFW_setRootWindow(RGFW_window* win) { RGFW_root = win; } -RGFW_window* RGFW_getRootWindow(void) { return RGFW_root; } - -/* do a basic initialization for RGFW_window, this is to standard it for each OS */ -void RGFW_window_basic_init(RGFW_window* win, RGFW_rect rect, RGFW_windowFlags flags) { - RGFW_UNUSED(flags); - /* rect based the requested flags */ - if (RGFW_root == NULL) { - RGFW_setRootWindow(win); - RGFW_setTime(0); - #ifdef RGFW_X11 - RGFW_root->src.display = XOpenDisplay(NULL); - #endif - } - - #ifdef RGFW_X11 - win->src.clipboard = NULL; - win->src.display = RGFW_root->src.display; - RGFW_ASSERT(win->src.display != NULL); - #endif - - #if defined(RGFW_X11) || defined(RGFW_WINDOWS) - if (RGFW_hiddenMouse == NULL) { - u8 RGFW_blk[] = { 0, 0, 0, 0 }; - RGFW_hiddenMouse = RGFW_loadMouse(RGFW_blk, RGFW_AREA(1, 1), 4); - } - #endif - - if (!(win->_flags & RGFW_WINDOW_ALLOC)) win->_flags = 0; - - /* set and init the new window's data */ - win->r = rect; - win->event.droppedFilesCount = 0; - win->_flags |= flags; - win->event.keyMod = 0; - - win->event.droppedFiles = (char**)RGFW_ALLOC(RGFW_MAX_PATH * RGFW_MAX_DROPS); - for (u32 i = 0; i < RGFW_MAX_DROPS; i++) - win->event.droppedFiles[i] = (char*)(win->event.droppedFiles + RGFW_MAX_DROPS + (i * RGFW_MAX_PATH)); -} - -void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags flags) { - RGFW_windowFlags cmpFlags = win->_flags; - if (win->_flags & RGFW_WINDOW_INIT) cmpFlags = 0; - - #ifndef RGFW_NO_MONITOR - if (flags & RGFW_windowScaleToMonitor) RGFW_window_scaleToMonitor(win); - #endif - - if (flags & RGFW_windowCenter) RGFW_window_center(win); - if (flags & RGFW_windowCenterCursor) - RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); - if (flags & RGFW_windowNoBorder) RGFW_window_setBorder(win, 0); - else RGFW_window_setBorder(win, 1); - if (flags & RGFW_windowFullscreen) RGFW_window_setFullscreen(win, RGFW_TRUE); - else if (cmpFlags & RGFW_windowFullscreen) RGFW_window_setFullscreen(win, 0); - if (flags & RGFW_windowMaximize) RGFW_window_maximize(win); - else if (cmpFlags & RGFW_windowMaximize) RGFW_window_restore(win); - if (flags & RGFW_windowMinimize) RGFW_window_minimize(win); - else if (cmpFlags & RGFW_windowMinimize) RGFW_window_restore(win); - if (flags & RGFW_windowHideMouse) RGFW_window_showMouse(win, 0); - else if (cmpFlags & RGFW_windowHideMouse) RGFW_window_showMouse(win, 1); - if (flags & RGFW_windowCocoaCHDirToRes) RGFW_moveToMacOSResourceDir(); - if (flags & RGFW_windowFloating) RGFW_window_setFloating(win, 1); - else if (cmpFlags & RGFW_windowFloating) RGFW_window_setFloating(win, 0); - if (flags & RGFW_windowFocus) RGFW_window_focus(win); - win->_flags = flags | (win->_flags & RGFW_INTERNAL_FLAGS); -} - -RGFW_bool RGFW_window_isInFocus(RGFW_window* win) { return RGFW_BOOL(win->_flags & RGFW_windowFocus); } - -void RGFW_window_initBuffer(RGFW_window* win) { - RGFW_window_initBufferSize(win, RGFW_getScreenSize()); -} - -void RGFW_window_initBufferSize(RGFW_window* win, RGFW_area area) { - win->_flags |= RGFW_BUFFER_ALLOC; - #ifndef RGFW_WINDOWS - RGFW_window_initBufferPtr(win, (u8*)RGFW_ALLOC(area.w * area.h * 4), area); - #else /* windows's bitmap allocs memory for us */ - RGFW_window_initBufferPtr(win, (u8*)NULL, area); - #endif -} - -#ifdef RGFW_MACOS -RGFWDEF void RGFW_window_cocoaSetLayer(RGFW_window* win, void* layer); -RGFWDEF void* RGFW_cocoaGetLayer(void); -#endif - -const char* RGFW_className = NULL; -void RGFW_setClassName(const char* name) { RGFW_className = name; } - -#ifndef RGFW_X11 -void RGFW_setXInstName(const char* name) { RGFW_UNUSED(name); } -#endif - -RGFW_keyState RGFW_mouseButtons[RGFW_mouseFinal] = { {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - -RGFW_bool RGFW_isMousePressed(RGFW_window* win, RGFW_mouseButton button) { - return RGFW_mouseButtons[button].current && (win == NULL || RGFW_window_isInFocus(win)); -} -RGFW_bool RGFW_wasMousePressed(RGFW_window* win, RGFW_mouseButton button) { - return RGFW_mouseButtons[button].prev && (win != NULL || RGFW_window_isInFocus(win)); -} -RGFW_bool RGFW_isMouseHeld(RGFW_window* win, RGFW_mouseButton button) { - return (RGFW_isMousePressed(win, button) && RGFW_wasMousePressed(win, button)); -} -RGFW_bool RGFW_isMouseReleased(RGFW_window* win, RGFW_mouseButton button) { - return (!RGFW_isMousePressed(win, button) && RGFW_wasMousePressed(win, button)); -} - -RGFW_point RGFW_window_getMousePoint(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - return win->_lastMousePoint; -} - -RGFW_bool RGFW_isPressed(RGFW_window* win, RGFW_key key) { - return RGFW_keyboard[key].current && (win == NULL || RGFW_window_isInFocus(win)); -} - -RGFW_bool RGFW_wasPressed(RGFW_window* win, RGFW_key key) { - return RGFW_keyboard[key].prev && (win == NULL || RGFW_window_isInFocus(win)); -} - -RGFW_bool RGFW_isHeld(RGFW_window* win, RGFW_key key) { - return (RGFW_isPressed(win, key) && RGFW_wasPressed(win, key)); -} - -RGFW_bool RGFW_isClicked(RGFW_window* win, RGFW_key key) { - return (RGFW_wasPressed(win, key) && !RGFW_isPressed(win, key)); -} - -RGFW_bool RGFW_isReleased(RGFW_window* win, RGFW_key key) { - return (!RGFW_isPressed(win, key) && RGFW_wasPressed(win, key)); -} - -#ifndef RGFW_CUSTOM_BACKEND -void RGFW_window_makeCurrent(RGFW_window* win) { -#if defined(RGFW_OPENGL) - RGFW_window_makeCurrent_OpenGL(win); -#else - RGFW_UNUSED(win); -#endif -} -#endif - -RGFWDEF void RGFW_setBit(u32* data, u32 bit, RGFW_bool value); -void RGFW_setBit(u32* data, u32 bit, RGFW_bool value) { - if (value) - *data |= bit; - else if (!value && (*(data) & bit)) - *data ^= bit; -} - -void RGFW_window_setGPURender(RGFW_window* win, RGFW_bool set) { - RGFW_setBit(&win->_flags, RGFW_NO_GPU_RENDER, !set); -} - -void RGFW_window_setCPURender(RGFW_window* win, RGFW_bool set) { - RGFW_setBit(&win->_flags, RGFW_NO_CPU_RENDER, !set); -} - -void RGFW_window_center(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - RGFW_area screenR = RGFW_getScreenSize(); - RGFW_window_move(win, RGFW_POINT((screenR.w - win->r.w) / 2, (screenR.h - win->r.h) / 2)); -} - -RGFW_bool RGFW_monitor_scaleToWindow(RGFW_monitor mon, RGFW_window* win) { - RGFW_ASSERT(win != NULL); - - RGFW_monitorMode mode; - mode.area = RGFW_AREA(win->r.w, win->r.h); - return RGFW_monitor_requestMode(mon, mode, RGFW_monitorScale); -} - - -void RGFW_splitBPP(u32 bpp, RGFW_monitorMode* mode) { - if (bpp == 32) bpp = 24; - mode->red = mode->green = mode->blue = bpp / 3; - - u32 delta = bpp - (mode->red * 3); // handle leftovers - if (delta >= 1) mode->green = mode->green + 1; - if (delta == 2) mode->red = mode->red + 1; -} - -RGFW_bool RGFW_monitorModeCompare(RGFW_monitorMode mon, RGFW_monitorMode mon2, RGFW_modeRequest request) { - return (((mon.area.w == mon2.area.w && mon.area.h == mon2.area.h) || !(request & RGFW_monitorScale)) && - ((mon.refreshRate == mon2.refreshRate) || !(request & RGFW_monitorRefresh)) && - ((mon.red == mon2.red && mon.green == mon2.green && mon.blue == mon2.blue) || !(request & RGFW_monitorRGB))); -} - -RGFW_bool RGFW_window_shouldClose(RGFW_window* win) { - return (win == NULL || win->event.type == RGFW_quit || RGFW_isPressed(win, RGFW_escape)); -} - -void RGFW_window_setShouldClose(RGFW_window* win) { win->event.type = RGFW_quit; RGFW_windowQuitCallback(win); } - -#ifndef RGFW_NO_MONITOR -void RGFW_window_scaleToMonitor(RGFW_window* win) { - RGFW_monitor monitor = RGFW_window_getMonitor(win); - if (monitor.scaleX == 0 && monitor.scaleY == 0) - return; - - RGFW_window_resize(win, RGFW_AREA((u32)(monitor.scaleX * (float)win->r.w), (u32)(monitor.scaleY * (float)win->r.h))); -} - -void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor m) { - RGFW_window_move(win, RGFW_POINT(m.x + win->r.x, m.y + win->r.y)); -} -#endif - -RGFW_bool RGFW_window_setIcon(RGFW_window* win, u8* icon, RGFW_area a, i32 channels) { - return RGFW_window_setIconEx(win, icon, a, channels, RGFW_iconBoth); -} - -RGFWDEF void RGFW_captureCursor(RGFW_window* win, RGFW_rect); -RGFWDEF void RGFW_releaseCursor(RGFW_window* win); - -void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area) { - if ((win->_flags & RGFW_HOLD_MOUSE)) - return; - - if (!area.w && !area.h) - area = RGFW_AREA(win->r.w / 2, win->r.h / 2); - - win->_flags |= RGFW_HOLD_MOUSE; - RGFW_captureCursor(win, win->r); - RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); -} - -void RGFW_window_mouseUnhold(RGFW_window* win) { - win->_flags &= ~RGFW_HOLD_MOUSE; - RGFW_releaseCursor(win); -} - -u32 RGFW_checkFPS(double startTime, u32 frameCount, u32 fpsCap) { - double deltaTime = RGFW_getTime() - startTime; - if (deltaTime == 0) return 0; - - double fps = (frameCount / deltaTime); /* the numer of frames over the time it took for them to render */ - if (fpsCap && fps > fpsCap) { - double frameTime = frameCount / (float)fpsCap; /* how long it should take to finish the frames */ - double sleepTime = frameTime - deltaTime; /* subtract how long it should have taken with how long it did take */ - - if (sleepTime > 0) RGFW_sleep((u32)(sleepTime * 1000)); - } - - return (u32) fps; -} - -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) -void RGFW_RGB_to_BGR(RGFW_window* win, u8* data) { - #if !defined(RGFW_BUFFER_BGR) && !defined(RGFW_OSMESA) - u32 x, y; - for (y = 0; y < (u32)win->r.h; y++) { - for (x = 0; x < (u32)win->r.w; x++) { - u32 index = (y * 4 * win->bufferSize.w) + x * 4; - - u8 red = data[index]; - data[index] = win->buffer[index + 2]; - data[index + 2] = red; - } - } - #endif -} -#endif - -u32 RGFW_isPressedGamepad(RGFW_window* win, u8 c, RGFW_gamepadCodes button) { - RGFW_UNUSED(win); - return RGFW_gamepadPressed[c][button].current; -} -u32 RGFW_wasPressedGamepad(RGFW_window* win, u8 c, RGFW_gamepadCodes button) { - RGFW_UNUSED(win); - return RGFW_gamepadPressed[c][button].prev; -} -u32 RGFW_isReleasedGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button) { - RGFW_UNUSED(win); - return !RGFW_isPressedGamepad(win, controller, button) && RGFW_wasPressedGamepad(win, controller, button); -} -u32 RGFW_isHeldGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button) { - RGFW_UNUSED(win); - return RGFW_isPressedGamepad(win, controller, button) && RGFW_wasPressedGamepad(win, controller, button); -} - -RGFW_point RGFW_getGamepadAxis(RGFW_window* win, u16 controller, u16 whichAxis) { - RGFW_UNUSED(win); - return RGFW_gamepadAxes[controller][whichAxis]; -} -const char* RGFW_getGamepadName(RGFW_window* win, u16 controller) { - RGFW_UNUSED(win); - return (const char*)RGFW_gamepads_name[controller]; -} - -size_t RGFW_getGamepadCount(RGFW_window* win) { - RGFW_UNUSED(win); - return RGFW_gamepadCount; -} - -RGFW_gamepadType RGFW_getGamepadType(RGFW_window* win, u16 controller) { - RGFW_UNUSED(win); - return RGFW_gamepads_type[controller]; -} - -RGFWDEF void RGFW_updateKeyMod(RGFW_window* win, RGFW_keymod mod, RGFW_bool value); -void RGFW_updateKeyMod(RGFW_window* win, RGFW_keymod mod, RGFW_bool value) { - RGFW_setBit((u32*)&win->event.keyMod, mod, value); -} - -RGFWDEF void RGFW_updateKeyModsPro(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll); -void RGFW_updateKeyModsPro(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll) { - RGFW_updateKeyMod(win, RGFW_modCapsLock, capital); - RGFW_updateKeyMod(win, RGFW_modNumLock, numlock); - RGFW_updateKeyMod(win, RGFW_modControl, control); - RGFW_updateKeyMod(win, RGFW_modAlt, alt); - RGFW_updateKeyMod(win, RGFW_modShift, shift); - RGFW_updateKeyMod(win, RGFW_modSuper, super); - RGFW_updateKeyMod(win, RGFW_modScrollLock, scroll); -} - -RGFWDEF void RGFW_updateKeyMods(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool scroll); -void RGFW_updateKeyMods(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool scroll) { - RGFW_updateKeyModsPro(win, capital, numlock, - RGFW_isPressed(win, RGFW_controlL) || RGFW_isPressed(win, RGFW_controlR), - RGFW_isPressed(win, RGFW_altL) || RGFW_isPressed(win, RGFW_altR), - RGFW_isPressed(win, RGFW_shiftL) || RGFW_isPressed(win, RGFW_shiftR), - RGFW_isPressed(win, RGFW_superL) || RGFW_isPressed(win, RGFW_superR), - scroll); -} - -RGFWDEF void RGFW_window_showMouseFlags(RGFW_window* win, RGFW_bool show); -void RGFW_window_showMouseFlags(RGFW_window* win, RGFW_bool show) { - if (show && (win->_flags & RGFW_windowHideMouse)) - win->_flags ^= RGFW_windowHideMouse; - else if (!show && !(win->_flags & RGFW_windowHideMouse)) - win->_flags |= RGFW_windowHideMouse; -} - -RGFW_bool RGFW_window_mouseHidden(RGFW_window* win) { - return (RGFW_bool)RGFW_BOOL(win->_flags & RGFW_windowHideMouse); -} - -RGFW_bool RGFW_window_borderless(RGFW_window* win) { - return (RGFW_bool)RGFW_BOOL(win->_flags & RGFW_windowNoBorder); -} - -RGFW_bool RGFW_window_isFullscreen(RGFW_window* win){ return RGFW_BOOL(win->_flags & RGFW_windowFullscreen); } -RGFW_bool RGFW_window_allowsDND(RGFW_window* win) { return RGFW_BOOL(win->_flags & RGFW_windowAllowDND); } - -#ifndef RGFW_WINDOWS -void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow) { - RGFW_setBit(&win->_flags, RGFW_windowAllowDND, allow); -} -#endif - -#if defined(RGFW_X11) || defined(RGFW_MACOS) || defined(RGFW_WASM) || defined(RGFW_WAYLAND) -#include -struct timespec; - -#ifndef RGFW_NO_UNIX_CLOCK -int nanosleep(const struct timespec* duration, struct timespec* rem); -int clock_gettime(clockid_t clk_id, struct timespec* tp); -#endif - -int setenv(const char *name, const char *value, int overwrite); -#endif - -#if defined(RGFW_X11) || defined(RGFW_WINDOWS) -void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show) { - RGFW_window_showMouseFlags(win, show); - if (show == 0) - RGFW_window_setMouse(win, RGFW_hiddenMouse); - else - RGFW_window_setMouseDefault(win); -} -#endif - -#ifndef RGFW_MACOS -void RGFW_moveToMacOSResourceDir(void) { } -#endif - -/* - graphics API specific code (end of generic code) - starts here -*/ - - -/* - OpenGL defines start here (Normal, EGL, OSMesa) -*/ - -#if defined(RGFW_OPENGL) || defined(RGFW_EGL) - -#ifdef RGFW_WINDOWS - #define WIN32_LEAN_AND_MEAN - #define OEMRESOURCE - #include -#endif - -#if !defined(__APPLE__) && !defined(RGFW_NO_GL_HEADER) - #include -#elif defined(__APPLE__) - #ifndef GL_SILENCE_DEPRECATION - #define GL_SILENCE_DEPRECATION - #endif - #include - #include -#endif - -/* EGL, normal OpenGL only */ -#ifndef RGFW_EGL -i32 RGFW_GL_HINTS[RGFW_glFinalHint] = {8, 4, -#else -i32 RGFW_GL_HINTS[RGFW_glFinalHint] = {0, 0, -#endif - 0, 0, 1, 8, 8, 8, 8, 24, 0, 0, 0, 0, 0, 0, 0, 0, RGFW_glReleaseNone, RGFW_glCore, 0, 0}; - -void RGFW_setGLHint(RGFW_glHints hint, i32 value) { - if (hint < RGFW_glFinalHint && hint) RGFW_GL_HINTS[hint] = value; -} - -/* OPENGL normal only (no EGL / OSMesa) */ -#if !defined(RGFW_EGL) && !defined(RGFW_CUSTOM_BACKEND) - -#define RGFW_GL_RENDER_TYPE RGFW_OS_BASED_VALUE(GLX_X_VISUAL_TYPE, 0x2003, 73, 0) - #define RGFW_GL_ALPHA_SIZE RGFW_OS_BASED_VALUE(GLX_ALPHA_SIZE, 0x201b, 11, 0) - #define RGFW_GL_DEPTH_SIZE RGFW_OS_BASED_VALUE(GLX_DEPTH_SIZE, 0x2022, 12, 0) - #define RGFW_GL_DOUBLEBUFFER RGFW_OS_BASED_VALUE(GLX_DOUBLEBUFFER, 0x2011, 5, 0) - #define RGFW_GL_STENCIL_SIZE RGFW_OS_BASED_VALUE(GLX_STENCIL_SIZE, 0x2023, 13, 0) - #define RGFW_GL_SAMPLES RGFW_OS_BASED_VALUE(GLX_SAMPLES, 0x2042, 55, 0) - #define RGFW_GL_STEREO RGFW_OS_BASED_VALUE(GLX_STEREO, 0x2012, 6, 0) - #define RGFW_GL_AUX_BUFFERS RGFW_OS_BASED_VALUE(GLX_AUX_BUFFERS, 0x2024, 7, 0) - -#if defined(RGFW_X11) || defined(RGFW_WINDOWS) - #define RGFW_GL_DRAW RGFW_OS_BASED_VALUE(GLX_X_RENDERABLE, 0x2001, 0, 0) - #define RGFW_GL_DRAW_TYPE RGFW_OS_BASED_VALUE(GLX_RENDER_TYPE, 0x2013, 0, 0) - #define RGFW_GL_FULL_FORMAT RGFW_OS_BASED_VALUE(GLX_TRUE_COLOR, 0x2027, 0, 0) - #define RGFW_GL_RED_SIZE RGFW_OS_BASED_VALUE(GLX_RED_SIZE, 0x2015, 0, 0) - #define RGFW_GL_GREEN_SIZE RGFW_OS_BASED_VALUE(GLX_GREEN_SIZE, 0x2017, 0, 0) - #define RGFW_GL_BLUE_SIZE RGFW_OS_BASED_VALUE(GLX_BLUE_SIZE, 0x2019, 0, 0) - #define RGFW_GL_USE_RGBA RGFW_OS_BASED_VALUE(GLX_RGBA_BIT, 0x202B, 0, 0) - #define RGFW_GL_ACCUM_RED_SIZE RGFW_OS_BASED_VALUE(14, 0x201E, 0, 0) - #define RGFW_GL_ACCUM_GREEN_SIZE RGFW_OS_BASED_VALUE(15, 0x201F, 0, 0) - #define RGFW_GL_ACCUM_BLUE_SIZE RGFW_OS_BASED_VALUE(16, 0x2020, 0, 0) - #define RGFW_GL_ACCUM_ALPHA_SIZE RGFW_OS_BASED_VALUE(17, 0x2021, 0, 0) - #define RGFW_GL_SRGB RGFW_OS_BASED_VALUE(0x20b2, 0x3089, 0, 0) - #define RGFW_GL_NOERROR RGFW_OS_BASED_VALUE(0x31b3, 0x31b3, 0, 0) - #define RGFW_GL_FLAGS RGFW_OS_BASED_VALUE(GLX_CONTEXT_FLAGS_ARB, 0x2094, 0, 0) - #define RGFW_GL_RELEASE_BEHAVIOR RGFW_OS_BASED_VALUE(GLX_CONTEXT_RELEASE_BEHAVIOR_ARB, 0x2097 , 0, 0) - #define RGFW_GL_CONTEXT_RELEASE RGFW_OS_BASED_VALUE(GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB, 0x2098, 0, 0) - #define RGFW_GL_CONTEXT_NONE RGFW_OS_BASED_VALUE(GLX_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB, 0x0000, 0, 0) - #define RGFW_GL_FLAGS RGFW_OS_BASED_VALUE(GLX_CONTEXT_FLAGS_ARB, 0x2094, 0, 0) - #define RGFW_GL_DEBUG_BIT RGFW_OS_BASED_VALUE(GLX_CONTEXT_FLAGS_ARB, 0x2094, 0, 0) - #define RGFW_GL_ROBUST_BIT RGFW_OS_BASED_VALUE(GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB, 0x00000004, 0, 0) -#endif - -#ifdef RGFW_WINDOWS - #define WGL_SUPPORT_OPENGL_ARB 0x2010 - #define WGL_COLOR_BITS_ARB 0x2014 - #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_SAMPLE_BUFFERS_ARB 0x2041 - #define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9 - #define WGL_PIXEL_TYPE_ARB 0x2013 - #define WGL_TYPE_RGBA_ARB 0x202B - - #define WGL_TRANSPARENT_ARB 0x200A -#endif - -/* The window'ing api needs to know how to render the data we (or opengl) give it - MacOS and Windows do this using a structure called a "pixel format" - X11 calls it a "Visual" - This function returns the attributes for the format we want */ -u32* RGFW_initFormatAttribs(u32 useSoftware) { - RGFW_UNUSED(useSoftware); - static u32 attribs[] = { - #if defined(RGFW_X11) || defined(RGFW_WINDOWS) - RGFW_GL_RENDER_TYPE, - RGFW_GL_FULL_FORMAT, - RGFW_GL_DRAW, 1, - RGFW_GL_DRAW_TYPE , RGFW_GL_USE_RGBA, - #endif - - #ifdef RGFW_X11 - GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT, - #endif - - #ifdef RGFW_MACOS - 72, - 8, 24, - #endif - - #ifdef RGFW_WINDOWS - WGL_SUPPORT_OPENGL_ARB, 1, - WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, - WGL_COLOR_BITS_ARB, 32, - #endif - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - - size_t index = (sizeof(attribs) / sizeof(attribs[0])) - 27; - - #define RGFW_GL_ADD_ATTRIB(attrib, attVal) \ - if (attVal) { \ - attribs[index] = attrib;\ - attribs[index + 1] = attVal;\ - index += 2;\ - } - - #if defined(RGFW_MACOS) && defined(RGFW_COCOA_GRAPHICS_SWITCHING) - RGFW_GL_ADD_ATTRIB(96, kCGLPFASupportsAutomaticGraphicsSwitching); - #endif - - RGFW_GL_ADD_ATTRIB(RGFW_GL_DOUBLEBUFFER, 1); - - RGFW_GL_ADD_ATTRIB(RGFW_GL_ALPHA_SIZE, RGFW_GL_HINTS[RGFW_glAlpha]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_DEPTH_SIZE, RGFW_GL_HINTS[RGFW_glDepth]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_STENCIL_SIZE, RGFW_GL_HINTS[RGFW_glStencil]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_STEREO, RGFW_GL_HINTS[RGFW_glStereo]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_AUX_BUFFERS, RGFW_GL_HINTS[RGFW_glAuxBuffers]); - - #if defined(RGFW_X11) || defined(RGFW_WINDOWS) - RGFW_GL_ADD_ATTRIB(RGFW_GL_RED_SIZE, RGFW_GL_HINTS[RGFW_glRed]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_GREEN_SIZE, RGFW_GL_HINTS[RGFW_glBlue]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_BLUE_SIZE, RGFW_GL_HINTS[RGFW_glGreen]); - #endif - - #if defined(RGFW_X11) || defined(RGFW_WINDOWS) - RGFW_GL_ADD_ATTRIB(RGFW_GL_ACCUM_RED_SIZE, RGFW_GL_HINTS[RGFW_glAccumRed]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_ACCUM_GREEN_SIZE, RGFW_GL_HINTS[RGFW_glAccumBlue]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_ACCUM_BLUE_SIZE, RGFW_GL_HINTS[RGFW_glAccumGreen]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_ACCUM_ALPHA_SIZE, RGFW_GL_HINTS[RGFW_glAccumAlpha]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_SRGB, RGFW_GL_HINTS[RGFW_glSRGB]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_NOERROR, RGFW_GL_HINTS[RGFW_glNoError]); - - if (RGFW_GL_HINTS[RGFW_glReleaseBehavior] == RGFW_releaseFlush) { - RGFW_GL_ADD_ATTRIB(RGFW_GL_RELEASE_BEHAVIOR, RGFW_GL_CONTEXT_RELEASE); - } else if (RGFW_GL_HINTS[RGFW_glReleaseBehavior] == RGFW_glReleaseNone) { - RGFW_GL_ADD_ATTRIB(RGFW_GL_RELEASE_BEHAVIOR, RGFW_GL_CONTEXT_NONE); - } - - u32 flags = 0; - if (RGFW_GL_HINTS[RGFW_glDebug]) flags |= RGFW_GL_DEBUG_BIT; - if (RGFW_GL_HINTS[RGFW_glRobustness]) flags |= RGFW_GL_ROBUST_BIT; - RGFW_GL_ADD_ATTRIB(RGFW_GL_FLAGS, flags); - #else - u32 accumSize = (RGFW_GL_HINTS[RGFW_glAccumRed] + RGFW_GL_HINTS[RGFW_glAccumGreen] + RGFW_GL_HINTS[RGFW_glAccumBlue] + RGFW_GL_HINTS[RGFW_glAccumAlpha]) / 4; - RGFW_GL_ADD_ATTRIB(14, accumSize); - #endif - - #ifndef RGFW_X11 - RGFW_GL_ADD_ATTRIB(RGFW_GL_SAMPLES, RGFW_GL_HINTS[RGFW_glSamples]); - #endif - - #ifdef RGFW_MACOS - if (useSoftware) { - RGFW_GL_ADD_ATTRIB(70, kCGLRendererGenericFloatID); - } else { - attribs[index] = RGFW_GL_RENDER_TYPE; - index += 1; - } - #endif - - #ifdef RGFW_MACOS - /* macOS has the surface attribs and the opengl attribs connected for some reason - maybe this is to give macOS more control to limit openGL/the opengl version? */ - - attribs[index] = 99; - attribs[index + 1] = 0x1000; - - - if (RGFW_GL_HINTS[RGFW_glMinor] >= 4 || RGFW_GL_HINTS[RGFW_glMinor] >= 3) { - attribs[index + 1] = (u32) ((RGFW_GL_HINTS[RGFW_glMinor] >= 4) ? 0x4100 : 0x3200); - } - #endif - - RGFW_GL_ADD_ATTRIB(0, 0); - - return attribs; -} - -/* EGL only (no OSMesa nor normal OPENGL) */ -#elif defined(RGFW_EGL) - -#include - -#if defined(RGFW_LINK_EGL) - typedef EGLBoolean(EGLAPIENTRY* PFN_eglInitialize)(EGLDisplay, EGLint*, EGLint*); - - PFNEGLINITIALIZEPROC eglInitializeSource; - PFNEGLGETCONFIGSPROC eglGetConfigsSource; - PFNEGLCHOOSECONFIgamepadROC eglChooseConfigSource; - PFNEGLCREATEWINDOWSURFACEPROC eglCreateWindowSurfaceSource; - PFNEGLCREATECONTEXTPROC eglCreateContextSource; - PFNEGLMAKECURRENTPROC eglMakeCurrentSource; - PFNEGLGETDISPLAYPROC eglGetDisplaySource; - PFNEGLSWAPBUFFERSPROC eglSwapBuffersSource; - PFNEGLSWAPINTERVALPROC eglSwapIntervalSource; - PFNEGLBINDAPIPROC eglBindAPISource; - PFNEGLDESTROYCONTEXTPROC eglDestroyContextSource; - PFNEGLTERMINATEPROC eglTerminateSource; - PFNEGLDESTROYSURFACEPROC eglDestroySurfaceSource; - - #define eglInitialize eglInitializeSource - #define eglGetConfigs eglGetConfigsSource - #define eglChooseConfig eglChooseConfigSource - #define eglCreateWindowSurface eglCreateWindowSurfaceSource - #define eglCreateContext eglCreateContextSource - #define eglMakeCurrent eglMakeCurrentSource - #define eglGetDisplay eglGetDisplaySource - #define eglSwapBuffers eglSwapBuffersSource - #define eglSwapInterval eglSwapIntervalSource - #define eglBindAPI eglBindAPISource - #define eglDestroyContext eglDestroyContextSource - #define eglTerminate eglTerminateSource - #define eglDestroySurface eglDestroySurfaceSource; -#endif - - -#define EGL_SURFACE_MAJOR_VERSION_KHR 0x3098 -#define EGL_SURFACE_MINOR_VERSION_KHR 0x30fb - -#ifndef RGFW_GL_ADD_ATTRIB -#define RGFW_GL_ADD_ATTRIB(attrib, attVal) \ - if (attVal) { \ - attribs[index] = attrib;\ - attribs[index + 1] = attVal;\ - index += 2;\ - } -#endif - - -void RGFW_createOpenGLContext(RGFW_window* win) { -#if defined(RGFW_LINK_EGL) - eglInitializeSource = (PFNEGLINITIALIZEPROC) eglGetProcAddress("eglInitialize"); - eglGetConfigsSource = (PFNEGLGETCONFIGSPROC) eglGetProcAddress("eglGetConfigs"); - eglChooseConfigSource = (PFNEGLCHOOSECONFIgamepadROC) eglGetProcAddress("eglChooseConfig"); - eglCreateWindowSurfaceSource = (PFNEGLCREATEWINDOWSURFACEPROC) eglGetProcAddress("eglCreateWindowSurface"); - eglCreateContextSource = (PFNEGLCREATECONTEXTPROC) eglGetProcAddress("eglCreateContext"); - eglMakeCurrentSource = (PFNEGLMAKECURRENTPROC) eglGetProcAddress("eglMakeCurrent"); - eglGetDisplaySource = (PFNEGLGETDISPLAYPROC) eglGetProcAddress("eglGetDisplay"); - eglSwapBuffersSource = (PFNEGLSWAPBUFFERSPROC) eglGetProcAddress("eglSwapBuffers"); - eglSwapIntervalSource = (PFNEGLSWAPINTERVALPROC) eglGetProcAddress("eglSwapInterval"); - eglBindAPISource = (PFNEGLBINDAPIPROC) eglGetProcAddress("eglBindAPI"); - eglDestroyContextSource = (PFNEGLDESTROYCONTEXTPROC) eglGetProcAddress("eglDestroyContext"); - eglTerminateSource = (PFNEGLTERMINATEPROC) eglGetProcAddress("eglTerminate"); - eglDestroySurfaceSource = (PFNEGLDESTROYSURFACEPROC) eglGetProcAddress("eglDestroySurface"); -#endif /* RGFW_LINK_EGL */ - - #ifdef RGFW_WINDOWS - win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.hdc); - #elif defined(RGFW_MACOS) - win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType)0); - #elif defined(RGFW_WAYLAND) - if (RGFW_useWaylandBool) - win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.wl_display); - else - win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.display); - #else - win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.display); - #endif - - EGLint major, minor; - - eglInitialize(win->src.EGL_display, &major, &minor); - - #ifndef EGL_OPENGL_ES1_BIT - #define EGL_OPENGL_ES1_BIT 0x1 - #endif - - EGLint egl_config[] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RENDERABLE_TYPE, - #ifdef RGFW_OPENGL_ES1 - EGL_OPENGL_ES1_BIT, - #elif defined(RGFW_OPENGL_ES3) - EGL_OPENGL_ES3_BIT, - #elif defined(RGFW_OPENGL_ES2) - EGL_OPENGL_ES2_BIT, - #else - EGL_OPENGL_BIT, - #endif - EGL_NONE, EGL_NONE - }; - - { - size_t index = 7; - EGLint* attribs = egl_config; - - RGFW_GL_ADD_ATTRIB(EGL_RED_SIZE, RGFW_GL_HINTS[RGFW_glRed]); - RGFW_GL_ADD_ATTRIB(EGL_GREEN_SIZE, RGFW_GL_HINTS[RGFW_glBlue]); - RGFW_GL_ADD_ATTRIB(EGL_BLUE_SIZE, RGFW_GL_HINTS[RGFW_glGreen]); - RGFW_GL_ADD_ATTRIB(EGL_ALPHA_SIZE, RGFW_GL_HINTS[RGFW_glAlpha]); - RGFW_GL_ADD_ATTRIB(EGL_DEPTH_SIZE, RGFW_GL_HINTS[RGFW_glDepth]); - - if (RGFW_GL_HINTS[RGFW_glSRGB]) - RGFW_GL_ADD_ATTRIB(0x3089, RGFW_GL_HINTS[RGFW_glSRGB]); - - RGFW_GL_ADD_ATTRIB(EGL_NONE, EGL_NONE); - } - - EGLConfig config; - EGLint numConfigs; - eglChooseConfig(win->src.EGL_display, egl_config, &config, 1, &numConfigs); - - #if defined(RGFW_MACOS) - void* layer = RGFW_cocoaGetLayer(); - - RGFW_window_cocoaSetLayer(win, layer); - - win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) layer, NULL); - #elif defined(RGFW_WINDOWS) - win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.window, NULL); - #elif defined(RGFW_WAYLAND) - if (RGFW_useWaylandBool) - win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.eglWindow, NULL); - else - win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.window, NULL); - #else - win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.window, NULL); - #endif - - EGLint attribs[] = { - EGL_CONTEXT_CLIENT_VERSION, - #ifdef RGFW_OPENGL_ES1 - 1, - #else - 2, - #endif - EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE - }; - - size_t index = 4; - RGFW_GL_ADD_ATTRIB(EGL_STENCIL_SIZE, RGFW_GL_HINTS[RGFW_glStencil]); - RGFW_GL_ADD_ATTRIB(EGL_SAMPLES, RGFW_GL_HINTS[RGFW_glSamples]); - - if (RGFW_GL_HINTS[RGFW_glDoubleBuffer]) - RGFW_GL_ADD_ATTRIB(EGL_RENDER_BUFFER, EGL_BACK_BUFFER); - - if (RGFW_GL_HINTS[RGFW_glMinor]) { - attribs[1] = RGFW_GL_HINTS[RGFW_glMinor]; - - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_MAJOR_VERSION, RGFW_GL_HINTS[RGFW_glMinor]); - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_MINOR_VERSION, RGFW_GL_HINTS[RGFW_glMajor]); - - if (RGFW_GL_HINTS[RGFW_glProfile] == RGFW_glCore) { - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT); - } - else { - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT); - } - } - - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_ROBUST_ACCESS, RGFW_GL_HINTS[RGFW_glRobustness]); - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_DEBUG, RGFW_GL_HINTS[RGFW_glDebug]); - if (RGFW_GL_HINTS[RGFW_glReleaseBehavior] == RGFW_releaseFlush) { - RGFW_GL_ADD_ATTRIB(0x2097, 0x2098); - } else { - RGFW_GL_ADD_ATTRIB(0x2097, 0x0000); - } - - #if defined(RGFW_OPENGL_ES1) || defined(RGFW_OPENGL_ES2) || defined(RGFW_OPENGL_ES3) - eglBindAPI(EGL_OPENGL_ES_API); - #else - eglBindAPI(EGL_OPENGL_API); - #endif - - win->src.EGL_context = eglCreateContext(win->src.EGL_display, config, EGL_NO_CONTEXT, attribs); - - if (win->src.EGL_context == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEGLContext, RGFW_DEBUG_CTX(win, 0), "failed to create an EGL opengl context"); - return; - } - - eglMakeCurrent(win->src.EGL_display, win->src.EGL_surface, win->src.EGL_surface, win->src.EGL_context); - eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); -} - -void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { - eglMakeCurrent(win->src.EGL_display, win->src.EGL_surface, win->src.EGL_surface, win->src.EGL_context); -} - -void* RGFW_getCurrent_OpenGL(void) { return eglGetCurrentContext(); } - -#ifdef RGFW_APPLE -void* RGFWnsglFramework = NULL; -#elif defined(RGFW_WINDOWS) -static HMODULE RGFW_wgl_dll = NULL; -#endif - -void* RGFW_getProcAddress(const char* procname) { - #if defined(RGFW_WINDOWS) - void* proc = (void*) GetProcAddress(RGFW_wgl_dll, procname); - - if (proc) - return proc; - #endif - - return (void*) eglGetProcAddress(procname); -} - -void RGFW_closeEGL(RGFW_window* win) { - eglDestroySurface(win->src.EGL_display, win->src.EGL_surface); - eglDestroyContext(win->src.EGL_display, win->src.EGL_context); - - eglTerminate(win->src.EGL_display); -} - -void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { - RGFW_ASSERT(win != NULL); - - eglSwapInterval(win->src.EGL_display, swapInterval); - -} - -#endif /* RGFW_EGL */ - -/* - end of RGFW_EGL defines -*/ -/* end of RGFW_GL (OpenGL, EGL, OSMesa )*/ - -/* - RGFW_VULKAN defines -*/ -#elif defined(RGFW_VULKAN) - -VkResult RGFW_window_createVKSurface(RGFW_window* win, VkInstance instance, VkSurfaceKHR* surface) { - assert(win != NULL); assert(instance); - assert(surface != NULL); - - *surface = VK_NULL_HANDLE; - -#ifdef RGFW_X11 - VkXlibSurfaceCreateInfoKHR x11 = { VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, 0, 0, (Display*) win->src.display, (Window) win->src.window }; - return vkCreateXlibSurfaceKHR(instance, &x11, NULL, surface); -#elif defined(RGFW_WINDOWS) - VkWin32SurfaceCreateInfoKHR win32 = { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, 0, 0, GetModuleHandle(NULL), (HWND)win->src.window }; - - return vkCreateWin32SurfaceKHR(instance, &win32, NULL, surface); -#elif defined(RGFW_MACOS) && !defined(RGFW_MACOS_X11) - VkMacOSSurfaceCreateFlagsMVK macos = { VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK, 0, 0, vulkWin->display, (void *)win->src.window }; - - return vkCreateMacOSSurfaceMVK(instance, &macos, NULL, surface); -#endif -} -#endif /* end of RGFW_vulkan */ - -/* -This is where OS specific stuff starts -*/ - - -#if (defined(RGFW_WAYLAND) || defined(RGFW_X11)) && !defined(RGFW_NO_LINUX) - int RGFW_eventWait_forceStop[] = {0, 0, 0}; /* for wait events */ - - #if defined(__linux__) - #include - #include - #include - #include - - u32 RGFW_linux_updateGamepad(RGFW_window* win) { - /* check for new gamepads */ - static const char* str[] = {"/dev/input/js0", "/dev/input/js1", "/dev/input/js2", "/dev/input/js3", "/dev/input/js4", "/dev/input/js5"}; - static u8 RGFW_rawGamepads[6]; - - for (size_t i = 0; i < 6; i++) { - size_t index = RGFW_gamepadCount; - if (RGFW_rawGamepads[i]) { - struct input_id device_info; - if (ioctl(RGFW_rawGamepads[i], EVIOCGID, &device_info) == -1) { - if (errno == ENODEV) { - RGFW_rawGamepads[i] = 0; - } - } - continue; - } - - i32 js = open(str[i], O_RDONLY); - - if (js <= 0) - break; - - if (RGFW_gamepadCount >= 4) { - close(js); - break; - } - - RGFW_rawGamepads[i] = 1; - - int axes, buttons; - if (ioctl(js, JSIOCGAXES, &axes) < 0 || ioctl(js, JSIOCGBUTTONS, &buttons) < 0) { - close(js); - continue; - } - - if (buttons <= 5 || buttons >= 30) { - close(js); - continue; - } - - RGFW_gamepadCount++; - - RGFW_gamepads[index] = js; - - ioctl(js, JSIOCGNAME(sizeof(RGFW_gamepads_name[index])), RGFW_gamepads_name[index]); - RGFW_gamepads_name[index][sizeof(RGFW_gamepads_name[index]) - 1] = 0; - - u8 j; - for (j = 0; j < 16; j++) - RGFW_gamepadPressed[index][j] = (RGFW_keyState){0, 0}; - - win->event.type = RGFW_gamepadConnected; - - RGFW_gamepads_type[index] = RGFW_gamepadUnknown; - if (RGFW_STRSTR(RGFW_gamepads_name[index], "Microsoft") || RGFW_STRSTR(RGFW_gamepads_name[index], "X-Box")) - RGFW_gamepads_type[index] = RGFW_gamepadMicrosoft; - else if (RGFW_STRSTR(RGFW_gamepads_name[index], "PlayStation") || RGFW_STRSTR(RGFW_gamepads_name[index], "PS3") || RGFW_STRSTR(RGFW_gamepads_name[index], "PS4") || RGFW_STRSTR(RGFW_gamepads_name[index], "PS5")) - RGFW_gamepads_type[index] = RGFW_gamepadSony; - else if (RGFW_STRSTR(RGFW_gamepads_name[index], "Nintendo")) - RGFW_gamepads_type[index] = RGFW_gamepadNintendo; - else if (RGFW_STRSTR(RGFW_gamepads_name[index], "Logitech")) - RGFW_gamepads_type[index] = RGFW_gamepadLogitech; - - win->event.gamepad = index; - RGFW_gamepadCallback(win, index, 1); - return 1; - } - - /* check gamepad events */ - u8 i; - - for (i = 0; i < RGFW_gamepadCount; i++) { - struct js_event e; - if (RGFW_gamepads[i] == 0) - continue; - - i32 flags = fcntl(RGFW_gamepads[i], F_GETFL, 0); - fcntl(RGFW_gamepads[i], F_SETFL, flags | O_NONBLOCK); - - ssize_t bytes; - while ((bytes = read(RGFW_gamepads[i], &e, sizeof(e))) > 0) { - switch (e.type) { - case JS_EVENT_BUTTON: { - size_t typeIndex = 0; - if (RGFW_gamepads_type[i] == RGFW_gamepadMicrosoft) typeIndex = 1; - else if (RGFW_gamepads_type[i] == RGFW_gamepadLogitech) typeIndex = 2; - - win->event.type = e.value ? RGFW_gamepadButtonPressed : RGFW_gamepadButtonReleased; - u8 RGFW_linux2RGFW[3][RGFW_gamepadR3 + 8] = {{ /* ps */ - RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadY, RGFW_gamepadX, RGFW_gamepadL1, RGFW_gamepadR1, RGFW_gamepadL2, RGFW_gamepadR2, - RGFW_gamepadSelect, RGFW_gamepadStart, RGFW_gamepadHome, RGFW_gamepadL3, RGFW_gamepadR3, RGFW_gamepadUp, RGFW_gamepadDown, RGFW_gamepadLeft, RGFW_gamepadRight, - },{ /* xbox */ - RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadX, RGFW_gamepadY, RGFW_gamepadL1, RGFW_gamepadR1, RGFW_gamepadSelect, RGFW_gamepadStart, - RGFW_gamepadHome, RGFW_gamepadL3, RGFW_gamepadR3, 255, 255, RGFW_gamepadUp, RGFW_gamepadDown, RGFW_gamepadLeft, RGFW_gamepadRight - },{ /* Logitech */ - RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadX, RGFW_gamepadY, RGFW_gamepadL1, RGFW_gamepadR1, RGFW_gamepadL2, RGFW_gamepadR2, - RGFW_gamepadSelect, RGFW_gamepadStart, RGFW_gamepadHome, RGFW_gamepadL3, RGFW_gamepadR3, RGFW_gamepadUp, RGFW_gamepadDown, RGFW_gamepadLeft, RGFW_gamepadRight - } - }; - - win->event.button = RGFW_linux2RGFW[typeIndex][e.number]; - win->event.gamepad = i; - if (win->event.button == 255) break; - - RGFW_gamepadPressed[i][win->event.button].prev = RGFW_gamepadPressed[i][win->event.button].current; - RGFW_gamepadPressed[i][win->event.button].current = e.value; - RGFW_gamepadButtonCallback(win, i, win->event.button, e.value); - - return 1; - } - case JS_EVENT_AXIS: { - size_t axis = e.number / 2; - if (axis == 2) axis = 1; - - ioctl(RGFW_gamepads[i], JSIOCGAXES, &win->event.axisesCount); - win->event.axisesCount = 2; - - if (axis < 3) { - if (e.number == 0 || e.number == 3) - RGFW_gamepadAxes[i][axis].x = (e.value / 32767.0f) * 100; - else if (e.number == 1 || e.number == 4) { - RGFW_gamepadAxes[i][axis].y = (e.value / 32767.0f) * 100; - } - } - - win->event.axis[axis] = RGFW_gamepadAxes[i][axis]; - win->event.type = RGFW_gamepadAxisMove; - win->event.gamepad = i; - win->event.whichAxis = axis; - RGFW_gamepadAxisCallback(win, i, win->event.axis, win->event.axisesCount, win->event.whichAxis); - return 1; - } - default: break; - } - } - if (bytes == -1 && errno == ENODEV) { - RGFW_gamepadCount--; - close(RGFW_gamepads[i]); - RGFW_gamepads[i] = 0; - - win->event.type = RGFW_gamepadDisconnected; - win->event.gamepad = i; - RGFW_gamepadCallback(win, i, 0); - return 1; - } - } - return 0; - } - - #endif -#endif - - - -/* - - Start of Wayland defines - - -*/ - -#ifdef RGFW_WAYLAND -/* -Wayland TODO: (out of date) -- fix RGFW_keyPressed lock state - - RGFW_windowMoved, the window was moved (by the user) - RGFW_windowResized the window was resized (by the user), [on WASM this means the browser was resized] - RGFW_windowRefresh The window content needs to be refreshed - - RGFW_DND a file has been dropped into the window - RGFW_DNDInit - -- window args: - #define RGFW_windowNoResize the window cannot be resized by the user - #define RGFW_windowAllowDND the window supports drag and drop - #define RGFW_scaleToMonitor scale the window to the screen - -- other missing functions functions ("TODO wayland") (~30 functions) -- fix buffer rendering weird behavior -*/ -#include -#include -#include -#include -#include -#include -#include -#include - -RGFW_window* RGFW_key_win = NULL; - -/* wayland global garbage (wayland bad, X11 is fine (ish) (not really)) */ -#include "xdg-shell.h" -#include "xdg-decoration-unstable-v1.h" - -static struct xkb_context *xkb_context; -static struct xkb_keymap *keymap = NULL; -static struct xkb_state *xkb_state = NULL; -enum zxdg_toplevel_decoration_v1_mode client_preferred_mode, RGFW_current_mode; -static struct zxdg_decoration_manager_v1 *decoration_manager = NULL; - -struct wl_cursor_theme* RGFW_wl_cursor_theme = NULL; -struct wl_surface* RGFW_cursor_surface = NULL; -struct wl_cursor_image* RGFW_cursor_image = NULL; - -static void xdg_wm_base_ping_handler(void *data, - struct xdg_wm_base *wm_base, uint32_t serial) -{ - RGFW_UNUSED(data); - xdg_wm_base_pong(wm_base, serial); -} - -static const struct xdg_wm_base_listener xdg_wm_base_listener = { - .ping = xdg_wm_base_ping_handler, -}; - -RGFW_bool RGFW_wl_configured = 0; - -static void xdg_surface_configure_handler(void *data, - struct xdg_surface *xdg_surface, uint32_t serial) -{ - RGFW_UNUSED(data); - xdg_surface_ack_configure(xdg_surface, serial); - RGFW_wl_configured = 1; -} - -static const struct xdg_surface_listener xdg_surface_listener = { - .configure = xdg_surface_configure_handler, -}; - -static void xdg_toplevel_configure_handler(void *data, - struct xdg_toplevel *toplevel, int32_t width, int32_t height, - struct wl_array *states) -{ - RGFW_UNUSED(data); RGFW_UNUSED(toplevel); RGFW_UNUSED(states); -} - -static void xdg_toplevel_close_handler(void *data, - struct xdg_toplevel *toplevel) -{ - RGFW_UNUSED(data); - RGFW_window* win = (RGFW_window*)xdg_toplevel_get_user_data(toplevel); - if (win == NULL) - win = RGFW_key_win; - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_quit, ._win = win}); - RGFW_windowQuitCallback(win); -} - -static void shm_format_handler(void *data, - struct wl_shm *shm, uint32_t format) -{ - RGFW_UNUSED(data); RGFW_UNUSED(shm); -} - -static const struct wl_shm_listener shm_listener = { - .format = shm_format_handler, -}; - -static const struct xdg_toplevel_listener xdg_toplevel_listener = { - .configure = xdg_toplevel_configure_handler, - .close = xdg_toplevel_close_handler, -}; - -RGFW_window* RGFW_mouse_win = NULL; - -static void pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { - RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(serial); RGFW_UNUSED(surface_x); RGFW_UNUSED(surface_y); - RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); - RGFW_mouse_win = win; - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseEnter, - .point = RGFW_POINT(wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)), - ._win = win}); - - RGFW_mouseNotifyCallBack(win, win->event.point, RGFW_TRUE); -} -static void pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { - RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(serial); RGFW_UNUSED(surface); - RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); - if (RGFW_mouse_win == win) - RGFW_mouse_win = NULL; - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseLeave, - .point = win->event.point, - ._win = win}); - - RGFW_mouseNotifyCallBack(win, win->event.point, RGFW_FALSE); -} -static void pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y) { - RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(x); RGFW_UNUSED(y); - - RGFW_ASSERT(RGFW_mouse_win != NULL); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mousePosChanged, - .point = RGFW_POINT(wl_fixed_to_double(x), wl_fixed_to_double(y)), - ._win = RGFW_mouse_win}); - - RGFW_mousePosCallback(RGFW_mouse_win, RGFW_POINT(wl_fixed_to_double(x), wl_fixed_to_double(y)), RGFW_mouse_win->event.vector); -} -static void pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { - RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(serial); - RGFW_ASSERT(RGFW_mouse_win != NULL); - - u32 b = (button - 0x110) + 1; - - /* flip right and middle button codes */ - if (b == 2) b = 3; - else if (b == 3) b = 2; - - RGFW_mouseButtons[b].prev = RGFW_mouseButtons[b].current; - RGFW_mouseButtons[b].current = state; - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonPressed + state, - .button = b, - ._win = RGFW_mouse_win}); - RGFW_mouseButtonCallback(RGFW_mouse_win, b, 0, state); -} -static void pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { - RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(axis); - RGFW_ASSERT(RGFW_mouse_win != NULL); - - double scroll = wl_fixed_to_double(value); - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonPressed, - .button = RGFW_mouseScrollUp + (scroll < 0), - .scroll = scroll, - ._win = RGFW_mouse_win}); - - RGFW_mouseButtonCallback(RGFW_mouse_win, RGFW_mouseScrollUp + (scroll < 0), scroll, 1); -} - -void RGFW_doNothing(void) { } -static struct wl_pointer_listener pointer_listener = (struct wl_pointer_listener){&pointer_enter, &pointer_leave, &pointer_motion, &pointer_button, &pointer_axis, (void*)&RGFW_doNothing, (void*)&RGFW_doNothing, (void*)&RGFW_doNothing, (void*)&RGFW_doNothing, (void*)&RGFW_doNothing, (void*)&RGFW_doNothing}; - -static void keyboard_keymap (void *data, struct wl_keyboard *keyboard, uint32_t format, int32_t fd, uint32_t size) { - RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(format); - - char *keymap_string = mmap (NULL, size, PROT_READ, MAP_SHARED, fd, 0); - xkb_keymap_unref (keymap); - keymap = xkb_keymap_new_from_string (xkb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); - - munmap (keymap_string, size); - close (fd); - xkb_state_unref (xkb_state); - xkb_state = xkb_state_new (keymap); -} -static void keyboard_enter (void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { - RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(keys); - - RGFW_key_win = (RGFW_window*)wl_surface_get_user_data(surface); - - RGFW_key_win->_flags |= RGFW_windowFocus; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusIn, ._win = RGFW_key_win}); - RGFW_focusCallback(RGFW_key_win, RGFW_TRUE); -} -static void keyboard_leave (void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { - RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); - - RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); - if (RGFW_key_win == win) - RGFW_key_win = NULL; - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusOut, ._win = win}); - win->_flags &= ~RGFW_windowFocus; - RGFW_focusCallback(win, RGFW_FALSE); -} -static void keyboard_key (void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { - RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); - - if (RGFW_key_win == NULL) return; - - xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, key + 8); - - u32 RGFW_key = RGFW_apiKeyToRGFW(key + 8); - RGFW_keyboard[RGFW_key].prev = RGFW_keyboard[RGFW_key].current; - RGFW_keyboard[RGFW_key].current = state; - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_keyPressed + state, - .key = RGFW_key, - .keyChar = (u8)keysym, - .repeat = RGFW_isHeld(RGFW_key_win, RGFW_key), - ._win = RGFW_key_win}); - - RGFW_updateKeyMods(RGFW_key_win, xkb_keymap_mod_get_index(keymap, "Lock"), xkb_keymap_mod_get_index(keymap, "Mod2"), xkb_keymap_mod_get_index(keymap, "ScrollLock")); - RGFW_keyCallback(RGFW_key_win, RGFW_key, (u8)keysym, RGFW_key_win->event.keyMod, state); -} -static void keyboard_modifiers (void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { - RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); - xkb_state_update_mask (xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); -} -static struct wl_keyboard_listener keyboard_listener = {&keyboard_keymap, &keyboard_enter, &keyboard_leave, &keyboard_key, &keyboard_modifiers, (void*)&RGFW_doNothing}; - -static void seat_capabilities (void *data, struct wl_seat *seat, uint32_t capabilities) { - RGFW_UNUSED(data); - - if (capabilities & WL_SEAT_CAPABILITY_POINTER) { - struct wl_pointer *pointer = wl_seat_get_pointer (seat); - wl_pointer_add_listener (pointer, &pointer_listener, NULL); - } - if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { - struct wl_keyboard *keyboard = wl_seat_get_keyboard (seat); - wl_keyboard_add_listener (keyboard, &keyboard_listener, NULL); - } -} -static struct wl_seat_listener seat_listener = {&seat_capabilities, (void*)&RGFW_doNothing}; - -static void wl_global_registry_handler(void *data, - struct wl_registry *registry, uint32_t id, const char *interface, - uint32_t version) -{ - RGFW_window* win = (RGFW_window*)data; - RGFW_UNUSED(version); - if (RGFW_STRNCMP(interface, "wl_compositor", 16) == 0) { - win->src.compositor = wl_registry_bind(registry, - id, &wl_compositor_interface, 4); - } else if (RGFW_STRNCMP(interface, "xdg_wm_base", 12) == 0) { - win->src.xdg_wm_base = wl_registry_bind(registry, - id, &xdg_wm_base_interface, 1); - } else if (RGFW_STRNCMP(interface, zxdg_decoration_manager_v1_interface.name, 255) == 0) { - decoration_manager = wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1); - } else if (RGFW_STRNCMP(interface, "wl_shm", 7) == 0) { - win->src.shm = wl_registry_bind(registry, - id, &wl_shm_interface, 1); - wl_shm_add_listener(win->src.shm, &shm_listener, NULL); - } else if (RGFW_STRNCMP(interface,"wl_seat", 8) == 0) { - win->src.seat = wl_registry_bind(registry, id, &wl_seat_interface, 1); - wl_seat_add_listener(win->src.seat, &seat_listener, NULL); - } -} - -static void wl_global_registry_remove(void *data, struct wl_registry *registry, uint32_t name) { RGFW_UNUSED(data); RGFW_UNUSED(registry); RGFW_UNUSED(name); } -static const struct wl_registry_listener registry_listener = { - .global = wl_global_registry_handler, - .global_remove = wl_global_registry_remove, -}; - -static const char *get_mode_name(enum zxdg_toplevel_decoration_v1_mode mode) { - switch (mode) { - case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE: - return "client-side decorations"; - case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE: - return "server-side decorations"; - } - abort(); -} - - -static void decoration_handle_configure(void *data, - struct zxdg_toplevel_decoration_v1 *decoration, - enum zxdg_toplevel_decoration_v1_mode mode) { - RGFW_UNUSED(data); RGFW_UNUSED(decoration); - RGFW_current_mode = mode; -} - -static const struct zxdg_toplevel_decoration_v1_listener decoration_listener = { - .configure = decoration_handle_configure, -}; - -static void randname(char *buf) { - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - long r = ts.tv_nsec; - for (int i = 0; i < 6; ++i) { - buf[i] = 'A'+(r&15)+(r&16)*2; - r >>= 5; - } -} - -size_t wl_stringlen(char* name) { - size_t i = 0; - for (i; name[i]; i++); - return i; -} - -static int anonymous_shm_open(void) { - char name[] = "/RGFW-wayland-XXXXXX"; - int retries = 100; - - do { - randname(name + wl_stringlen(name) - 6); - - --retries; - // shm_open guarantees that O_CLOEXEC is set - int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); - if (fd >= 0) { - shm_unlink(name); - return fd; - } - } while (retries > 0 && errno == EEXIST); - - return -1; -} - -int create_shm_file(off_t size) { - int fd = anonymous_shm_open(); - if (fd < 0) { - return fd; - } - - if (ftruncate(fd, size) < 0) { - close(fd); - return -1; - } - - return fd; -} - -static void wl_surface_frame_done(void *data, struct wl_callback *cb, uint32_t time) { - RGFW_UNUSED(data); RGFW_UNUSED(cb); RGFW_UNUSED(time); - - #ifdef RGFW_BUFFER - RGFW_window* win = (RGFW_window*)data; - if ((win->_flags & RGFW_NO_CPU_RENDER)) - return; - - wl_surface_attach(win->src.surface, win->src.wl_buffer, 0, 0); - wl_surface_damage_buffer(win->src.surface, 0, 0, win->r.w, win->r.h); - wl_surface_commit(win->src.surface); - #endif -} - -static const struct wl_callback_listener wl_surface_frame_listener = { - .done = wl_surface_frame_done, -}; -#endif /* RGFW_WAYLAND */ -#if !defined(RGFW_NO_X11) && defined(RGFW_WAYLAND) -void RGFW_useWayland(RGFW_bool wayland) { RGFW_useWaylandBool = wayland; } -#define RGFW_GOTO_WAYLAND(fallback) if (RGFW_useWaylandBool && fallback == 0) goto wayland -#else -#define RGFW_GOTO_WAYLAND(fallback) -void RGFW_useWayland(RGFW_bool wayland) { RGFW_UNUSED(wayland); } -#endif - -/* - End of Wayland defines -*/ - -/* - - -Start of Linux / Unix defines - - -*/ - -#ifdef RGFW_UNIX -#if !defined(RGFW_NO_X11_CURSOR) && defined(RGFW_X11) -#include -#endif - -#include - -#ifndef RGFW_NO_DPI -#include -#include -#endif - -#include -#include -#include -#include - -#include /* for converting keycode to string */ -#include /* for hiding */ -#include -#include -#include - -#include /* for data limits (mainly used in drag and drop functions) */ -#include - - -#if defined(__linux__) && !defined(RGFW_NO_LINUX) -#include -#endif - -/* atoms needed for drag and drop */ -Atom XdndAware, XtextPlain, XtextUriList; -Atom RGFW_XUTF8_STRING = 0; - -Atom wm_delete_window = 0, RGFW_XCLIPBOARD = 0; - -#if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) - typedef XcursorImage* (*PFN_XcursorImageCreate)(int, int); - typedef void (*PFN_XcursorImageDestroy)(XcursorImage*); - typedef Cursor(*PFN_XcursorImageLoadCursor)(Display*, const XcursorImage*); -#endif -#ifdef RGFW_OPENGL - typedef GLXContext(*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); -#endif - -#if !defined(RGFW_NO_X11_XI_PRELOAD) - typedef int (* PFN_XISelectEvents)(Display*,Window,XIEventMask*,int); - PFN_XISelectEvents XISelectEventsSRC = NULL; - #define XISelectEvents XISelectEventsSRC - - void* X11Xihandle = NULL; -#endif - -#if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) - PFN_XcursorImageLoadCursor XcursorImageLoadCursorSRC = NULL; - PFN_XcursorImageCreate XcursorImageCreateSRC = NULL; - PFN_XcursorImageDestroy XcursorImageDestroySRC = NULL; - - #define XcursorImageLoadCursor XcursorImageLoadCursorSRC - #define XcursorImageCreate XcursorImageCreateSRC - #define XcursorImageDestroy XcursorImageDestroySRC - - void* X11Cursorhandle = NULL; -#endif - -const char* RGFW_instName = NULL; -void RGFW_setXInstName(const char* name) { - RGFW_instName = name; -} - -#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) - void* RGFW_getProcAddress(const char* procname) { return (void*) glXGetProcAddress((GLubyte*) procname); } -#endif - -void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area) { - RGFW_GOTO_WAYLAND(0); - - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - win->buffer = (u8*)buffer; - win->bufferSize = area; - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoBuffer, RGFW_DEBUG_CTX(win, 0), "createing a 4 channel buffer"); - #ifdef RGFW_X11 - #ifdef RGFW_OSMESA - win->src.ctx = OSMesaCreateContext(OSMESA_BGRA, NULL); - OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); - #endif - - win->src.bitmap = XCreateImage( - win->src.display, XDefaultVisual(win->src.display, XDefaultScreen(win->src.display)), - 32, ZPixmap, 0, NULL, area.w, area.h, - 32, 0 - ); - #endif - #ifdef RGFW_WAYLAND - wayland: - size_t size = win->r.w * win->r.h * 4; - int fd = create_shm_file(size); - if (fd < 0) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, RGFW_DEBUG_CTX(win, fd),"Failed to create a buffer."); - exit(1); - - win->src.buffer = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (win->src.buffer == MAP_FAILED) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, RGFW_DEBUG_CTX(win, MAP_FAILED), "mmap failed!"); - close(fd); - exit(1); - } - - win->_flags |= RGFW_BUFFER_ALLOC; - - struct wl_shm_pool* pool = wl_shm_create_pool(win->src.shm, fd, size); - win->src.wl_buffer = wl_shm_pool_create_buffer(pool, 0, win->r.w, win->r.h, win->r.w * 4, - WL_SHM_FORMAT_ARGRGFW_bool888); - wl_shm_pool_destroy(pool); - - close(fd); - - wl_surface_attach(win->src.surface, win->src.wl_buffer, 0, 0); - wl_surface_commit(win->src.surface); - - u8 color[] = {0x00, 0x00, 0x00, 0xFF}; - - size_t i; - for (i = 0; i < area.w * area.h * 4; i += 4) { - RGFW_MEMCPY(&win->buffer[i], color, 4); - } - - RGFW_MEMCPY(win->src.buffer, win->buffer, win->r.w * win->r.h * 4); - - #if defined(RGFW_OSMESA) - win->src.ctx = OSMesaCreateContext(OSMESA_BGRA, NULL); - OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); - #endif - #endif - #else - #ifdef RGFW_WAYLAND - wayland: - #endif - - RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); - #endif -} - -#define RGFW_LOAD_ATOM(name) \ - static Atom name = 0; \ - if (name == 0) name = XInternAtom(RGFW_root->src.display, #name, False); - -void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { - RGFW_setBit(&win->_flags, RGFW_windowNoBorder, !border); - - RGFW_GOTO_WAYLAND(0); - #ifdef RGFW_X11 - RGFW_LOAD_ATOM(_MOTIF_WM_HINTS); - - struct __x11WindowHints { - unsigned long flags, functions, decorations, status; - long input_mode; - } hints; - hints.flags = 2; - hints.decorations = border; - - XChangeProperty(win->src.display, win->src.window, _MOTIF_WM_HINTS, _MOTIF_WM_HINTS, 32, - PropModeReplace, (u8*)&hints, 5 - ); - - if (RGFW_window_isHidden(win) == 0) { - RGFW_window_hide(win); - RGFW_window_show(win); - } - - #endif - #ifdef RGFW_WAYLAND - wayland: - #endif -} - -void RGFW_releaseCursor(RGFW_window* win) { -RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - XUngrabPointer(win->src.display, CurrentTime); - - /* disable raw input */ - unsigned char mask[] = { 0 }; - XIEventMask em; - em.deviceid = XIAllMasterDevices; - em.mask_len = sizeof(mask); - em.mask = mask; - - XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); -#endif -#ifdef RGFW_WAYLAND - wayland: -#endif -} - -void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { -RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - /* enable raw input */ - unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; - XISetMask(mask, XI_RawMotion); - - XIEventMask em; - em.deviceid = XIAllMasterDevices; - em.mask_len = sizeof(mask); - em.mask = mask; - - XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); - - XGrabPointer(win->src.display, win->src.window, True, PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); - RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (i32)(r.w / 2), win->r.y + (i32)(r.h / 2))); -#endif -#ifdef RGFW_WAYLAND - wayland: -#endif -} - -#define RGFW_LOAD_LIBRARY(x, lib) if (x == NULL) x = dlopen(lib, RTLD_LAZY | RTLD_LOCAL) -#define RGFW_PROC_DEF(proc, name) if (name##SRC == NULL && proc != NULL) name##SRC = (PFN_##name)(void*)dlsym(proc, #name) - -RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { - #ifdef RGFW_USE_XDL - XDL_init(); - #endif - - #if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) - #if defined(__CYGWIN__) - RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor-1.so"); - #elif defined(__OpenBSD__) || defined(__NetBSD__) - RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor.so"); - #else - RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor.so.1"); - #endif - RGFW_PROC_DEF(X11Cursorhandle, XcursorImageCreate); - RGFW_PROC_DEF(X11Cursorhandle, XcursorImageDestroy); - RGFW_PROC_DEF(X11Cursorhandle, XcursorImageLoadCursor); - #endif - - #if !defined(RGFW_NO_X11_XI_PRELOAD) - #if defined(__CYGWIN__) - RGFW_LOAD_LIBRARY(X11Xihandle, "libXi-6.so"); - #elif defined(__OpenBSD__) || defined(__NetBSD__) - RGFW_LOAD_LIBRARY(X11Xihandle, "libXi.so"); - #else - RGFW_LOAD_LIBRARY(X11Xihandle, "libXi.so.6"); - #endif - RGFW_PROC_DEF(X11Xihandle, XISelectEvents); - #endif - - XInitThreads(); /*!< init X11 threading */ - - if (flags & RGFW_windowOpenglSoftware) - setenv("LIBGL_ALWAYS_SOFTWARE", "1", 1); - - RGFW_window_basic_init(win, rect, flags); - -#ifdef RGFW_WAYLAND - win->src.compositor = NULL; -#endif - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - u64 event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | FocusChangeMask | LeaveWindowMask | EnterWindowMask | ExposureMask; /*!< X11 events accepted */ - - #if defined(RGFW_OPENGL) && !defined(RGFW_EGL) - u32* visual_attribs = (u32*)RGFW_initFormatAttribs(flags & RGFW_windowOpenglSoftware); - i32 fbcount; - GLXFBConfig* fbc = glXChooseFBConfig(win->src.display, DefaultScreen(win->src.display), (i32*) visual_attribs, &fbcount); - - i32 best_fbc = -1; - - if (fbcount == 0) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to find any valid GLX visual configs"); - return NULL; - } - - u32 i; - for (i = 0; i < (u32)fbcount; i++) { - XVisualInfo* vi = glXGetVisualFromFBConfig(win->src.display, fbc[i]); - if (vi == NULL) - continue; - - XFree(vi); - - i32 samp_buf, samples; - glXGetFBConfigAttrib(win->src.display, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf); - glXGetFBConfigAttrib(win->src.display, fbc[i], GLX_SAMPLES, &samples); - - if ((!(flags & RGFW_windowTransparent) || vi->depth == 32) && - (best_fbc < 0 || samp_buf) && (samples == RGFW_GL_HINTS[RGFW_glSamples] || best_fbc == -1)) { - best_fbc = i; - } - } - - if (best_fbc == -1) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to get a valid GLX visual"); - return NULL; - } - - GLXFBConfig bestFbc = fbc[best_fbc]; - - /* Get a visual */ - XVisualInfo* vi = glXGetVisualFromFBConfig(win->src.display, bestFbc); - - XFree(fbc); - #else - XVisualInfo viNorm; - - viNorm.visual = DefaultVisual(win->src.display, DefaultScreen(win->src.display)); - - viNorm.depth = 0; - XVisualInfo* vi = &viNorm; - - XMatchVisualInfo(win->src.display, DefaultScreen(win->src.display), 32, TrueColor, vi); /*!< for RGBA backgrounds */ - #endif - /* make X window attrubutes */ - XSetWindowAttributes swa; - Colormap cmap; - - swa.colormap = cmap = XCreateColormap(win->src.display, - DefaultRootWindow(win->src.display), - vi->visual, AllocNone); - - swa.background_pixmap = None; - swa.border_pixel = 0; - swa.event_mask = event_mask; - - swa.background_pixel = 0; - - /* create the window */ - win->src.window = XCreateWindow(win->src.display, DefaultRootWindow(win->src.display), win->r.x, win->r.y, win->r.w, win->r.h, - 0, vi->depth, InputOutput, vi->visual, - CWColormap | CWBorderPixel | CWBackPixel | CWEventMask, &swa); - - XFreeColors(win->src.display, cmap, NULL, 0, 0); - - win->src.gc = XCreateGC(win->src.display, win->src.window, 0, NULL); - - #if defined(RGFW_OPENGL) && !defined(RGFW_EGL) - XFree(vi); - #endif - - // In your .desktop app, if you set the property - // StartupWMClass=RGFW that will assoicate the launcher icon - // with your application - robrohan - - if (RGFW_className == NULL) - RGFW_className = (char*)name; - - XClassHint hint; - hint.res_class = (char*)RGFW_className; - if (RGFW_instName == NULL) hint.res_name = (char*)name; - else hint.res_name = (char*)RGFW_instName; - XSetClassHint(win->src.display, win->src.window, &hint); - - if ((flags & RGFW_windowNoInitAPI) == 0) { - #if defined(RGFW_OPENGL) && !defined(RGFW_EGL) /* This is the second part of setting up opengl. This is where we ask OpenGL for a specific version. */ - i32 context_attribs[7] = { 0, 0, 0, 0, 0, 0, 0 }; - context_attribs[0] = GLX_CONTEXT_PROFILE_MASK_ARB; - if (RGFW_GL_HINTS[RGFW_glProfile] == RGFW_glCore) - context_attribs[1] = GLX_CONTEXT_CORE_PROFILE_BIT_ARB; - else - context_attribs[1] = GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; - - if (RGFW_GL_HINTS[RGFW_glMinor] || RGFW_GL_HINTS[RGFW_glMajor]) { - context_attribs[2] = GLX_CONTEXT_MAJOR_VERSION_ARB; - context_attribs[3] = RGFW_GL_HINTS[RGFW_glMinor]; - context_attribs[4] = GLX_CONTEXT_MINOR_VERSION_ARB; - context_attribs[5] = RGFW_GL_HINTS[RGFW_glMajor]; - } - - glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0; - glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc) - glXGetProcAddressARB((GLubyte*) "glXCreateContextAttribsARB"); - - GLXContext ctx = NULL; - - if (RGFW_root != NULL && RGFW_root != win) - ctx = RGFW_root->src.ctx; - - win->src.ctx = glXCreateContextAttribsARB(win->src.display, bestFbc, ctx, True, context_attribs); - #endif - } - - #ifndef RGFW_NO_MONITOR - if (flags & RGFW_windowScaleToMonitor) - RGFW_window_scaleToMonitor(win); - #endif - - if (flags & RGFW_windowNoResize) { /* make it so the user can't resize the window */ - XSizeHints sh; - sh.flags = (1L << 4) | (1L << 5); - sh.min_width = sh.max_width = win->r.w; - sh.min_height = sh.max_height = win->r.h; - - XSetWMSizeHints(win->src.display, (Drawable) win->src.window, &sh, XA_WM_NORMAL_HINTS); - - win->_flags |= RGFW_windowNoResize; - } - - XSelectInput(win->src.display, (Drawable) win->src.window, event_mask); /*!< tell X11 what events we want */ - - /* make it so the user can't close the window until the program does */ - if (wm_delete_window == 0) { - wm_delete_window = XInternAtom(win->src.display, "WM_DELETE_WINDOW", False); - RGFW_XUTF8_STRING = XInternAtom(win->src.display, "UTF8_STRING", False); - RGFW_XCLIPBOARD = XInternAtom(win->src.display, "CLIPBOARD", False); - } - - XSetWMProtocols(win->src.display, (Drawable) win->src.window, &wm_delete_window, 1); - - /* connect the context to the window */ - #if defined(RGFW_OPENGL) && !defined(RGFW_EGL) - if ((flags & RGFW_windowNoInitAPI) == 0) - glXMakeCurrent(win->src.display, (Drawable) win->src.window, (GLXContext) win->src.ctx); - #endif - - /* set the background */ - RGFW_window_setName(win, name); - - XMapWindow(win->src.display, (Drawable) win->src.window); /* draw the window */ - XMoveWindow(win->src.display, (Drawable) win->src.window, win->r.x, win->r.y); /*!< move the window to it's proper cords */ - - if (flags & RGFW_windowAllowDND) { /* init drag and drop atoms and turn on drag and drop for this window */ - win->_flags |= RGFW_windowAllowDND; - - /* actions */ - XtextUriList = XInternAtom(win->src.display, "text/uri-list", False); - XtextPlain = XInternAtom(win->src.display, "text/plain", False); - XdndAware = XInternAtom(win->src.display, "XdndAware", False); - const u8 version = 5; - - XChangeProperty(win->src.display, win->src.window, - XdndAware, 4, 32, - PropModeReplace, &version, 1); /*!< turns on drag and drop */ - } - - #ifdef RGFW_EGL - if ((flags & RGFW_windowNoInitAPI) == 0) - RGFW_createOpenGLContext(win); - #endif - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); - RGFW_window_setMouseDefault(win); - RGFW_window_setFlags(win, flags); - - return win; /*return newly created window */ -#endif -#ifdef RGFW_WAYLAND - wayland: - RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningWayland, RGFW_DEBUG_CTX(win, 0), "RGFW Wayland support is experimental"); - - win->src.wl_display = wl_display_connect(NULL); - if (win->src.wl_display == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errWayland, RGFW_DEBUG_CTX(win, 0), "Failed to load Wayland display"); - #ifdef RGFW_X11 - RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningWayland, RGFW_DEBUG_CTX(win, 0), "Falling back to X11"); - RGFW_useWayland(0); - return RGFW_createWindowPtr(name, rect, flags, win); - #endif - return NULL; - } - - - #ifdef RGFW_X11 - XSetWindowAttributes attributes; - attributes.background_pixel = 0; - attributes.override_redirect = True; - - win->src.window = XCreateWindow(win->src.display, DefaultRootWindow(win->src.display), 0, 0, 1, 1, 0, CopyFromParent, InputOutput, CopyFromParent, - CWBackPixel | CWOverrideRedirect, &attributes); - - XMapWindow(win->src.display, win->src.window); - XFlush(win->src.display); - if (wm_delete_window == 0) { - wm_delete_window = XInternAtom(win->src.display, "WM_DELETE_WINDOW", False); - RGFW_XUTF8_STRING = XInternAtom(win->src.display, "UTF8_STRING", False); - RGFW_XCLIPBOARD = XInternAtom(win->src.display, "CLIPBOARD", False); - } - #endif - - struct wl_registry *registry = wl_display_get_registry(win->src.wl_display); - wl_registry_add_listener(registry, ®istry_listener, win); - - wl_display_roundtrip(win->src.wl_display); - wl_display_dispatch(win->src.wl_display); - - if (win->src.compositor == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errWayland, RGFW_DEBUG_CTX(win, 0), "Can't find compositor."); - return NULL; - } - - if (RGFW_wl_cursor_theme == NULL) { - RGFW_wl_cursor_theme = wl_cursor_theme_load(NULL, 24, win->src.shm); - RGFW_cursor_surface = wl_compositor_create_surface(win->src.compositor); - - struct wl_cursor* cursor = wl_cursor_theme_get_cursor(RGFW_wl_cursor_theme, "left_ptr"); - RGFW_cursor_image = cursor->images[0]; - struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(RGFW_cursor_image); - - wl_surface_attach(RGFW_cursor_surface, cursor_buffer, 0, 0); - wl_surface_commit(RGFW_cursor_surface); - } - - xdg_wm_base_add_listener(win->src.xdg_wm_base, &xdg_wm_base_listener, NULL); - - xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - - win->src.surface = wl_compositor_create_surface(win->src.compositor); - wl_surface_set_user_data(win->src.surface, win); - - win->src.xdg_surface = xdg_wm_base_get_xdg_surface(win->src.xdg_wm_base, win->src.surface); - xdg_surface_add_listener(win->src.xdg_surface, &xdg_surface_listener, NULL); - - xdg_wm_base_set_user_data(win->src.xdg_wm_base, win); - - win->src.xdg_toplevel = xdg_surface_get_toplevel(win->src.xdg_surface); - xdg_toplevel_set_user_data(win->src.xdg_toplevel, win); - xdg_toplevel_set_title(win->src.xdg_toplevel, name); - xdg_toplevel_add_listener(win->src.xdg_toplevel, &xdg_toplevel_listener, NULL); - - xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->r.w, win->r.h); - - if (!(flags & RGFW_windowNoBorder)) { - win->src.decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( - decoration_manager, win->src.xdg_toplevel); - } - - if (flags & RGFW_windowOpenglSoftware) - setenv("LIBGL_ALWAYS_SOFTWARE", "1", 1); - - wl_display_roundtrip(win->src.wl_display); - - wl_surface_commit(win->src.surface); - - /* wait for the surface to be configured */ - while (wl_display_dispatch(win->src.wl_display) != -1 && !RGFW_wl_configured) { } - - #ifdef RGFW_OPENGL - if ((flags & RGFW_windowNoInitAPI) == 0) { - win->src.eglWindow = wl_egl_window_create(win->src.surface, win->r.w, win->r.h); - RGFW_createOpenGLContext(win); - } - #endif - struct wl_callback* callback = wl_surface_frame(win->src.surface); - wl_callback_add_listener(callback, &wl_surface_frame_listener, win); - wl_surface_commit(win->src.surface); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); - - #ifndef RGFW_NO_MONITOR - if (flags & RGFW_windowScaleToMonitor) - RGFW_window_scaleToMonitor(win); - #endif - - RGFW_window_setMouseDefault(win); - RGFW_window_setFlags(win, flags); - return win; /* return newly created window */ -#endif -} - -RGFW_area RGFW_getScreenSize(void) { - RGFW_GOTO_WAYLAND(1); - RGFW_ASSERT(RGFW_root != NULL); - - #ifdef RGFW_X11 - Screen* scrn = DefaultScreenOfDisplay(RGFW_root->src.display); - return RGFW_AREA(scrn->width, scrn->height); - #endif - #ifdef RGFW_WAYLAND - wayland: return RGFW_AREA(RGFW_root->r.w, RGFW_root->r.h); // TODO - #endif -} - -RGFW_point RGFW_getGlobalMousePoint(void) { - RGFW_ASSERT(RGFW_root != NULL); - - RGFW_point RGFWMouse; - - i32 x, y; - u32 z; - Window window1, window2; - XQueryPointer(RGFW_root->src.display, XDefaultRootWindow(RGFW_root->src.display), &window1, &window2, &RGFWMouse.x, &RGFWMouse.y, &x, &y, &z); - - return RGFWMouse; -} - -RGFWDEF void RGFW_XHandleClipboardSelection(RGFW_window* win, XEvent* event); - -void RGFW_XHandleClipboardSelection(RGFW_window* win, XEvent* event) { - RGFW_LOAD_ATOM(ATOM_PAIR); - RGFW_LOAD_ATOM(MULTIPLE); - RGFW_LOAD_ATOM(TARGETS); - RGFW_LOAD_ATOM(SAVE_TARGETS); - - const XSelectionRequestEvent* request = &event->xselectionrequest; - const Atom formats[] = { RGFW_XUTF8_STRING, XA_STRING }; - const int formatCount = sizeof(formats) / sizeof(formats[0]); - - if (request->target == TARGETS) { - const Atom targets[] = { TARGETS, MULTIPLE, RGFW_XUTF8_STRING, XA_STRING }; - - XChangeProperty(win->src.display, request->requestor, request->property, - XA_ATOM, 32, PropModeReplace, (u8*) targets, sizeof(targets) / sizeof(Atom)); - } else if (request->target == MULTIPLE) { - Atom* targets = NULL; - - Atom actualType = 0; - int actualFormat = 0; - unsigned long count = 0, bytesAfter = 0; - - XGetWindowProperty(RGFW_root->src.display, request->requestor, request->property, 0, LONG_MAX, - False, ATOM_PAIR, &actualType, &actualFormat, &count, &bytesAfter, (u8**) &targets); - - unsigned long i; - for (i = 0; i < (u32)count; i += 2) { - if (targets[i] == RGFW_XUTF8_STRING || targets[i] == XA_STRING) - XChangeProperty(RGFW_root->src.display, request->requestor, targets[i + 1], targets[i], - 8, PropModeReplace, (const unsigned char *)win->src.clipboard, win->src.clipboard_len); - else - targets[i + 1] = None; - } - - XChangeProperty(RGFW_root->src.display, - request->requestor, request->property, ATOM_PAIR, 32, - PropModeReplace, (u8*) targets, count); - - XFlush(RGFW_root->src.display); - XFree(targets); - } else if (request->target == SAVE_TARGETS) - XChangeProperty(win->src.display, request->requestor, request->property, 0, 32, PropModeReplace, NULL, 0); - else { - for (int i = 0; i < formatCount; i++) { - if (request->target != formats[i]) - continue; - XChangeProperty(win->src.display, request->requestor, request->property, request->target, - 8, PropModeReplace, (u8*) win->src.clipboard, win->src.clipboard_len); - } - } - - XEvent reply = { SelectionNotify }; - reply.xselection.property = request->property; - reply.xselection.display = request->display; - reply.xselection.requestor = request->requestor; - reply.xselection.selection = request->selection; - reply.xselection.target = request->target; - reply.xselection.time = request->time; - - XSendEvent(win->src.display, request->requestor, False, 0, &reply); -} - -char* RGFW_strtok(char* str, const char* delimStr) { - static char* static_str = NULL; - - if (str != NULL) - static_str = str; - - if (static_str == NULL) { - return NULL; - } - - while (*static_str != '\0') { - RGFW_bool delim = 0; - for (const char* d = delimStr; *d != '\0'; d++) { - if (*static_str == *d) { - delim = 1; - break; - } - } - if (!delim) - break; - static_str++; - } - - if (*static_str == '\0') - return NULL; - - char* token_start = static_str; - while (*static_str != '\0') { - int delim = 0; - for (const char* d = delimStr; *d != '\0'; d++) { - if (*static_str == *d) { - delim = 1; - break; - } - } - - if (delim) { - *static_str = '\0'; - static_str++; - break; - } - static_str++; - } - - return token_start; -} - -RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { - RGFW_event* ev = RGFW_window_checkEventCore(win); - if (ev) { - if (ev == (RGFW_event*)-1) return NULL; - return ev; - } - - #if defined(__linux__) && !defined(RGFW_NO_LINUX) - if (RGFW_linux_updateGamepad(win)) return &win->event; - #endif - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - RGFW_LOAD_ATOM(XdndTypeList); - RGFW_LOAD_ATOM(XdndSelection); - RGFW_LOAD_ATOM(XdndEnter); - RGFW_LOAD_ATOM(XdndPosition); - RGFW_LOAD_ATOM(XdndStatus); - RGFW_LOAD_ATOM(XdndLeave); - RGFW_LOAD_ATOM(XdndDrop); - RGFW_LOAD_ATOM(XdndFinished); - RGFW_LOAD_ATOM(XdndActionCopy); - - XPending(win->src.display); - - XEvent E; /*!< raw X11 event */ - - /* if there is no unread qued events, get a new one */ - if ((QLength(win->src.display) || XEventsQueued(win->src.display, QueuedAlready) + XEventsQueued(win->src.display, QueuedAfterReading)) - && win->event.type != RGFW_quit - ) - XNextEvent(win->src.display, &E); - else { - return NULL; - } - - win->event.type = 0; - - /* xdnd data */ - static Window source = 0; - static long version = 0; - static i32 format = 0; - - XEvent reply = { ClientMessage }; - - switch (E.type) { - case KeyPress: - case KeyRelease: { - win->event.repeat = RGFW_FALSE; - /* check if it's a real key release */ - if (E.type == KeyRelease && XEventsQueued(win->src.display, QueuedAfterReading)) { /* get next event if there is one */ - XEvent NE; - XPeekEvent(win->src.display, &NE); - - if (E.xkey.time == NE.xkey.time && E.xkey.keycode == NE.xkey.keycode) /* check if the current and next are both the same */ - win->event.repeat = RGFW_TRUE; - } - - /* set event key data */ - win->event.key = RGFW_apiKeyToRGFW(E.xkey.keycode); - KeySym sym = (KeySym)XkbKeycodeToKeysym(win->src.display, E.xkey.keycode, 0, E.xkey.state & ShiftMask ? 1 : 0); - - if ((E.xkey.state & LockMask) && sym >= XK_a && sym <= XK_z) - sym = (E.xkey.state & ShiftMask) ? sym + 32 : sym - 32; - if ((u8)sym != (u32)sym) - sym = 0; - - win->event.keyChar = (u8)sym; - - RGFW_keyboard[win->event.key].prev = RGFW_isPressed(win, win->event.key); - - /* get keystate data */ - win->event.type = (E.type == KeyPress) ? RGFW_keyPressed : RGFW_keyReleased; - - XKeyboardState keystate; - XGetKeyboardControl(win->src.display, &keystate); - - RGFW_keyboard[win->event.key].current = (E.type == KeyPress); - - XkbStateRec state; - XkbGetState(win->src.display, XkbUseCoreKbd, &state); - RGFW_updateKeyMods(win, (state.locked_mods & LockMask), (state.locked_mods & Mod2Mask), (state.locked_mods & Mod3Mask)); - - RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, (E.type == KeyPress)); - break; - } - case ButtonPress: - case ButtonRelease: - if (E.xbutton.button > RGFW_mouseFinal) { /* skip this event */ - XFlush(win->src.display); - return RGFW_window_checkEvent(win); - } - - win->event.type = RGFW_mouseButtonPressed + (E.type == ButtonRelease); // the events match - win->event.button = E.xbutton.button - 1; - switch(win->event.button) { - case RGFW_mouseScrollUp: - win->event.scroll = 1; - break; - case RGFW_mouseScrollDown: - win->event.scroll = -1; - break; - default: break; - } - - RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; - - if (win->event.repeat == RGFW_FALSE) - win->event.repeat = RGFW_isPressed(win, win->event.key); - - RGFW_mouseButtons[win->event.button].current = (E.type == ButtonPress); - RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, (E.type == ButtonPress)); - break; - - case MotionNotify: - win->event.point.x = E.xmotion.x; - win->event.point.y = E.xmotion.y; - - win->event.vector.x = win->event.point.x - win->_lastMousePoint.x; - win->event.vector.y = win->event.point.y - win->_lastMousePoint.y; - win->_lastMousePoint = win->event.point; - - win->event.type = RGFW_mousePosChanged; - RGFW_mousePosCallback(win, win->event.point, win->event.vector); - break; - - case GenericEvent: { - /* MotionNotify is used for mouse events if the mouse isn't held */ - if (!(win->_flags & RGFW_HOLD_MOUSE)) { - XFreeEventData(win->src.display, &E.xcookie); - break; - } - - XGetEventData(win->src.display, &E.xcookie); - if (E.xcookie.evtype == XI_RawMotion) { - XIRawEvent *raw = (XIRawEvent *)E.xcookie.data; - if (raw->valuators.mask_len == 0) { - XFreeEventData(win->src.display, &E.xcookie); - break; - } - - double deltaX = 0.0f; - double deltaY = 0.0f; - - /* check if relative motion data exists where we think it does */ - if (XIMaskIsSet(raw->valuators.mask, 0) != 0) - deltaX += raw->raw_values[0]; - if (XIMaskIsSet(raw->valuators.mask, 1) != 0) - deltaY += raw->raw_values[1]; - - win->event.vector = RGFW_POINT((i32)deltaX, (i32)deltaY); - win->event.point.x = win->_lastMousePoint.x + win->event.vector.x; - win->event.point.y = win->_lastMousePoint.y + win->event.vector.y; - win->_lastMousePoint = win->event.point; - - RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); - - win->event.type = RGFW_mousePosChanged; - RGFW_mousePosCallback(win, win->event.point, win->event.vector); - } - - XFreeEventData(win->src.display, &E.xcookie); - break; - } - - case Expose: - win->event.type = RGFW_windowRefresh; - RGFW_windowRefreshCallback(win); - break; - case MapNotify: case UnmapNotify: RGFW_window_checkMode(win); break; - case ClientMessage: { - /* if the client closed the window */ - if (E.xclient.data.l[0] == (long)wm_delete_window) { - win->event.type = RGFW_quit; - RGFW_windowQuitCallback(win); - break; - } - - for (size_t i = 0; i < win->event.droppedFilesCount; i++) { - win->event.droppedFiles[i][0] = '\0'; - } - win->event.droppedFilesCount = 0; - - if ((win->_flags & RGFW_windowAllowDND) == 0) - break; - - reply.xclient.window = source; - reply.xclient.format = 32; - reply.xclient.data.l[0] = (long)win->src.window; - reply.xclient.data.l[1] = 0; - reply.xclient.data.l[2] = None; - - if (E.xclient.message_type == XdndEnter) { - if (version > 5) - break; - - unsigned long count; - Atom* formats; - Atom real_formats[6]; - Bool list = E.xclient.data.l[1] & 1; - - source = E.xclient.data.l[0]; - version = E.xclient.data.l[1] >> 24; - format = None; - if (list) { - Atom actualType; - i32 actualFormat; - unsigned long bytesAfter; - - XGetWindowProperty( - win->src.display, source, XdndTypeList, - 0, LONG_MAX, False, 4, - &actualType, &actualFormat, &count, &bytesAfter, (u8**)&formats - ); - } else { - count = 0; - - for (size_t i = 2; i < 5; i++) { - Window format = E.xclient.data.l[i]; - if (format != None) { - real_formats[count] = format; - count += 1; - } - } - - formats = real_formats; - } - - for (size_t i = 0; i < count; i++) { - if (formats[i] == XtextUriList || formats[i] == XtextPlain) { - format = (int)formats[i]; - break; - } - } - - if (list) { - XFree(formats); - } - - break; - } - - if (E.xclient.message_type == XdndPosition) { - const i32 xabs = (E.xclient.data.l[2] >> 16) & 0xffff; - const i32 yabs = (E.xclient.data.l[2]) & 0xffff; - Window dummy; - i32 xpos, ypos; - - if (version > 5) - break; - - XTranslateCoordinates( - win->src.display, XDefaultRootWindow(win->src.display), win->src.window, - xabs, yabs, &xpos, &ypos, &dummy - ); - - win->event.point.x = xpos; - win->event.point.y = ypos; - - reply.xclient.window = source; - reply.xclient.message_type = XdndStatus; - - if (format) { - reply.xclient.data.l[1] = 1; - if (version >= 2) - reply.xclient.data.l[4] = (long)XdndActionCopy; - } - - XSendEvent(win->src.display, source, False, NoEventMask, &reply); - XFlush(win->src.display); - break; - } - if (E.xclient.message_type != XdndDrop) - break; - - if (version > 5) - break; - - win->event.type = RGFW_DNDInit; - - if (format) { - Time time = (version >= 1) - ? (Time)E.xclient.data.l[2] - : CurrentTime; - - XConvertSelection( - win->src.display, XdndSelection, (Atom)format, - XdndSelection, win->src.window, time - ); - } else if (version >= 2) { - XEvent reply = { ClientMessage }; - - XSendEvent(win->src.display, source, False, NoEventMask, &reply); - XFlush(win->src.display); - } - - RGFW_dndInitCallback(win, win->event.point); - } break; - case SelectionRequest: - RGFW_XHandleClipboardSelection(win, &E); - XFlush(win->src.display); - return RGFW_window_checkEvent(win); - case SelectionNotify: { - /* this is only for checking for xdnd drops */ - if (E.xselection.property != XdndSelection || !(win->_flags & RGFW_windowAllowDND)) - break; - char* data; - unsigned long result; - - Atom actualType; - i32 actualFormat; - unsigned long bytesAfter; - - XGetWindowProperty(win->src.display, E.xselection.requestor, E.xselection.property, 0, LONG_MAX, False, E.xselection.target, &actualType, &actualFormat, &result, &bytesAfter, (u8**) &data); - - if (result == 0) - break; - - const char* prefix = (const char*)"file://"; - - char* line; - - win->event.droppedFilesCount = 0; - - win->event.type = RGFW_DND; - - while ((line = (char*)RGFW_strtok(data, "\r\n"))) { - char path[RGFW_MAX_PATH]; - - data = NULL; - - if (line[0] == '#') - continue; - - char* l; - for (l = line; 1; l++) { - if ((l - line) > 7) - break; - else if (*l != prefix[(l - line)]) - break; - else if (*l == '\0' && prefix[(l - line)] == '\0') { - line += 7; - while (*line != '/') - line++; - break; - } else if (*l == '\0') - break; - } - - win->event.droppedFilesCount++; - - size_t index = 0; - while (*line) { - if (line[0] == '%' && line[1] && line[2]) { - const char digits[3] = { line[1], line[2], '\0' }; - path[index] = (char) RGFW_STRTOL(digits, NULL, 16); - line += 2; - } else - path[index] = *line; - - index++; - line++; - } - path[index] = '\0'; - RGFW_MEMCPY(win->event.droppedFiles[win->event.droppedFilesCount - 1], path, index + 1); - } - - if (data) - XFree(data); - - if (version >= 2) { - XEvent reply = { ClientMessage }; - reply.xclient.format = 32; - reply.xclient.message_type = XdndFinished; - reply.xclient.data.l[1] = result; - reply.xclient.data.l[2] = XdndActionCopy; - - XSendEvent(win->src.display, source, False, NoEventMask, &reply); - XFlush(win->src.display); - } - - RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); - break; - } - case FocusIn: - if ((win->_flags & RGFW_windowFullscreen)) - XMapRaised(win->src.display, win->src.window); - - win->_flags |= RGFW_windowFocus; - win->event.type = RGFW_focusIn; - RGFW_focusCallback(win, 1); - break; - case FocusOut: - if ((win->_flags & RGFW_windowFullscreen)) - RGFW_window_minimize(win); - - win->_flags &= ~RGFW_windowFocus; - win->event.type = RGFW_focusOut; - RGFW_focusCallback(win, 0); - break; - case PropertyNotify: RGFW_window_checkMode(win); break; - case EnterNotify: { - win->event.type = RGFW_mouseEnter; - win->event.point.x = E.xcrossing.x; - win->event.point.y = E.xcrossing.y; - RGFW_mouseNotifyCallBack(win, win->event.point, 1); - break; - } - - case LeaveNotify: { - win->event.type = RGFW_mouseLeave; - RGFW_mouseNotifyCallBack(win, win->event.point, 0); - break; - } - - case ConfigureNotify: { - /* detect resize */ - RGFW_window_checkMode(win); - if (E.xconfigure.width != win->r.w || E.xconfigure.height != win->r.h) { - win->event.type = RGFW_windowResized; - win->r = RGFW_RECT(win->r.x, win->r.y, E.xconfigure.width, E.xconfigure.height); - RGFW_windowResizeCallback(win, win->r); - break; - } - - /* detect move */ - if (E.xconfigure.x != win->r.x || E.xconfigure.y != win->r.y) { - win->event.type = RGFW_windowMoved; - win->r = RGFW_RECT(E.xconfigure.x, E.xconfigure.y, win->r.w, win->r.h); - RGFW_windowMoveCallback(win, win->r); - break; - } - - break; - } - default: - XFlush(win->src.display); - return RGFW_window_checkEvent(win); - } - - XFlush(win->src.display); - if (win->event.type) return &win->event; - else return NULL; -#endif -#ifdef RGFW_WAYLAND - wayland: - if (win->_flags & RGFW_windowHide) - return NULL; - - if (wl_display_roundtrip(win->src.wl_display) == -1) - return NULL; - return NULL; -#endif -} - -void RGFW_window_move(RGFW_window* win, RGFW_point v) { - RGFW_ASSERT(win != NULL); - win->r.x = v.x; - win->r.y = v.y; - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - XMoveWindow(win->src.display, win->src.window, v.x, v.y); -#endif -#ifdef RGFW_WAYLAND - wayland: - RGFW_ASSERT(win != NULL); - - if (win->src.compositor) { - struct wl_pointer *pointer = wl_seat_get_pointer(win->src.seat); - if (!pointer) { - return; - } - - wl_display_flush(win->src.wl_display); - } -#endif -} - - -void RGFW_window_resize(RGFW_window* win, RGFW_area a) { - RGFW_ASSERT(win != NULL); - win->r.w = a.w; - win->r.h = a.h; - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - XResizeWindow(win->src.display, win->src.window, a.w, a.h); - - if (!(win->_flags & RGFW_windowNoResize)) - return; - - XSizeHints sh; - sh.flags = (1L << 4) | (1L << 5); - sh.min_width = sh.max_width = a.w; - sh.min_height = sh.max_height = a.h; - - XSetWMSizeHints(win->src.display, (Drawable) win->src.window, &sh, XA_WM_NORMAL_HINTS); -#endif -#ifdef RGFW_WAYLAND - wayland: - if (win->src.compositor) { - xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->r.w, win->r.h); - #ifdef RGFW_OPENGL - wl_egl_window_resize(win->src.eglWindow, a.w, a.h, 0, 0); - #endif - } -#endif -} - -void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { - RGFW_ASSERT(win != NULL); - - if (a.w == 0 && a.h == 0) - return; - - XSizeHints hints; - long flags; - - XGetWMNormalHints(win->src.display, win->src.window, &hints, &flags); - - hints.flags |= PAspect; - - hints.min_aspect.x = hints.max_aspect.x = a.w; - hints.min_aspect.y = hints.max_aspect.y = a.h; - - XSetWMNormalHints(win->src.display, win->src.window, &hints); -} - -void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { - RGFW_ASSERT(win != NULL); - - if (a.w == 0 && a.h == 0) - return; - - XSizeHints hints; - long flags; - - XGetWMNormalHints(win->src.display, win->src.window, &hints, &flags); - - hints.flags |= PMinSize; - - hints.min_width = a.w; - hints.min_height = a.h; - - XSetWMNormalHints(win->src.display, win->src.window, &hints); -} - -void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { - RGFW_ASSERT(win != NULL); - - if (a.w == 0 && a.h == 0) - return; - - XSizeHints hints; - long flags; - - XGetWMNormalHints(win->src.display, win->src.window, &hints, &flags); - - hints.flags |= PMaxSize; - - hints.max_width = a.w; - hints.max_height = a.h; - - XSetWMNormalHints(win->src.display, win->src.window, &hints); -} - -void RGFW_toggleXMaximized(RGFW_window* win, RGFW_bool maximized) { - RGFW_ASSERT(win != NULL); - RGFW_LOAD_ATOM(_NET_WM_STATE); - RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_VERT); - RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ); - - XEvent xev = {0}; - xev.type = ClientMessage; - xev.xclient.window = win->src.window; - xev.xclient.message_type = _NET_WM_STATE; - xev.xclient.format = 32; - xev.xclient.data.l[0] = maximized; - xev.xclient.data.l[1] = _NET_WM_STATE_MAXIMIZED_HORZ; - xev.xclient.data.l[2] = _NET_WM_STATE_MAXIMIZED_VERT; - xev.xclient.data.l[3] = 0; - xev.xclient.data.l[4] = 0; - - XSendEvent(win->src.display, DefaultRootWindow(win->src.display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); -} - -void RGFW_window_maximize(RGFW_window* win) { - win->_oldRect = win->r; - RGFW_toggleXMaximized(win, 1); -} - -void RGFW_window_focus(RGFW_window* win) { - RGFW_ASSERT(win); - - XWindowAttributes attr; - XGetWindowAttributes(win->src.display, win->src.window, &attr); - if (attr.map_state != IsViewable) return; - - XSetInputFocus(win->src.display, win->src.window, RevertToPointerRoot, CurrentTime); - XFlush(win->src.display); -} - -void RGFW_window_raise(RGFW_window* win) { - RGFW_ASSERT(win); - XRaiseWindow(win->src.display, win->src.window); - XMapRaised(win->src.display, win->src.window); -} - -void RGFW_window_setXAtom(RGFW_window* win, Atom netAtom, RGFW_bool fullscreen) { - RGFW_ASSERT(win != NULL); - RGFW_LOAD_ATOM(_NET_WM_STATE); - - XEvent xev = {0}; - xev.xclient.type = ClientMessage; - xev.xclient.serial = 0; - xev.xclient.send_event = True; - xev.xclient.message_type = _NET_WM_STATE; - xev.xclient.window = win->src.window; - xev.xclient.format = 32; - xev.xclient.data.l[0] = fullscreen; - xev.xclient.data.l[1] = netAtom; - xev.xclient.data.l[2] = 0; - - XSendEvent(win->src.display, DefaultRootWindow(win->src.display), False, SubstructureNotifyMask | SubstructureRedirectMask, &xev); -} - -void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { - RGFW_ASSERT(win != NULL); - if (fullscreen) { - win->_flags |= RGFW_windowFullscreen; - win->_oldRect = win->r; - } - else win->_flags &= ~RGFW_windowFullscreen; - - RGFW_LOAD_ATOM(_NET_WM_STATE_FULLSCREEN); - - RGFW_window_setXAtom(win, _NET_WM_STATE_FULLSCREEN, fullscreen); - - XRaiseWindow(win->src.display, win->src.window); - XMapRaised(win->src.display, win->src.window); -} - -void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { - RGFW_ASSERT(win != NULL); - - RGFW_LOAD_ATOM(_NET_WM_STATE_ABOVE); - RGFW_window_setXAtom(win, _NET_WM_STATE_ABOVE, floating); -} - -void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { - RGFW_ASSERT(win != NULL); - const u32 value = (u32) (0xffffffffu * (double) opacity); - RGFW_LOAD_ATOM(NET_WM_WINDOW_OPACITY); - XChangeProperty(win->src.display, win->src.window, - NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) &value, 1); -} - -void RGFW_window_minimize(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - if (RGFW_window_isMaximized(win)) return; - - win->_oldRect = win->r; - XIconifyWindow(win->src.display, win->src.window, DefaultScreen(win->src.display)); - XFlush(win->src.display); -} - -void RGFW_window_restore(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - RGFW_toggleXMaximized(win, 0); - - win->r = win->_oldRect; - RGFW_window_move(win, RGFW_POINT(win->r.x, win->r.y)); - RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h)); - - RGFW_window_show(win); - XFlush(win->src.display); -} - -RGFW_bool RGFW_window_isFloating(RGFW_window* win) { - RGFW_LOAD_ATOM(_NET_WM_STATE); - RGFW_LOAD_ATOM(_NET_WM_STATE_ABOVE); - - Atom actual_type; - int actual_format; - unsigned long nitems, bytes_after; - Atom* prop_return = NULL; - - int status = XGetWindowProperty(win->src.display, win->src.window, _NET_WM_STATE, 0, (~0L), False, XA_ATOM, - &actual_type, &actual_format, &nitems, &bytes_after, - (unsigned char **)&prop_return); - - if (status != Success || actual_type != XA_ATOM) - return RGFW_FALSE; - - for (unsigned long i = 0; i < nitems; i++) - if (prop_return[i] == _NET_WM_STATE_ABOVE) return RGFW_TRUE; - - if (prop_return) - XFree(prop_return); - - return RGFW_FALSE; -} - -void RGFW_window_setName(RGFW_window* win, const char* name) { - RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); - #ifdef RGFW_X11 - XStoreName(win->src.display, win->src.window, name); - - RGFW_LOAD_ATOM(_NET_WM_NAME); - XChangeProperty( - win->src.display, win->src.window, _NET_WM_NAME, RGFW_XUTF8_STRING, - 8, PropModeReplace, (u8*)name, 256 - ); - #endif - #ifdef RGFW_WAYLAND - wayland: - if (win->src.compositor) - xdg_toplevel_set_title(win->src.xdg_toplevel, name); - #endif -} - -void* RGFW_libxshape = NULL; - -#ifndef RGFW_NO_PASSTHROUGH - -void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { - RGFW_ASSERT(win != NULL); - - #if defined(__CYGWIN__) - RGFW_LOAD_LIBRARY(RGFW_libxshape, "libXext-6.so"); - #elif defined(__OpenBSD__) || defined(__NetBSD__) - RGFW_LOAD_LIBRARY(RGFW_libxshape, "libXext.so"); - #else - RGFW_LOAD_LIBRARY(RGFW_libxshape, "libXext.so.6"); - #endif - - typedef void (* PFN_XShapeCombineMask)(Display*,Window,int,int,int,Pixmap,int); - static PFN_XShapeCombineMask XShapeCombineMaskSRC; - - typedef void (* PFN_XShapeCombineRegion)(Display*,Window,int,int,int,Region,int); - static PFN_XShapeCombineRegion XShapeCombineRegionSRC; - - RGFW_PROC_DEF(RGFW_libxshape, XShapeCombineRegion); - RGFW_PROC_DEF(RGFW_libxshape, XShapeCombineMask); - - if (passthrough) { - Region region = XCreateRegion(); - XShapeCombineRegionSRC(win->src.display, win->src.window, ShapeInput, 0, 0, region, ShapeSet); - XDestroyRegion(region); - - return; - } - - XShapeCombineMaskSRC(win->src.display, win->src.window, ShapeInput, 0, 0, None, ShapeSet); -} - -#endif /* RGFW_NO_PASSTHROUGH */ - -RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* icon, RGFW_area a, i32 channels, u8 type) { - RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - RGFW_LOAD_ATOM(_NET_WM_ICON); - if (icon == NULL || (channels != 3 && channels != 4)) { - RGFW_bool res = (RGFW_bool)XChangeProperty( - win->src.display, win->src.window, _NET_WM_ICON, XA_CARDINAL, 32, - PropModeReplace, (u8*)NULL, 0 - ); - return res; - } - - i32 count = 2 + (a.w * a.h); - - unsigned long* data = (unsigned long*) RGFW_ALLOC(count * sizeof(unsigned long)); - data[0] = (unsigned long)a.w; - data[1] = (unsigned long)a.h; - - unsigned long* target = &data[2]; - u32 x, y; - - for (x = 0; x < a.w; x++) { - for (y = 0; y < a.h; y++) { - size_t i = y * a.w + x; - u32 alpha = (channels == 4) ? icon[i * 4 + 3] : 0xFF; - - target[i] = (unsigned long)((icon[i * 4 + 0]) << 16) | - (unsigned long)((icon[i * 4 + 1]) << 8) | - (unsigned long)((icon[i * 4 + 2]) << 0) | - (unsigned long)(alpha << 24); - } - } - - RGFW_bool res = RGFW_TRUE; - if (type & RGFW_iconTaskbar) { - res = (RGFW_bool)XChangeProperty( - win->src.display, win->src.window, _NET_WM_ICON, XA_CARDINAL, 32, - PropModeReplace, (u8*)data, count - ); - } - - if (type & RGFW_iconWindow) { - XWMHints wm_hints; - wm_hints.flags = IconPixmapHint; - - int depth = DefaultDepth(win->src.display, DefaultScreen(win->src.display)); - XImage *image = XCreateImage(win->src.display, DefaultVisual(win->src.display, DefaultScreen(win->src.display)), - depth, ZPixmap, 0, (char *)target, a.w, a.h, 32, 0); - - wm_hints.icon_pixmap = XCreatePixmap(win->src.display, win->src.window, a.w, a.h, depth); - XPutImage(win->src.display, wm_hints.icon_pixmap, win->src.gc, image, 0, 0, 0, 0, a.w, a.h); - image->data = NULL; - XDestroyImage(image); - - XSetWMHints(win->src.display, win->src.window, &wm_hints); - } - - RGFW_FREE(data); - XFlush(win->src.display); - return RGFW_BOOL(res); -#endif -#ifdef RGFW_WAYLAND - wayland: - return RGFW_FALSE; -#endif -} - -RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { - RGFW_ASSERT(icon); - RGFW_ASSERT(channels == 3 || channels == 4); - RGFW_GOTO_WAYLAND(0); - -#ifdef RGFW_X11 -#ifndef RGFW_NO_X11_CURSOR - XcursorImage* native = XcursorImageCreate(a.w, a.h); - native->xhot = 0; - native->yhot = 0; - - XcursorPixel* target = native->pixels; - for (size_t x = 0; x < a.w; x++) { - for (size_t y = 0; y < a.h; y++) { - size_t i = y * a.w + x; - u32 alpha = (channels == 4) ? icon[i * 4 + 3] : 0xFF; - - target[i] = (u32)((icon[i * 4 + 0]) << 16) - | (u32)((icon[i * 4 + 1]) << 8) - | (u32)((icon[i * 4 + 2]) << 0) - | (u32)(alpha << 24); - } - } - - Cursor cursor = XcursorImageLoadCursor(RGFW_root->src.display, native); - XcursorImageDestroy(native); - - return (void*)cursor; -#else - RGFW_UNUSED(image); RGFW_UNUSED(a.w); RGFW_UNUSED(channels); - return NULL; -#endif -#endif -#ifdef RGFW_WAYLAND - wayland: - RGFW_UNUSED(icon); RGFW_UNUSED(a); RGFW_UNUSED(channels); - return NULL; // TODO -#endif -} - -void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { -RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - RGFW_ASSERT(win && mouse); - XDefineCursor(win->src.display, win->src.window, (Cursor)mouse); -#endif -#ifdef RGFW_WAYLAND - wayland: -#endif -} - -void RGFW_freeMouse(RGFW_mouse* mouse) { -RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - RGFW_ASSERT(mouse); - XFreeCursor(RGFW_root->src.display, (Cursor)mouse); -#endif -#ifdef RGFW_WAYLAND - wayland: -#endif -} - -void RGFW_window_moveMouse(RGFW_window* win, RGFW_point p) { -RGFW_GOTO_WAYLAND(1); -#ifdef RGFW_X11 - RGFW_ASSERT(win != NULL); - - XEvent event; - XQueryPointer(win->src.display, DefaultRootWindow(win->src.display), - &event.xbutton.root, &event.xbutton.window, - &event.xbutton.x_root, &event.xbutton.y_root, - &event.xbutton.x, &event.xbutton.y, - &event.xbutton.state); - - win->_lastMousePoint = RGFW_POINT(p.x - win->r.x, p.y - win->r.y); - if (event.xbutton.x == p.x && event.xbutton.y == p.y) - return; - - XWarpPointer(win->src.display, None, win->src.window, 0, 0, 0, 0, (int) p.x - win->r.x, (int) p.y - win->r.y); -#endif -#ifdef RGFW_WAYLAND - wayland: -#endif -} - -RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { - return RGFW_window_setMouseStandard(win, RGFW_mouseArrow); -} - -RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { - RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - static const u8 mouseIconSrc[] = { XC_arrow, XC_left_ptr, XC_xterm, XC_crosshair, XC_hand2, XC_sb_h_double_arrow, XC_sb_v_double_arrow, XC_bottom_left_corner, XC_bottom_right_corner, XC_fleur, XC_X_cursor}; - - if (mouse > (sizeof(mouseIconSrc) / sizeof(u8))) - return RGFW_FALSE; - - mouse = mouseIconSrc[mouse]; - - Cursor cursor = XCreateFontCursor(win->src.display, mouse); - XDefineCursor(win->src.display, win->src.window, (Cursor) cursor); - - XFreeCursor(win->src.display, (Cursor) cursor); - return RGFW_TRUE; -#endif -#ifdef RGFW_WAYLAND - wayland: - static const char* iconStrings[] = { "left_ptr", "left_ptr", "text", "cross", "pointer", "e-resize", "n-resize", "nw-resize", "ne-resize", "all-resize", "not-allowed" }; - - struct wl_cursor* wlcursor = wl_cursor_theme_get_cursor(RGFW_wl_cursor_theme, iconStrings[mouse]); - RGFW_cursor_image = wlcursor->images[0]; - struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(RGFW_cursor_image); - - wl_surface_attach(RGFW_cursor_surface, cursor_buffer, 0, 0); - wl_surface_commit(RGFW_cursor_surface); - return RGFW_TRUE; -#endif -} - -void RGFW_window_hide(RGFW_window* win) { - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - XUnmapWindow(win->src.display, win->src.window); -#endif -#ifdef RGFW_WAYLAND - wayland: - wl_surface_attach(win->src.surface, NULL, 0, 0); - wl_surface_commit(win->src.surface); - win->_flags |= RGFW_windowHide; -#endif -} - -void RGFW_window_show(RGFW_window* win) { - win->_flags &= ~RGFW_windowHide; - if (win->_flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - XMapWindow(win->src.display, win->src.window); -#endif -#ifdef RGFW_WAYLAND - wayland: - //wl_surface_attach(win->src.surface, win->rc., 0, 0); - wl_surface_commit(win->src.surface); -#endif -} - -RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { - RGFW_GOTO_WAYLAND(1); - #ifdef RGFW_X11 - - if (XGetSelectionOwner(RGFW_root->src.display, RGFW_XCLIPBOARD) == RGFW_root->src.window) { - if (str != NULL) - RGFW_STRNCPY(str, RGFW_root->src.clipboard, RGFW_root->src.clipboard_len); - return (RGFW_ssize_t)RGFW_root->src.clipboard_len; - } - - XEvent event; - int format; - unsigned long N, sizeN; - char* data; - Atom target; - - RGFW_LOAD_ATOM(XSEL_DATA); - - XConvertSelection(RGFW_root->src.display, RGFW_XCLIPBOARD, RGFW_XUTF8_STRING, XSEL_DATA, RGFW_root->src.window, CurrentTime); - XSync(RGFW_root->src.display, 0); - XNextEvent(RGFW_root->src.display, &event); - - if (event.type != SelectionNotify || event.xselection.selection != RGFW_XCLIPBOARD || event.xselection.property == 0) - return -1; - - XGetWindowProperty(event.xselection.display, event.xselection.requestor, - event.xselection.property, 0L, (~0L), 0, AnyPropertyType, &target, - &format, &sizeN, &N, (u8**) &data); - - RGFW_ssize_t size; - if (sizeN > strCapacity && str != NULL) - size = -1; - - if ((target == RGFW_XUTF8_STRING || target == XA_STRING) && str != NULL) { - RGFW_MEMCPY(str, data, sizeN); - str[sizeN] = '\0'; - XFree(data); - } else if (str != NULL) size = -1; - - XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property); - size = sizeN; - return size; - #endif - #if defined(RGFW_WAYLAND) - wayland: return 0; - #endif -} - -void RGFW_XHandleClipboardSelectionLoop(RGFW_window* win) { - RGFW_LOAD_ATOM(SAVE_TARGETS); - - for (;;) { - XEvent event; - XNextEvent(win->src.display, &event); - switch (event.type) { - case SelectionRequest: - return RGFW_XHandleClipboardSelection(win, &event); - case SelectionNotify: - if (event.xselection.target == SAVE_TARGETS) - return; - break; - default: break; - } - } -} - -void RGFW_writeClipboard(const char* text, u32 textLen) { - RGFW_GOTO_WAYLAND(1); - #ifdef RGFW_X11 - RGFW_LOAD_ATOM(SAVE_TARGETS); - - /* request ownership of the clipboard section and request to convert it, this means its our job to convert it */ - XSetSelectionOwner(RGFW_root->src.display, RGFW_XCLIPBOARD, RGFW_root->src.window, CurrentTime); - if (XGetSelectionOwner(RGFW_root->src.display, RGFW_XCLIPBOARD) != RGFW_root->src.window) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, RGFW_DEBUG_CTX(RGFW_root, 0), "X11 failed to become owner of clipboard selection"); - return; - } - - if (RGFW_root->src.clipboard) - RGFW_FREE(RGFW_root->src.clipboard); - - RGFW_root->src.clipboard = (char*)RGFW_ALLOC(textLen); - RGFW_STRNCPY(RGFW_root->src.clipboard, text, textLen); - RGFW_root->src.clipboard_len = textLen; -#ifdef RGFW_WAYLAND - if (RGFW_useWaylandBool) - RGFW_XHandleClipboardSelectionLoop(RGFW_root); -#endif - - #endif - #if defined(RGFW_WAYLAND) - wayland: - #endif -} - -RGFW_bool RGFW_window_isHidden(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - - XWindowAttributes windowAttributes; - XGetWindowAttributes(win->src.display, win->src.window, &windowAttributes); - - return (windowAttributes.map_state == IsUnmapped && !RGFW_window_isMinimized(win)); -} - -RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - RGFW_LOAD_ATOM(WM_STATE); - - Atom actual_type; - i32 actual_format; - unsigned long nitems, bytes_after; - unsigned char* prop_data; - - i16 status = XGetWindowProperty(win->src.display, win->src.window, WM_STATE, 0, 2, False, - AnyPropertyType, &actual_type, &actual_format, - &nitems, &bytes_after, &prop_data); - - if (status == Success && nitems >= 1 && *((int*) prop_data) == IconicState) { - XFree(prop_data); - return RGFW_TRUE; - } - - if (prop_data != NULL) - XFree(prop_data); - - XWindowAttributes windowAttributes; - XGetWindowAttributes(win->src.display, win->src.window, &windowAttributes); - return windowAttributes.map_state != IsViewable; -} - -RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - RGFW_LOAD_ATOM(_NET_WM_STATE); - RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_VERT); - RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ); - - Atom actual_type; - i32 actual_format; - unsigned long nitems, bytes_after; - unsigned char* prop_data; - - i16 status = XGetWindowProperty(win->src.display, win->src.window, _NET_WM_STATE, 0, 1024, False, - XA_ATOM, &actual_type, &actual_format, - &nitems, &bytes_after, &prop_data); - - if (status != Success) { - if (prop_data != NULL) - XFree(prop_data); - - return RGFW_FALSE; - } - - Atom* atoms = (Atom*) prop_data; - u64 i; - for (i = 0; i < nitems; ++i) { - if (atoms[i] == _NET_WM_STATE_MAXIMIZED_VERT || - atoms[i] == _NET_WM_STATE_MAXIMIZED_HORZ) { - XFree(prop_data); - return RGFW_TRUE; - } - } - - return RGFW_FALSE; -} - -#ifndef RGFW_NO_DPI -static u32 RGFW_XCalculateRefreshRate(XRRModeInfo mi) { - if (mi.hTotal == 0 || mi.vTotal == 0) return 0; - - return (u32) RGFW_ROUND((double) mi.dotClock / ((double) mi.hTotal * (double) mi.vTotal)); -} -#endif - - -static float XGetSystemContentDPI(Display* display, i32 screen) { - float dpi = 96.0f; - - #ifndef RGFW_NO_DPI - RGFW_UNUSED(screen); - char* rms = XResourceManagerString(display); - XrmDatabase db = NULL; - if (rms) db = XrmGetStringDatabase(rms); - - if (rms && db) { - XrmValue value; - char* type = NULL; - - if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value) && type && RGFW_STRNCMP(type, "String", 7) == 0) - dpi = (float)atof(value.addr); - XrmDestroyDatabase(db); - } - #else - dpi = RGFW_ROUND(DisplayWidth(display, screen) / (DisplayWidthMM(display, screen) / 25.4)); - #endif - - return dpi; -} - -RGFW_monitor RGFW_XCreateMonitor(i32 screen) { - RGFW_monitor monitor; - - Display* display; - if (RGFW_root == NULL) - display = XOpenDisplay(NULL); - else - display = RGFW_root->src.display; - - if (screen == -1) screen = DefaultScreen(display); - - Screen* scrn = DefaultScreenOfDisplay(display); - RGFW_area size = RGFW_AREA(scrn->width, scrn->height); - - monitor.x = 0; - monitor.y = 0; - monitor.mode.area = RGFW_AREA(size.w, size.h); - monitor.physW = DisplayWidthMM(display, screen) / 25.4; - monitor.physH = DisplayHeightMM(display, screen) / 25.4; - - RGFW_splitBPP(DefaultDepth(display, DefaultScreen(display)), &monitor.mode); - - char* name = XDisplayName((const char*)display); - RGFW_MEMCPY(monitor.name, name, 128); - - float dpi = XGetSystemContentDPI(display, screen); - monitor.pixelRatio = dpi >= 192.0f ? 2 : 1; - monitor.scaleX = (float) (dpi) / 96.0f; - monitor.scaleY = (float) (dpi) / 96.0f; - - #ifndef RGFW_NO_DPI - XRRScreenResources* sr = XRRGetScreenResourcesCurrent(display, RootWindow(display, screen)); - monitor.mode.refreshRate = RGFW_XCalculateRefreshRate(sr->modes[screen]); - - XRRCrtcInfo* ci = NULL; - int crtc = screen; - - if (sr->ncrtc > crtc) { - ci = XRRGetCrtcInfo(display, sr, sr->crtcs[crtc]); - } - #endif - - #ifndef RGFW_NO_DPI - XRROutputInfo* info = XRRGetOutputInfo (display, sr, sr->outputs[screen]); - - if (info == NULL || ci == NULL) { - XRRFreeScreenResources(sr); - XCloseDisplay(display); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); - return monitor; - } - - - float physW = info->mm_width / 25.4; - float physH = info->mm_height / 25.4; - - RGFW_MEMCPY(monitor.name, info->name, 128); - - if (physW && physH) { - monitor.physW = physW; - monitor.physH = physH; - } - - monitor.x = ci->x; - monitor.y = ci->y; - - float w = ci->width; - float h = ci->height; - - if (w && h) { - monitor.mode.area.w = w; - monitor.mode.area.h = h; - } - #endif - - #ifndef RGFW_NO_DPI - XRRFreeCrtcInfo(ci); - XRRFreeScreenResources(sr); - #endif - - if (RGFW_root == NULL) XCloseDisplay(display); - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); - return monitor; -} - -RGFW_monitor* RGFW_getMonitors(void) { - static RGFW_monitor monitors[7]; - - RGFW_GOTO_WAYLAND(1); - #ifdef RGFW_X11 - - Display* display; - if (RGFW_root == NULL) display = XOpenDisplay(NULL); - else display = RGFW_root->src.display; - - size_t i; - for (i = 0; i < (size_t)ScreenCount(display) && i < 6; i++) - monitors[i] = RGFW_XCreateMonitor(i); - - if (RGFW_root == NULL) XCloseDisplay(display); - - return monitors; - #endif - #ifdef RGFW_WAYLAND - wayland: return monitors; // TODO WAYLAND - #endif -} - -RGFW_monitor RGFW_getPrimaryMonitor(void) { - RGFW_GOTO_WAYLAND(1); - #ifdef RGFW_X11 - return RGFW_XCreateMonitor(-1); - #endif - #ifdef RGFW_WAYLAND - wayland: return (RGFW_monitor){ }; // TODO WAYLAND - #endif -} - -RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { - RGFW_GOTO_WAYLAND(1); -#ifdef RGFW_X11 - #ifndef RGFW_NO_DPI - XRRScreenResources* screenRes = XRRGetScreenResources(RGFW_root->src.display, DefaultRootWindow(RGFW_root->src.display)); - if (screenRes == NULL) return RGFW_FALSE; - for (int i = 0; i < screenRes->ncrtc; i++) { - XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(RGFW_root->src.display, screenRes, screenRes->crtcs[i]); - if (!crtcInfo) continue; - - if (mon.x == crtcInfo->x && mon.y == crtcInfo->y && (u32)mon.mode.area.w == crtcInfo->width && (u32)mon.mode.area.h == crtcInfo->height) { - RRMode rmode = None; - for (int index = 0; index < screenRes->nmode; index++) { - RGFW_monitorMode foundMode; - foundMode.area = RGFW_AREA(screenRes->modes[index].width, screenRes->modes[index].height); - foundMode.refreshRate = RGFW_XCalculateRefreshRate(screenRes->modes[index]); - RGFW_splitBPP(DefaultDepth(RGFW_root->src.display, DefaultScreen(RGFW_root->src.display)), &foundMode); - - if (RGFW_monitorModeCompare(mode, foundMode, request)) { - rmode = screenRes->modes[index].id; - - RROutput output = screenRes->outputs[i]; - XRROutputInfo* info = XRRGetOutputInfo(RGFW_root->src.display, screenRes, output); - if (info) { - XRRSetCrtcConfig(RGFW_root->src.display, screenRes, screenRes->crtcs[i], - CurrentTime, 0, 0, rmode, RR_Rotate_0, &output, 1); - XRRFreeOutputInfo(info); - XRRFreeCrtcInfo(crtcInfo); - XRRFreeScreenResources(screenRes); - return RGFW_TRUE; - } - } - } - - XRRFreeCrtcInfo(crtcInfo); - XRRFreeScreenResources(screenRes); - return RGFW_FALSE; - } - - XRRFreeCrtcInfo(crtcInfo); - } - - XRRFreeScreenResources(screenRes); - return RGFW_FALSE; - #endif -#endif -#ifdef RGFW_WAYLAND -wayland: -#endif - return RGFW_FALSE; -} - -RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(1); -#ifdef RGFW_X11 - XWindowAttributes attrs; - if (!XGetWindowAttributes(win->src.display, win->src.window, &attrs)) { - return (RGFW_monitor){}; - } - - size_t i; - for (i = 0; i < (size_t)ScreenCount(win->src.display) && i < 6; i++) { - Screen* screen = ScreenOfDisplay(win->src.display, i); - if (attrs.x >= 0 && attrs.x < XWidthOfScreen(screen) && - attrs.y >= 0 && attrs.y < XHeightOfScreen(screen)) - return RGFW_XCreateMonitor(i); - } -#endif -#ifdef RGFW_WAYLAND -wayland: -#endif - return (RGFW_monitor){}; - -} - -#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) -void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { - if (win == NULL) - glXMakeCurrent(NULL, (Drawable)NULL, (GLXContext) NULL); - else - glXMakeCurrent(win->src.display, (Drawable) win->src.window, (GLXContext) win->src.ctx); -} -void* RGFW_getCurrent_OpenGL(void) { return glXGetCurrentContext(); } -#endif - -void RGFW_window_swapBuffers(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - /* clear the window */ - if (!(win->_flags & RGFW_NO_CPU_RENDER)) { - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - win->src.bitmap->data = (char*) win->buffer; - RGFW_RGB_to_BGR(win, (u8*)win->src.bitmap->data); - XPutImage(win->src.display, win->src.window, win->src.gc, win->src.bitmap, 0, 0, 0, 0, win->bufferSize.w, win->bufferSize.h); - win->src.bitmap->data = NULL; - #endif - } - - if (!(win->_flags & RGFW_NO_GPU_RENDER)) { - #ifdef RGFW_EGL - eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); - #elif defined(RGFW_OPENGL) - glXSwapBuffers(win->src.display, win->src.window); - #endif - } - return; -#endif -#ifdef RGFW_WAYLAND - wayland: - #if defined(RGFW_BUFFER) || defined(RGFW_OSMESA) - #if !defined(RGFW_BUFFER_BGR) && !defined(RGFW_OSMESA) - RGFW_RGB_to_BGR(win, win->src.buffer); - #else - for (size_t y = 0; y < win->r.h; y++) { - u32 index = (y * 4 * win->r.w); - u32 index2 = (y * 4 * win->bufferSize.w); - RGFW_MEMCPY(&win->src.buffer[index], &win->buffer[index2], win->r.w * 4); - } - #endif - - wl_surface_frame_done(win, NULL, 0); - if (!(win->_flags & RGFW_NO_GPU_RENDER)) - #endif - { - #ifdef RGFW_OPENGL - eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); - #endif - } - - wl_surface_commit(win->src.surface); -#endif -} - -#if !defined(RGFW_EGL) - -void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { - RGFW_ASSERT(win != NULL); - - #if defined(RGFW_OPENGL) - ((PFNGLXSWAPINTERVALEXTPROC) glXGetProcAddress((GLubyte*) "glXSwapIntervalEXT"))(win->src.display, win->src.window, swapInterval); - #else - RGFW_UNUSED(swapInterval); - #endif -} -#endif - - -void RGFW_window_close(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - #ifdef RGFW_X11 - /* to save the clipboard on the x server after the window is closed */ - RGFW_LOAD_ATOM(CLIPBOARD_MANAGER); - RGFW_LOAD_ATOM(SAVE_TARGETS); - if (XGetSelectionOwner(win->src.display, RGFW_XCLIPBOARD) == win->src.window) { - XConvertSelection(win->src.display, CLIPBOARD_MANAGER, SAVE_TARGETS, None, win->src.window, CurrentTime); - RGFW_XHandleClipboardSelectionLoop(win); - } - if (win->src.clipboard) { - RGFW_FREE(win->src.clipboard); - win->src.clipboard = NULL; - } - - RGFW_GOTO_WAYLAND(0); - - /* ungrab pointer if it was grabbed */ - if (win->_flags & RGFW_HOLD_MOUSE) - XUngrabPointer(win->src.display, CurrentTime); - - #ifdef RGFW_EGL - RGFW_closeEGL(win); - #endif - - if (RGFW_hiddenMouse != NULL && win == RGFW_root) { - RGFW_freeMouse(RGFW_hiddenMouse); - RGFW_hiddenMouse = 0; - } - - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - if (win->buffer != NULL) { - if ((win->_flags & RGFW_BUFFER_ALLOC)) - RGFW_FREE(win->buffer); - XDestroyImage((XImage*) win->src.bitmap); - } - #endif - - if (win->src.display) { - XFreeGC(win->src.display, win->src.gc); - #if defined(RGFW_OPENGL) && !defined(RGFW_EGL) - glXDestroyContext(win->src.display, win->src.ctx); - #endif - if ((Drawable) win->src.window) - XDestroyWindow(win->src.display, (Drawable) win->src.window); /*!< close the window */ - - if (win == RGFW_root) { - XCloseDisplay(win->src.display); /*!< kill the x server connection */ - RGFW_root = NULL; - } - } - - /* set cleared display / window to NULL for error checking */ - win->src.display = 0; - win->src.window = 0; - - #define RGFW_FREE_LIBRARY(x) if (x != NULL) dlclose(x); x = NULL; - if (win == RGFW_root) { - #if !defined(RGFW_NO_X11_CURSOR_PRELOAD) && !defined(RGFW_NO_X11_CURSOR) - RGFW_FREE_LIBRARY(X11Cursorhandle); - #endif - #if !defined(RGFW_NO_X11_XI_PRELOAD) - RGFW_FREE_LIBRARY(X11Xihandle); - #endif - - #ifdef RGFW_USE_XDL - XDL_close(); - #endif - - #ifndef RGFW_NO_PASSTHROUGH - RGFW_FREE_LIBRARY(RGFW_libxshape); - #endif - - #ifndef RGFW_NO_LINUX - if (RGFW_eventWait_forceStop[0] || RGFW_eventWait_forceStop[1]){ - close(RGFW_eventWait_forceStop[0]); - close(RGFW_eventWait_forceStop[1]); - } - - u8 i; - for (i = 0; i < RGFW_gamepadCount; i++) { - if(RGFW_gamepads[i]) - close(RGFW_gamepads[i]); - } - #endif - } - RGFW_clipboard_switch(NULL); - RGFW_FREE(win->event.droppedFiles); - if ((win->_flags & RGFW_WINDOW_ALLOC)) - RGFW_FREE(win); - return; - #endif - - #ifdef RGFW_WAYLAND - wayland: - - #ifdef RGFW_X11 - XDestroyWindow(win->src.display, (Drawable) win->src.window); - #endif - - #ifdef RGFW_EGL - RGFW_closeEGL(win); - #endif - - if (RGFW_root == win) { - #ifdef RGFW_X11 - XCloseDisplay(win->src.display); /*!< kill connection to the x server */ - #endif - RGFW_root = NULL; - } - - xdg_toplevel_destroy(win->src.xdg_toplevel); - xdg_surface_destroy(win->src.xdg_surface); - wl_surface_destroy(win->src.surface); - - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - wl_buffer_destroy(win->src.wl_buffer); - if ((win->_flags & RGFW_BUFFER_ALLOC)) - RGFW_FREE(win->buffer); - - munmap(win->src.buffer, win->r.w * win->r.h * 4); - #endif - - wl_display_disconnect(win->src.wl_display); - RGFW_clipboard_switch(NULL); - RGFW_FREE(win->event.droppedFiles); - if ((win->_flags & RGFW_WINDOW_ALLOC)) - RGFW_FREE(win); - #endif -} - - -/* - End of X11 linux / wayland / unix defines -*/ - -#include -#include -#include - -void RGFW_stopCheckEvents(void) { - - RGFW_eventWait_forceStop[2] = 1; - while (1) { - const char byte = 0; - const ssize_t result = write(RGFW_eventWait_forceStop[1], &byte, 1); - if (result == 1 || result == -1) - break; - } -} - -void RGFW_window_eventWait(RGFW_window* win, u32 waitMS) { - if (waitMS == 0) return; - - u8 i; - if (RGFW_eventWait_forceStop[0] == 0 || RGFW_eventWait_forceStop[1] == 0) { - if (pipe(RGFW_eventWait_forceStop) != -1) { - fcntl(RGFW_eventWait_forceStop[0], F_GETFL, 0); - fcntl(RGFW_eventWait_forceStop[0], F_GETFD, 0); - fcntl(RGFW_eventWait_forceStop[1], F_GETFL, 0); - fcntl(RGFW_eventWait_forceStop[1], F_GETFD, 0); - } - } - - struct pollfd fds[] = { - #ifdef RGFW_WAYLAND - { wl_display_get_fd(win->src.wl_display), POLLIN, 0 }, - #else - { ConnectionNumber(win->src.display), POLLIN, 0 }, - #endif - { RGFW_eventWait_forceStop[0], POLLIN, 0 }, - #if defined(__linux__) - { -1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0} - #endif - }; - - u8 index = 2; - - #if defined(__linux__) - for (i = 0; i < RGFW_gamepadCount; i++) { - if (RGFW_gamepads[i] == 0) - continue; - - fds[index].fd = RGFW_gamepads[i]; - index++; - } - #endif - - - u64 start = RGFW_getTimeNS(); - - - #ifdef RGFW_WAYLAND - while (wl_display_dispatch(win->src.wl_display) <= 0 && waitMS != RGFW_eventWaitNext) { - #else - while (XPending(win->src.display) == 0 && waitMS != RGFW_eventWaitNext) { - #endif - if (poll(fds, index, (int)waitMS) <= 0) - break; - - if (waitMS != RGFW_eventWaitNext) { - waitMS -= (RGFW_getTimeNS() - start) / 1e+6; - } - } - - /* drain any data in the stop request */ - if (RGFW_eventWait_forceStop[2]) { - char data[64]; - (void)!read(RGFW_eventWait_forceStop[0], data, sizeof(data)); - - RGFW_eventWait_forceStop[2] = 0; - } -} - -i32 RGFW_getClock(void) { - static i32 clock = -1; - if (clock != -1) return clock; - - #if defined(_POSIX_MONOTONIC_CLOCK) - struct timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) - clock = CLOCK_MONOTONIC; - #else - clock = CLOCK_REALTIME; - #endif - - return clock; -} - -u64 RGFW_getTimerFreq(void) { return 1000000000LLU; } -u64 RGFW_getTimerValue(void) { - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - return (u64)ts.tv_sec * RGFW_getTimerFreq() + (u64)ts.tv_nsec; -} -#endif /* end of wayland or X11 defines */ - - -/* - - Start of Windows defines - - -*/ - -#ifdef RGFW_WINDOWS -#define WIN32_LEAN_AND_MEAN -#define OEMRESOURCE -#include - -#include -#include -#include -#include -#include -#include -#include - -__declspec(dllimport) int __stdcall WideCharToMultiByte( UINT CodePage, DWORD dwFlags, const WCHAR* lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar); - -#ifndef RGFW_NO_XINPUT - typedef DWORD (WINAPI * PFN_XInputGetState)(DWORD,XINPUT_STATE*); - PFN_XInputGetState XInputGetStateSRC = NULL; - #define XInputGetState XInputGetStateSRC - - typedef DWORD (WINAPI * PFN_XInputGetKeystroke)(DWORD, DWORD, PXINPUT_KEYSTROKE); - PFN_XInputGetKeystroke XInputGetKeystrokeSRC = NULL; - #define XInputGetKeystroke XInputGetKeystrokeSRC - - static HMODULE RGFW_XInput_dll = NULL; -#endif - -char* RGFW_createUTF8FromWideStringWin32(const WCHAR* source); - -#define GL_FRONT 0x0404 -#define GL_BACK 0x0405 -#define GL_LEFT 0x0406 -#define GL_RIGHT 0x0407 - -typedef int (*PFN_wglGetSwapIntervalEXT)(void); -PFN_wglGetSwapIntervalEXT wglGetSwapIntervalEXTSrc = NULL; -#define wglGetSwapIntervalEXT wglGetSwapIntervalEXTSrc - - -void* RGFWgamepadApi = NULL; - -/* these two wgl functions need to be preloaded */ -typedef HGLRC (WINAPI *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hdc, HGLRC hglrc, const int *attribList); -PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL; - -#ifndef RGFW_EGL - static HMODULE RGFW_wgl_dll = NULL; -#endif - -#ifndef RGFW_NO_LOAD_WGL - typedef HGLRC(WINAPI* PFN_wglCreateContext)(HDC); - typedef BOOL(WINAPI* PFN_wglDeleteContext)(HGLRC); - typedef PROC(WINAPI* PFN_wglGetProcAddress)(LPCSTR); - typedef BOOL(WINAPI* PFN_wglMakeCurrent)(HDC, HGLRC); - typedef HDC(WINAPI* PFN_wglGetCurrentDC)(void); - typedef HGLRC(WINAPI* PFN_wglGetCurrentContext)(void); - typedef BOOL(WINAPI* PFN_wglShareLists)(HGLRC, HGLRC); - - PFN_wglCreateContext wglCreateContextSRC; - PFN_wglDeleteContext wglDeleteContextSRC; - PFN_wglGetProcAddress wglGetProcAddressSRC; - PFN_wglMakeCurrent wglMakeCurrentSRC; - PFN_wglGetCurrentDC wglGetCurrentDCSRC; - PFN_wglGetCurrentContext wglGetCurrentContextSRC; - PFN_wglShareLists wglShareListsSRC; - - #define wglCreateContext wglCreateContextSRC - #define wglDeleteContext wglDeleteContextSRC - #define wglGetProcAddress wglGetProcAddressSRC - #define wglMakeCurrent wglMakeCurrentSRC - #define wglGetCurrentDC wglGetCurrentDCSRC - #define wglGetCurrentContext wglGetCurrentContextSRC - #define wglShareLists wglShareListsSRC -#endif - -#ifdef RGFW_OPENGL - void* RGFW_getProcAddress(const char* procname) { - void* proc = (void*) wglGetProcAddress(procname); - if (proc) - return proc; - - return (void*) GetProcAddress(RGFW_wgl_dll, procname); - } - - typedef HRESULT (APIENTRY* PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc, const int* piAttribIList, const FLOAT* pfAttribFList, UINT nMaxFormats, int* piFormats, UINT* nNumFormats); - static PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = NULL; -#endif - -LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { - RGFW_window* win = (RGFW_window*)GetPropA(hWnd, "RGFW"); - - RECT windowRect; - GetWindowRect(hWnd, &windowRect); - - switch (message) { - case WM_CLOSE: - RGFW_windowQuitCallback(win); - win->event.type = RGFW_quit; - return 0; - case WM_ACTIVATE: { - if (win == NULL) return DefWindowProcW(hWnd, message, wParam, lParam); - - RGFW_bool inFocus = RGFW_BOOL(LOWORD(wParam) != WA_INACTIVE); - if (inFocus) win->_flags |= RGFW_windowFocus; - else win->_flags &= ~RGFW_windowFocus; - RGFW_eventQueuePush((RGFW_event){.type = (RGFW_eventType)((u8)RGFW_focusOut - inFocus), ._win = win}); - - RGFW_focusCallback(win, inFocus); - - if ((win->_flags & RGFW_windowFullscreen) == 0) - return DefWindowProcW(hWnd, message, wParam, lParam); - - win->_flags &= ~RGFW_EVENT_PASSED; - if (inFocus == RGFW_FALSE) RGFW_window_minimize(win); - else RGFW_window_setFullscreen(win, 1); - return DefWindowProcW(hWnd, message, wParam, lParam); - } - case WM_MOVE: - if (win == NULL) return DefWindowProcW(hWnd, message, wParam, lParam); - - win->r.x = windowRect.left; - win->r.y = windowRect.top; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMoved, ._win = win}); - RGFW_windowMoveCallback(win, win->r); - return DefWindowProcW(hWnd, message, wParam, lParam); - case WM_SIZE: { - if (win == NULL) return DefWindowProcW(hWnd, message, wParam, lParam); - - if (win->src.aspectRatio.w != 0 && win->src.aspectRatio.h != 0) { - double aspectRatio = (double)win->src.aspectRatio.w / win->src.aspectRatio.h; - - int width = windowRect.right - windowRect.left; - int height = windowRect.bottom - windowRect.top; - int newHeight = (int)(width / aspectRatio); - int newWidth = (int)(height * aspectRatio); - - if (win->r.w > windowRect.right - windowRect.left || - win->r.h > (i32)((windowRect.bottom - windowRect.top) - win->src.hOffset)) - { - if (newHeight > height) windowRect.right = windowRect.left + newWidth; - else windowRect.bottom = windowRect.top + newHeight; - } else { - if (newHeight < height) windowRect.right = windowRect.left + newWidth; - else windowRect.bottom = windowRect.top + newHeight; - } - - RGFW_window_resize(win, RGFW_AREA((windowRect.right - windowRect.left), - (windowRect.bottom - windowRect.top) - win->src.hOffset)); - } - - win->r.w = windowRect.right - windowRect.left; - win->r.h = (windowRect.bottom - windowRect.top) - win->src.hOffset; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowResized, ._win = win}); - RGFW_windowResizeCallback(win, win->r); - RGFW_window_checkMode(win); - return DefWindowProcW(hWnd, message, wParam, lParam); - } - case WM_GETMINMAXINFO: { - if (win == NULL) - return DefWindowProcW(hWnd, message, wParam, lParam); - - MINMAXINFO* mmi = (MINMAXINFO*) lParam; - mmi->ptMinTrackSize.x = win->src.minSize.w; - mmi->ptMinTrackSize.y = win->src.minSize.h; - if (win->src.maxSize.w == 0 && win->src.maxSize.h == 0) - return DefWindowProcW(hWnd, message, wParam, lParam); - - mmi->ptMaxTrackSize.x = win->src.maxSize.w; - mmi->ptMaxTrackSize.y = win->src.maxSize.h; - return DefWindowProcW(hWnd, message, wParam, lParam); - } - case WM_PAINT: { - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowRefresh, ._win = win}); - RGFW_windowRefreshCallback(win); - return DefWindowProcW(hWnd, message, wParam, lParam); - } - default: break; - } - return DefWindowProcW(hWnd, message, wParam, lParam); -} - -#ifndef RGFW_NO_DPI - static HMODULE RGFW_Shcore_dll = NULL; - typedef HRESULT (WINAPI * PFN_GetDpiForMonitor)(HMONITOR,MONITOR_DPI_TYPE,UINT*,UINT*); - PFN_GetDpiForMonitor GetDpiForMonitorSRC = NULL; - #define GetDpiForMonitor GetDpiForMonitorSRC -#endif - -#ifndef RGFW_NO_DWM -static HMODULE RGFW_dwm_dll = NULL; -typedef struct { DWORD dwFlags; int fEnable; HRGN hRgnBlur; int fTransitionOnMaximized;} DWM_BLURBEHIND; -typedef HRESULT (WINAPI * PFN_DwmEnableBlurBehindWindow)(HWND, const DWM_BLURBEHIND*); -PFN_DwmEnableBlurBehindWindow DwmEnableBlurBehindWindowSRC = NULL; -#endif - -#if !defined(RGFW_NO_LOAD_WINMM) && !defined(RGFW_NO_WINMM) - static HMODULE RGFW_winmm_dll = NULL; - typedef u32 (WINAPI * PFN_timeBeginPeriod)(u32); - typedef PFN_timeBeginPeriod PFN_timeEndPeriod; - PFN_timeBeginPeriod timeBeginPeriodSRC, timeEndPeriodSRC; - #define timeBeginPeriod timeBeginPeriodSRC - #define timeEndPeriod timeEndPeriodSRC -#elif !defined(RGFW_NO_WINMM) - __declspec(dllimport) u32 __stdcall timeBeginPeriod(u32 uPeriod); - __declspec(dllimport) u32 __stdcall timeEndPeriod(u32 uPeriod); -#endif - -#define RGFW_PROC_DEF(proc, name) if (name##SRC == NULL && proc != NULL) name##SRC = (PFN_##name)(void*)GetProcAddress(proc, #name) - -#ifndef RGFW_NO_XINPUT -void RGFW_loadXInput(void) { - u32 i; - static const char* names[] = {"xinput1_4.dll", "xinput9_1_0.dll", "xinput1_2.dll", "xinput1_1.dll"}; - - for (i = 0; i < sizeof(names) / sizeof(const char*) && (XInputGetStateSRC == NULL || XInputGetStateSRC != NULL); i++) { - RGFW_XInput_dll = LoadLibraryA(names[i]); - RGFW_PROC_DEF(RGFW_XInput_dll, XInputGetState); - RGFW_PROC_DEF(RGFW_XInput_dll, XInputGetKeystroke); - } - - if (XInputGetStateSRC == NULL) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errFailedFuncLoad, RGFW_DEBUG_CTX(.win = RGFW_root, .srcError = 0), "Failed to load XInputGetState"); - if (XInputGetKeystrokeSRC == NULL) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errFailedFuncLoad, RGFW_DEBUG_CTX(.win = RGFW_root, .srcError = 0), "Failed to load XInputGetKeystroke"); -} -#endif - -void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area){ -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - win->buffer = buffer; - win->bufferSize = area; - - BITMAPV5HEADER bi = { 0 }; - ZeroMemory(&bi, sizeof(bi)); - bi.bV5Size = sizeof(bi); - bi.bV5Width = area.w; - bi.bV5Height = -((LONG) area.h); - bi.bV5Planes = 1; - bi.bV5BitCount = 32; - bi.bV5Compression = BI_RGB; - - win->src.bitmap = CreateDIBSection(win->src.hdc, - (BITMAPINFO*) &bi, DIB_RGB_COLORS, - (void**) &win->src.bitmapBits, - NULL, (DWORD) 0); - - if (win->buffer == NULL) - win->buffer = win->src.bitmapBits; - - win->src.hdcMem = CreateCompatibleDC(win->src.hdc); - SelectObject(win->src.hdcMem, win->src.bitmap); - - #if defined(RGFW_OSMESA) - win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); - OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); - #endif - #else - RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); /*!< if buffer rendering is not being used */ - #endif -} - -void RGFW_releaseCursor(RGFW_window* win) { - RGFW_UNUSED(win); - ClipCursor(NULL); - const RAWINPUTDEVICE id = { 0x01, 0x02, RIDEV_REMOVE, NULL }; - RegisterRawInputDevices(&id, 1, sizeof(id)); -} - -void RGFW_captureCursor(RGFW_window* win, RGFW_rect rect) { - RGFW_UNUSED(win); RGFW_UNUSED(rect); - - RECT clipRect; - GetClientRect(win->src.window, &clipRect); - ClientToScreen(win->src.window, (POINT*) &clipRect.left); - ClientToScreen(win->src.window, (POINT*) &clipRect.right); - ClipCursor(&clipRect); - - const RAWINPUTDEVICE id = { 0x01, 0x02, 0, win->src.window }; - RegisterRawInputDevices(&id, 1, sizeof(id)); -} - -#define RGFW_LOAD_LIBRARY(x, lib) if (x == NULL) x = LoadLibraryA(lib) - -#ifdef RGFW_DIRECTX - -#define OEMRESOURCE -#include - -#ifndef __cplusplus - #define __uuidof(T) IID_##T -#endif - -int RGFW_window_createDXSwapChain(RGFW_window* win, IDXGIFactory* pFactory, IUnknown* pDevice, IDXGISwapChain** swapchain) { - RGFW_ASSERT(win && pFactory && pDevice && swapchain); - - static DXGI_SWAP_CHAIN_DESC swapChainDesc = { 0 }; - swapChainDesc.BufferCount = 2; - swapChainDesc.BufferDesc.Width = win->r.w; - swapChainDesc.BufferDesc.Height = win->r.h; - swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - swapChainDesc.OutputWindow = (HWND)win->src.window; - swapChainDesc.SampleDesc.Count = 1; - swapChainDesc.SampleDesc.Quality = 0; - swapChainDesc.Windowed = TRUE; - swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; - - HRESULT hr = pFactory->lpVtbl->CreateSwapChain(pFactory, (IUnknown*)pDevice, &swapChainDesc, swapchain); - if (FAILED(hr)) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errDirectXContext, RGFW_DEBUG_CTX(.win = win, .srcError = hr), "Failed to create DirectX swap chain!"); - return -2; - } - - return 0; -} -#endif - -void RGFW_win32_makeWindowTransparent(RGFW_window* win) { - if (!(win->_flags & RGFW_windowTransparent)) return; - - #ifndef RGFW_NO_DWM - if (DwmEnableBlurBehindWindowSRC != NULL) { - DWM_BLURBEHIND bb = {0, 0, 0, 0}; - bb.dwFlags = 0x1; - bb.fEnable = TRUE; - bb.hRgnBlur = NULL; - DwmEnableBlurBehindWindowSRC(win->src.window, &bb); - - } else - #endif - { - SetWindowLong(win->src.window, GWL_EXSTYLE, WS_EX_LAYERED); - SetLayeredWindowAttributes(win->src.window, 0, 128, LWA_ALPHA); - } -} - -RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { - #ifndef RGFW_NO_XINPUT - if (RGFW_XInput_dll == NULL) - RGFW_loadXInput(); - #endif - - #ifndef RGFW_NO_DPI - #if (_WIN32_WINNT >= 0x0600) - SetProcessDPIAware(); - #endif - #endif - - #ifndef RGFW_NO_WINMM - #ifndef RGFW_NO_LOAD_WINMM - RGFW_LOAD_LIBRARY(RGFW_winmm_dll, "winmm.dll"); - RGFW_PROC_DEF(RGFW_winmm_dll, timeBeginPeriod); - RGFW_PROC_DEF(RGFW_winmm_dll, timeEndPeriod); - #endif - timeBeginPeriod(1); - #endif - - #ifndef RGFW_NO_DWM - RGFW_LOAD_LIBRARY(RGFW_dwm_dll, "dwmapi.dll"); - RGFW_PROC_DEF(RGFW_dwm_dll, DwmEnableBlurBehindWindow); - #endif - - RGFW_LOAD_LIBRARY(RGFW_wgl_dll, "opengl32.dll"); - #ifndef RGFW_NO_LOAD_WGL - RGFW_PROC_DEF(RGFW_wgl_dll, wglCreateContext); - RGFW_PROC_DEF(RGFW_wgl_dll, wglDeleteContext); - RGFW_PROC_DEF(RGFW_wgl_dll, wglDeleteContext); - RGFW_PROC_DEF(RGFW_wgl_dll, wglGetProcAddress); - RGFW_PROC_DEF(RGFW_wgl_dll, wglMakeCurrent); - RGFW_PROC_DEF(RGFW_wgl_dll, wglGetCurrentDC); - RGFW_PROC_DEF(RGFW_wgl_dll, wglGetCurrentContext); - RGFW_PROC_DEF(RGFW_wgl_dll, wglShareLists); - #endif - - if (name[0] == 0) name = (char*) " "; - - RGFW_window_basic_init(win, rect, flags); - - win->src.hIconSmall = win->src.hIconBig = NULL; - win->src.maxSize = RGFW_AREA(0, 0); - win->src.minSize = RGFW_AREA(0, 0); - win->src.aspectRatio = RGFW_AREA(0, 0); - - HINSTANCE inh = GetModuleHandleA(NULL); - - #ifndef __cplusplus - WNDCLASSW Class = { 0 }; /*!< Setup the Window class. */ - #else - WNDCLASSW Class = { }; - #endif - - if (RGFW_className == NULL) - RGFW_className = (char*)name; - - wchar_t wide_class[255]; - MultiByteToWideChar(CP_UTF8, 0, RGFW_className, -1, wide_class, 255); - - Class.lpszClassName = wide_class; - Class.hInstance = inh; - Class.hCursor = LoadCursor(NULL, IDC_ARROW); - Class.lpfnWndProc = WndProcW; - Class.cbClsExtra = sizeof(RGFW_window*); - - Class.hIcon = (HICON)LoadImageA(GetModuleHandleW(NULL), "RGFW_ICON", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); - if (Class.hIcon == NULL) - Class.hIcon = (HICON)LoadImageA(NULL, (LPCSTR)IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); - - RegisterClassW(&Class); - - DWORD window_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN; - - RECT windowRect, clientRect; - - if (!(flags & RGFW_windowNoBorder)) { - window_style |= WS_CAPTION | WS_SYSMENU | WS_BORDER | WS_MINIMIZEBOX; - - if (!(flags & RGFW_windowNoResize)) - window_style |= WS_SIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME; - } else - window_style |= WS_POPUP | WS_VISIBLE | WS_SYSMENU | WS_MINIMIZEBOX; - - wchar_t wide_name[255]; - MultiByteToWideChar(CP_UTF8, 0, name, -1, wide_name, 255); - - HWND dummyWin = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->r.x, win->r.y, win->r.w, win->r.h, 0, 0, inh, 0); - - GetWindowRect(dummyWin, &windowRect); - GetClientRect(dummyWin, &clientRect); - - win->src.hOffset = (windowRect.bottom - windowRect.top) - (clientRect.bottom - clientRect.top); - win->src.window = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->r.x, win->r.y, win->r.w, win->r.h + win->src.hOffset, 0, 0, inh, 0); - - SetPropA(win->src.window, "RGFW", win); - - if (flags & RGFW_windowAllowDND) { - win->_flags |= RGFW_windowAllowDND; - RGFW_window_setDND(win, 1); - } - win->src.hdc = GetDC(win->src.window); - - if ((flags & RGFW_windowNoInitAPI) == 0) { - #ifdef RGFW_OPENGL - HDC dummy_dc = GetDC(dummyWin); - - u32 pfd_flags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL; - - //if (RGFW_DOUBLE_BUFFER) - pfd_flags |= PFD_DOUBLEBUFFER; - - PIXELFORMATDESCRIPTOR pfd = { - sizeof(PIXELFORMATDESCRIPTOR), // Size of the descriptor - 1, // Version - pfd_flags, // Flags to specify what the pixel format supports (e.g., PFD_SUPPORT_OPENGL) - PFD_TYPE_RGBA, // Pixel type is RGBA - 32, // Color bits (red, green, blue channels) - 0, 0, 0, 0, 0, 0, // No color bits for unused channels - 8, // Alpha bits (important for transparency) - 0, // No accumulation buffer bits needed - 0, 0, 0, 0, // No accumulation bits - 32, // Depth buffer bits - 8, // Stencil buffer bits - 0, // Auxiliary buffer bits (unused) - PFD_MAIN_PLANE, // Use the main plane for rendering - 0, 0, 0, 0, 0 // Reserved fields - }; - - - int pixel_format = ChoosePixelFormat(dummy_dc, &pfd); - SetPixelFormat(dummy_dc, pixel_format, &pfd); - - HGLRC dummy_context = wglCreateContext(dummy_dc); - wglMakeCurrent(dummy_dc, dummy_context); - - if (wglChoosePixelFormatARB == NULL) { - wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC) (void*) wglGetProcAddress("wglCreateContextAttribsARB"); - wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC) (void*)wglGetProcAddress("wglChoosePixelFormatARB"); - } - - wglMakeCurrent(dummy_dc, 0); - wglDeleteContext(dummy_context); - ReleaseDC(dummyWin, dummy_dc); - - /* try to create the pixel format we want for opengl and then try to create an opengl context for the specified version */ - if (wglCreateContextAttribsARB != NULL) { - PIXELFORMATDESCRIPTOR pfd = {sizeof(pfd), 1, pfd_flags, PFD_TYPE_RGBA, 32, 8, PFD_MAIN_PLANE, 24, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - - if (flags & RGFW_windowOpenglSoftware) - pfd.dwFlags |= PFD_GENERIC_FORMAT | PFD_GENERIC_ACCELERATED; - - if (wglChoosePixelFormatARB != NULL) { - i32* pixel_format_attribs = (i32*)RGFW_initFormatAttribs(flags & RGFW_windowOpenglSoftware); - - int pixel_format; - UINT num_formats; - wglChoosePixelFormatARB(win->src.hdc, pixel_format_attribs, 0, 1, &pixel_format, &num_formats); - if (!num_formats) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to create a pixel format for WGL"); - - DescribePixelFormat(win->src.hdc, pixel_format, sizeof(pfd), &pfd); - if (!SetPixelFormat(win->src.hdc, pixel_format, &pfd)) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to set the WGL pixel format"); - } - - /* create opengl/WGL context for the specified version */ - u32 index = 0; - i32 attribs[40]; - - if (RGFW_GL_HINTS[RGFW_glProfile]== RGFW_glCore) { - SET_ATTRIB(WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB); - } - else { - SET_ATTRIB(WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB); - } - - if (RGFW_GL_HINTS[RGFW_glMinor] || RGFW_GL_HINTS[RGFW_glMajor]) { - SET_ATTRIB(WGL_CONTEXT_MAJOR_VERSION_ARB, RGFW_GL_HINTS[RGFW_glMinor]); - SET_ATTRIB(WGL_CONTEXT_MINOR_VERSION_ARB, RGFW_GL_HINTS[RGFW_glMajor]); - } - - SET_ATTRIB(0, 0); - - win->src.ctx = (HGLRC)wglCreateContextAttribsARB(win->src.hdc, NULL, attribs); - } else { /* fall back to a default context (probably opengl 2 or something) */ - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to create an accelerated OpenGL Context"); - - int pixel_format = ChoosePixelFormat(win->src.hdc, &pfd); - SetPixelFormat(win->src.hdc, pixel_format, &pfd); - - win->src.ctx = wglCreateContext(win->src.hdc); - } - - wglMakeCurrent(win->src.hdc, win->src.ctx); - #endif - } - - #ifdef RGFW_OPENGL - if ((flags & RGFW_windowNoInitAPI) == 0) { - ReleaseDC(win->src.window, win->src.hdc); - win->src.hdc = GetDC(win->src.window); - wglMakeCurrent(win->src.hdc, win->src.ctx); - } - #endif - - DestroyWindow(dummyWin); - - #ifdef RGFW_EGL - if ((flags & RGFW_windowNoInitAPI) == 0) - RGFW_createOpenGLContext(win); - #endif - - ShowWindow(win->src.window, SW_SHOWNORMAL); - RGFW_window_setFlags(win, flags); - - RGFW_win32_makeWindowTransparent(win); - - #ifdef RGFW_OPENGL - if (RGFW_root != win) - wglShareLists(RGFW_root->src.ctx, win->src.ctx); - #endif - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); - return win; -} - -void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { - RGFW_setBit(&win->_flags, RGFW_windowNoBorder, !border); - DWORD style = GetWindowLong(win->src.window, GWL_STYLE); - - if (border == 0) { - SetWindowLong(win->src.window, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW); - SetWindowPos( - win->src.window, HWND_TOP, 0, 0, 0, 0, - SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE - ); - } - else { - SetWindowLong(win->src.window, GWL_STYLE, style | WS_OVERLAPPEDWINDOW); - SetWindowPos( - win->src.window, HWND_TOP, 0, 0, 0, 0, - SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE - ); - } -} - -void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow) { - RGFW_setBit(&win->_flags, RGFW_windowAllowDND, allow); - DragAcceptFiles(win->src.window, allow); -} - -RGFW_area RGFW_getScreenSize(void) { - HDC dc = GetDC(NULL); - RGFW_area area = RGFW_AREA(GetDeviceCaps(dc, HORZRES), GetDeviceCaps(dc, VERTRES)); - ReleaseDC(NULL, dc); - return area; -} - -RGFW_point RGFW_getGlobalMousePoint(void) { - POINT p; - GetCursorPos(&p); - - return RGFW_POINT(p.x, p.y); -} - -void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { - RGFW_ASSERT(win != NULL); - win->src.aspectRatio = a; -} - -void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { - RGFW_ASSERT(win != NULL); - win->src.minSize = a; -} - -void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { - RGFW_ASSERT(win != NULL); - win->src.maxSize = a; -} - -void RGFW_window_focus(RGFW_window* win) { - RGFW_ASSERT(win); - SetForegroundWindow(win->src.window); - SetFocus(win->src.window); -} - -void RGFW_window_raise(RGFW_window* win) { - RGFW_ASSERT(win); - BringWindowToTop(win->src.window); - SetWindowPos(win->src.window, HWND_TOP, win->r.x, win->r.y, win->r.w, win->r.h, SWP_NOSIZE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); -} - -void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { - RGFW_ASSERT(win != NULL); - - if (fullscreen == RGFW_FALSE) { - RGFW_window_setBorder(win, 1); - SetWindowPos(win->src.window, HWND_NOTOPMOST, win->_oldRect.x, win->_oldRect.y, win->_oldRect.w, win->_oldRect.h + win->src.hOffset, - SWP_NOOWNERZORDER | SWP_FRAMECHANGED); - - win->_flags &= ~RGFW_windowFullscreen; - win->r = win->_oldRect; - return; - } - - win->_flags |= RGFW_windowFullscreen; - - RGFW_monitor mon = RGFW_window_getMonitor(win); - RGFW_window_setBorder(win, 0); - SetWindowPos(win->src.window, HWND_TOPMOST, 0, 0, mon.mode.area.w, mon.mode.area.h, SWP_NOOWNERZORDER | SWP_FRAMECHANGED); - - win->_oldRect = win->r; - win->r = RGFW_RECT(0, 0, mon.mode.area.w, mon.mode.area.h); -} - -void RGFW_window_maximize(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - RGFW_window_hide(win); - ShowWindow(win->src.window, SW_MAXIMIZE); -} - -void RGFW_window_minimize(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - ShowWindow(win->src.window, SW_MINIMIZE); -} - -void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { - RGFW_ASSERT(win != NULL); - if (floating) SetWindowPos(win->src.window, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); - else SetWindowPos(win->src.window, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); -} - -void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { - SetWindowLong(win->src.window, GWL_EXSTYLE, WS_EX_LAYERED); - SetLayeredWindowAttributes(win->src.window, 0, opacity, LWA_ALPHA); -} - -void RGFW_window_restore(RGFW_window* win) { RGFW_window_show(win); } - -RGFW_bool RGFW_window_isFloating(RGFW_window* win) { - return (GetWindowLongPtr(win->src.window, GWL_EXSTYLE) & WS_EX_TOPMOST) != 0; -} - -u8 RGFW_xinput2RGFW[] = { - RGFW_gamepadA, /* or PS X button */ - RGFW_gamepadB, /* or PS circle button */ - RGFW_gamepadX, /* or PS square button */ - RGFW_gamepadY, /* or PS triangle button */ - RGFW_gamepadR1, /* right bumper */ - RGFW_gamepadL1, /* left bump */ - RGFW_gamepadL2, /* left trigger */ - RGFW_gamepadR2, /* right trigger */ - 0, 0, 0, 0, 0, 0, 0, 0, - RGFW_gamepadUp, /* dpad up */ - RGFW_gamepadDown, /* dpad down */ - RGFW_gamepadLeft, /* dpad left */ - RGFW_gamepadRight, /* dpad right */ - RGFW_gamepadStart, /* start button */ - RGFW_gamepadSelect,/* select button */ - RGFW_gamepadL3, - RGFW_gamepadR3, -}; - -static i32 RGFW_checkXInput(RGFW_window* win, RGFW_event* e) { - #ifndef RGFW_NO_XINPUT - - RGFW_UNUSED(win); - size_t i; - for (i = 0; i < 4; i++) { - XINPUT_KEYSTROKE keystroke; - - if (XInputGetKeystroke == NULL) - return 0; - - DWORD result = XInputGetKeystroke((DWORD)i, 0, &keystroke); - - if ((keystroke.Flags & XINPUT_KEYSTROKE_REPEAT) == 0 && result != ERROR_EMPTY) { - if (result != ERROR_SUCCESS) - return 0; - - if (keystroke.VirtualKey > VK_PAD_RTHUMB_PRESS) - continue; - - //gamepad + 1 = RGFW_gamepadButtonReleased - e->type = RGFW_gamepadButtonPressed + !(keystroke.Flags & XINPUT_KEYSTROKE_KEYDOWN); - e->button = RGFW_xinput2RGFW[keystroke.VirtualKey - 0x5800]; - RGFW_gamepadPressed[i][e->button].prev = RGFW_gamepadPressed[i][e->button].current; - RGFW_gamepadPressed[i][e->button].current = (keystroke.Flags & XINPUT_KEYSTROKE_KEYDOWN); - - RGFW_gamepadButtonCallback(win, i, e->button, e->type == RGFW_gamepadButtonPressed); - return 1; - } - - XINPUT_STATE state; - if (XInputGetState == NULL || - XInputGetState((DWORD) i, &state) == ERROR_DEVICE_NOT_CONNECTED - ) { - if (RGFW_gamepads[i] == 0) - continue; - - RGFW_gamepads[i] = 0; - RGFW_gamepadCount--; - - win->event.type = RGFW_gamepadDisconnected; - win->event.gamepad = i; - RGFW_gamepadCallback(win, i, 0); - return 1; - } - - if (RGFW_gamepads[i] == 0) { - RGFW_gamepads[i] = 1; - RGFW_gamepadCount++; - - char str[] = "Microsoft X-Box (XInput device)"; - RGFW_MEMCPY(RGFW_gamepads_name[i], str, sizeof(str)); - RGFW_gamepads_name[i][sizeof(RGFW_gamepads_name[i]) - 1] = '\0'; - win->event.type = RGFW_gamepadConnected; - win->event.gamepad = i; - RGFW_gamepads_type[i] = RGFW_gamepadMicrosoft; - - RGFW_gamepadCallback(win, i, 1); - return 1; - } - -#define INPUT_DEADZONE ( 0.24f * (float)(0x7FFF) ) // Default to 24% of the +/- 32767 range. This is a reasonable default value but can be altered if needed. - - if ((state.Gamepad.sThumbLX < INPUT_DEADZONE && - state.Gamepad.sThumbLX > -INPUT_DEADZONE) && - (state.Gamepad.sThumbLY < INPUT_DEADZONE && - state.Gamepad.sThumbLY > -INPUT_DEADZONE)) - { - state.Gamepad.sThumbLX = 0; - state.Gamepad.sThumbLY = 0; - } - - if ((state.Gamepad.sThumbRX < INPUT_DEADZONE && - state.Gamepad.sThumbRX > -INPUT_DEADZONE) && - (state.Gamepad.sThumbRY < INPUT_DEADZONE && - state.Gamepad.sThumbRY > -INPUT_DEADZONE)) - { - state.Gamepad.sThumbRX = 0; - state.Gamepad.sThumbRY = 0; - } - - e->axisesCount = 2; - RGFW_point axis1 = RGFW_POINT(((float)state.Gamepad.sThumbLX / 32768.0f) * 100, ((float)state.Gamepad.sThumbLY / -32768.0f) * 100); - RGFW_point axis2 = RGFW_POINT(((float)state.Gamepad.sThumbRX / 32768.0f) * 100, ((float)state.Gamepad.sThumbRY / -32768.0f) * 100); - - if (axis1.x != e->axis[0].x || axis1.y != e->axis[0].y){ - win->event.whichAxis = 0; - - e->type = RGFW_gamepadAxisMove; - e->axis[0] = axis1; - RGFW_gamepadAxes[i][0] = e->axis[0]; - - RGFW_gamepadAxisCallback(win, e->gamepad, e->axis, e->axisesCount, e->whichAxis); - return 1; - } - - if (axis2.x != e->axis[1].x || axis2.y != e->axis[1].y) { - win->event.whichAxis = 1; - e->type = RGFW_gamepadAxisMove; - e->axis[1] = axis2; - RGFW_gamepadAxes[i][1] = e->axis[1]; - - RGFW_gamepadAxisCallback(win, e->gamepad, e->axis, e->axisesCount, e->whichAxis); - return 1; - } - } - - #endif - - return 0; -} - -void RGFW_stopCheckEvents(void) { - PostMessageW(RGFW_root->src.window, WM_NULL, 0, 0); -} - -void RGFW_window_eventWait(RGFW_window* win, u32 waitMS) { - RGFW_UNUSED(win); - MsgWaitForMultipleObjects(0, NULL, FALSE, (DWORD)waitMS, QS_ALLINPUT); -} - -RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { - RGFW_event* ev = RGFW_window_checkEventCore(win); - if (ev) { - if (ev == (RGFW_event*)-1) return NULL; - return ev; - } - - static HDROP drop; - if (win->event.type == RGFW_DNDInit) { - if (win->event.droppedFilesCount) { - u32 i; - for (i = 0; i < win->event.droppedFilesCount; i++) - win->event.droppedFiles[i][0] = '\0'; - } - - win->event.droppedFilesCount = 0; - win->event.droppedFilesCount = DragQueryFileW(drop, 0xffffffff, NULL, 0); - - u32 i; - for (i = 0; i < win->event.droppedFilesCount; i++) { - UINT length = DragQueryFileW(drop, i, NULL, 0); - if (length == 0) - continue; - - WCHAR buffer[RGFW_MAX_PATH * 2]; - if (length > (RGFW_MAX_PATH * 2) - 1) - length = RGFW_MAX_PATH * 2; - - DragQueryFileW(drop, i, buffer, length + 1); - - char* str = RGFW_createUTF8FromWideStringWin32(buffer); - if (str != NULL) - RGFW_MEMCPY(win->event.droppedFiles[i], str, length + 1); - - win->event.droppedFiles[i][RGFW_MAX_PATH - 1] = '\0'; - } - - DragFinish(drop); - RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); - - win->event.type = RGFW_DND; - return &win->event; - } - - if (RGFW_checkXInput(win, &win->event)) - return &win->event; - - static BYTE keyboardState[256]; - GetKeyboardState(keyboardState); - - if (!IsWindow(win->src.window)) { - win->event.type = RGFW_quit; - RGFW_windowQuitCallback(win); - return &win->event; - } - - MSG msg; - if (PeekMessageA(&msg, win->src.window, 0u, 0u, PM_REMOVE) == 0) - return NULL; - - switch (msg.message) { - case WM_CLOSE: - case WM_QUIT: - RGFW_windowQuitCallback(win); - win->event.type = RGFW_quit; - break; - #if(_WIN32_WINNT >= 0x0600) - case WM_DWMCOMPOSITIONCHANGED: - case WM_DWMCOLORIZATIONCOLORCHANGED: - RGFW_win32_makeWindowTransparent(win); - break; - #endif - - case WM_MOUSELEAVE: - win->event.type = RGFW_mouseLeave; - win->_flags |= RGFW_MOUSE_LEFT; - RGFW_mouseNotifyCallBack(win, win->event.point, 0); - break; - case WM_SYSKEYUP: case WM_KEYUP: { - i32 scancode = (HIWORD(msg.lParam) & (KF_EXTENDED | 0xff)); - if (scancode == 0) - scancode = MapVirtualKeyW((u32)msg.wParam, MAPVK_VK_TO_VSC); - - switch (scancode) { - case 0x54: scancode = 0x137; break; /* Alt+PrtS */ - case 0x146: scancode = 0x45; break; /* Ctrl+Pause */ - case 0x136: scancode = 0x36; break; /* CJK IME sets the extended bit for right Shift */ - default: break; - } - - win->event.key = RGFW_apiKeyToRGFW((u32) scancode); - - if (msg.wParam == VK_CONTROL) { - if (HIWORD(msg.lParam) & KF_EXTENDED) - win->event.key = RGFW_controlR; - else win->event.key = RGFW_controlL; - } - - wchar_t charBuffer; - ToUnicodeEx(msg.wParam, scancode, keyboardState, (wchar_t*)&charBuffer, 1, 0, NULL); - - win->event.keyChar = (u8)charBuffer; - - RGFW_keyboard[win->event.key].prev = RGFW_isPressed(win, win->event.key); - win->event.type = RGFW_keyReleased; - RGFW_keyboard[win->event.key].current = 0; - - RGFW_updateKeyMods(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001), (GetKeyState(VK_SCROLL) & 0x0001)); - - RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, 0); - break; - } - case WM_SYSKEYDOWN: case WM_KEYDOWN: { - i32 scancode = (HIWORD(msg.lParam) & (KF_EXTENDED | 0xff)); - if (scancode == 0) - scancode = MapVirtualKeyW((u32)msg.wParam, MAPVK_VK_TO_VSC); - - switch (scancode) { - case 0x54: scancode = 0x137; break; /* Alt+PrtS */ - case 0x146: scancode = 0x45; break; /* Ctrl+Pause */ - case 0x136: scancode = 0x36; break; /* CJK IME sets the extended bit for right Shift */ - default: break; - } - - win->event.key = RGFW_apiKeyToRGFW((u32) scancode); - - if (msg.wParam == VK_CONTROL) { - if (HIWORD(msg.lParam) & KF_EXTENDED) - win->event.key = RGFW_controlR; - else win->event.key = RGFW_controlL; - } - - wchar_t charBuffer; - ToUnicodeEx(msg.wParam, scancode, keyboardState, &charBuffer, 1, 0, NULL); - win->event.keyChar = (u8)charBuffer; - - RGFW_keyboard[win->event.key].prev = RGFW_isPressed(win, win->event.key); - - win->event.type = RGFW_keyPressed; - win->event.repeat = RGFW_isPressed(win, win->event.key); - RGFW_keyboard[win->event.key].current = 1; - RGFW_updateKeyMods(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001), (GetKeyState(VK_SCROLL) & 0x0001)); - - RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, 1); - break; - } - - case WM_MOUSEMOVE: { - if ((win->_flags & RGFW_HOLD_MOUSE)) - break; - - win->event.type = RGFW_mousePosChanged; - - i32 x = GET_X_LPARAM(msg.lParam); - i32 y = GET_Y_LPARAM(msg.lParam); - - RGFW_mousePosCallback(win, win->event.point, win->event.vector); - - if (win->_flags & RGFW_MOUSE_LEFT) { - win->_flags &= ~RGFW_MOUSE_LEFT; - win->event.type = RGFW_mouseEnter; - RGFW_mouseNotifyCallBack(win, win->event.point, 1); - } - - win->event.point.x = x; - win->event.point.y = y; - win->_lastMousePoint = RGFW_POINT(x, y); - - break; - } - case WM_INPUT: { - if (!(win->_flags & RGFW_HOLD_MOUSE)) - break; - - unsigned size = sizeof(RAWINPUT); - static RAWINPUT raw = {}; - - GetRawInputData((HRAWINPUT)msg.lParam, RID_INPUT, &raw, &size, sizeof(RAWINPUTHEADER)); - - if (raw.header.dwType != RIM_TYPEMOUSE || (raw.data.mouse.lLastX == 0 && raw.data.mouse.lLastY == 0) ) - break; - - if (raw.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { - POINT pos = {0, 0}; - int width, height; - - if (raw.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) { - pos.x += GetSystemMetrics(SM_XVIRTUALSCREEN); - pos.y += GetSystemMetrics(SM_YVIRTUALSCREEN); - width = GetSystemMetrics(SM_CXVIRTUALSCREEN); - height = GetSystemMetrics(SM_CYVIRTUALSCREEN); - } - else { - width = GetSystemMetrics(SM_CXSCREEN); - height = GetSystemMetrics(SM_CYSCREEN); - } - - pos.x += (int) ((raw.data.mouse.lLastX / 65535.f) * width); - pos.y += (int) ((raw.data.mouse.lLastY / 65535.f) * height); - ScreenToClient(win->src.window, &pos); - - win->event.vector.x = pos.x - win->_lastMousePoint.x; - win->event.vector.y = pos.y - win->_lastMousePoint.y; - } else { - win->event.vector.x = raw.data.mouse.lLastX; - win->event.vector.y = raw.data.mouse.lLastY; - } - - win->event.type = RGFW_mousePosChanged; - win->_lastMousePoint.x += win->event.vector.x; - win->_lastMousePoint.y += win->event.vector.y; - win->event.point = win->_lastMousePoint; - RGFW_mousePosCallback(win, win->event.point, win->event.vector); - break; - } - case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: case WM_XBUTTONDOWN: - if (msg.message == WM_XBUTTONDOWN) - win->event.button = RGFW_mouseMisc1 + (GET_XBUTTON_WPARAM(msg.wParam) == XBUTTON2); - else win->event.button = (msg.message == WM_LBUTTONDOWN) ? RGFW_mouseLeft : - (msg.message == WM_RBUTTONDOWN) ? RGFW_mouseRight : RGFW_mouseMiddle; - - win->event.type = RGFW_mouseButtonPressed; - RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; - RGFW_mouseButtons[win->event.button].current = 1; - RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); - break; - case WM_LBUTTONUP: case WM_RBUTTONUP: case WM_MBUTTONUP: case WM_XBUTTONUP: - if (msg.message == WM_XBUTTONUP) - win->event.button = RGFW_mouseMisc1 + (GET_XBUTTON_WPARAM(msg.wParam) == XBUTTON2); - else win->event.button = (msg.message == WM_LBUTTONUP) ? RGFW_mouseLeft : - (msg.message == WM_RBUTTONUP) ? RGFW_mouseRight : RGFW_mouseMiddle; - win->event.type = RGFW_mouseButtonReleased; - RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; - RGFW_mouseButtons[win->event.button].current = 0; - RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 0); - break; - case WM_MOUSEWHEEL: - if (msg.wParam > 0) - win->event.button = RGFW_mouseScrollUp; - else - win->event.button = RGFW_mouseScrollDown; - - RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; - RGFW_mouseButtons[win->event.button].current = 1; - - win->event.scroll = (SHORT) HIWORD(msg.wParam) / (double) WHEEL_DELTA; - - win->event.type = RGFW_mouseButtonPressed; - RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); - break; - case WM_DROPFILES: { - win->event.type = RGFW_DNDInit; - - drop = (HDROP) msg.wParam; - POINT pt; - - /* Move the mouse to the position of the drop */ - DragQueryPoint(drop, &pt); - - win->event.point.x = pt.x; - win->event.point.y = pt.y; - - RGFW_dndInitCallback(win, win->event.point); - } - break; - default: - TranslateMessage(&msg); - DispatchMessageA(&msg); - return RGFW_window_checkEvent(win); - } - - TranslateMessage(&msg); - DispatchMessageA(&msg); - - return &win->event; -} - -RGFW_bool RGFW_window_isHidden(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - - return IsWindowVisible(win->src.window) == 0 && !RGFW_window_isMinimized(win); -} - -RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - - #ifndef __cplusplus - WINDOWPLACEMENT placement = { 0 }; - #else - WINDOWPLACEMENT placement = { }; - #endif - GetWindowPlacement(win->src.window, &placement); - return placement.showCmd == SW_SHOWMINIMIZED; -} - -RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - - #ifndef __cplusplus - WINDOWPLACEMENT placement = { 0 }; - #else - WINDOWPLACEMENT placement = { }; - #endif - GetWindowPlacement(win->src.window, &placement); - return placement.showCmd == SW_SHOWMAXIMIZED || IsZoomed(win->src.window); -} - -typedef struct { int iIndex; HMONITOR hMonitor; } RGFW_mInfo; -BOOL CALLBACK GetMonitorByHandle(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { - RGFW_UNUSED(hdcMonitor); - RGFW_UNUSED(lprcMonitor); - - RGFW_mInfo* info = (RGFW_mInfo*) dwData; - if (info->hMonitor == hMonitor) - return RGFW_FALSE; - - info->iIndex++; - return RGFW_TRUE; -} - -#ifndef RGFW_NO_MONITOR - -RGFW_monitor win32CreateMonitor(HMONITOR src) { - RGFW_monitor monitor; - MONITORINFOEX monitorInfo; - - monitorInfo.cbSize = sizeof(MONITORINFOEX); - GetMonitorInfoA(src, (LPMONITORINFO)&monitorInfo); - - RGFW_mInfo info; - info.iIndex = 0; - info.hMonitor = src; - - /* get the monitor's index */ - DISPLAY_DEVICEA dd; - dd.cb = sizeof(dd); - - for (DWORD deviceNum = 0; EnumDisplayDevicesA(NULL, deviceNum, &dd, 0); deviceNum++) { - if (!(dd.StateFlags & DISPLAY_DEVICE_ACTIVE)) - continue; - - DEVMODE dm; - ZeroMemory(&dm, sizeof(dm)); - dm.dmSize = sizeof(dm); - - if (EnumDisplaySettingsA(dd.DeviceName, ENUM_CURRENT_SETTINGS, &dm)) { - monitor.mode.refreshRate = dm.dmDisplayFrequency; - RGFW_splitBPP(dm.dmBitsPerPel, &monitor.mode); - } - - DISPLAY_DEVICEA mdd; - mdd.cb = sizeof(mdd); - - if (EnumDisplayDevicesA(dd.DeviceName, info.iIndex, &mdd, 0)) { - RGFW_MEMCPY(monitor.name, mdd.DeviceString, 128); - break; - } - } - - - monitor.x = monitorInfo.rcWork.left; - monitor.y = monitorInfo.rcWork.top; - monitor.mode.area.w = monitorInfo.rcWork.right - monitorInfo.rcWork.left; - monitor.mode.area.h = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top; - - HDC hdc = CreateDC(monitorInfo.szDevice, NULL, NULL, NULL); - /* get pixels per inch */ - float dpiX = (float)GetDeviceCaps(hdc, LOGPIXELSX); - float dpiY = (float)GetDeviceCaps(hdc, LOGPIXELSX); - - monitor.scaleX = dpiX / 96.0f; - monitor.scaleY = dpiY / 96.0f; - monitor.pixelRatio = dpiX >= 192.0f ? 2 : 1; - - monitor.physW = GetDeviceCaps(hdc, HORZSIZE) / 25.4; - monitor.physH = GetDeviceCaps(hdc, VERTSIZE) / 25.4; - DeleteDC(hdc); - - #ifndef RGFW_NO_DPI - RGFW_LOAD_LIBRARY(RGFW_Shcore_dll, "shcore.dll"); - RGFW_PROC_DEF(RGFW_Shcore_dll, GetDpiForMonitor); - - if (GetDpiForMonitor != NULL) { - u32 x, y; - GetDpiForMonitor(src, MDT_EFFECTIVE_DPI, &x, &y); - monitor.scaleX = (float) (x) / (float) 96.0f; - monitor.scaleY = (float) (y) / (float) 96.0f; - monitor.pixelRatio = dpiX >= 192.0f ? 2 : 1; - } - #endif - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); - return monitor; -} -#endif /* RGFW_NO_MONITOR */ - -#ifndef RGFW_NO_MONITOR - -RGFW_monitor RGFW_monitors[6]; -BOOL CALLBACK GetMonitorHandle(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { - RGFW_UNUSED(hdcMonitor); - RGFW_UNUSED(lprcMonitor); - - RGFW_mInfo* info = (RGFW_mInfo*) dwData; - - if (info->iIndex >= 6) - return FALSE; - - RGFW_monitors[info->iIndex] = win32CreateMonitor(hMonitor); - info->iIndex++; - - return TRUE; -} - -RGFW_monitor RGFW_getPrimaryMonitor(void) { - #ifdef __cplusplus - return win32CreateMonitor(MonitorFromPoint({ 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); - #else - return win32CreateMonitor(MonitorFromPoint((POINT) { 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); - #endif -} - -RGFW_monitor* RGFW_getMonitors(void) { - RGFW_mInfo info; - info.iIndex = 0; - while (EnumDisplayMonitors(NULL, NULL, GetMonitorHandle, (LPARAM) &info)); - - return RGFW_monitors; -} - -RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { - HMONITOR src = MonitorFromWindow(win->src.window, MONITOR_DEFAULTTOPRIMARY); - return win32CreateMonitor(src); -} - -RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { - HMONITOR src = MonitorFromPoint((POINT) { mon.x, mon.y }, MONITOR_DEFAULTTOPRIMARY); - - MONITORINFOEX monitorInfo; - monitorInfo.cbSize = sizeof(MONITORINFOEX); - GetMonitorInfoA(src, (LPMONITORINFO)&monitorInfo); - - DISPLAY_DEVICE dd; - dd.cb = sizeof(dd); - - // Enumerate display devices - for (DWORD deviceNum = 0; EnumDisplayDevicesA(NULL, deviceNum, &dd, 0); deviceNum++) { - if (!(dd.StateFlags & DISPLAY_DEVICE_ACTIVE)) - continue; - - DEVMODE dm; - ZeroMemory(&dm, sizeof(dm)); - dm.dmSize = sizeof(dm); - - if (EnumDisplaySettingsA(dd.DeviceName, ENUM_CURRENT_SETTINGS, &dm)) { - if (request & RGFW_monitorScale) { - dm.dmFields |= DM_PELSWIDTH | DM_PELSHEIGHT; - dm.dmPelsWidth = mode.area.w; - dm.dmPelsHeight = mode.area.h; - } - - if (request & RGFW_monitorRefresh) { - dm.dmFields |= DM_DISPLAYFREQUENCY; - dm.dmDisplayFrequency = mode.refreshRate; - } - - if (request & RGFW_monitorRGB) { - dm.dmFields |= DM_BITSPERPEL; - dm.dmBitsPerPel = mode.red + mode.green + mode.blue; - } - - if (ChangeDisplaySettingsEx(dd.DeviceName, &dm, NULL, CDS_TEST, NULL) == DISP_CHANGE_SUCCESSFUL) { - if (ChangeDisplaySettingsEx(dd.DeviceName, &dm, NULL, CDS_UPDATEREGISTRY, NULL) == DISP_CHANGE_SUCCESSFUL) - return RGFW_TRUE; - return RGFW_FALSE; - } else return RGFW_FALSE; - } - } - - return RGFW_FALSE; -} - -#endif -HICON RGFW_loadHandleImage(u8* src, RGFW_area a, BOOL icon) { - BITMAPV5HEADER bi; - ZeroMemory(&bi, sizeof(bi)); - bi.bV5Size = sizeof(bi); - bi.bV5Width = a.w; - bi.bV5Height = -((LONG) a.h); - bi.bV5Planes = 1; - bi.bV5BitCount = 32; - bi.bV5Compression = BI_BITFIELDS; - bi.bV5RedMask = 0x000000ff; - bi.bV5GreenMask = 0x0000ff00; - bi.bV5BlueMask = 0x00ff0000; - bi.bV5AlphaMask = 0xff000000; - - HDC dc = GetDC(NULL); - u8* target = NULL; - - HBITMAP color = CreateDIBSection(dc, - (BITMAPINFO*) &bi, DIB_RGB_COLORS, (void**) &target, - NULL, (DWORD) 0); - - memcpy(target, src, a.w * a.h * 4); - ReleaseDC(NULL, dc); - - HBITMAP mask = CreateBitmap(a.w, a.h, 1, 1, NULL); - - ICONINFO ii; - ZeroMemory(&ii, sizeof(ii)); - ii.fIcon = icon; - ii.xHotspot = a.w / 2; - ii.yHotspot = a.h / 2; - ii.hbmMask = mask; - ii.hbmColor = color; - - HICON handle = CreateIconIndirect(&ii); - - DeleteObject(color); - DeleteObject(mask); - - return handle; -} - -void* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { - RGFW_UNUSED(channels); - - HCURSOR cursor = (HCURSOR) RGFW_loadHandleImage(icon, a, FALSE); - return cursor; -} - -void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { - RGFW_ASSERT(win && mouse); - SetClassLongPtrA(win->src.window, GCLP_HCURSOR, (LPARAM) mouse); - SetCursor((HCURSOR)mouse); -} - -void RGFW_freeMouse(RGFW_mouse* mouse) { - RGFW_ASSERT(mouse); - DestroyCursor((HCURSOR)mouse); -} - -RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { - return RGFW_window_setMouseStandard(win, RGFW_mouseArrow); -} - -RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { - RGFW_ASSERT(win != NULL); - - static const u32 mouseIconSrc[] = {OCR_NORMAL, OCR_NORMAL, OCR_IBEAM, OCR_CROSS, OCR_HAND, OCR_SIZEWE, OCR_SIZENS, OCR_SIZENWSE, OCR_SIZENESW, OCR_SIZEALL, OCR_NO}; - if (mouse > (sizeof(mouseIconSrc) / sizeof(u32))) - return RGFW_FALSE; - - char* icon = MAKEINTRESOURCEA(mouseIconSrc[mouse]); - - SetClassLongPtrA(win->src.window, GCLP_HCURSOR, (LPARAM) LoadCursorA(NULL, icon)); - SetCursor(LoadCursorA(NULL, icon)); - return RGFW_TRUE; -} - -void RGFW_window_hide(RGFW_window* win) { - ShowWindow(win->src.window, SW_HIDE); -} - -void RGFW_window_show(RGFW_window* win) { - if (win->_flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); - ShowWindow(win->src.window, SW_RESTORE); -} - -#define RGFW_FREE_LIBRARY(x) if (x != NULL) FreeLibrary(x); x = NULL; - -void RGFW_window_close(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - - #ifdef RGFW_EGL - RGFW_closeEGL(win); - #endif - - #ifdef RGFW_BUFFER - DeleteDC(win->src.hdcMem); - DeleteObject(win->src.bitmap); - #endif - - #ifdef RGFW_OPENGL - wglDeleteContext((HGLRC) win->src.ctx); /*!< delete opengl context */ - #endif - ReleaseDC(win->src.window, win->src.hdc); /*!< delete device context */ - DestroyWindow(win->src.window); /*!< delete window */ - - if (win->src.hIconSmall) DestroyIcon(win->src.hIconSmall); - if (win->src.hIconBig) DestroyIcon(win->src.hIconBig); - - if (win == RGFW_root) { - #ifndef RGFW_NO_XINPUT - RGFW_FREE_LIBRARY(RGFW_XInput_dll); - #endif - - #ifndef RGFW_NO_DPI - RGFW_FREE_LIBRARY(RGFW_Shcore_dll); - #endif - - #ifndef RGFW_NO_WINMM - timeEndPeriod(1); - #ifndef RGFW_NO_LOAD_WINMM - RGFW_FREE_LIBRARY(RGFW_winmm_dll); - #endif - #endif - - RGFW_FREE_LIBRARY(RGFW_wgl_dll); - RGFW_root = NULL; - - if (RGFW_hiddenMouse != NULL) { - RGFW_freeMouse(RGFW_hiddenMouse); - RGFW_hiddenMouse = 0; - } - } - - RGFW_clipboard_switch(NULL); - RGFW_FREE(win->event.droppedFiles); - - if ((win->_flags & RGFW_WINDOW_ALLOC)) - RGFW_FREE(win); -} - -void RGFW_window_move(RGFW_window* win, RGFW_point v) { - RGFW_ASSERT(win != NULL); - - win->r.x = v.x; - win->r.y = v.y; - SetWindowPos(win->src.window, HWND_TOP, win->r.x, win->r.y, 0, 0, SWP_NOSIZE); -} - -void RGFW_window_resize(RGFW_window* win, RGFW_area a) { - RGFW_ASSERT(win != NULL); - - win->r.w = a.w; - win->r.h = a.h; - SetWindowPos(win->src.window, HWND_TOP, 0, 0, win->r.w, win->r.h + win->src.hOffset, SWP_NOMOVE); -} - - -void RGFW_window_setName(RGFW_window* win, const char* name) { - RGFW_ASSERT(win != NULL); - - wchar_t wide_name[255]; - MultiByteToWideChar(CP_UTF8, 0, name, -1, wide_name, 255); - SetWindowTextW(win->src.window, wide_name); -} - -#ifndef RGFW_NO_PASSTHROUGH - -void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { - RGFW_ASSERT(win != NULL); - - COLORREF key = 0; - BYTE alpha = 0; - DWORD flags = 0; - DWORD exStyle = GetWindowLongW(win->src.window, GWL_EXSTYLE); - - if (exStyle & WS_EX_LAYERED) - GetLayeredWindowAttributes(win->src.window, &key, &alpha, &flags); - - if (passthrough) - exStyle |= (WS_EX_TRANSPARENT | WS_EX_LAYERED); - else { - exStyle &= ~WS_EX_TRANSPARENT; - if (exStyle & WS_EX_LAYERED && !(flags & LWA_ALPHA)) - exStyle &= ~WS_EX_LAYERED; - } - - SetWindowLongW(win->src.window, GWL_EXSTYLE, exStyle); - - if (passthrough) - SetLayeredWindowAttributes(win->src.window, key, alpha, flags); -} -#endif - -RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* src, RGFW_area a, i32 channels, u8 type) { - RGFW_ASSERT(win != NULL); - #ifndef RGFW_WIN95 - RGFW_UNUSED(channels); - - if (win->src.hIconSmall && (type & RGFW_iconWindow)) DestroyIcon(win->src.hIconSmall); - if (win->src.hIconBig && (type & RGFW_iconTaskbar)) DestroyIcon(win->src.hIconBig); - - if (src == NULL) { - HICON defaultIcon = LoadIcon(NULL, IDI_APPLICATION); - if (type & RGFW_iconWindow) - SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)defaultIcon); - if (type & RGFW_iconTaskbar) - SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)defaultIcon); - return RGFW_TRUE; - } - - if (type & RGFW_iconWindow) { - win->src.hIconSmall = RGFW_loadHandleImage(src, a, TRUE); - SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)win->src.hIconSmall); - } - if (type & RGFW_iconTaskbar) { - win->src.hIconBig = RGFW_loadHandleImage(src, a, TRUE); - SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)win->src.hIconBig); - } - return RGFW_TRUE; - #else - RGFW_UNUSED(src); - RGFW_UNUSED(a); - RGFW_UNUSED(channels); - return RGFW_FALSE; - #endif -} - -RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { - /* Open the clipboard */ - if (OpenClipboard(NULL) == 0) - return -1; - - /* Get the clipboard data as a Unicode string */ - HANDLE hData = GetClipboardData(CF_UNICODETEXT); - if (hData == NULL) { - CloseClipboard(); - return -1; - } - - wchar_t* wstr = (wchar_t*) GlobalLock(hData); - - RGFW_ssize_t textLen = 0; - - { - setlocale(LC_ALL, "en_US.UTF-8"); - - textLen = wcstombs(NULL, wstr, 0) + 1; - if (str != NULL && (RGFW_ssize_t)strCapacity <= textLen - 1) - textLen = 0; - - if (str != NULL && textLen) { - if (textLen > 1) - wcstombs(str, wstr, (textLen) ); - - str[textLen] = '\0'; - } - } - - /* Release the clipboard data */ - GlobalUnlock(hData); - CloseClipboard(); - - return textLen; -} - -void RGFW_writeClipboard(const char* text, u32 textLen) { - HANDLE object; - WCHAR* buffer; - - object = GlobalAlloc(GMEM_MOVEABLE, (1 + textLen) * sizeof(WCHAR)); - if (!object) - return; - - buffer = (WCHAR*) GlobalLock(object); - if (!buffer) { - GlobalFree(object); - return; - } - - MultiByteToWideChar(CP_UTF8, 0, text, -1, buffer, textLen); - GlobalUnlock(object); - - if (!OpenClipboard(RGFW_root->src.window)) { - GlobalFree(object); - return; - } - - EmptyClipboard(); - SetClipboardData(CF_UNICODETEXT, object); - CloseClipboard(); -} - -void RGFW_window_moveMouse(RGFW_window* win, RGFW_point p) { - RGFW_ASSERT(win != NULL); - win->_lastMousePoint = RGFW_POINT(p.x - win->r.x, p.y - win->r.y); - SetCursorPos(p.x, p.y); -} - -#ifdef RGFW_OPENGL -void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { - if (win == NULL) - wglMakeCurrent(NULL, NULL); - else - wglMakeCurrent(win->src.hdc, (HGLRC) win->src.ctx); -} -void* RGFW_getCurrent_OpenGL(void) { return wglGetCurrentContext(); } -#endif - -#ifndef RGFW_EGL - -void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { - RGFW_ASSERT(win != NULL); - - #if defined(RGFW_OPENGL) - typedef BOOL(APIENTRY* PFNWGLSWAPINTERVALEXTPROC)(int interval); - static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; - static void* loadSwapFunc = (void*) 1; - - if (loadSwapFunc == NULL) { - RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, RGFW_DEBUG_CTX(win, 0), "wglSwapIntervalEXT not supported"); - return; - } - - if (wglSwapIntervalEXT == NULL) { - loadSwapFunc = (void*) wglGetProcAddress("wglSwapIntervalEXT"); - wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC) loadSwapFunc; - } - - if (wglSwapIntervalEXT(swapInterval) == FALSE) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to set swap interval"); - #else - RGFW_UNUSED(swapInterval); - #endif -} - -#endif - -void RGFW_window_swapBuffers(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - /* clear the window */ - - if (!(win->_flags & RGFW_NO_CPU_RENDER)) { - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - if (win->buffer != win->src.bitmapBits) - memcpy(win->src.bitmapBits, win->buffer, win->bufferSize.w * win->bufferSize.h * 4); - - RGFW_RGB_to_BGR(win, win->src.bitmapBits); - BitBlt(win->src.hdc, 0, 0, win->r.w, win->r.h, win->src.hdcMem, 0, 0, SRCCOPY); - #endif - } - - if (!(win->_flags & RGFW_NO_GPU_RENDER)) { - #ifdef RGFW_EGL - eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); - #elif defined(RGFW_OPENGL) - SwapBuffers(win->src.hdc); - #endif - } -} - -char* RGFW_createUTF8FromWideStringWin32(const WCHAR* source) { - if (source == NULL) { - return NULL; - } - i32 size = WideCharToMultiByte(CP_UTF8, 0, source, -1, NULL, 0, NULL, NULL); - if (!size) { - return NULL; - } - - static char target[RGFW_MAX_PATH * 2]; - if (size > RGFW_MAX_PATH * 2) - size = RGFW_MAX_PATH * 2; - - target[size] = 0; - - if (!WideCharToMultiByte(CP_UTF8, 0, source, -1, target, size, NULL, NULL)) { - return NULL; - } - - return target; -} - -u64 RGFW_getTimerFreq(void) { - static u64 frequency = 0; - if (frequency == 0) QueryPerformanceFrequency((LARGE_INTEGER*)&frequency); - - return frequency; -} - -u64 RGFW_getTimerValue(void) { - u64 value; - QueryPerformanceCounter((LARGE_INTEGER*)&value); - return value; -} - -void RGFW_sleep(u64 ms) { - Sleep(ms); -} - -#ifndef RGFW_NO_THREADS - -RGFW_thread RGFW_createThread(RGFW_threadFunc_ptr ptr, void* args) { return CreateThread(NULL, 0, ptr, args, 0, NULL); } -void RGFW_cancelThread(RGFW_thread thread) { CloseHandle((HANDLE) thread); } -void RGFW_joinThread(RGFW_thread thread) { WaitForSingleObject((HANDLE) thread, INFINITE); } -void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { SetThreadPriority((HANDLE) thread, priority); } - -#endif -#endif /* RGFW_WINDOWS */ - -/* - End of Windows defines -*/ - - - -/* - - Start of MacOS defines - - -*/ - -#if defined(RGFW_MACOS) -/* - based on silicon.h - start of cocoa wrapper -*/ - -#include -#include -#include -#include -#include -#include - -typedef CGRect NSRect; -typedef CGPoint NSPoint; -typedef CGSize NSSize; - -typedef const char* NSPasteboardType; -typedef unsigned long NSUInteger; -typedef long NSInteger; -typedef NSInteger NSModalResponse; - -#ifdef __arm64__ - /* ARM just uses objc_msgSend */ -#define abi_objc_msgSend_stret objc_msgSend -#define abi_objc_msgSend_fpret objc_msgSend -#else /* __i386__ */ - /* x86 just uses abi_objc_msgSend_fpret and (NSColor *)objc_msgSend_id respectively */ -#define abi_objc_msgSend_stret objc_msgSend_stret -#define abi_objc_msgSend_fpret objc_msgSend_fpret -#endif - -#define NSAlloc(nsclass) objc_msgSend_id((id)nsclass, sel_registerName("alloc")) -#define objc_msgSend_bool(x, y) ((BOOL (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) -#define objc_msgSend_void(x, y) ((void (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) -#define objc_msgSend_void_id(x, y, z) ((void (*)(id, SEL, id))objc_msgSend) ((id)x, (SEL)y, (id)z) -#define objc_msgSend_uint(x, y) ((NSUInteger (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) -#define objc_msgSend_void_bool(x, y, z) ((void (*)(id, SEL, BOOL))objc_msgSend) ((id)(x), (SEL)y, (BOOL)z) -#define objc_msgSend_bool_void(x, y) ((BOOL (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) -#define objc_msgSend_void_SEL(x, y, z) ((void (*)(id, SEL, SEL))objc_msgSend) ((id)(x), (SEL)y, (SEL)z) -#define objc_msgSend_id(x, y) ((id (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) -#define objc_msgSend_id_id(x, y, z) ((id (*)(id, SEL, id))objc_msgSend) ((id)(x), (SEL)y, (id)z) -#define objc_msgSend_id_bool(x, y, z) ((BOOL (*)(id, SEL, id))objc_msgSend) ((id)(x), (SEL)y, (id)z) -#define objc_msgSend_int(x, y, z) ((id (*)(id, SEL, int))objc_msgSend) ((id)(x), (SEL)y, (int)z) -#define objc_msgSend_arr(x, y, z) ((id (*)(id, SEL, int))objc_msgSend) ((id)(x), (SEL)y, (int)z) -#define objc_msgSend_ptr(x, y, z) ((id (*)(id, SEL, void*))objc_msgSend) ((id)(x), (SEL)y, (void*)z) -#define objc_msgSend_class(x, y) ((id (*)(Class, SEL))objc_msgSend) ((Class)(x), (SEL)y) -#define objc_msgSend_class_char(x, y, z) ((id (*)(Class, SEL, char*))objc_msgSend) ((Class)(x), (SEL)y, (char*)z) - -id NSApp = NULL; - -#define NSRelease(obj) objc_msgSend_void((id)obj, sel_registerName("release")) - -id NSString_stringWithUTF8String(const char* str) { - return ((id(*)(id, SEL, const char*))objc_msgSend) - ((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), str); -} - -const char* NSString_to_char(id str) { - return ((const char* (*)(id, SEL)) objc_msgSend) ((id)(id)str, sel_registerName("UTF8String")); -} - -void si_impl_func_to_SEL_with_name(const char* class_name, const char* register_name, void* function) { - Class selected_class; - - if (RGFW_STRNCMP(class_name, "NSView", 6) == 0) { - selected_class = objc_getClass("ViewClass"); - } else if (RGFW_STRNCMP(class_name, "NSWindow", 8) == 0) { - selected_class = objc_getClass("WindowClass"); - } else { - selected_class = objc_getClass(class_name); - } - - class_addMethod(selected_class, sel_registerName(register_name), (IMP) function, 0); -} - -/* Header for the array. */ -typedef struct siArrayHeader { - size_t count; - /* TODO(EimaMei): Add a `type_width` later on. */ -} siArrayHeader; - -/* Gets the header of the siArray. */ -#define SI_ARRAY_HEADER(s) ((siArrayHeader*)s - 1) -#define si_array_len(array) (SI_ARRAY_HEADER(array)->count) -#define si_func_to_SEL(class_name, function) si_impl_func_to_SEL_with_name(class_name, #function":", (void*)function) -/* Creates an Objective-C method (SEL) from a regular C function with the option to set the register name.*/ -#define si_func_to_SEL_with_name(class_name, register_name, function) si_impl_func_to_SEL_with_name(class_name, register_name":", (void*)function) - -unsigned char* NSBitmapImageRep_bitmapData(id imageRep) { - return ((unsigned char* (*)(id, SEL))objc_msgSend) ((id)imageRep, sel_registerName("bitmapData")); -} - -typedef RGFW_ENUM(NSUInteger, NSBitmapFormat) { - NSBitmapFormatAlphaFirst = 1 << 0, // 0 means is alpha last (RGBA, CMYKA, etc.) - NSBitmapFormatAlphaNonpremultiplied = 1 << 1, // 0 means is premultiplied - NSBitmapFormatFloatingpointSamples = 1 << 2, // 0 is integer - - NSBitmapFormatSixteenBitLittleEndian API_AVAILABLE(macos(10.10)) = (1 << 8), - NSBitmapFormatThirtyTwoBitLittleEndian API_AVAILABLE(macos(10.10)) = (1 << 9), - NSBitmapFormatSixteenBitBigEndian API_AVAILABLE(macos(10.10)) = (1 << 10), - NSBitmapFormatThirtyTwoBitBigEndian API_AVAILABLE(macos(10.10)) = (1 << 11) -}; - -id NSBitmapImageRep_initWithBitmapData(unsigned char** planes, NSInteger width, NSInteger height, NSInteger bps, NSInteger spp, bool alpha, bool isPlanar, const char* colorSpaceName, NSBitmapFormat bitmapFormat, NSInteger rowBytes, NSInteger pixelBits) { - SEL func = sel_registerName("initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel:"); - - return (id) ((id(*)(id, SEL, unsigned char**, NSInteger, NSInteger, NSInteger, NSInteger, bool, bool, id, NSBitmapFormat, NSInteger, NSInteger))objc_msgSend) - (NSAlloc((id)objc_getClass("NSBitmapImageRep")), func, planes, width, height, bps, spp, alpha, isPlanar, NSString_stringWithUTF8String(colorSpaceName), bitmapFormat, rowBytes, pixelBits); -} - -id NSColor_colorWithSRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) { - void* nsclass = objc_getClass("NSColor"); - SEL func = sel_registerName("colorWithSRGBRed:green:blue:alpha:"); - return ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend) - ((id)nsclass, func, red, green, blue, alpha); -} - -id NSCursor_initWithImage(id newImage, NSPoint aPoint) { - SEL func = sel_registerName("initWithImage:hotSpot:"); - void* nsclass = objc_getClass("NSCursor"); - - return (id) ((id(*)(id, SEL, id, NSPoint))objc_msgSend) - (NSAlloc(nsclass), func, newImage, aPoint); -} - -void NSImage_addRepresentation(id image, id imageRep) { - SEL func = sel_registerName("addRepresentation:"); - objc_msgSend_void_id(image, func, (id)imageRep); -} - -id NSImage_initWithSize(NSSize size) { - SEL func = sel_registerName("initWithSize:"); - return ((id(*)(id, SEL, NSSize))objc_msgSend) - (NSAlloc((id)objc_getClass("NSImage")), func, size); -} -#define NS_OPENGL_ENUM_DEPRECATED(minVers, maxVers) API_AVAILABLE(macos(minVers)) -typedef RGFW_ENUM(NSInteger, NSOpenGLContextParameter) { - NSOpenGLContextParameterSwapInterval NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 222, /* 1 param. 0 -> Don't sync, 1 -> Sync to vertical retrace */ - NSOpenGLContextParametectxaceOrder NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 235, /* 1 param. 1 -> Above Window (default), -1 -> Below Window */ - NSOpenGLContextParametectxaceOpacity NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 236, /* 1 param. 1-> Surface is opaque (default), 0 -> non-opaque */ - NSOpenGLContextParametectxaceBackingSize NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 304, /* 2 params. Width/height of surface backing size */ - NSOpenGLContextParameterReclaimResources NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 308, /* 0 params. */ - NSOpenGLContextParameterCurrentRendererID NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 309, /* 1 param. Retrieves the current renderer ID */ - NSOpenGLContextParameterGPUVertexProcessing NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 310, /* 1 param. Currently processing vertices with GPU (get) */ - NSOpenGLContextParameterGPUFragmentProcessing NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 311, /* 1 param. Currently processing fragments with GPU (get) */ - NSOpenGLContextParameterHasDrawable NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 314, /* 1 param. Boolean returned if drawable is attached */ - NSOpenGLContextParameterMPSwapsInFlight NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 315, /* 1 param. Max number of swaps queued by the MP GL engine */ - - NSOpenGLContextParameterSwapRectangle API_DEPRECATED("", macos(10.0, 10.14)) = 200, /* 4 params. Set or get the swap rectangle {x, y, w, h} */ - NSOpenGLContextParameterSwapRectangleEnable API_DEPRECATED("", macos(10.0, 10.14)) = 201, /* Enable or disable the swap rectangle */ - NSOpenGLContextParameterRasterizationEnable API_DEPRECATED("", macos(10.0, 10.14)) = 221, /* Enable or disable all rasterization */ - NSOpenGLContextParameterStateValidation API_DEPRECATED("", macos(10.0, 10.14)) = 301, /* Validate state for multi-screen functionality */ - NSOpenGLContextParametectxaceSurfaceVolatile API_DEPRECATED("", macos(10.0, 10.14)) = 306, /* 1 param. Surface volatile state */ -}; - -typedef RGFW_ENUM(NSInteger, NSWindowButton) { - NSWindowCloseButton = 0, - NSWindowMiniaturizeButton = 1, - NSWindowZoomButton = 2, - NSWindowToolbarButton = 3, - NSWindowDocumentIconButton = 4, - NSWindowDocumentVersionsButton = 6, - NSWindowFullScreenButton = 7, -}; - - -void NSOpenGLContext_setValues(id context, const int* vals, NSOpenGLContextParameter param) { - SEL func = sel_registerName("setValues:forParameter:"); - ((void (*)(id, SEL, const int*, NSOpenGLContextParameter))objc_msgSend) - (context, func, vals, param); -} - -void* NSOpenGLPixelFormat_initWithAttributes(const uint32_t* attribs) { - SEL func = sel_registerName("initWithAttributes:"); - return (void*) ((id(*)(id, SEL, const uint32_t*))objc_msgSend) - (NSAlloc((id)objc_getClass("NSOpenGLPixelFormat")), func, attribs); -} - -id NSOpenGLView_initWithFrame(NSRect frameRect, uint32_t* format) { - SEL func = sel_registerName("initWithFrame:pixelFormat:"); - return (id) ((id(*)(id, SEL, NSRect, uint32_t*))objc_msgSend) - (NSAlloc((id)objc_getClass("NSOpenGLView")), func, frameRect, format); -} - -void NSCursor_performSelector(id cursor, SEL selector) { - SEL func = sel_registerName("performSelector:"); - objc_msgSend_void_SEL(cursor, func, selector); -} - -id NSPasteboard_generalPasteboard(void) { - return (id) objc_msgSend_id((id)objc_getClass("NSPasteboard"), sel_registerName("generalPasteboard")); -} - -id* cstrToNSStringArray(char** strs, size_t len) { - static id nstrs[6]; - size_t i; - for (i = 0; i < len; i++) - nstrs[i] = NSString_stringWithUTF8String(strs[i]); - - return nstrs; -} - -const char* NSPasteboard_stringForType(id pasteboard, NSPasteboardType dataType, size_t* len) { - SEL func = sel_registerName("stringForType:"); - id nsstr = NSString_stringWithUTF8String(dataType); - id nsString = ((id(*)(id, SEL, id))objc_msgSend)(pasteboard, func, nsstr); - const char* str = NSString_to_char(nsString); - if (len != NULL) - *len = (size_t)((NSUInteger(*)(id, SEL, int))objc_msgSend)(nsString, sel_registerName("maximumLengthOfBytesUsingEncoding:"), 4); - return str; -} - -id c_array_to_NSArray(void* array, size_t len) { - SEL func = sel_registerName("initWithObjects:count:"); - void* nsclass = objc_getClass("NSArray"); - return ((id (*)(id, SEL, void*, NSUInteger))objc_msgSend) - (NSAlloc(nsclass), func, array, len); -} - -void NSregisterForDraggedTypes(id view, NSPasteboardType* newTypes, size_t len) { - id* ntypes = cstrToNSStringArray((char**)newTypes, len); - - id array = c_array_to_NSArray(ntypes, len); - objc_msgSend_void_id(view, sel_registerName("registerForDraggedTypes:"), array); - NSRelease(array); -} - -NSInteger NSPasteBoard_declareTypes(id pasteboard, NSPasteboardType* newTypes, size_t len, void* owner) { - id* ntypes = cstrToNSStringArray((char**)newTypes, len); - - SEL func = sel_registerName("declareTypes:owner:"); - - id array = c_array_to_NSArray(ntypes, len); - - NSInteger output = ((NSInteger(*)(id, SEL, id, void*))objc_msgSend) - (pasteboard, func, array, owner); - NSRelease(array); - - return output; -} - -bool NSPasteBoard_setString(id pasteboard, const char* stringToWrite, NSPasteboardType dataType) { - SEL func = sel_registerName("setString:forType:"); - return ((bool (*)(id, SEL, id, id))objc_msgSend) - (pasteboard, func, NSString_stringWithUTF8String(stringToWrite), NSString_stringWithUTF8String(dataType)); -} - -#define NSRetain(obj) objc_msgSend_void((id)obj, sel_registerName("retain")) - -typedef enum NSApplicationActivationPolicy { - NSApplicationActivationPolicyRegular, - NSApplicationActivationPolicyAccessory, - NSApplicationActivationPolicyProhibited -} NSApplicationActivationPolicy; - -typedef RGFW_ENUM(u32, NSBackingStoreType) { - NSBackingStoreRetained = 0, - NSBackingStoreNonretained = 1, - NSBackingStoreBuffered = 2 -}; - -typedef RGFW_ENUM(u32, NSWindowStyleMask) { - NSWindowStyleMaskBorderless = 0, - NSWindowStyleMaskTitled = 1 << 0, - NSWindowStyleMaskClosable = 1 << 1, - NSWindowStyleMaskMiniaturizable = 1 << 2, - NSWindowStyleMaskResizable = 1 << 3, - NSWindowStyleMaskTexturedBackground = 1 << 8, /* deprecated */ - NSWindowStyleMaskUnifiedTitleAndToolbar = 1 << 12, - NSWindowStyleMaskFullScreen = 1 << 14, - NSWindowStyleMaskFullSizeContentView = 1 << 15, - NSWindowStyleMaskUtilityWindow = 1 << 4, - NSWindowStyleMaskDocModalWindow = 1 << 6, - NSWindowStyleMaskNonactivatingpanel = 1 << 7, - NSWindowStyleMaskHUDWindow = 1 << 13 -}; - -NSPasteboardType const NSPasteboardTypeString = "public.utf8-plain-text"; // Replaces NSStringPasteboardType - - -typedef RGFW_ENUM(i32, NSDragOperation) { - NSDragOperationNone = 0, - NSDragOperationCopy = 1, - NSDragOperationLink = 2, - NSDragOperationGeneric = 4, - NSDragOperationPrivate = 8, - NSDragOperationMove = 16, - NSDragOperationDelete = 32, - NSDragOperationEvery = ULONG_MAX, - - //NSDragOperationAll_Obsolete API_DEPRECATED("", macos(10.0,10.10)) = 15, // Use NSDragOperationEvery - //NSDragOperationAll API_DEPRECATED("", macos(10.0,10.10)) = NSDragOperationAll_Obsolete, // Use NSDragOperationEvery -}; - -void* NSArray_objectAtIndex(id array, NSUInteger index) { - SEL func = sel_registerName("objectAtIndex:"); - return ((id(*)(id, SEL, NSUInteger))objc_msgSend)(array, func, index); -} - -id NSWindow_contentView(id window) { - SEL func = sel_registerName("contentView"); - return objc_msgSend_id(window, func); -} - -/* - End of cocoa wrapper -*/ - -#ifdef RGFW_OPENGL -CFBundleRef RGFWnsglFramework = NULL; - -void* RGFW_getProcAddress(const char* procname) { - if (RGFWnsglFramework == NULL) - RGFWnsglFramework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); - - CFStringRef symbolName = CFStringCreateWithCString(kCFAllocatorDefault, procname, kCFStringEncodingASCII); - - void* symbol = (void*)CFBundleGetFunctionPointerForName(RGFWnsglFramework, symbolName); - - CFRelease(symbolName); - - return symbol; -} -#endif - -id NSWindow_delegate(RGFW_window* win) { - return (id) objc_msgSend_id((id)win->src.window, sel_registerName("delegate")); -} - -u32 RGFW_OnClose(id self) { - RGFW_window* win = NULL; - object_getInstanceVariable(self, (const char*)"RGFW_window", (void**)&win); - if (win == NULL) - return true; - - win->event.type = RGFW_quit; - RGFW_windowQuitCallback(win); - - return false; -} - -/* NOTE(EimaMei): Fixes the constant clicking when the app is running under a terminal. */ -bool acceptsFirstResponder(void) { return true; } -bool performKeyEquivalent(id event) { RGFW_UNUSED(event); return true; } - -NSDragOperation draggingEntered(id self, SEL sel, id sender) { - RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); - - return NSDragOperationCopy; -} -NSDragOperation draggingUpdated(id self, SEL sel, id sender) { - RGFW_UNUSED(sel); - - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL || (!(win->_flags & RGFW_windowAllowDND))) - return 0; - - NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(sender, sel_registerName("draggingLocation")); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_DNDInit, - .point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)), - ._win = win}); - - RGFW_dndInitCallback(win, win->event.point); - return NSDragOperationCopy; -} -bool prepareForDragOperation(id self) { - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL) - return true; - - if (!(win->_flags & RGFW_windowAllowDND)) { - return false; - } - - return true; -} - -void RGFW__osxDraggingEnded(id self, SEL sel, id sender) { RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); return; } - -/* NOTE(EimaMei): Usually, you never need 'id self, SEL cmd' for C -> Obj-C methods. This isn't the case. */ -bool performDragOperation(id self, SEL sel, id sender) { - RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); - - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - - if (win == NULL) - return false; - - // id pasteBoard = objc_msgSend_id(sender, sel_registerName("draggingPasteboard")); - - ///////////////////////////// - id pasteBoard = objc_msgSend_id(sender, sel_registerName("draggingPasteboard")); - - // Get the types of data available on the pasteboard - id types = objc_msgSend_id(pasteBoard, sel_registerName("types")); - - // Get the string type for file URLs - id fileURLsType = objc_msgSend_class_char(objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "NSFilenamesPboardType"); - - // Check if the pasteboard contains file URLs - if (objc_msgSend_id_bool(types, sel_registerName("containsObject:"), fileURLsType) == 0) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, RGFW_DEBUG_CTX(win, 0), "No files found on the pasteboard."); - return 0; - } - - id fileURLs = objc_msgSend_id_id(pasteBoard, sel_registerName("propertyListForType:"), fileURLsType); - int count = ((int (*)(id, SEL))objc_msgSend)(fileURLs, sel_registerName("count")); - - if (count == 0) - return 0; - - for (int i = 0; i < count; i++) { - id fileURL = objc_msgSend_arr(fileURLs, sel_registerName("objectAtIndex:"), i); - const char *filePath = ((const char* (*)(id, SEL))objc_msgSend)(fileURL, sel_registerName("UTF8String")); - RGFW_MEMCPY(win->event.droppedFiles[i], filePath, RGFW_MAX_PATH); - win->event.droppedFiles[i][RGFW_MAX_PATH - 1] = '\0'; - } - NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(sender, sel_registerName("draggingLocation")); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_DND, - .point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)), - .droppedFilesCount = count, - ._win = win}); - - RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); - - return false; -} - -#ifndef RGFW_NO_IOKIT -#include -#include - -IOHIDDeviceRef RGFW_osxControllers[4] = {NULL}; - -int findControllerIndex(IOHIDDeviceRef device) { - for (int i = 0; i < 4; i++) - if (RGFW_osxControllers[i] == device) - return i; - return -1; -} - -void RGFW__osxInputValueChangedCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value) { - RGFW_UNUSED(context); RGFW_UNUSED(result); RGFW_UNUSED(sender); - IOHIDElementRef element = IOHIDValueGetElement(value); - - IOHIDDeviceRef device = IOHIDElementGetDevice(element); - size_t index = findControllerIndex(device); - - uint32_t usagePage = IOHIDElementGetUsagePage(element); - uint32_t usage = IOHIDElementGetUsage(element); - - CFIndex intValue = IOHIDValueGetIntegerValue(value); - - u8 RGFW_osx2RGFWSrc[2][RGFW_gamepadFinal] = {{ - 0, RGFW_gamepadSelect, RGFW_gamepadL3, RGFW_gamepadR3, RGFW_gamepadStart, - RGFW_gamepadUp, RGFW_gamepadRight, RGFW_gamepadDown, RGFW_gamepadLeft, - RGFW_gamepadL2, RGFW_gamepadR2, RGFW_gamepadL1, RGFW_gamepadR1, - RGFW_gamepadY, RGFW_gamepadB, RGFW_gamepadA, RGFW_gamepadX, RGFW_gamepadHome}, - {0, RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadR3, RGFW_gamepadX, - RGFW_gamepadY, RGFW_gamepadRight, RGFW_gamepadL1, RGFW_gamepadR1, - RGFW_gamepadL2, RGFW_gamepadR2, RGFW_gamepadDown, RGFW_gamepadStart, - RGFW_gamepadUp, RGFW_gamepadL3, RGFW_gamepadSelect, RGFW_gamepadStart, RGFW_gamepadHome} - }; - - u8* RGFW_osx2RGFW = RGFW_osx2RGFWSrc[0]; - if (RGFW_gamepads_type[index] == RGFW_gamepadMicrosoft) - RGFW_osx2RGFW = RGFW_osx2RGFWSrc[1]; - - switch (usagePage) { - case kHIDPage_Button: { - u8 button = 0; - if (usage < sizeof(RGFW_osx2RGFW)) - button = RGFW_osx2RGFW[usage]; - - RGFW_gamepadButtonCallback(RGFW_root, index, button, intValue); - RGFW_gamepadPressed[index][button].prev = RGFW_gamepadPressed[index][button].current; - RGFW_gamepadPressed[index][button].current = intValue; - RGFW_eventQueuePush((RGFW_event){.type = intValue ? RGFW_gamepadButtonPressed: RGFW_gamepadButtonReleased, - .button = button, - .gamepad = index, - ._win = RGFW_root}); - break; - } - case kHIDPage_GenericDesktop: { - CFIndex logicalMin = IOHIDElementGetLogicalMin(element); - CFIndex logicalMax = IOHIDElementGetLogicalMax(element); - - if (logicalMax <= logicalMin) return; - if (intValue < logicalMin) intValue = logicalMin; - if (intValue > logicalMax) intValue = logicalMax; - - i8 value = (i8)(-100.0 + ((intValue - logicalMin) * 200.0) / (logicalMax - logicalMin)); - - u8 whichAxis = 0; - switch (usage) { - case kHIDUsage_GD_X: RGFW_gamepadAxes[index][0].x = value; whichAxis = 0; break; - case kHIDUsage_GD_Y: RGFW_gamepadAxes[index][0].y = value; whichAxis = 0; break; - case kHIDUsage_GD_Z: RGFW_gamepadAxes[index][1].x = value; whichAxis = 1; break; - case kHIDUsage_GD_Rz: RGFW_gamepadAxes[index][1].y = value; whichAxis = 1; break; - default: return; - } - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_gamepadAxisMove, - .gamepad = index, - .axis = {RGFW_gamepadAxes[index][0], RGFW_gamepadAxes[index][1], - RGFW_gamepadAxes[index][2], RGFW_gamepadAxes[index][3]}, - .whichAxis = whichAxis, - ._win = RGFW_root}); - - RGFW_gamepadAxisCallback(RGFW_root, index, RGFW_gamepadAxes[index], 2, whichAxis); - } - } -} - -void RGFW__osxDeviceAddedCallback(void* context, IOReturn result, void *sender, IOHIDDeviceRef device) { - RGFW_UNUSED(context); RGFW_UNUSED(result); RGFW_UNUSED(sender); - CFTypeRef usageRef = (CFTypeRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDPrimaryUsageKey)); - int usage = 0; - if (usageRef) - CFNumberGetValue((CFNumberRef)usageRef, kCFNumberIntType, (void*)&usage); - - if (usage != kHIDUsage_GD_Joystick && usage != kHIDUsage_GD_GamePad && usage != kHIDUsage_GD_MultiAxisController) { - return; - } - - for (size_t i = 0; i < 4; i++) { - if (RGFW_osxControllers[i] != NULL) - continue; - - RGFW_osxControllers[i] = device; - - IOHIDDeviceRegisterInputValueCallback(device, RGFW__osxInputValueChangedCallback, NULL); - - CFStringRef deviceName = (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); - if (deviceName) - CFStringGetCString(deviceName, RGFW_gamepads_name[i], sizeof(RGFW_gamepads_name[i]), kCFStringEncodingUTF8); - - RGFW_gamepads_type[i] = RGFW_gamepadUnknown; - if (RGFW_STRSTR(RGFW_gamepads_name[i], "Microsoft") || RGFW_STRSTR(RGFW_gamepads_name[i], "X-Box") || RGFW_STRSTR(RGFW_gamepads_name[i], "Xbox")) - RGFW_gamepads_type[i] = RGFW_gamepadMicrosoft; - else if (RGFW_STRSTR(RGFW_gamepads_name[i], "PlayStation") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS3") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS4") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS5")) - RGFW_gamepads_type[i] = RGFW_gamepadSony; - else if (RGFW_STRSTR(RGFW_gamepads_name[i], "Nintendo")) - RGFW_gamepads_type[i] = RGFW_gamepadNintendo; - else if (RGFW_STRSTR(RGFW_gamepads_name[i], "Logitech")) - RGFW_gamepads_type[i] = RGFW_gamepadLogitech; - - RGFW_gamepads[i] = i; - RGFW_gamepadCount++; - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_gamepadConnected, - .gamepad = i, - ._win = RGFW_root}); - - RGFW_gamepadCallback(RGFW_root, i, 1); - break; - } -} - -void RGFW__osxDeviceRemovedCallback(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) { - RGFW_UNUSED(context); RGFW_UNUSED(result); RGFW_UNUSED(sender); RGFW_UNUSED(device); - CFNumberRef usageRef = (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDPrimaryUsageKey)); - int usage = 0; - if (usageRef) - CFNumberGetValue(usageRef, kCFNumberIntType, &usage); - - if (usage != kHIDUsage_GD_Joystick && usage != kHIDUsage_GD_GamePad && usage != kHIDUsage_GD_MultiAxisController) { - return; - } - - i32 index = findControllerIndex(device); - if (index != -1) - RGFW_osxControllers[index] = NULL; - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_gamepadDisconnected, - .gamepad = index, - ._win = RGFW_root}); - RGFW_gamepadCallback(RGFW_root, index, 0); - - RGFW_gamepadCount--; -} - -RGFWDEF void RGFW_osxInitIOKit(void); -void RGFW_osxInitIOKit(void) { - IOHIDManagerRef hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); - if (!hidManager) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errIOKit, RGFW_DEBUG_CTX(RGFW_root, 0), "Failed to create IOHIDManager."); - return; - } - - CFMutableDictionaryRef matchingDictionary = CFDictionaryCreateMutable( - kCFAllocatorDefault, - 0, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks - ); - if (!matchingDictionary) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errIOKit, RGFW_DEBUG_CTX(RGFW_root, 0), "Failed to create matching dictionary for IOKit."); - CFRelease(hidManager); - return; - } - - CFDictionarySetValue( - matchingDictionary, - CFSTR(kIOHIDDeviceUsagePageKey), - CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, (int[]){kHIDPage_GenericDesktop}) - ); - - IOHIDManagerSetDeviceMatching(hidManager, matchingDictionary); - - IOHIDManagerRegisterDeviceMatchingCallback(hidManager, RGFW__osxDeviceAddedCallback, NULL); - IOHIDManagerRegisterDeviceRemovalCallback(hidManager, RGFW__osxDeviceRemovedCallback, NULL); - - IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); - - IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone); - - // Execute the run loop once in order to register any initially-attached joysticks - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); -} -#endif - -void RGFW_moveToMacOSResourceDir(void) { - char resourcesPath[255]; - - CFBundleRef bundle = CFBundleGetMainBundle(); - if (!bundle) - return; - - CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(bundle); - CFStringRef last = CFURLCopyLastPathComponent(resourcesURL); - - if ( - CFStringCompare(CFSTR("Resources"), last, 0) != kCFCompareEqualTo || - CFURLGetFileSystemRepresentation(resourcesURL, true, (u8*) resourcesPath, 255) == 0 - ) { - CFRelease(last); - CFRelease(resourcesURL); - return; - } - - CFRelease(last); - CFRelease(resourcesURL); - - chdir(resourcesPath); -} - - -void RGFW__osxWindowDeminiaturize(id self, SEL sel) { - RGFW_UNUSED(sel); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL) return; - - win->_flags |= RGFW_windowMinimize; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowRestored, ._win = win}); - RGFW_windowRestoredCallback(win, win->r); - -} -void RGFW__osxWindowMiniaturize(id self, SEL sel) { - RGFW_UNUSED(sel); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL) return; - - win->_flags &= ~RGFW_windowMinimize; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMinimized, ._win = win}); - RGFW_windowMinimizedCallback(win, win->r); - -} - -void RGFW__osxWindowBecameKey(id self, SEL sel) { - RGFW_UNUSED(sel); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL) return; - - win->_flags |= RGFW_windowFocus; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusIn, ._win = win}); - RGFW_focusCallback(win, RGFW_TRUE); -} - -void RGFW__osxWindowResignKey(id self, SEL sel) { - RGFW_UNUSED(sel); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL) return; - - win->_flags &= ~RGFW_windowFocus; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusOut, ._win = win}); - RGFW_focusCallback(win, RGFW_FALSE); -} - -NSSize RGFW__osxWindowResize(id self, SEL sel, NSSize frameSize) { - RGFW_UNUSED(sel); - - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL) return frameSize; - - win->r.w = frameSize.width; - win->r.h = frameSize.height; - - RGFW_monitor mon = RGFW_window_getMonitor(win); - if ((i32)mon.mode.area.w == win->r.w && (i32)mon.mode.area.h - 102 <= win->r.h) { - win->_flags |= RGFW_windowMaximize; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMaximized, ._win = win}); - RGFW_windowMaximizedCallback(win, win->r); - } else if (win->_flags & RGFW_windowMaximize) { - win->_flags &= ~RGFW_windowMaximize; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowRestored, ._win = win}); - RGFW_windowRestoredCallback(win, win->r); - - } - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowResized, ._win = win}); - RGFW_windowResizeCallback(win, win->r); - return frameSize; -} - -void RGFW__osxWindowMove(id self, SEL sel) { - RGFW_UNUSED(sel); - - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL) return; - - NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.window, sel_registerName("frame")); - win->r.x = (i32) frame.origin.x; - win->r.y = (i32) frame.origin.y; - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMoved, ._win = win}); - RGFW_windowMoveCallback(win, win->r); -} - -void RGFW__osxUpdateLayer(id self, SEL sel) { - RGFW_UNUSED(sel); - - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL) - return; - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowRefresh, ._win = win}); - RGFW_windowRefreshCallback(win); -} - -void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area) { - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - win->buffer = buffer; - win->bufferSize = area; - win->_flags |= RGFW_BUFFER_ALLOC; - #ifdef RGFW_OSMESA - win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); - OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); - #endif - #else - RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); /*!< if buffer rendering is not being used */ - #endif -} - -void RGFW_window_cocoaSetLayer(RGFW_window* win, void* layer) { - objc_msgSend_void_id((id)win->src.view, sel_registerName("setLayer"), (id)layer); -} - -void* RGFW_cocoaGetLayer(void) { - return objc_msgSend_class((id)objc_getClass("CAMetalLayer"), (SEL)sel_registerName("layer")); -} - - -NSPasteboardType const NSPasteboardTypeURL = "public.url"; -NSPasteboardType const NSPasteboardTypeFileURL = "public.file-url"; - -RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { - static u8 RGFW_loaded = 0; - - /* NOTE(EimaMei): Why does Apple hate good code? Like wtf, who thought of methods being a great idea??? - Imagine a universe, where MacOS had a proper system API (we would probably have like 20% better performance). - */ - si_func_to_SEL_with_name("NSObject", "windowShouldClose", (void*)RGFW_OnClose); - - /* NOTE(EimaMei): Fixes the 'Boop' sfx from constantly playing each time you click a key. Only a problem when running in the terminal. */ - si_func_to_SEL("NSWindow", acceptsFirstResponder); - si_func_to_SEL("NSWindow", performKeyEquivalent); - - // RR Create an autorelease pool - id pool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); - pool = objc_msgSend_id(pool, sel_registerName("init")); - - if (NSApp == NULL) { - NSApp = objc_msgSend_id((id)objc_getClass("NSApplication"), sel_registerName("sharedApplication")); - - ((void (*)(id, SEL, NSUInteger))objc_msgSend) - (NSApp, sel_registerName("setActivationPolicy:"), NSApplicationActivationPolicyRegular); - - #ifndef RGFW_NO_IOKIT - RGFW_osxInitIOKit(); - #endif - } - - RGFW_window_basic_init(win, rect, flags); - - RGFW_window_setMouseDefault(win); - - NSRect windowRect; - windowRect.origin.x = win->r.x; - windowRect.origin.y = win->r.y; - windowRect.size.width = win->r.w; - windowRect.size.height = win->r.h; - - NSBackingStoreType macArgs = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSBackingStoreBuffered | NSWindowStyleMaskTitled; - - if (!(flags & RGFW_windowNoResize)) - macArgs |= NSWindowStyleMaskResizable; - if (!(flags & RGFW_windowNoBorder)) - macArgs |= NSWindowStyleMaskTitled; - { - void* nsclass = objc_getClass("NSWindow"); - SEL func = sel_registerName("initWithContentRect:styleMask:backing:defer:"); - - win->src.window = ((id(*)(id, SEL, NSRect, NSWindowStyleMask, NSBackingStoreType, bool))objc_msgSend) - (NSAlloc(nsclass), func, windowRect, macArgs, macArgs, false); - } - - id str = NSString_stringWithUTF8String(name); - objc_msgSend_void_id((id)win->src.window, sel_registerName("setTitle:"), str); - - #ifdef RGFW_EGL - if ((flags & RGFW_windowNoInitAPI) == 0) - RGFW_createOpenGLContext(win); - #endif - - #ifdef RGFW_OPENGL - - if ((flags & RGFW_windowNoInitAPI) == 0) { - void* attrs = RGFW_initFormatAttribs(flags & RGFW_windowOpenglSoftware); - void* format = NSOpenGLPixelFormat_initWithAttributes((uint32_t*)attrs); - - if (format == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to load pixel format for OpenGL"); - void* attrs = RGFW_initFormatAttribs(1); - format = NSOpenGLPixelFormat_initWithAttributes((uint32_t*)attrs); - - if (format == NULL) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "and loading software rendering OpenGL failed"); - else - RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, RGFW_DEBUG_CTX(win, 0), "Switching to software rendering"); - } - - /* the pixel format can be passed directly to opengl context creation to create a context - this is because the format also includes information about the opengl version (which may be a bad thing) */ - win->src.view = NSOpenGLView_initWithFrame((NSRect){{0, 0}, {win->r.w, win->r.h}}, (uint32_t*)format); - objc_msgSend_void(win->src.view, sel_registerName("prepareOpenGL")); - win->src.ctx = objc_msgSend_id(win->src.view, sel_registerName("openGLContext")); - } else - #endif - { - NSRect contentRect = (NSRect){{0, 0}, {win->r.w, win->r.h}}; - win->src.view = ((id(*)(id, SEL, NSRect))objc_msgSend) - (NSAlloc((id)objc_getClass("NSView")), sel_registerName("initWithFrame:"), - contentRect); - } - - void* contentView = NSWindow_contentView((id)win->src.window); - objc_msgSend_void_bool(contentView, sel_registerName("setWantsLayer:"), true); - - objc_msgSend_void_id((id)win->src.window, sel_registerName("setContentView:"), win->src.view); - - #ifdef RGFW_OPENGL - if ((flags & RGFW_windowNoInitAPI) == 0) - objc_msgSend_void(win->src.ctx, sel_registerName("makeCurrentContext")); - #endif - - if (flags & RGFW_windowTransparent) { - #ifdef RGFW_OPENGL - if ((flags & RGFW_windowNoInitAPI) == 0) { - i32 opacity = 0; - #define NSOpenGLCPSurfaceOpacity 236 - NSOpenGLContext_setValues((id)win->src.ctx, &opacity, NSOpenGLCPSurfaceOpacity); - } - #endif - - objc_msgSend_void_bool(win->src.window, sel_registerName("setOpaque:"), false); - - objc_msgSend_void_id((id)win->src.window, sel_registerName("setBackgroundColor:"), - NSColor_colorWithSRGB(0, 0, 0, 0)); - } - - Class delegateClass = objc_allocateClassPair(objc_getClass("NSObject"), "WindowDelegate", 0); - - class_addIvar( - delegateClass, "RGFW_window", - sizeof(RGFW_window*), rint(log2(sizeof(RGFW_window*))), - "L" - ); - - - class_addMethod(delegateClass, sel_registerName("windowWillResize:toSize:"), (IMP) RGFW__osxWindowResize, "{NSSize=ff}@:{NSSize=ff}"); - class_addMethod(delegateClass, sel_registerName("updateLayer:"), (IMP) RGFW__osxUpdateLayer, ""); - class_addMethod(delegateClass, sel_registerName("windowWillMove:"), (IMP) RGFW__osxWindowMove, ""); - class_addMethod(delegateClass, sel_registerName("windowDidMove:"), (IMP) RGFW__osxWindowMove, ""); - class_addMethod(delegateClass, sel_registerName("windowDidMiniaturize:"), (IMP) RGFW__osxWindowMiniaturize, ""); - class_addMethod(delegateClass, sel_registerName("windowDidDeminiaturize:"), (IMP) RGFW__osxWindowDeminiaturize, ""); - class_addMethod(delegateClass, sel_registerName("windowDidBecomeKey:"), (IMP) RGFW__osxWindowBecameKey, ""); - class_addMethod(delegateClass, sel_registerName("windowDidResignKey:"), (IMP) RGFW__osxWindowResignKey, ""); - class_addMethod(delegateClass, sel_registerName("draggingEntered:"), (IMP)draggingEntered, "l@:@"); - class_addMethod(delegateClass, sel_registerName("draggingUpdated:"), (IMP)draggingUpdated, "l@:@"); - class_addMethod(delegateClass, sel_registerName("draggingExited:"), (IMP)RGFW__osxDraggingEnded, "v@:@"); - class_addMethod(delegateClass, sel_registerName("draggingEnded:"), (IMP)RGFW__osxDraggingEnded, "v@:@"); - class_addMethod(delegateClass, sel_registerName("prepareForDragOperation:"), (IMP)prepareForDragOperation, "B@:@"); - class_addMethod(delegateClass, sel_registerName("performDragOperation:"), (IMP)performDragOperation, "B@:@"); - - id delegate = objc_msgSend_id(NSAlloc(delegateClass), sel_registerName("init")); - - if (RGFW_COCOA_FRAME_NAME) - objc_msgSend_ptr(win->src.view, sel_registerName("setFrameAutosaveName:"), RGFW_COCOA_FRAME_NAME); - - object_setInstanceVariable(delegate, "RGFW_window", win); - - objc_msgSend_void_id((id)win->src.window, sel_registerName("setDelegate:"), delegate); - - if (flags & RGFW_windowAllowDND) { - win->_flags |= RGFW_windowAllowDND; - - NSPasteboardType types[] = {NSPasteboardTypeURL, NSPasteboardTypeFileURL, NSPasteboardTypeString}; - NSregisterForDraggedTypes((id)win->src.window, types, 3); - } - - // Show the window - objc_msgSend_void_bool(NSApp, sel_registerName("activateIgnoringOtherApps:"), true); - ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("makeKeyAndOrderFront:"), (SEL)NULL); - objc_msgSend_void_bool(win->src.window, sel_registerName("setIsVisible:"), true); - - if (!RGFW_loaded) { - objc_msgSend_void(win->src.window, sel_registerName("makeMainWindow")); - - RGFW_loaded = 1; - } - - objc_msgSend_void(win->src.window, sel_registerName("makeKeyWindow")); - - objc_msgSend_void(NSApp, sel_registerName("finishLaunching")); - - RGFW_window_setFlags(win, flags); - - NSRetain(win->src.window); - NSRetain(NSApp); - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); - return win; -} - -void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { - NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.window, sel_registerName("frame")); - NSRect content = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.view, sel_registerName("frame")); - float offset = 0; - - RGFW_setBit(&win->_flags, RGFW_windowNoBorder, !border); - NSBackingStoreType storeType = NSWindowStyleMaskBorderless | NSWindowStyleMaskFullSizeContentView; - if (border) - storeType = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable; - if (!(win->_flags & RGFW_windowNoResize)) { - storeType |= NSWindowStyleMaskResizable; - } - - ((void (*)(id, SEL, NSBackingStoreType))objc_msgSend)((id)win->src.window, sel_registerName("setStyleMask:"), storeType); - - if (!border) { - id miniaturizeButton = objc_msgSend_int((id)win->src.window, sel_registerName("standardWindowButton:"), NSWindowMiniaturizeButton); - id titleBarView = objc_msgSend_id(miniaturizeButton, sel_registerName("superview")); - objc_msgSend_void_bool(titleBarView, sel_registerName("setHidden:"), true); - - offset = frame.size.height - content.size.height; - } - - RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h + offset)); - win->r.h -= offset; -} - -RGFW_area RGFW_getScreenSize(void) { - static CGDirectDisplayID display = 0; - - if (display == 0) - display = CGMainDisplayID(); - - return RGFW_AREA(CGDisplayPixelsWide(display), CGDisplayPixelsHigh(display)); -} - -RGFW_point RGFW_getGlobalMousePoint(void) { - RGFW_ASSERT(RGFW_root != NULL); - - CGEventRef e = CGEventCreate(NULL); - CGPoint point = CGEventGetLocation(e); - CFRelease(e); - - return RGFW_POINT((u32) point.x, (u32) point.y); /*!< the point is loaded during event checks */ -} - -typedef RGFW_ENUM(u32, NSEventType) { /* various types of events */ - NSEventTypeLeftMouseDown = 1, - NSEventTypeLeftMouseUp = 2, - NSEventTypeRightMouseDown = 3, - NSEventTypeRightMouseUp = 4, - NSEventTypeMouseMoved = 5, - NSEventTypeLeftMouseDragged = 6, - NSEventTypeRightMouseDragged = 7, - NSEventTypeMouseEntered = 8, - NSEventTypeMouseExited = 9, - NSEventTypeKeyDown = 10, - NSEventTypeKeyUp = 11, - NSEventTypeFlagsChanged = 12, - NSEventTypeAppKitDefined = 13, - NSEventTypeSystemDefined = 14, - NSEventTypeApplicationDefined = 15, - NSEventTypePeriodic = 16, - NSEventTypeCursorUpdate = 17, - NSEventTypeScrollWheel = 22, - NSEventTypeTabletPoint = 23, - NSEventTypeTabletProximity = 24, - NSEventTypeOtherMouseDown = 25, - NSEventTypeOtherMouseUp = 26, - NSEventTypeOtherMouseDragged = 27, - /* The following event types are available on some hardware on 10.5.2 and later */ - NSEventTypeGesture API_AVAILABLE(macos(10.5)) = 29, - NSEventTypeMagnify API_AVAILABLE(macos(10.5)) = 30, - NSEventTypeSwipe API_AVAILABLE(macos(10.5)) = 31, - NSEventTypeRotate API_AVAILABLE(macos(10.5)) = 18, - NSEventTypeBeginGesture API_AVAILABLE(macos(10.5)) = 19, - NSEventTypeEndGesture API_AVAILABLE(macos(10.5)) = 20, - - NSEventTypeSmartMagnify API_AVAILABLE(macos(10.8)) = 32, - NSEventTypeQuickLook API_AVAILABLE(macos(10.8)) = 33, - - NSEventTypePressure API_AVAILABLE(macos(10.10.3)) = 34, - NSEventTypeDirectTouch API_AVAILABLE(macos(10.10)) = 37, - - NSEventTypeChangeMode API_AVAILABLE(macos(10.15)) = 38, -}; - -typedef RGFW_ENUM(unsigned long long, NSEventMask) { /* masks for the types of events */ - NSEventMaskLeftMouseDown = 1ULL << NSEventTypeLeftMouseDown, - NSEventMaskLeftMouseUp = 1ULL << NSEventTypeLeftMouseUp, - NSEventMaskRightMouseDown = 1ULL << NSEventTypeRightMouseDown, - NSEventMaskRightMouseUp = 1ULL << NSEventTypeRightMouseUp, - NSEventMaskMouseMoved = 1ULL << NSEventTypeMouseMoved, - NSEventMaskLeftMouseDragged = 1ULL << NSEventTypeLeftMouseDragged, - NSEventMaskRightMouseDragged = 1ULL << NSEventTypeRightMouseDragged, - NSEventMaskMouseEntered = 1ULL << NSEventTypeMouseEntered, - NSEventMaskMouseExited = 1ULL << NSEventTypeMouseExited, - NSEventMaskKeyDown = 1ULL << NSEventTypeKeyDown, - NSEventMaskKeyUp = 1ULL << NSEventTypeKeyUp, - NSEventMaskFlagsChanged = 1ULL << NSEventTypeFlagsChanged, - NSEventMaskAppKitDefined = 1ULL << NSEventTypeAppKitDefined, - NSEventMaskSystemDefined = 1ULL << NSEventTypeSystemDefined, - NSEventMaskApplicationDefined = 1ULL << NSEventTypeApplicationDefined, - NSEventMaskPeriodic = 1ULL << NSEventTypePeriodic, - NSEventMaskCursorUpdate = 1ULL << NSEventTypeCursorUpdate, - NSEventMaskScrollWheel = 1ULL << NSEventTypeScrollWheel, - NSEventMaskTabletPoint = 1ULL << NSEventTypeTabletPoint, - NSEventMaskTabletProximity = 1ULL << NSEventTypeTabletProximity, - NSEventMaskOtherMouseDown = 1ULL << NSEventTypeOtherMouseDown, - NSEventMaskOtherMouseUp = 1ULL << NSEventTypeOtherMouseUp, - NSEventMaskOtherMouseDragged = 1ULL << NSEventTypeOtherMouseDragged, - /* The following event masks are available on some hardware on 10.5.2 and later */ - NSEventMaskGesture API_AVAILABLE(macos(10.5)) = 1ULL << NSEventTypeGesture, - NSEventMaskMagnify API_AVAILABLE(macos(10.5)) = 1ULL << NSEventTypeMagnify, - NSEventMaskSwipe API_AVAILABLE(macos(10.5)) = 1ULL << NSEventTypeSwipe, - NSEventMaskRotate API_AVAILABLE(macos(10.5)) = 1ULL << NSEventTypeRotate, - NSEventMaskBeginGesture API_AVAILABLE(macos(10.5)) = 1ULL << NSEventTypeBeginGesture, - NSEventMaskEndGesture API_AVAILABLE(macos(10.5)) = 1ULL << NSEventTypeEndGesture, - - /* Note: You can only use these event masks on 64 bit. In other words, you cannot setup a local, nor global, event monitor for these event types on 32 bit. Also, you cannot search the event queue for them (nextEventMatchingMask:...) on 32 bit. - */ - NSEventMaskSmartMagnify API_AVAILABLE(macos(10.8)) = 1ULL << NSEventTypeSmartMagnify, - NSEventMaskPressure API_AVAILABLE(macos(10.10.3)) = 1ULL << NSEventTypePressure, - NSEventMaskDirectTouch API_AVAILABLE(macos(10.12.2)) = 1ULL << NSEventTypeDirectTouch, - - NSEventMaskChangeMode API_AVAILABLE(macos(10.15)) = 1ULL << NSEventTypeChangeMode, - - NSEventMaskAny = ULONG_MAX, - -}; - -typedef enum NSEventModifierFlags { - NSEventModifierFlagCapsLock = 1 << 16, - NSEventModifierFlagShift = 1 << 17, - NSEventModifierFlagControl = 1 << 18, - NSEventModifierFlagOption = 1 << 19, - NSEventModifierFlagCommand = 1 << 20, - NSEventModifierFlagNumericPad = 1 << 21 -} NSEventModifierFlags; - -void RGFW_stopCheckEvents(void) { - id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); - eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); - - id e = (id) ((id(*)(id, SEL, NSEventType, NSPoint, NSEventModifierFlags, void*, NSInteger, void**, short, NSInteger, NSInteger))objc_msgSend) - (NSApp, sel_registerName("otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:"), - NSEventTypeApplicationDefined, (NSPoint){0, 0}, (NSEventModifierFlags)0, NULL, (NSInteger)0, NULL, 0, 0, 0); - - ((void (*)(id, SEL, id, bool))objc_msgSend) - (NSApp, sel_registerName("postEvent:atStart:"), e, 1); - - objc_msgSend_bool_void(eventPool, sel_registerName("drain")); -} - -void RGFW_window_eventWait(RGFW_window* win, u32 waitMS) { - RGFW_UNUSED(win); - - id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); - eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); - - void* date = (void*) ((id(*)(Class, SEL, double))objc_msgSend) - (objc_getClass("NSDate"), sel_registerName("dateWithTimeIntervalSinceNow:"), waitMS); - - id e = (id) ((id(*)(id, SEL, NSEventMask, void*, id, bool))objc_msgSend) - (NSApp, sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"), - ULONG_MAX, date, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); - - - if (e) { - ((void (*)(id, SEL, id, bool))objc_msgSend) - (NSApp, sel_registerName("postEvent:atStart:"), e, 1); - } - - objc_msgSend_bool_void(eventPool, sel_registerName("drain")); -} - -RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { - RGFW_event* ev = RGFW_window_checkEventCore(win); - if (ev) { - if (ev == (RGFW_event*)-1) return NULL; - ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); - return ev; - } - - id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); - eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); - - static SEL eventFunc = (SEL)NULL; - if (eventFunc == NULL) - eventFunc = sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"); - - void* date = NULL; - - id e = (id) ((id(*)(id, SEL, NSEventMask, void*, id, bool))objc_msgSend) - (NSApp, eventFunc, ULONG_MAX, date, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); - - if (e == NULL) { - objc_msgSend_bool_void(eventPool, sel_registerName("drain")); - objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), e); - ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); - return NULL; - } - - if (objc_msgSend_id(e, sel_registerName("window")) != win->src.window) { - ((void (*)(id, SEL, id, bool))objc_msgSend) - (NSApp, sel_registerName("postEvent:atStart:"), e, 0); - - objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), e); - objc_msgSend_bool_void(eventPool, sel_registerName("drain")); - ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); - return NULL; - } - - if (win->event.droppedFilesCount) { - u32 i; - for (i = 0; i < win->event.droppedFilesCount; i++) - win->event.droppedFiles[i][0] = '\0'; - } - - win->event.droppedFilesCount = 0; - win->event.type = 0; - - u32 type = objc_msgSend_uint(e, sel_registerName("type")); - switch (type) { - case NSEventTypeMouseEntered: { - win->event.type = RGFW_mouseEnter; - NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(e, sel_registerName("locationInWindow")); - - win->event.point = RGFW_POINT((i32) p.x, (i32) (win->r.h - p.y)); - RGFW_mouseNotifyCallBack(win, win->event.point, 1); - break; - } - - case NSEventTypeMouseExited: - win->event.type = RGFW_mouseLeave; - RGFW_mouseNotifyCallBack(win, win->event.point, 0); - break; - - case NSEventTypeKeyDown: { - u32 key = (u16) objc_msgSend_uint(e, sel_registerName("keyCode")); - - u32 mappedKey = *((u32*)((char*)(const char*) NSString_to_char(objc_msgSend_id(e, sel_registerName("charactersIgnoringModifiers"))))); - if (((u8)mappedKey) == 239) - mappedKey = 0; - - win->event.keyChar = (u8)mappedKey; - - win->event.key = RGFW_apiKeyToRGFW(key); - RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; - - win->event.type = RGFW_keyPressed; - win->event.repeat = RGFW_isPressed(win, win->event.key); - RGFW_keyboard[win->event.key].current = 1; - - RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, 1); - break; - } - - case NSEventTypeKeyUp: { - u32 key = (u16) objc_msgSend_uint(e, sel_registerName("keyCode")); - - u32 mappedKey = *((u32*)((char*)(const char*) NSString_to_char(objc_msgSend_id(e, sel_registerName("charactersIgnoringModifiers"))))); - if (((u8)mappedKey) == 239) - mappedKey = 0; - - win->event.keyChar = (u8)mappedKey; - - win->event.key = RGFW_apiKeyToRGFW(key); - - RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; - - win->event.type = RGFW_keyReleased; - - RGFW_keyboard[win->event.key].current = 0; - RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, 0); - break; - } - - case NSEventTypeFlagsChanged: { - u32 flags = objc_msgSend_uint(e, sel_registerName("modifierFlags")); - RGFW_updateKeyModsPro(win, ((u32)(flags & NSEventModifierFlagCapsLock) % 255), ((flags & NSEventModifierFlagNumericPad) % 255), - ((flags & NSEventModifierFlagControl) % 255), ((flags & NSEventModifierFlagOption) % 255), - ((flags & NSEventModifierFlagShift) % 255), ((flags & NSEventModifierFlagCommand) % 255), 0); - u8 i; - for (i = 0; i < 9; i++) - RGFW_keyboard[i + RGFW_capsLock].prev = 0; - - for (i = 0; i < 5; i++) { - u32 shift = (1 << (i + 16)); - u32 key = i + RGFW_capsLock; - - if ((flags & shift) && !RGFW_wasPressed(win, key)) { - RGFW_keyboard[key].current = 1; - - if (key != RGFW_capsLock) - RGFW_keyboard[key+ 4].current = 1; - - win->event.type = RGFW_keyPressed; - win->event.key = key; - break; - } - - if (!(flags & shift) && RGFW_wasPressed(win, key)) { - RGFW_keyboard[key].current = 0; - - if (key != RGFW_capsLock) - RGFW_keyboard[key + 4].current = 0; - - win->event.type = RGFW_keyReleased; - win->event.key = key; - break; - } - } - - RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, win->event.type == RGFW_keyPressed); - - break; - } - case NSEventTypeLeftMouseDragged: - case NSEventTypeOtherMouseDragged: - case NSEventTypeRightMouseDragged: - case NSEventTypeMouseMoved: { - win->event.type = RGFW_mousePosChanged; - NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(e, sel_registerName("locationInWindow")); - win->event.point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); - - p.x = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaX")); - p.y = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaY")); - win->event.vector = RGFW_POINT((i32)p.x, (i32)p.y); - - win->_lastMousePoint = win->event.point; - RGFW_mousePosCallback(win, win->event.point, win->event.vector); - break; - } - case NSEventTypeLeftMouseDown: case NSEventTypeRightMouseDown: case NSEventTypeOtherMouseDown: { - u32 buttonNumber = objc_msgSend_uint(e, sel_registerName("buttonNumber")); - switch (buttonNumber) { - case 0: win->event.button = RGFW_mouseLeft; break; - case 1: win->event.button = RGFW_mouseRight; break; - case 2: win->event.button = RGFW_mouseMiddle; break; - default: win->event.button = buttonNumber; - } - - win->event.type = RGFW_mouseButtonPressed; - RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; - RGFW_mouseButtons[win->event.button].current = 1; - RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); - break; - } - case NSEventTypeLeftMouseUp: case NSEventTypeRightMouseUp: case NSEventTypeOtherMouseUp: { - u32 buttonNumber = objc_msgSend_uint(e, sel_registerName("buttonNumber")); - switch (buttonNumber) { - case 0: win->event.button = RGFW_mouseLeft; break; - case 1: win->event.button = RGFW_mouseRight; break; - case 2: win->event.button = RGFW_mouseMiddle; break; - default: win->event.button = buttonNumber; - } - RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; - RGFW_mouseButtons[win->event.button].current = 0; - win->event.type = RGFW_mouseButtonReleased; - RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 0); - break; - } - case NSEventTypeScrollWheel: { - double deltaY = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaY")); - - if (deltaY > 0) { - win->event.button = RGFW_mouseScrollUp; - } - else if (deltaY < 0) { - win->event.button = RGFW_mouseScrollDown; - } - - RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; - RGFW_mouseButtons[win->event.button].current = 1; - - win->event.scroll = deltaY; - - win->event.type = RGFW_mouseButtonPressed; - RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); - break; - } - - default: - objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), e); - ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); - return RGFW_window_checkEvent(win); - } - - objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), e); - ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); - objc_msgSend_bool_void(eventPool, sel_registerName("drain")); - return &win->event; -} - - -void RGFW_window_move(RGFW_window* win, RGFW_point v) { - RGFW_ASSERT(win != NULL); - - win->r.x = v.x; - win->r.y = v.y; - ((void(*)(id, SEL, NSRect, bool, bool))objc_msgSend) - ((id)win->src.window, sel_registerName("setFrame:display:animate:"), (NSRect){{win->r.x, win->r.y}, {win->r.w, win->r.h}}, true, true); -} - -void RGFW_window_resize(RGFW_window* win, RGFW_area a) { - RGFW_ASSERT(win != NULL); - - NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.window, sel_registerName("frame")); - NSRect content = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.view, sel_registerName("frame")); - float offset = frame.size.height - content.size.height; - - win->r.w = a.w; - win->r.h = a.h; - - ((void(*)(id, SEL, NSRect, bool, bool))objc_msgSend) - ((id)win->src.window, sel_registerName("setFrame:display:animate:"), (NSRect){{win->r.x, win->r.y}, {win->r.w, win->r.h + offset}}, true, true); -} - -void RGFW_window_focus(RGFW_window* win) { - RGFW_ASSERT(win); - objc_msgSend_void_bool(NSApp, sel_registerName("activateIgnoringOtherApps:"), true); - ((void (*)(id, SEL))objc_msgSend)((id)win->src.window, sel_registerName("makeKeyWindow")); -} - -void RGFW_window_raise(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("orderFront:"), (SEL)NULL); - objc_msgSend_void_id(win->src.window, sel_registerName("setLevel:"), kCGNormalWindowLevelKey); -} - -void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { - RGFW_ASSERT(win != NULL); - if (fullscreen && (win->_flags & RGFW_windowFullscreen)) return; - if (!fullscreen && !(win->_flags & RGFW_windowFullscreen)) return; - - if (fullscreen) { - win->_oldRect = win->r; - RGFW_monitor mon = RGFW_window_getMonitor(win); - win->r = RGFW_RECT(0, 0, mon.x, mon.y); - win->_flags |= RGFW_windowFullscreen; - RGFW_window_resize(win, RGFW_AREA(mon.mode.area.w, mon.mode.area.h)); - RGFW_window_move(win, RGFW_POINT(0, 0)); - } - objc_msgSend_void_SEL(win->src.window, sel_registerName("toggleFullScreen:"), NULL); - - if (!fullscreen) { - win->r = win->_oldRect; - win->_flags &= ~RGFW_windowFullscreen; - - RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h)); - RGFW_window_move(win, RGFW_POINT(win->r.x, win->r.y)); - } -} - -void RGFW_window_maximize(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - if (RGFW_window_isMaximized(win)) return; - - win->_flags |= RGFW_windowMaximize; - objc_msgSend_void_SEL(win->src.window, sel_registerName("zoom:"), NULL); -} - -void RGFW_window_minimize(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - objc_msgSend_void_SEL(win->src.window, sel_registerName("performMiniaturize:"), NULL); -} - -void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { - RGFW_ASSERT(win != NULL); - if (floating) objc_msgSend_void_id(win->src.window, sel_registerName("setLevel:"), kCGFloatingWindowLevelKey); - else objc_msgSend_void_id(win->src.window, sel_registerName("setLevel:"), kCGNormalWindowLevelKey); -} - -void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { - objc_msgSend_int(win->src.window, sel_registerName("setAlphaValue:"), opacity); - objc_msgSend_void_bool(win->src.window, sel_registerName("setOpaque:"), (opacity < (u8)255)); - - if (opacity) - objc_msgSend_void_id((id)win->src.window, sel_registerName("setBackgroundColor:"), NSColor_colorWithSRGB(0, 0, 0, opacity)); - -} - -void RGFW_window_restore(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - - if (RGFW_window_isMaximized(win)) - objc_msgSend_void_SEL(win->src.window, sel_registerName("zoom:"), NULL); - - objc_msgSend_void_SEL(win->src.window, sel_registerName("deminiaturize:"), NULL); - RGFW_window_show(win); -} - -RGFW_bool RGFW_window_isFloating(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - int level = ((int (*)(id, SEL))objc_msgSend) ((id)(win->src.window), (SEL)sel_registerName("level")); - return level > kCGNormalWindowLevelKey; -} - -void RGFW_window_setName(RGFW_window* win, const char* name) { - RGFW_ASSERT(win != NULL); - - id str = NSString_stringWithUTF8String(name); - objc_msgSend_void_id((id)win->src.window, sel_registerName("setTitle:"), str); -} - -#ifndef RGFW_NO_PASSTHROUGH -void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { - objc_msgSend_void_bool(win->src.window, sel_registerName("setIgnoresMouseEvents:"), passthrough); -} -#endif - -void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { - if (a.w == 0 && a.h == 0) a = RGFW_AREA(1, 1); - - ((void (*)(id, SEL, NSSize))objc_msgSend) - ((id)win->src.window, sel_registerName("setContentAspectRatio:"), (NSSize){a.w, a.h}); -} - -void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { - ((void (*)(id, SEL, NSSize))objc_msgSend) - ((id)win->src.window, sel_registerName("setMinSize:"), (NSSize){a.w, a.h}); -} - -void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { - if (a.w == 0 && a.h == 0) { - a = RGFW_getScreenSize(); - } - - ((void (*)(id, SEL, NSSize))objc_msgSend) - ((id)win->src.window, sel_registerName("setMaxSize:"), (NSSize){a.w, a.h}); -} - -RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, RGFW_area area, i32 channels, u8 type) { - RGFW_ASSERT(win != NULL); - RGFW_UNUSED(type); - - if (data == NULL) { - objc_msgSend_void_id(NSApp, sel_registerName("setApplicationIconImage:"), NULL); - return RGFW_TRUE; - } - - /* code by EimaMei */ - // Make a bitmap representation, then copy the loaded image into it. - id representation = NSBitmapImageRep_initWithBitmapData(NULL, area.w, area.h, 8, channels, (channels == 4), false, "NSCalibratedRGBColorSpace", 1 << 1, area.w * channels, 8 * channels); - RGFW_MEMCPY(NSBitmapImageRep_bitmapData(representation), data, area.w * area.h * channels); - - // Add ze representation. - id dock_image = NSImage_initWithSize((NSSize){area.w, area.h}); - NSImage_addRepresentation(dock_image, representation); - - // Finally, set the dock image to it. - objc_msgSend_void_id(NSApp, sel_registerName("setApplicationIconImage:"), dock_image); - // Free the garbage. - NSRelease(dock_image); - NSRelease(representation); - - return RGFW_TRUE; -} - -id NSCursor_arrowStr(const char* str) { - void* nclass = objc_getClass("NSCursor"); - SEL func = sel_registerName(str); - return (id) objc_msgSend_id(nclass, func); -} - -RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { - if (icon == NULL) { - objc_msgSend_void(NSCursor_arrowStr("arrowCursor"), sel_registerName("set")); - return NULL; - } - - /* NOTE(EimaMei): Code by yours truly. */ - // Make a bitmap representation, then copy the loaded image into it. - id representation = NSBitmapImageRep_initWithBitmapData(NULL, a.w, a.h, 8, channels, (channels == 4), false, "NSCalibratedRGBColorSpace", 1 << 1, a.w * channels, 8 * channels); - RGFW_MEMCPY(NSBitmapImageRep_bitmapData(representation), icon, a.w * a.h * channels); - - // Add ze representation. - id cursor_image = NSImage_initWithSize((NSSize){a.w, a.h}); - NSImage_addRepresentation(cursor_image, representation); - - // Finally, set the cursor image. - id cursor = NSCursor_initWithImage(cursor_image, (NSPoint){0.0, 0.0}); - - // Free the garbage. - NSRelease(cursor_image); - NSRelease(representation); - - return (void*)cursor; -} - -void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { - RGFW_ASSERT(win != NULL); RGFW_ASSERT(mouse); - objc_msgSend_void((id)mouse, sel_registerName("set")); -} - -void RGFW_freeMouse(RGFW_mouse* mouse) { - RGFW_ASSERT(mouse); - NSRelease((id)mouse); -} - -RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { - return RGFW_window_setMouseStandard(win, RGFW_mouseArrow); -} - -void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show) { - RGFW_window_showMouseFlags(win, show); - if (show) CGDisplayShowCursor(kCGDirectMainDisplay); - else CGDisplayHideCursor(kCGDirectMainDisplay); -} - -RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 stdMouses) { - static const char* mouseIconSrc[] = {"arrowCursor", "arrowCursor", "IBeamCursor", "crosshairCursor", "pointingHandCursor", "resizeLeftRightCursor", "resizeUpDownCursor", "_windowResizeNorthWestSouthEastCursor", "_windowResizeNorthEastSouthWestCursor", "closedHandCursor", "operationNotAllowedCursor"}; - if (stdMouses > ((sizeof(mouseIconSrc)) / (sizeof(char*)))) - return RGFW_FALSE; - - const char* mouseStr = mouseIconSrc[stdMouses]; - id mouse = NSCursor_arrowStr(mouseStr); - - if (mouse == NULL) - return RGFW_FALSE; - - RGFW_UNUSED(win); - CGDisplayShowCursor(kCGDirectMainDisplay); - objc_msgSend_void(mouse, sel_registerName("set")); - - return RGFW_TRUE; -} - -void RGFW_releaseCursor(RGFW_window* win) { - RGFW_UNUSED(win); - CGAssociateMouseAndMouseCursorPosition(1); -} - -void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { - RGFW_UNUSED(win); - - CGWarpMouseCursorPosition(CGPointMake(r.x + (r.w / 2), r.y + (r.h / 2))); - CGAssociateMouseAndMouseCursorPosition(0); -} - -void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v) { - RGFW_UNUSED(win); - - win->_lastMousePoint = RGFW_POINT(v.x - win->r.x, v.y - win->r.y); - CGWarpMouseCursorPosition(CGPointMake(v.x, v.y)); -} - - -void RGFW_window_hide(RGFW_window* win) { - objc_msgSend_void_bool(win->src.window, sel_registerName("setIsVisible:"), false); -} - -void RGFW_window_show(RGFW_window* win) { - if (win->_flags & RGFW_windowFocusOnShow) - ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("makeKeyAndOrderFront:"), NULL); - - ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("orderFront:"), NULL); - objc_msgSend_void_bool(win->src.window, sel_registerName("setIsVisible:"), true); -} - -RGFW_bool RGFW_window_isHidden(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - - bool visible = objc_msgSend_bool(win->src.window, sel_registerName("isVisible")); - return visible == NO && !RGFW_window_isMinimized(win); -} - -RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - - return objc_msgSend_bool(win->src.window, sel_registerName("isMiniaturized")) == YES; -} - -RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - RGFW_bool b = objc_msgSend_bool(win->src.window, sel_registerName("isZoomed")); - return b; -} - -id RGFW_getNSScreenForDisplayID(CGDirectDisplayID display) { - Class NSScreenClass = objc_getClass("NSScreen"); - - id screens = objc_msgSend_id(NSScreenClass, sel_registerName("screens")); - - NSUInteger count = (NSUInteger)objc_msgSend_uint(screens, sel_registerName("count")); - - for (NSUInteger i = 0; i < count; i++) { - id screen = ((id (*)(id, SEL, int))objc_msgSend) (screens, sel_registerName("objectAtIndex:"), (int)i); - id description = objc_msgSend_id(screen, sel_registerName("deviceDescription")); - id screenNumberKey = NSString_stringWithUTF8String("NSScreenNumber"); - id screenNumber = objc_msgSend_id_id(description, sel_registerName("objectForKey:"), screenNumberKey); - - if ((CGDirectDisplayID)objc_msgSend_uint(screenNumber, sel_registerName("unsignedIntValue")) == display) { - return screen; - } - } - - return NULL; -} - - -u32 RGFW_osx_getRefreshRate(CGDirectDisplayID display, CGDisplayModeRef mode) { - if (mode) { - u32 refreshRate = (int)CGDisplayModeGetRefreshRate(mode); - if (refreshRate != 0) return refreshRate; - } - - CVDisplayLinkRef link; - CVDisplayLinkCreateWithCGDisplay(display, &link); - const CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link); - if (!(time.flags & kCVTimeIsIndefinite)) - return (int) (time.timeScale / (double) time.timeValue); - - return 0; -} - -RGFW_monitor RGFW_NSCreateMonitor(CGDirectDisplayID display, id screen) { - RGFW_monitor monitor; - - const char name[] = "MacOS\0"; - RGFW_MEMCPY(monitor.name, name, 6); - - CGRect bounds = CGDisplayBounds(display); - monitor.x = bounds.origin.x; - monitor.y = bounds.origin.y; - monitor.mode.area = RGFW_AREA((int) bounds.size.width, (int) bounds.size.height); - - monitor.mode.red = 8; monitor.mode.green = 8; monitor.mode.blue = 8; - - CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display); - monitor.mode.refreshRate = RGFW_osx_getRefreshRate(display, mode); - CFRelease(mode); - - CGSize screenSizeMM = CGDisplayScreenSize(display); - monitor.physW = (float)screenSizeMM.width / 25.4f; - monitor.physH = (float)screenSizeMM.height / 25.4f; - - float ppi_width = (monitor.mode.area.w/monitor.physW); - float ppi_height = (monitor.mode.area.h/monitor.physH); - - monitor.pixelRatio = ((CGFloat (*)(id, SEL))abi_objc_msgSend_fpret) (screen, sel_registerName("backingScaleFactor")); - float dpi = 96.0f * monitor.pixelRatio; - - monitor.scaleX = ((i32)(((float) (ppi_width) / dpi) * 10.0f)) / 10.0f; - monitor.scaleY = ((i32)(((float) (ppi_height) / dpi) * 10.0f)) / 10.0f; - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); - return monitor; -} - - -RGFW_monitor* RGFW_getMonitors(void) { - static CGDirectDisplayID displays[7]; - u32 count; - - if (CGGetActiveDisplayList(6, displays, &count) != kCGErrorSuccess) - return NULL; - - static RGFW_monitor monitors[7]; - - for (u32 i = 0; i < count; i++) - monitors[i] = RGFW_NSCreateMonitor(displays[i], RGFW_getNSScreenForDisplayID(displays[i])); - - return monitors; -} - -RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { - CGPoint point = { mon.x, mon.y }; - - CGDirectDisplayID display; - uint32_t displayCount = 0; - CGError err = CGGetDisplaysWithPoint(point, 1, &display, &displayCount); - if (err != kCGErrorSuccess || displayCount != 1) - return RGFW_FALSE; - - CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL); - - if (allModes == NULL) - return RGFW_FALSE; - - for (CFIndex i = 0; i < CFArrayGetCount(allModes); i++) { - CGDisplayModeRef cmode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i); - - RGFW_monitorMode foundMode; - foundMode.area = RGFW_AREA(CGDisplayModeGetWidth(cmode), CGDisplayModeGetHeight(cmode)); - foundMode.refreshRate = RGFW_osx_getRefreshRate(display, cmode); - foundMode.red = 8; foundMode.green = 8; foundMode.blue = 8; - - if (RGFW_monitorModeCompare(mode, foundMode, request)) { - CGError err = CGDisplaySetDisplayMode(display, cmode, NULL); - if (err == kCGErrorSuccess) { - CFRelease(allModes); - return RGFW_TRUE; - } - break; - } - } - - CFRelease(allModes); - - return RGFW_FALSE; -} - -RGFW_monitor RGFW_getPrimaryMonitor(void) { - CGDirectDisplayID primary = CGMainDisplayID(); - return RGFW_NSCreateMonitor(primary, RGFW_getNSScreenForDisplayID(primary)); -} - -RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { - id screen = objc_msgSend_id(win->src.window, sel_registerName("screen")); - id description = objc_msgSend_id(screen, sel_registerName("deviceDescription")); - id screenNumberKey = NSString_stringWithUTF8String("NSScreenNumber"); - id screenNumber = objc_msgSend_id_id(description, sel_registerName("objectForKey:"), screenNumberKey); - - CGDirectDisplayID display = (CGDirectDisplayID)objc_msgSend_uint(screenNumber, sel_registerName("unsignedIntValue")); - - return RGFW_NSCreateMonitor(display, screen); -} - -RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { - size_t clip_len; - char* clip = (char*)NSPasteboard_stringForType(NSPasteboard_generalPasteboard(), NSPasteboardTypeString, &clip_len); - if (clip == NULL) return -1; - - if (str != NULL) { - if (strCapacity < clip_len) - return 0; - - RGFW_MEMCPY(str, clip, clip_len); - - str[clip_len] = '\0'; - } - - return (RGFW_ssize_t)clip_len; -} - -void RGFW_writeClipboard(const char* text, u32 textLen) { - RGFW_UNUSED(textLen); - - NSPasteboardType array[] = { NSPasteboardTypeString, NULL }; - NSPasteBoard_declareTypes(NSPasteboard_generalPasteboard(), array, 1, NULL); - - NSPasteBoard_setString(NSPasteboard_generalPasteboard(), text, NSPasteboardTypeString); -} - - #ifdef RGFW_OPENGL - void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - objc_msgSend_void(win->src.ctx, sel_registerName("makeCurrentContext")); - } - void* RGFW_getCurrent_OpenGL(void) { - return objc_msgSend_id(objc_getClass("NSOpenGLContext"), sel_registerName("currentContext")); - } - #endif - - #if !defined(RGFW_EGL) - - void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { - RGFW_ASSERT(win != NULL); - #if defined(RGFW_OPENGL) - - NSOpenGLContext_setValues((id)win->src.ctx, &swapInterval, 222); - #else - RGFW_UNUSED(swapInterval); - #endif - } - - #endif - -// Function to create a CGImageRef from an array of bytes -CGImageRef createImageFromBytes(unsigned char *buffer, int width, int height) -{ - // Define color space - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - // Create bitmap context - CGContextRef context = CGBitmapContextCreate( - buffer, - width, height, - 8, - width * 4, - colorSpace, - kCGImageAlphaPremultipliedLast); - // Create image from bitmap context - CGImageRef image = CGBitmapContextCreateImage(context); - // Release the color space and context - CGColorSpaceRelease(colorSpace); - CGContextRelease(context); - - return image; -} - -void RGFW_window_swapBuffers(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - /* clear the window */ - - if (!(win->_flags & RGFW_NO_CPU_RENDER)) { -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - id view = NSWindow_contentView((id)win->src.window); - id layer = objc_msgSend_id(view, sel_registerName("layer")); - - ((void(*)(id, SEL, NSRect))objc_msgSend)(layer, - sel_registerName("setFrame:"), - (NSRect){{0, 0}, {win->r.w, win->r.h}}); - - CGImageRef image = createImageFromBytes(win->buffer, win->r.w, win->r.h); - // Get the current graphics context - id graphicsContext = objc_msgSend_class(objc_getClass("NSGraphicsContext"), sel_registerName("currentContext")); - // Get the CGContext from the current NSGraphicsContext - id cgContext = objc_msgSend_id(graphicsContext, sel_registerName("graphicsPort")); - // Draw the image in the context - NSRect bounds = (NSRect){{0,0}, {win->r.w, win->r.h}}; - CGContextDrawImage((CGContextRef)cgContext, *(CGRect*)&bounds, image); - // Flush the graphics context to ensure the drawing is displayed - objc_msgSend_id(graphicsContext, sel_registerName("flushGraphics")); - - objc_msgSend_void_id(layer, sel_registerName("setContents:"), (id)image); - objc_msgSend_id(layer, sel_registerName("setNeedsDisplay")); - - CGImageRelease(image); -#endif - } - - if (!(win->_flags & RGFW_NO_GPU_RENDER)) { - #ifdef RGFW_EGL - eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); - #elif defined(RGFW_OPENGL) - objc_msgSend_void(win->src.ctx, sel_registerName("flushBuffer")); - #endif - } -} - -void RGFW_window_close(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - NSRelease(win->src.view); - - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - NSRelease(win->src.bitmap); - NSRelease(win->src.image); - if ((win->_flags & RGFW_BUFFER_ALLOC)) - RGFW_FREE(win->buffer); - #endif - - RGFW_clipboard_switch(NULL); - RGFW_FREE(win->event.droppedFiles); - - if ((win->_flags & RGFW_WINDOW_ALLOC)) - RGFW_FREE(win); -} - -u64 RGFW_getTimerFreq(void) { - static u64 freq = 0; - if (freq == 0) { - mach_timebase_info_data_t info; - mach_timebase_info(&info); - freq = (info.denom * 1e9) / info.numer; - } - - return freq; -} - -u64 RGFW_getTimerValue(void) { return (u64)mach_absolute_time(); } - -#endif /* RGFW_MACOS */ - -/* - End of MaOS defines -*/ - -/* - WASM defines -*/ - -#ifdef RGFW_WASM -EM_BOOL Emscripten_on_resize(int eventType, const EmscriptenUiEvent* e, void* userData) { - RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowResized, ._win = RGFW_root}); - RGFW_windowResizeCallback(RGFW_root, RGFW_RECT(0, 0, e->windowInnerWidth, e->windowInnerHeight)); - return EM_TRUE; -} - -EM_BOOL Emscripten_on_fullscreenchange(int eventType, const EmscriptenFullscreenChangeEvent* e, void* userData) { - RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - static u8 fullscreen = RGFW_FALSE; - static RGFW_rect ogRect; - - if (fullscreen == RGFW_FALSE) { - ogRect = RGFW_root->r; - } - - fullscreen = !fullscreen; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowResized, ._win = RGFW_root}); - RGFW_root->r = RGFW_RECT(0, 0, e->screenWidth, e->screenHeight); - - EM_ASM("Module.canvas.focus();"); - - if (fullscreen == RGFW_FALSE) { - RGFW_root->r = RGFW_RECT(0, 0, ogRect.w, ogRect.h); - // emscripten_request_fullscreen("#canvas", 0); - } else { - #if __EMSCRIPTEN_major__ >= 1 && __EMSCRIPTEN_minor__ >= 29 && __EMSCRIPTEN_tiny__ >= 0 - EmscriptenFullscreenStrategy FSStrat = {0}; - FSStrat.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH;//EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT;// : EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; - FSStrat.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF; - FSStrat.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; - emscripten_request_fullscreen_strategy("#canvas", 1, &FSStrat); - #else - emscripten_request_fullscreen("#canvas", 1); - #endif - } - - emscripten_set_canvas_element_size("#canvas", RGFW_root->r.w, RGFW_root->r.h); - - RGFW_windowResizeCallback(RGFW_root, RGFW_root->r); - return EM_TRUE; -} - - - -EM_BOOL Emscripten_on_focusin(int eventType, const EmscriptenFocusEvent* e, void* userData) { - RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusIn, ._win = RGFW_root}); - RGFW_root->_flags |= RGFW_windowFocus; - RGFW_focusCallback(RGFW_root, 1); - return EM_TRUE; -} - -EM_BOOL Emscripten_on_focusout(int eventType, const EmscriptenFocusEvent* e, void* userData) { - RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusOut, ._win = RGFW_root}); - RGFW_root->_flags &= ~RGFW_windowFocus; - RGFW_focusCallback(RGFW_root, 0); - return EM_TRUE; -} - -EM_BOOL Emscripten_on_mousemove(int eventType, const EmscriptenMouseEvent* e, void* userData) { - RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mousePosChanged, - .point = RGFW_POINT(e->targetX, e->targetY), - .vector = RGFW_POINT(e->movementX, e->movementY), - ._win = RGFW_root}); - - RGFW_root->_lastMousePoint = RGFW_POINT(e->targetX, e->targetY); - RGFW_mousePosCallback(RGFW_root, RGFW_POINT(e->targetX, e->targetY), RGFW_POINT(e->movementX, e->movementY)); - return EM_TRUE; -} - -EM_BOOL Emscripten_on_mousedown(int eventType, const EmscriptenMouseEvent* e, void* userData) { - RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - - int button = e->button; - if (button > 2) - button += 2; - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonPressed, - .point = RGFW_POINT(e->targetX, e->targetY), - .vector = RGFW_POINT(e->movementX, e->movementY), - .button = (u8)button, - .scroll = 0, - ._win = RGFW_root}); - RGFW_mouseButtons[button].prev = RGFW_mouseButtons[button].current; - RGFW_mouseButtons[button].current = 1; - - RGFW_mouseButtonCallback(RGFW_root, button, 0, 1); - return EM_TRUE; -} - -EM_BOOL Emscripten_on_mouseup(int eventType, const EmscriptenMouseEvent* e, void* userData) { - RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - - int button = e->button; - if (button > 2) - button += 2; - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonReleased, - .point = RGFW_POINT(e->targetX, e->targetY), - .vector = RGFW_POINT(e->movementX, e->movementY), - .button = (u8)button, - .scroll = 0, - ._win = RGFW_root}); - RGFW_mouseButtons[button].prev = RGFW_mouseButtons[button].current; - RGFW_mouseButtons[button].current = 0; - - RGFW_mouseButtonCallback(RGFW_root, button, 0, 0); - return EM_TRUE; -} - -EM_BOOL Emscripten_on_wheel(int eventType, const EmscriptenWheelEvent* e, void* userData) { - RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - - int button = RGFW_mouseScrollUp + (e->deltaY < 0); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonPressed, - .button = (u8)button, - .scroll = (double)(e->deltaY < 0 ? 1 : -1), - ._win = RGFW_root}); - RGFW_mouseButtons[button].prev = RGFW_mouseButtons[button].current; - RGFW_mouseButtons[button].current = 1; - RGFW_mouseButtonCallback(RGFW_root, button, e->deltaY < 0 ? 1 : -1, 1); - - return EM_TRUE; -} - -EM_BOOL Emscripten_on_touchstart(int eventType, const EmscriptenTouchEvent* e, void* userData) { - RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - - size_t i; - for (i = 0; i < (size_t)e->numTouches; i++) { - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonPressed, - .point = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), - .button = RGFW_mouseLeft, - ._win = RGFW_root}); - - RGFW_mouseButtons[RGFW_mouseLeft].prev = RGFW_mouseButtons[RGFW_mouseLeft].current; - RGFW_mouseButtons[RGFW_mouseLeft].current = 1; - - RGFW_root->_lastMousePoint = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY); - RGFW_mousePosCallback(RGFW_root, RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), RGFW_root->event.vector); - RGFW_mouseButtonCallback(RGFW_root, RGFW_mouseLeft, 0, 1); - } - - return EM_TRUE; -} -EM_BOOL Emscripten_on_touchmove(int eventType, const EmscriptenTouchEvent* e, void* userData) { - RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - - size_t i; - for (i = 0; i < (size_t)e->numTouches; i++) { - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mousePosChanged, - .point = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), - .button = RGFW_mouseLeft, - ._win = RGFW_root}); - - RGFW_root->_lastMousePoint = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY); - RGFW_mousePosCallback(RGFW_root, RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), RGFW_root->event.vector); - } - return EM_TRUE; -} - -EM_BOOL Emscripten_on_touchend(int eventType, const EmscriptenTouchEvent* e, void* userData) { - RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - - size_t i; - for (i = 0; i < (size_t)e->numTouches; i++) { - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonReleased, - .point = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), - .button = RGFW_mouseLeft, - ._win = RGFW_root}); - - RGFW_mouseButtons[RGFW_mouseLeft].prev = RGFW_mouseButtons[RGFW_mouseLeft].current; - RGFW_mouseButtons[RGFW_mouseLeft].current = 0; - - RGFW_root->_lastMousePoint = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY); - RGFW_mousePosCallback(RGFW_root, RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), RGFW_root->event.vector); - RGFW_mouseButtonCallback(RGFW_root, RGFW_mouseLeft, 0, 0); - } - return EM_TRUE; -} - -EM_BOOL Emscripten_on_touchcancel(int eventType, const EmscriptenTouchEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); return EM_TRUE; } - -EM_BOOL Emscripten_on_gamepad(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) { - RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - - if (gamepadEvent->index >= 4) - return 0; - - size_t i = gamepadEvent->index; - if (gamepadEvent->connected) { - RGFW_MEMCPY(RGFW_gamepads_name[gamepadEvent->index], gamepadEvent->id, sizeof(RGFW_gamepads_name[gamepadEvent->index])); - RGFW_gamepads_type[i] = RGFW_gamepadUnknown; - if (RGFW_STRSTR(RGFW_gamepads_name[i], "Microsoft") || RGFW_STRSTR(RGFW_gamepads_name[i], "X-Box")) - RGFW_gamepads_type[i] = RGFW_gamepadMicrosoft; - else if (RGFW_STRSTR(RGFW_gamepads_name[i], "PlayStation") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS3") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS4") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS5")) - RGFW_gamepads_type[i] = RGFW_gamepadSony; - else if (RGFW_STRSTR(RGFW_gamepads_name[i], "Nintendo")) - RGFW_gamepads_type[i] = RGFW_gamepadNintendo; - else if (RGFW_STRSTR(RGFW_gamepads_name[i], "Logitech")) - RGFW_gamepads_type[i] = RGFW_gamepadLogitech; - RGFW_gamepadCount++; - } else { - RGFW_gamepadCount--; - } - - RGFW_eventQueuePush((RGFW_event){.type = (RGFW_eventType)(gamepadEvent->connected ? RGFW_gamepadConnected : RGFW_gamepadConnected), - .gamepad = (u16)gamepadEvent->index, - ._win = RGFW_root}); - - RGFW_gamepadCallback(RGFW_root, gamepadEvent->index, gamepadEvent->connected); - RGFW_gamepads[gamepadEvent->index] = gamepadEvent->connected; - - return 1; // The event was consumed by the callback handler -} - -u32 RGFW_wASMPhysicalToRGFW(u32 hash) { - switch(hash) { /* 0x0000 */ - case 0x67243A2DU /* Escape */: return RGFW_escape; /* 0x0001 */ - case 0x67251058U /* Digit0 */: return RGFW_0; /* 0x0002 */ - case 0x67251059U /* Digit1 */: return RGFW_1; /* 0x0003 */ - case 0x6725105AU /* Digit2 */: return RGFW_2; /* 0x0004 */ - case 0x6725105BU /* Digit3 */: return RGFW_3; /* 0x0005 */ - case 0x6725105CU /* Digit4 */: return RGFW_4; /* 0x0006 */ - case 0x6725105DU /* Digit5 */: return RGFW_5; /* 0x0007 */ - case 0x6725105EU /* Digit6 */: return RGFW_6; /* 0x0008 */ - case 0x6725105FU /* Digit7 */: return RGFW_7; /* 0x0009 */ - case 0x67251050U /* Digit8 */: return RGFW_8; /* 0x000A */ - case 0x67251051U /* Digit9 */: return RGFW_9; /* 0x000B */ - case 0x92E14DD3U /* Minus */: return RGFW_minus; /* 0x000C */ - case 0x92E1FBACU /* Equal */: return RGFW_equals; /* 0x000D */ - case 0x36BF1CB5U /* Backspace */: return RGFW_backSpace; /* 0x000E */ - case 0x7B8E51E2U /* Tab */: return RGFW_tab; /* 0x000F */ - case 0x2C595B51U /* KeyQ */: return RGFW_q; /* 0x0010 */ - case 0x2C595B57U /* KeyW */: return RGFW_w; /* 0x0011 */ - case 0x2C595B45U /* KeyE */: return RGFW_e; /* 0x0012 */ - case 0x2C595B52U /* KeyR */: return RGFW_r; /* 0x0013 */ - case 0x2C595B54U /* KeyT */: return RGFW_t; /* 0x0014 */ - case 0x2C595B59U /* KeyY */: return RGFW_y; /* 0x0015 */ - case 0x2C595B55U /* KeyU */: return RGFW_u; /* 0x0016 */ - case 0x2C595B4FU /* KeyO */: return RGFW_o; /* 0x0018 */ - case 0x2C595B50U /* KeyP */: return RGFW_p; /* 0x0019 */ - case 0x45D8158CU /* BracketLeft */: return RGFW_closeBracket; /* 0x001A */ - case 0xDEEABF7CU /* BracketRight */: return RGFW_bracket; /* 0x001B */ - case 0x92E1C5D2U /* Enter */: return RGFW_return; /* 0x001C */ - case 0xE058958CU /* ControlLeft */: return RGFW_controlL; /* 0x001D */ - case 0x2C595B41U /* KeyA */: return RGFW_a; /* 0x001E */ - case 0x2C595B53U /* KeyS */: return RGFW_s; /* 0x001F */ - case 0x2C595B44U /* KeyD */: return RGFW_d; /* 0x0020 */ - case 0x2C595B46U /* KeyF */: return RGFW_f; /* 0x0021 */ - case 0x2C595B47U /* KeyG */: return RGFW_g; /* 0x0022 */ - case 0x2C595B48U /* KeyH */: return RGFW_h; /* 0x0023 */ - case 0x2C595B4AU /* KeyJ */: return RGFW_j; /* 0x0024 */ - case 0x2C595B4BU /* KeyK */: return RGFW_k; /* 0x0025 */ - case 0x2C595B4CU /* KeyL */: return RGFW_l; /* 0x0026 */ - case 0x2707219EU /* Semicolon */: return RGFW_semicolon; /* 0x0027 */ - case 0x92E0B58DU /* Quote */: return RGFW_apostrophe; /* 0x0028 */ - case 0x36BF358DU /* Backquote */: return RGFW_backtick; /* 0x0029 */ - case 0x26B1958CU /* ShiftLeft */: return RGFW_shiftL; /* 0x002A */ - case 0x36BF2438U /* Backslash */: return RGFW_backSlash; /* 0x002B */ - case 0x2C595B5AU /* KeyZ */: return RGFW_z; /* 0x002C */ - case 0x2C595B58U /* KeyX */: return RGFW_x; /* 0x002D */ - case 0x2C595B43U /* KeyC */: return RGFW_c; /* 0x002E */ - case 0x2C595B56U /* KeyV */: return RGFW_v; /* 0x002F */ - case 0x2C595B42U /* KeyB */: return RGFW_b; /* 0x0030 */ - case 0x2C595B4EU /* KeyN */: return RGFW_n; /* 0x0031 */ - case 0x2C595B4DU /* KeyM */: return RGFW_m; /* 0x0032 */ - case 0x92E1A1C1U /* Comma */: return RGFW_comma; /* 0x0033 */ - case 0x672FFAD4U /* Period */: return RGFW_period; /* 0x0034 */ - case 0x92E0A438U /* Slash */: return RGFW_slash; /* 0x0035 */ - case 0xC5A6BF7CU /* ShiftRight */: return RGFW_shiftR; - case 0x5D64DA91U /* NumpadMultiply */: return RGFW_multiply; - case 0xC914958CU /* AltLeft */: return RGFW_altL; /* 0x0038 */ - case 0x92E09CB5U /* Space */: return RGFW_space; /* 0x0039 */ - case 0xB8FAE73BU /* CapsLock */: return RGFW_capsLock; /* 0x003A */ - case 0x7174B789U /* F1 */: return RGFW_F1; /* 0x003B */ - case 0x7174B78AU /* F2 */: return RGFW_F2; /* 0x003C */ - case 0x7174B78BU /* F3 */: return RGFW_F3; /* 0x003D */ - case 0x7174B78CU /* F4 */: return RGFW_F4; /* 0x003E */ - case 0x7174B78DU /* F5 */: return RGFW_F5; /* 0x003F */ - case 0x7174B78EU /* F6 */: return RGFW_F6; /* 0x0040 */ - case 0x7174B78FU /* F7 */: return RGFW_F7; /* 0x0041 */ - case 0x7174B780U /* F8 */: return RGFW_F8; /* 0x0042 */ - case 0x7174B781U /* F9 */: return RGFW_F9; /* 0x0043 */ - case 0x7B8E57B0U /* F10 */: return RGFW_F10; /* 0x0044 */ - case 0xC925FCDFU /* Numpad7 */: return RGFW_multiply; /* 0x0047 */ - case 0xC925FCD0U /* Numpad8 */: return RGFW_KP_8; /* 0x0048 */ - case 0xC925FCD1U /* Numpad9 */: return RGFW_KP_9; /* 0x0049 */ - case 0x5EA3E8A4U /* NumpadSubtract */: return RGFW_minus; /* 0x004A */ - case 0xC925FCDCU /* Numpad4 */: return RGFW_KP_4; /* 0x004B */ - case 0xC925FCDDU /* Numpad5 */: return RGFW_KP_5; /* 0x004C */ - case 0xC925FCDEU /* Numpad6 */: return RGFW_KP_6; /* 0x004D */ - case 0xC925FCD9U /* Numpad1 */: return RGFW_KP_1; /* 0x004F */ - case 0xC925FCDAU /* Numpad2 */: return RGFW_KP_2; /* 0x0050 */ - case 0xC925FCDBU /* Numpad3 */: return RGFW_KP_3; /* 0x0051 */ - case 0xC925FCD8U /* Numpad0 */: return RGFW_KP_0; /* 0x0052 */ - case 0x95852DACU /* NumpadDecimal */: return RGFW_period; /* 0x0053 */ - case 0x7B8E57B1U /* F11 */: return RGFW_F11; /* 0x0057 */ - case 0x7B8E57B2U /* F12 */: return RGFW_F12; /* 0x0058 */ - case 0x7393FBACU /* NumpadEqual */: return RGFW_KP_Return; - case 0xB88EBF7CU /* AltRight */: return RGFW_altR; /* 0xE038 */ - case 0xC925873BU /* NumLock */: return RGFW_numLock; /* 0xE045 */ - case 0x2C595F45U /* Home */: return RGFW_home; /* 0xE047 */ - case 0xC91BB690U /* ArrowUp */: return RGFW_up; /* 0xE048 */ - case 0x672F9210U /* PageUp */: return RGFW_pageUp; /* 0xE049 */ - case 0x3799258CU /* ArrowLeft */: return RGFW_left; /* 0xE04B */ - case 0x4CE33F7CU /* ArrowRight */: return RGFW_right; /* 0xE04D */ - case 0x7B8E55DCU /* End */: return RGFW_end; /* 0xE04F */ - case 0x3799379EU /* ArrowDown */: return RGFW_down; /* 0xE050 */ - case 0xBA90179EU /* PageDown */: return RGFW_pageDown; /* 0xE051 */ - case 0x6723CB2CU /* Insert */: return RGFW_insert; /* 0xE052 */ - case 0x6725C50DU /* Delete */: return RGFW_delete; /* 0xE053 */ - case 0x6723658CU /* OSLeft */: return RGFW_superL; /* 0xE05B */ - case 0x39643F7CU /* MetaRight */: return RGFW_superR; /* 0xE05C */ - } - - return 0; -} - -void EMSCRIPTEN_KEEPALIVE RGFW_handleKeyEvent(char* key, char* code, RGFW_bool press) { - const char* iCode = code; - - u32 hash = 0; - while(*iCode) hash = ((hash ^ 0x7E057D79U) << 3) ^ (unsigned int)*iCode++; - - u32 physicalKey = RGFW_wASMPhysicalToRGFW(hash); - - u8 mappedKey = (u8)(*((u32*)key)); - - if (*((u16*)key) != mappedKey) { - mappedKey = 0; - if (*((u32*)key) == *((u32*)"Tab")) mappedKey = RGFW_tab; - } - - RGFW_eventQueuePush((RGFW_event){.type = (RGFW_eventType)(press ? RGFW_keyPressed : RGFW_keyReleased), - .key = (u8)physicalKey, - .keyChar = (u8)mappedKey, - .keyMod = RGFW_root->event.keyMod, - ._win = RGFW_root}); - - RGFW_keyboard[physicalKey].prev = RGFW_keyboard[physicalKey].current; - RGFW_keyboard[physicalKey].current = press; - - RGFW_keyCallback(RGFW_root, physicalKey, mappedKey, RGFW_root->event.keyMod, press); -} - -void EMSCRIPTEN_KEEPALIVE RGFW_handleKeyMods(RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll) { - RGFW_updateKeyModsPro(RGFW_root, capital, numlock, control, alt, shift, super, scroll); -} - -void EMSCRIPTEN_KEEPALIVE Emscripten_onDrop(size_t count) { - if (!(RGFW_root->_flags & RGFW_windowAllowDND)) - return; - - RGFW_eventQueuePush((RGFW_event){.type = RGFW_DND, - .droppedFilesCount = count, - ._win = RGFW_root}); - RGFW_dndCallback(RGFW_root, RGFW_root->event.droppedFiles, count); -} - -RGFW_bool RGFW_stopCheckEvents_bool = RGFW_FALSE; -void RGFW_stopCheckEvents(void) { - RGFW_stopCheckEvents_bool = RGFW_TRUE; -} - -void RGFW_window_eventWait(RGFW_window* win, u32 waitMS) { - RGFW_UNUSED(win); - if (waitMS == 0) return; - - u32 start = (u32)(((u64)RGFW_getTimeNS()) / 1e+6); - - while ((RGFW_eventLen == 0) && RGFW_stopCheckEvents_bool == RGFW_FALSE && - (waitMS != RGFW_eventWaitNext || (RGFW_getTimeNS() / 1e+6) - start < waitMS) - ) { - emscripten_sleep(0); - } - - RGFW_stopCheckEvents_bool = RGFW_FALSE; -} - -void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area){ - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - win->buffer = buffer; - win->bufferSize = area; - #ifdef RGFW_OSMESA - win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); - OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); - #endif - #else - RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); /*!< if buffer rendering is not being used */ - #endif -} - -void EMSCRIPTEN_KEEPALIVE RGFW_makeSetValue(size_t index, char* file) { - /* This seems like a terrible idea, don't replicate this unless you hate yourself or the OS */ - /* TODO: find a better way to do this - */ - RGFW_MEMCPY((char*)RGFW_root->event.droppedFiles[index], file, RGFW_MAX_PATH); -} - -#include -#include -#include -#include - -void EMSCRIPTEN_KEEPALIVE RGFW_mkdir(char* name) { mkdir(name, 0755); } - -void EMSCRIPTEN_KEEPALIVE RGFW_writeFile(const char *path, const char *data, size_t len) { - FILE* file = fopen(path, "w+"); - if (file == NULL) - return; - - fwrite(data, sizeof(char), len, file); - fclose(file); -} - -RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { - RGFW_UNUSED(name); - - RGFW_window_basic_init(win, rect, flags); - - #ifndef RGFW_WEBGPU - EmscriptenWebGLContextAttributes attrs; - attrs.alpha = RGFW_GL_HINTS[RGFW_glDepth]; - attrs.depth = RGFW_GL_HINTS[RGFW_glAlpha]; - attrs.stencil = RGFW_GL_HINTS[RGFW_glStencil]; - attrs.antialias = RGFW_GL_HINTS[RGFW_glSamples]; - attrs.premultipliedAlpha = EM_TRUE; - attrs.preserveDrawingBuffer = EM_FALSE; - - if (RGFW_GL_HINTS[RGFW_glDoubleBuffer] == 0) - attrs.renderViaOffscreenBackBuffer = 0; - else - attrs.renderViaOffscreenBackBuffer = RGFW_GL_HINTS[RGFW_glAuxBuffers]; - - attrs.failIfMajorPerformanceCaveat = EM_FALSE; - attrs.majorVersion = (RGFW_GL_HINTS[RGFW_glMinor] == 0) ? 1 : RGFW_GL_HINTS[RGFW_glMinor]; - attrs.minorVersion = RGFW_GL_HINTS[RGFW_glMajor]; - - attrs.enableExtensionsByDefault = EM_TRUE; - attrs.explicitSwapControl = EM_TRUE; - - emscripten_webgl_init_context_attributes(&attrs); - win->src.ctx = emscripten_webgl_create_context("#canvas", &attrs); - emscripten_webgl_make_context_current(win->src.ctx); - - #ifdef LEGACY_GL_EMULATION - EM_ASM("Module.useWebGL = true; GLImmediate.init();"); - #endif - #else - win->src.ctx = wgpuCreateInstance(NULL); - win->src.device = emscripten_webgpu_get_device(); - win->src.queue = wgpuDeviceGetQueue(win->src.device); - #endif - - emscripten_set_canvas_element_size("#canvas", rect.w, rect.h); - emscripten_set_window_title(name); - - /* load callbacks */ - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_resize); - emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, EM_FALSE, Emscripten_on_fullscreenchange); - emscripten_set_mousemove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousemove); - emscripten_set_touchstart_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchstart); - emscripten_set_touchend_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchend); - emscripten_set_touchmove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchmove); - emscripten_set_touchcancel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchcancel); - emscripten_set_mousedown_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousedown); - emscripten_set_mouseup_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mouseup); - emscripten_set_wheel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_wheel); - emscripten_set_focusin_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusin); - emscripten_set_focusout_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusout); - emscripten_set_gamepadconnected_callback(NULL, 1, Emscripten_on_gamepad); - emscripten_set_gamepaddisconnected_callback(NULL, 1, Emscripten_on_gamepad); - - if (flags & RGFW_windowAllowDND) { - win->_flags |= RGFW_windowAllowDND; - } - - EM_ASM({ - window.addEventListener("keydown", - (event) => { - var key = stringToNewUTF8(event.key); var code = stringToNewUTF8(event.code); - Module._RGFW_handleKeyMods(event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("Control"), event.getModifierState("Alt"), event.getModifierState("Shift"), event.getModifierState("Meta"), event.getModifierState("ScrollLock")); - Module._RGFW_handleKeyEvent(key, code, 1); - _free(key); _free(code); - }, - true); - window.addEventListener("keyup", - (event) => { - var key = stringToNewUTF8(event.key); var code = stringToNewUTF8(event.code); - Module._RGFW_handleKeyMods(event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("Control"), event.getModifierState("Alt"), event.getModifierState("Shift"), event.getModifierState("Meta"), event.getModifierState("ScrollLock")); - Module._RGFW_handleKeyEvent(key, code, 0); - _free(key); _free(code); - }, - true); - }); - - EM_ASM({ - var canvas = document.getElementById('canvas'); - canvas.addEventListener('drop', function(e) { - e.preventDefault(); - if (e.dataTransfer.file < 0) - return; - - var filenamesArray = []; - var count = e.dataTransfer.files.length; - - /* Read and save the files to emscripten's files */ - var drop_dir = '.rgfw_dropped_files'; - Module._RGFW_mkdir(drop_dir); - - for (var i = 0; i < count; i++) { - var file = e.dataTransfer.files[i]; - - var path = '/' + drop_dir + '/' + file.name.replace("//", '_'); - var reader = new FileReader(); - - reader.onloadend = (e) => { - if (reader.readyState != 2) { - out('failed to read dropped file: '+file.name+': '+reader.error); - } - else { - var data = e.target.result; - - _RGFW_writeFile(path, new Uint8Array(data), file.size); - } - }; - - reader.readAsArrayBuffer(file); - // This works weird on modern opengl - var filename = stringToNewUTF8(path); - - filenamesArray.push(filename); - - Module._RGFW_makeSetValue(i, filename); - } - - Module._Emscripten_onDrop(count); - - for (var i = 0; i < count; ++i) { - _free(filenamesArray[i]); - } - }, true); - - canvas.addEventListener('dragover', function(e) { e.preventDefault(); return false; }, true); - }); - - glViewport(0, 0, rect.w, rect.h); - - RGFW_window_setFlags(win, flags); - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); - return win; -} - -RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { - RGFW_event* ev = RGFW_window_checkEventCore(win); - if (ev) { - if (ev == (RGFW_event*)-1) return NULL; - return ev; - } - - emscripten_sample_gamepad_data(); - /* check gamepads */ - for (int i = 0; (i < emscripten_get_num_gamepads()) && (i < 4); i++) { - if (RGFW_gamepads[i] == 0) - continue; - EmscriptenGamepadEvent gamepadState; - - if (emscripten_get_gamepad_status(i, &gamepadState) != EMSCRIPTEN_RESULT_SUCCESS) - break; - - // Register buttons data for every connected gamepad - for (int j = 0; (j < gamepadState.numButtons) && (j < 16); j++) { - u32 map[] = { - RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadX, RGFW_gamepadY, - RGFW_gamepadL1, RGFW_gamepadR1, RGFW_gamepadL2, RGFW_gamepadR2, - RGFW_gamepadSelect, RGFW_gamepadStart, - RGFW_gamepadL3, RGFW_gamepadR3, - RGFW_gamepadUp, RGFW_gamepadDown, RGFW_gamepadLeft, RGFW_gamepadRight, RGFW_gamepadHome - }; - - - u32 button = map[j]; - if (button == 404) - continue; - - if (RGFW_gamepadPressed[i][button].current != gamepadState.digitalButton[j]) { - if (gamepadState.digitalButton[j]) - win->event.type = RGFW_gamepadButtonPressed; - else - win->event.type = RGFW_gamepadButtonReleased; - - win->event.gamepad = i; - win->event.button = map[j]; - - RGFW_gamepadPressed[i][button].prev = RGFW_gamepadPressed[i][button].current; - RGFW_gamepadPressed[i][button].current = gamepadState.digitalButton[j]; - - RGFW_gamepadButtonCallback(win, win->event.gamepad, win->event.button, gamepadState.digitalButton[j]); - return &win->event; - } - } - - for (int j = 0; (j < gamepadState.numAxes) && (j < 4); j += 2) { - win->event.axisesCount = gamepadState.numAxes / 2; - if (RGFW_gamepadAxes[i][(size_t)(j / 2)].x != (i8)(gamepadState.axis[j] * 100.0f) || - RGFW_gamepadAxes[i][(size_t)(j / 2)].y != (i8)(gamepadState.axis[j + 1] * 100.0f) - ) { - - RGFW_gamepadAxes[i][(size_t)(j / 2)].x = (i8)(gamepadState.axis[j] * 100.0f); - RGFW_gamepadAxes[i][(size_t)(j / 2)].y = (i8)(gamepadState.axis[j + 1] * 100.0f); - win->event.axis[(size_t)(j / 2)] = RGFW_gamepadAxes[i][(size_t)(j / 2)]; - - win->event.type = RGFW_gamepadAxisMove; - win->event.gamepad = i; - win->event.whichAxis = j / 2; - - RGFW_gamepadAxisCallback(win, win->event.gamepad, win->event.axis, win->event.axisesCount, win->event.whichAxis); - return &win->event; - } - } - } - - return NULL; -} - -void RGFW_window_resize(RGFW_window* win, RGFW_area a) { - RGFW_UNUSED(win); - emscripten_set_canvas_element_size("#canvas", a.w, a.h); -} - -/* NOTE: I don't know if this is possible */ -void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v) { RGFW_UNUSED(win); RGFW_UNUSED(v); } -/* this one might be possible but it looks iffy */ -RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { RGFW_UNUSED(channels); RGFW_UNUSED(a); RGFW_UNUSED(icon); return NULL; } - -void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { RGFW_UNUSED(win); RGFW_UNUSED(mouse); } -void RGFW_freeMouse(RGFW_mouse* mouse) { RGFW_UNUSED(mouse); } - -RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { - static const char cursors[11][12] = { - "default", "default", "text", "crosshair", - "pointer", "ew-resize", "ns-resize", "nwse-resize", "nesw-resize", - "move", "not-allowed" - }; - - RGFW_UNUSED(win); - EM_ASM( { document.getElementById("canvas").style.cursor = UTF8ToString($0); }, cursors[mouse]); - return RGFW_TRUE; -} - -RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { - return RGFW_window_setMouseStandard(win, RGFW_mouseNormal); -} - -void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show) { - RGFW_window_showMouseFlags(win, show); - if (show) - RGFW_window_setMouseDefault(win); - else - EM_ASM(document.getElementById('canvas').style.cursor = 'none';); -} - -RGFW_point RGFW_getGlobalMousePoint(void) { - RGFW_point point; - point.x = EM_ASM_INT({ - return window.mouseX || 0; - }); - point.y = EM_ASM_INT({ - return window.mouseY || 0; - }); - return point; -} - -void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { - RGFW_UNUSED(win); - - EM_ASM_({ - var canvas = document.getElementById('canvas'); - if ($0) { - canvas.style.pointerEvents = 'none'; - } else { - canvas.style.pointerEvents = 'auto'; - } - }, passthrough); -} - -void RGFW_writeClipboard(const char* text, u32 textLen) { - RGFW_UNUSED(textLen); - EM_ASM({ navigator.clipboard.writeText(UTF8ToString($0)); }, text); -} - - -RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { - RGFW_UNUSED(str); RGFW_UNUSED(strCapacity); - /* - placeholder code for later - I'm not sure if this is possible do the the async stuff - */ - return 0; -} - -void RGFW_window_swapBuffers(RGFW_window* win) { - RGFW_UNUSED(win); - - #ifdef RGFW_BUFFER - if (!(win->_flags & RGFW_NO_CPU_RENDER)) { - glEnable(GL_TEXTURE_2D); - - GLuint texture; - glGenTextures(1,&texture); - - glBindTexture(GL_TEXTURE_2D,texture); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - #ifdef RGFW_BUFFER_BGR - glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA, win->bufferSize.w, win->bufferSize.h, 0, GL_BGRA, GL_UNSIGNED_BYTE, win->buffer); - #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, win->bufferSize.w, win->bufferSize.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, win->buffer); - #endif - - float ratioX = ((float)win->r.w / (float)win->bufferSize.w); - float ratioY = ((float)win->r.h / (float)win->bufferSize.h); - - // Set up the viewport - glClear(GL_COLOR_BUFFER_BIT); - - glBegin(GL_TRIANGLES); - glTexCoord2f(0, ratioY); glColor3f(1, 1, 1); glVertex2f(-1, -1); - glTexCoord2f(0, 0); glColor3f(1, 1, 1); glVertex2f(-1, 1); - glTexCoord2f(ratioX, ratioY); glColor3f(1, 1, 1); glVertex2f(1, -1); - - glTexCoord2f(ratioX, 0); glColor3f(1, 1, 1); glVertex2f(1, 1); - glTexCoord2f(ratioX, ratioY); glColor3f(1, 1, 1); glVertex2f(1, -1); - glTexCoord2f(0, 0); glColor3f(1, 1, 1); glVertex2f(-1, 1); - glEnd(); - - glDeleteTextures(1, &texture); - } - #endif - -#ifndef RGFW_WEBGPU - emscripten_webgl_commit_frame(); -#endif - emscripten_sleep(0); -} - - -void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { -#ifndef RGFW_WEBGPU - if (win == NULL) - emscripten_webgl_make_context_current(0); - else - emscripten_webgl_make_context_current(win->src.ctx); -#endif -} - -#ifndef RGFW_WEBGPU -void* RGFW_getCurrent_OpenGL(void) { return (void*)emscripten_webgl_get_current_context(); } -#endif - -#ifndef RGFW_EGL -void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { RGFW_UNUSED(win); RGFW_UNUSED(swapInterval); } -#endif - -void RGFW_window_close(RGFW_window* win) { -#ifndef RGFW_WEBGPU - emscripten_webgl_destroy_context(win->src.ctx); -#endif - - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - if ((win->_flags & RGFW_BUFFER_ALLOC)) - RGFW_FREE(win->buffer); - #endif - - RGFW_clipboard_switch(NULL); - RGFW_FREE(win->event.droppedFiles); - - if ((win->_flags & RGFW_WINDOW_ALLOC)) - RGFW_FREE(win); -} - -int RGFW_innerWidth(void) { return EM_ASM_INT({ return window.innerWidth; }); } -int RGFW_innerHeight(void) { return EM_ASM_INT({ return window.innerHeight; }); } - -RGFW_area RGFW_getScreenSize(void) { - return RGFW_AREA(RGFW_innerWidth(), RGFW_innerHeight()); -} - -void* RGFW_getProcAddress(const char* procname) { - return emscripten_webgl_get_proc_address(procname); -} - -void RGFW_sleep(u64 milisecond) { - emscripten_sleep(milisecond); -} - -u64 RGFW_getTimerFreq(void) { return (u64)1000; } -u64 RGFW_getTimerValue(void) { return emscripten_get_now() * 1e+6; } - -void RGFW_releaseCursor(RGFW_window* win) { - RGFW_UNUSED(win); - emscripten_exit_pointerlock(); -} - -void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { - RGFW_UNUSED(win); RGFW_UNUSED(r); - - emscripten_request_pointerlock("#canvas", 1); -} - - -void RGFW_window_setName(RGFW_window* win, const char* name) { - RGFW_UNUSED(win); - emscripten_set_window_title(name); -} - -void RGFW_window_maximize(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - - RGFW_area screen = RGFW_getScreenSize(); - RGFW_window_move(win, RGFW_POINT(0, 0)); - RGFW_window_resize(win, screen); -} - -void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { - RGFW_ASSERT(win != NULL); - if (fullscreen) { - win->_flags |= RGFW_windowFullscreen; - EM_ASM( Module.requestFullscreen(false, true); ); - return; - } - win->_flags &= ~RGFW_windowFullscreen; - EM_ASM( Module.exitFullscreen(false, true); ); -} - -void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { - RGFW_UNUSED(win); - EM_ASM({ - var element = document.getElementById("canvas"); - if (element) - element.style.opacity = $1; - }, "elementId", opacity); -} - -/* unsupported functions */ -void RGFW_window_focus(RGFW_window* win) { RGFW_UNUSED(win); } -void RGFW_window_raise(RGFW_window* win) { RGFW_UNUSED(win); } -RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { RGFW_UNUSED(mon); RGFW_UNUSED(mode); RGFW_UNUSED(request); return RGFW_FALSE; } -RGFW_monitor* RGFW_getMonitors(void) { return NULL; } -RGFW_monitor RGFW_getPrimaryMonitor(void) { return (RGFW_monitor){}; } -void RGFW_window_move(RGFW_window* win, RGFW_point v) { RGFW_UNUSED(win); RGFW_UNUSED(v); } -void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { RGFW_UNUSED(win); RGFW_UNUSED(a); } -void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { RGFW_UNUSED(win); RGFW_UNUSED(a); } -void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { RGFW_UNUSED(win); RGFW_UNUSED(a); } -void RGFW_window_minimize(RGFW_window* win) { RGFW_UNUSED(win); } -void RGFW_window_restore(RGFW_window* win) { RGFW_UNUSED(win); } -void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { RGFW_UNUSED(win); RGFW_UNUSED(floating); } -void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { RGFW_UNUSED(win); RGFW_UNUSED(border); } -RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* icon, RGFW_area a, i32 channels, u8 type) { RGFW_UNUSED(win); RGFW_UNUSED(icon); RGFW_UNUSED(a); RGFW_UNUSED(channels); RGFW_UNUSED(type); return RGFW_FALSE; } -void RGFW_window_hide(RGFW_window* win) { RGFW_UNUSED(win); } -void RGFW_window_show(RGFW_window* win) {RGFW_UNUSED(win); } -RGFW_bool RGFW_window_isHidden(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } -RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } -RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } -RGFW_bool RGFW_window_isFloating(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } -RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { RGFW_UNUSED(win); return (RGFW_monitor){}; } -#endif - -/* end of web asm defines */ - -/* unix (macOS, linux, web asm) only stuff */ -#if defined(RGFW_X11) || defined(RGFW_MACOS) || defined(RGFW_WASM) || defined(RGFW_WAYLAND) -#ifndef RGFW_NO_THREADS -#include - -RGFW_thread RGFW_createThread(RGFW_threadFunc_ptr ptr, void* args) { - RGFW_UNUSED(args); - - RGFW_thread t; - pthread_create((pthread_t*) &t, NULL, *ptr, NULL); - return t; -} -void RGFW_cancelThread(RGFW_thread thread) { pthread_cancel((pthread_t) thread); } -void RGFW_joinThread(RGFW_thread thread) { pthread_join((pthread_t) thread, NULL); } - -#if defined(__linux__) -void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { pthread_setschedprio((pthread_t)thread, priority); } -#else -void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { RGFW_UNUSED(thread); RGFW_UNUSED(priority); } -#endif -#endif - -#ifndef RGFW_WASM -void RGFW_sleep(u64 ms) { - struct timespec time; - time.tv_sec = 0; - time.tv_nsec = ms * 1e+6; - - #ifndef RGFW_NO_UNIX_CLOCK - nanosleep(&time, NULL); - #endif -} -#endif - -#endif /* end of unix / mac stuff */ -#endif /* RGFW_IMPLEMENTATION */ - -#if defined(__cplusplus) && !defined(__EMSCRIPTEN__) -} - #ifdef __clang__ - #pragma clang diagnostic pop - #endif -#endif +/* +* +* RGFW 1.7 +* +* Copyright (C) 2022-25 ColleagueRiley +* +* libpng license +* +* 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. +* +* +*/ + +/* + (MAKE SURE RGFW_IMPLEMENTATION is in exactly one header or you use -D RGFW_IMPLEMENTATION) + #define RGFW_IMPLEMENTATION - makes it so source code is included with header +*/ + +/* + #define RGFW_IMPLEMENTATION - (required) makes it so the source code is included + #define RGFW_DEBUG - (optional) makes it so RGFW prints debug messages and errors when they're found + #define RGFW_OSMESA - (optional) use OSmesa as backend (instead of system's opengl api + regular opengl) + #define RGFW_BUFFER - (optional) draw directly to (RGFW) window pixel buffer that is drawn to screen (the buffer is in the RGBA format) + #define RGFW_EGL - (optional) use EGL for loading an OpenGL context (instead of the system's opengl api) + #define RGFW_OPENGL_ES1 - (optional) use EGL to load and use Opengl ES (version 1) for backend rendering (instead of the system's opengl api) + This version doesn't work for desktops (I'm pretty sure) + #define RGFW_OPENGL_ES2 - (optional) use OpenGL ES (version 2) + #define RGFW_OPENGL_ES3 - (optional) use OpenGL ES (version 3) + #define RGFW_DIRECTX - (optional) include integration directX functions (windows only) + #define RGFW_VULKAN - (optional) include helpful vulkan integration functions and macros + #define RGFW_WEBGPU - (optional) use webGPU for rendering (Web ONLY) + #define RGFW_NO_API - (optional) don't use any rendering API (no opengl, no vulkan, no directX) + + #define RGFW_LINK_EGL (optional) (windows only) if EGL is being used, if EGL functions should be defined dymanically (using GetProcAddress) + #define RGFW_X11 (optional) (unix only) if X11 should be used. This option is turned on by default by unix systems except for MacOS + #define RGFW_WAYLAND (optional) (unix only) use Wayland. (This can be used with X11) + #define RGFW_NO_X11 (optional) (unix only) don't fallback to X11 when using Wayland + #define RGFW_NO_LOAD_WGL (optional) (windows only) if WGL should be loaded dynamically during runtime + #define RGFW_NO_X11_CURSOR (optional) (unix only) don't use XCursor + #define RGFW_NO_X11_CURSOR_PRELOAD (optional) (unix only) use XCursor, but don't link it in code, (you'll have to link it with -lXcursor) + #define RGFW_NO_X11_EXT_PRELOAD (optional) (unix only) use Xext, but don't link it in code, (you'll have to link it with -lXext) + #define RGFW_NO_LOAD_WINMM (optional) (windows only) use winmm (timeBeginPeriod), but don't link it in code, (you'll have to link it with -lwinmm) + #define RGFW_NO_WINMM (optional) (windows only) don't use winmm + #define RGFW_NO_IOKIT (optional) (macOS) don't use IOKit + #define RGFW_NO_UNIX_CLOCK (optional) (unix) don't link unix clock functions + #define RGFW_NO_DWM (windows only) - do not use or link dwmapi + #define RGFW_USE_XDL (optional) (X11) if XDL (XLib Dynamic Loader) should be used to load X11 dynamically during runtime (must include XDL.h along with RGFW) + #define RGFW_COCOA_GRAPHICS_SWITCHING - (optional) (cocoa) use automatic graphics switching (allow the system to choose to use GPU or iGPU) + #define RGFW_COCOA_FRAME_NAME (optional) (cocoa) set frame name + #define RGFW_NO_DPI - do not calculate DPI (no XRM nor libShcore included) + #define RGFW_BUFFER_BGR - use the BGR format for bufffers instead of RGB, saves processing time + #define RGFW_ADVANCED_SMOOTH_RESIZE - use advanced methods for smooth resizing (may result in a spike in memory usage or worse performance) (eg. WM_TIMER and XSyncValue) + + #define RGFW_ALLOC x - choose the default allocation function (defaults to standard malloc) + #define RGFW_FREE x - choose the default deallocation function (defaults to standard free) + #define RGFW_USERPTR x - choose the default userptr sent to the malloc call, (NULL by default) + + #define RGFW_EXPORT - use when building RGFW + #define RGFW_IMPORT - use when linking with RGFW (not as a single-header) + + #define RGFW_USE_INT - force the use c-types rather than stdint.h (for systems that might not have stdint.h (msvc)) + #define RGFW_bool x - choose what type to use for bool, by default u32 is used +*/ + +/* +Example to get you started : + +linux : gcc main.c -lX11 -lXrandr -lGL +windows : gcc main.c -lopengl32 -lgdi32 +macos : gcc main.c -framework Cocoa -framework CoreVideo -framework OpenGL -framework IOKit + +#define RGFW_IMPLEMENTATION +#include "RGFW.h" + +u8 icon[4 * 3 * 3] = {0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF}; + +int main() { + RGFW_window* win = RGFW_createWindow("name", RGFW_RECT(100, 100, 500, 500), (u64)0); + + RGFW_window_setIcon(win, icon, RGFW_AREA(3, 3), 4); + + while (RGFW_window_shouldClose(win) == RGFW_FALSE) { + while (RGFW_window_checkEvent(win)) { + if (win->event.type == RGFW_quit || RGFW_isPressed(win, RGFW_escape)) + break; + } + + RGFW_window_swapBuffers(win); + + glClearColor(1, 1, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + } + + RGFW_window_close(win); +} + + compiling : + + if you wish to compile the library all you have to do is create a new file with this in it + + rgfw.c + #define RGFW_IMPLEMENTATION + #include "RGFW.h" + + You may also want to add + `#define RGFW_EXPORT` when compiling and + `#define RGFW_IMPORT`when linking RGFW on it's own: + this reduces inline functions and prevents bloat in the object file + + then you can use gcc (or whatever compile you wish to use) to compile the library into object file + + ex. gcc -c RGFW.c -fPIC + + after you compile the library into an object file, you can also turn the object file into an static or shared library + + (commands ar and gcc can be replaced with whatever equivalent your system uses) + + static : ar rcs RGFW.a RGFW.o + shared : + windows: + gcc -shared RGFW.o -lopengl32 -lgdi32 -o RGFW.dll + linux: + gcc -shared RGFW.o -lX11 -lGL -lXrandr -o RGFW.so + macos: + gcc -shared RGFW.o -framework CoreVideo -framework Cocoa -framework OpenGL -framework IOKit +*/ + + + +/* + Credits : + EimaMei/Sacode : Much of the code for creating windows using winapi, Wrote the Silicon library, helped with MacOS Support, siliapp.h -> referencing + + stb - This project is heavily inspired by the stb single header files + + GLFW: + certain parts of winapi and X11 are very poorly documented, + GLFW's source code was referenced and used throughout the project. + + contributors : (feel free to put yourself here if you contribute) + krisvers -> code review + EimaMei (SaCode) -> code review + Code-Nycticebus -> bug fixes + Rob Rohan -> X11 bugs and missing features, MacOS/Cocoa fixing memory issues/bugs + AICDG (@THISISAGOODNAME) -> vulkan support (example) + @Easymode -> support, testing/debugging, bug fixes and reviews + Joshua Rowe (omnisci3nce) - bug fix, review (macOS) + @lesleyrs -> bug fix, review (OpenGL) + Nick Porcino (meshula) - testing, organization, review (MacOS, examples) + @DarekParodia -> code review (X11) (C++) +*/ + +#if _MSC_VER + #pragma comment(lib, "gdi32") + #pragma comment(lib, "shell32") + #pragma comment(lib, "User32") + #pragma warning( push ) + #pragma warning( disable : 4996 4191 4127) + #if _MSC_VER < 600 + #define RGFW_C89 + #endif +#else + #if defined(__STDC__) && !defined(__STDC_VERSION__) + #define RGFW_C89 + #endif +#endif + +#ifndef RGFW_USERPTR + #define RGFW_USERPTR NULL +#endif + +#ifndef RGFW_UNUSED + #define RGFW_UNUSED(x) (void)(x) +#endif + +#ifndef RGFW_ROUND + #define RGFW_ROUND(x) (i32)((x) >= 0 ? (x) + 0.5f : (x) - 0.5f) +#endif + +#ifndef RGFW_ALLOC + #include + #define RGFW_ALLOC malloc + #define RGFW_FREE free +#endif + +#ifndef RGFW_ASSERT + #include + #define RGFW_ASSERT assert +#endif + +#if !defined(RGFW_MEMCPY) || !defined(RGFW_STRNCMP) || !defined(RGFW_STRNCPY) + #include +#endif + +#ifndef RGFW_MEMCPY + #define RGFW_MEMCPY(dist, src, len) memcpy(dist, src, len) +#endif + +#ifndef RGFW_STRNCMP + #define RGFW_STRNCMP(s1, s2, max) strncmp(s1, s2, max) +#endif + +#ifndef RGFW_STRNCPY + #define RGFW_STRNCPY(dist, src, len) strncpy(dist, src, len) +#endif + +#ifndef RGFW_STRSTR + #define RGFW_STRSTR(str, substr) strstr(str, substr) +#endif + +#ifndef RGFW_STRTOL + /* required for X11 XDnD and X11 Monitor DPI */ + #include + #define RGFW_STRTOL(str, endptr, base) strtol(str, endptr, base) + #define RGFW_ATOF(num) atof(num) +#endif + +#if !_MSC_VER + #ifndef inline + #ifndef __APPLE__ + #define inline __inline + #endif + #endif +#endif + +#ifdef RGFW_WIN95 /* for windows 95 testing (not that it really works) */ + #define RGFW_NO_MONITOR + #define RGFW_NO_PASSTHROUGH +#endif + +#if defined(RGFW_EXPORT) || defined(RGFW_IMPORT) + #if defined(_WIN32) + #if defined(__TINYC__) && (defined(RGFW_EXPORT) || defined(RGFW_IMPORT)) + #define __declspec(x) __attribute__((x)) + #endif + + #if defined(RGFW_EXPORT) + #define RGFWDEF __declspec(dllexport) + #else + #define RGFWDEF __declspec(dllimport) + #endif + #else + #if defined(RGFW_EXPORT) + #define RGFWDEF __attribute__((visibility("default"))) + #endif + #endif +#endif + +#ifndef RGFWDEF + #ifdef RGFW_C89 + #define RGFWDEF __inline + #else + #define RGFWDEF inline + #endif +#endif + +#ifndef RGFW_ENUM + #define RGFW_ENUM(type, name) type name; enum +#endif + + +#if defined(__cplusplus) && !defined(__EMSCRIPTEN__) + extern "C" { +#endif + + /* makes sure the header file part is only defined once by default */ +#ifndef RGFW_HEADER + +#define RGFW_HEADER + +#include +#ifndef RGFW_INT_DEFINED + #ifdef RGFW_USE_INT /* optional for any system that might not have stdint.h */ + typedef unsigned char u8; + typedef signed char i8; + typedef unsigned short u16; + typedef signed short i16; + typedef unsigned long int u32; + typedef signed long int i32; + typedef unsigned long long u64; + typedef signed long long i64; + #else /* use stdint standard types instead of c ""standard"" types */ + #include + + typedef uint8_t u8; + typedef int8_t i8; + typedef uint16_t u16; + typedef int16_t i16; + typedef uint32_t u32; + typedef int32_t i32; + typedef uint64_t u64; + typedef int64_t i64; + #endif + #define RGFW_INT_DEFINED +#endif + +#ifndef RGFW_BOOL_DEFINED + #define RGFW_BOOL_DEFINED + typedef u8 RGFW_bool; +#endif + +#define RGFW_BOOL(x) (RGFW_bool)((x) ? RGFW_TRUE : RGFW_FALSE) /* force an value to be 0 or 1 */ +#define RGFW_TRUE (RGFW_bool)1 +#define RGFW_FALSE (RGFW_bool)0 + +/* these OS macros look better & are standardized */ +/* plus it helps with cross-compiling */ + +#ifdef __EMSCRIPTEN__ + #define RGFW_WASM + + #if !defined(RGFW_NO_API) && !defined(RGFW_WEBGPU) + #define RGFW_OPENGL + #endif + + #ifdef RGFW_EGL + #undef RGFW_EGL + #endif + + #include + #include + + #ifdef RGFW_WEBGPU + #include + #endif +#endif + +#if defined(RGFW_X11) && defined(__APPLE__) && !defined(RGFW_CUSTOM_BACKEND) + #define RGFW_MACOS_X11 + #define RGFW_UNIX + #undef __APPLE__ +#endif + +#if defined(_WIN32) && !defined(RGFW_X11) && !defined(RGFW_UNIX) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) /* (if you're using X11 on windows some how) */ + #define RGFW_WINDOWS + /* make sure the correct architecture is defined */ + #if defined(_WIN64) + #define _AMD64_ + #undef _X86_ + #else + #undef _AMD64_ + #ifndef _X86_ + #define _X86_ + #endif + #endif + + #ifndef RGFW_NO_XINPUT + #ifdef __MINGW32__ /* try to find the right header */ + #include + #else + #include + #endif + #endif +#endif +#if defined(RGFW_WAYLAND) + #define RGFW_DEBUG /* wayland will be in debug mode by default for now */ + #if !defined(RGFW_NO_API) && (!defined(RGFW_BUFFER) || defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) + #define RGFW_EGL + #define RGFW_OPENGL + #define RGFW_UNIX + #include + #endif + + #include +#endif +#if !defined(RGFW_NO_X11) && !defined(RGFW_NO_X11) && (defined(__unix__) || defined(RGFW_MACOS_X11) || defined(RGFW_X11)) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) + #define RGFW_MACOS_X11 + #define RGFW_X11 + #define RGFW_UNIX + #include + #include +#elif defined(__APPLE__) && !defined(RGFW_MACOS_X11) && !defined(RGFW_X11) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) + #define RGFW_MACOS + #if !defined(RGFW_BUFFER_BGR) + #define RGFW_BUFFER_BGR + #else + #undef RGFW_BUFFER_BGR + #endif +#endif + +#if (defined(RGFW_OPENGL_ES1) || defined(RGFW_OPENGL_ES2) || defined(RGFW_OPENGL_ES3)) && !defined(RGFW_EGL) + #define RGFW_EGL +#endif + +#if !defined(RGFW_OSMESA) && !defined(RGFW_EGL) && !defined(RGFW_OPENGL) && !defined(RGFW_DIRECTX) && !defined(RGFW_BUFFER) && !defined(RGFW_NO_API) + #define RGFW_OPENGL +#endif + +#ifdef RGFW_EGL + #include +#elif defined(RGFW_OSMESA) + #ifdef RGFW_WINDOWS + #define OEMRESOURCE + #include + #ifndef GLAPIENTRY + #define GLAPIENTRY APIENTRY + #endif + #ifndef GLAPI + #define GLAPI WINGDIAPI + #endif + #endif + + #ifndef __APPLE__ + #include + #else + #include + #endif +#endif + +#if (defined(RGFW_OPENGL) || defined(RGFW_WEGL)) && defined(_MSC_VER) + #pragma comment(lib, "opengl32") +#endif + +#if defined(RGFW_OPENGL) && defined(RGFW_X11) + #ifndef GLX_MESA_swap_control + #define GLX_MESA_swap_control + #endif + #include /* GLX defs, xlib.h, gl.h */ +#endif + +#define RGFW_COCOA_FRAME_NAME NULL + +/*! (unix) Toggle use of wayland. This will be on by default if you use `RGFW_WAYLAND` (if you don't use RGFW_WAYLAND, you don't expose WAYLAND functions) + this is mostly used to allow you to force the use of XWayland +*/ +RGFWDEF void RGFW_useWayland(RGFW_bool wayland); +RGFWDEF RGFW_bool RGFW_usingWayland(void); +/* + regular RGFW stuff +*/ + +#define RGFW_key u8 + +typedef RGFW_ENUM(u8, RGFW_eventType) { + /*! event codes */ + RGFW_eventNone = 0, /*!< no event has been sent */ + RGFW_keyPressed, /* a key has been pressed */ + RGFW_keyReleased, /*!< a key has been released */ + /*! key event note + the code of the key pressed is stored in + RGFW_event.key + !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! + + while a string version is stored in + RGFW_event.KeyString + + RGFW_event.keyMod holds the current keyMod + this means if CapsLock, NumLock are active or not + */ + RGFW_mouseButtonPressed, /*!< a mouse button has been pressed (left,middle,right) */ + RGFW_mouseButtonReleased, /*!< a mouse button has been released (left,middle,right) */ + RGFW_mousePosChanged, /*!< the position of the mouse has been changed */ + /*! mouse event note + the x and y of the mouse can be found in the vector, RGFW_event.point + + RGFW_event.button holds which mouse button was pressed + */ + RGFW_gamepadConnected, /*!< a gamepad was connected */ + RGFW_gamepadDisconnected, /*!< a gamepad was disconnected */ + RGFW_gamepadButtonPressed, /*!< a gamepad button was pressed */ + RGFW_gamepadButtonReleased, /*!< a gamepad button was released */ + RGFW_gamepadAxisMove, /*!< an axis of a gamepad was moved */ + /*! gamepad event note + RGFW_event.gamepad holds which gamepad was altered, if any + RGFW_event.button holds which gamepad button was pressed + + RGFW_event.axis holds the data of all the axises + RGFW_event.axisesCount says how many axises there are + */ + RGFW_windowMoved, /*!< the window was moved (by the user) */ + RGFW_windowResized, /*!< the window was resized (by the user), [on WASM this means the browser was resized] */ + RGFW_focusIn, /*!< window is in focus now */ + RGFW_focusOut, /*!< window is out of focus now */ + RGFW_mouseEnter, /* mouse entered the window */ + RGFW_mouseLeave, /* mouse left the window */ + RGFW_windowRefresh, /* The window content needs to be refreshed */ + + /* attribs change event note + The event data is sent straight to the window structure + with win->r.x, win->r.y, win->r.w and win->r.h + */ + RGFW_quit, /*!< the user clicked the quit button */ + RGFW_DND, /*!< a file has been dropped into the window */ + RGFW_DNDInit, /*!< the start of a dnd event, when the place where the file drop is known */ + /* dnd data note + The x and y coords of the drop are stored in the vector RGFW_event.point + + RGFW_event.droppedFilesCount holds how many files were dropped + + This is also the size of the array which stores all the dropped file string, + RGFW_event.droppedFiles + */ + RGFW_windowMaximized, /*!< the window was maximized */ + RGFW_windowMinimized, /*!< the window was minimized */ + RGFW_windowRestored, /*!< the window was restored */ + RGFW_scaleUpdated /*!< content scale factor changed */ +}; + +/*! mouse button codes (RGFW_event.button) */ +typedef RGFW_ENUM(u8, RGFW_mouseButton) { + RGFW_mouseLeft = 0, /*!< left mouse button is pressed */ + RGFW_mouseMiddle, /*!< mouse-wheel-button is pressed */ + RGFW_mouseRight, /*!< right mouse button is pressed */ + RGFW_mouseScrollUp, /*!< mouse wheel is scrolling up */ + RGFW_mouseScrollDown, /*!< mouse wheel is scrolling down */ + RGFW_mouseMisc1, RGFW_mouseMisc2, RGFW_mouseMisc3, RGFW_mouseMisc4, RGFW_mouseMisc5, + RGFW_mouseFinal +}; + +#ifndef RGFW_MAX_PATH +#define RGFW_MAX_PATH 260 /* max length of a path (for dnd) */ +#endif +#ifndef RGFW_MAX_DROPS +#define RGFW_MAX_DROPS 260 /* max items you can drop at once */ +#endif + +#define RGFW_BIT(x) (1 << x) + +/* for RGFW_event.lockstate */ +typedef RGFW_ENUM(u8, RGFW_keymod) { + RGFW_modCapsLock = RGFW_BIT(0), + RGFW_modNumLock = RGFW_BIT(1), + RGFW_modControl = RGFW_BIT(2), + RGFW_modAlt = RGFW_BIT(3), + RGFW_modShift = RGFW_BIT(4), + RGFW_modSuper = RGFW_BIT(5), + RGFW_modScrollLock = RGFW_BIT(6) +}; + +/*! gamepad button codes (based on xbox/playstation), you may need to change these values per controller */ +typedef RGFW_ENUM(u8, RGFW_gamepadCodes) { + RGFW_gamepadNone = 0, /*!< or PS X button */ + RGFW_gamepadA, /*!< or PS X button */ + RGFW_gamepadB, /*!< or PS circle button */ + RGFW_gamepadY, /*!< or PS triangle button */ + RGFW_gamepadX, /*!< or PS square button */ + RGFW_gamepadStart, /*!< start button */ + RGFW_gamepadSelect, /*!< select button */ + RGFW_gamepadHome, /*!< home button */ + RGFW_gamepadUp, /*!< dpad up */ + RGFW_gamepadDown, /*!< dpad down */ + RGFW_gamepadLeft, /*!< dpad left */ + RGFW_gamepadRight, /*!< dpad right */ + RGFW_gamepadL1, /*!< left bump */ + RGFW_gamepadL2, /*!< left trigger */ + RGFW_gamepadR1, /*!< right bumper */ + RGFW_gamepadR2, /*!< right trigger */ + RGFW_gamepadL3, /* left thumb stick */ + RGFW_gamepadR3, /*!< right thumb stick */ + RGFW_gamepadFinal +}; + +/*! basic vector type, if there's not already a point/vector type of choice */ +#ifndef RGFW_point + typedef struct { i32 x, y; } RGFW_point; +#endif + +/*! basic rect type, if there's not already a rect type of choice */ +#ifndef RGFW_rect + typedef struct { i32 x, y, w, h; } RGFW_rect; +#endif + +/*! basic area type, if there's not already a area type of choice */ +#ifndef RGFW_area + typedef struct { u32 w, h; } RGFW_area; +#endif + +#if defined(__cplusplus) && !defined(__APPLE__) +#define RGFW_POINT(x, y) {(i32)x, (i32)y} +#define RGFW_RECT(x, y, w, h) {(i32)x, (i32)y, (i32)w, (i32)h} +#define RGFW_AREA(w, h) {(u32)w, (u32)h} +#else +#define RGFW_POINT(x, y) (RGFW_point){(i32)(x), (i32)(y)} +#define RGFW_RECT(x, y, w, h) (RGFW_rect){(i32)(x), (i32)(y), (i32)(w), (i32)(h)} +#define RGFW_AREA(w, h) (RGFW_area){(u32)(w), (u32)(h)} +#endif + +#ifndef RGFW_NO_MONITOR + /* monitor mode data | can be changed by the user (with functions)*/ + typedef struct RGFW_monitorMode { + RGFW_area area; /*!< monitor workarea size */ + u32 refreshRate; /*!< monitor refresh rate */ + u8 red, blue, green; + } RGFW_monitorMode; + + /*! structure for monitor data */ + typedef struct RGFW_monitor { + i32 x, y; /*!< x - y of the monitor workarea */ + char name[128]; /*!< monitor name */ + float scaleX, scaleY; /*!< monitor content scale */ + float pixelRatio; /*!< pixel ratio for monitor (1.0 for regular, 2.0 for hiDPI) */ + float physW, physH; /*!< monitor physical size in inches */ + + RGFW_monitorMode mode; + } RGFW_monitor; + + /*! get an array of all the monitors (max 6) */ + RGFWDEF RGFW_monitor* RGFW_getMonitors(size_t* len); + /*! get the primary monitor */ + RGFWDEF RGFW_monitor RGFW_getPrimaryMonitor(void); + + typedef RGFW_ENUM(u8, RGFW_modeRequest) { + RGFW_monitorScale = RGFW_BIT(0), /*!< scale the monitor size */ + RGFW_monitorRefresh = RGFW_BIT(1), /*!< change the refresh rate */ + RGFW_monitorRGB = RGFW_BIT(2), /*!< change the monitor RGB bits size */ + RGFW_monitorAll = RGFW_monitorScale | RGFW_monitorRefresh | RGFW_monitorRGB + }; + + /*! request a specific mode */ + RGFWDEF RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request); + /*! check if 2 monitor modes are the same */ + RGFWDEF RGFW_bool RGFW_monitorModeCompare(RGFW_monitorMode mon, RGFW_monitorMode mon2, RGFW_modeRequest request); +#endif + +/* RGFW mouse loading */ +typedef void RGFW_mouse; + +/*!< loads mouse icon from bitmap (similar to RGFW_window_setIcon). Icon NOT resized by default */ +RGFWDEF RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels); +/*!< frees RGFW_mouse data */ +RGFWDEF void RGFW_freeMouse(RGFW_mouse* mouse); + +/* NOTE: some parts of the data can represent different things based on the event (read comments in RGFW_event struct) */ +/*! Event structure for checking/getting events */ +typedef struct RGFW_event { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_point point; /*!< mouse x, y of event (or drop point) */ + RGFW_point vector; /*!< raw mouse movement */ + float scaleX, scaleY; /*!< DPI scaling */ + + RGFW_key key; /*!< the physical key of the event, refers to where key is physically !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! */ + u8 keyChar; /*!< mapped key char of the event */ + + RGFW_bool repeat; /*!< key press event repeated (the key is being held) */ + RGFW_keymod keyMod; + + u8 button; /* !< which mouse (or gamepad) button was pressed */ + double scroll; /*!< the raw mouse scroll value */ + + u16 gamepad; /*! which gamepad this event applies to (if applicable to any) */ + u8 axisesCount; /*!< number of axises */ + + u8 whichAxis; /* which axis was effected */ + RGFW_point axis[4]; /*!< x, y of axises (-100 to 100) */ + + /*! drag and drop data */ + /* 260 max paths with a max length of 260 */ + char** droppedFiles; /*!< dropped files */ + size_t droppedFilesCount; /*!< house many files were dropped */ + + void* _win; /*!< the window this event applies too (for event queue events) */ +} RGFW_event; + +/*! source data for the window (used by the APIs) */ +#ifdef RGFW_WINDOWS +typedef struct RGFW_window_src { + HWND window; /*!< source window */ + HDC hdc; /*!< source HDC */ + u32 hOffset; /*!< height offset for window */ + HICON hIconSmall, hIconBig; /*!< source window icons */ + #if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) + HGLRC ctx; /*!< source graphics context */ + #elif defined(RGFW_OSMESA) + OSMesaContext ctx; + #elif defined(RGFW_EGL) + EGLSurface EGL_surface; + EGLDisplay EGL_display; + EGLContext EGL_context; + #endif + + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + HDC hdcMem; + HBITMAP bitmap; + u8* bitmapBits; + #endif + RGFW_area maxSize, minSize, aspectRatio; /*!< for setting max/min resize (RGFW_WINDOWS) */ +} RGFW_window_src; +#elif defined(RGFW_UNIX) +typedef struct RGFW_window_src { +#if defined(RGFW_X11) + Display* display; /*!< source display */ + Window window; /*!< source window */ + #if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) + GLXContext ctx; /*!< source graphics context */ + GLXFBConfig bestFbc; + #elif defined(RGFW_OSMESA) + OSMesaContext ctx; + #elif defined(RGFW_EGL) + EGLSurface EGL_surface; + EGLDisplay EGL_display; + EGLContext EGL_context; + #endif + + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + XImage* bitmap; + #endif + GC gc; + XVisualInfo visual; + #ifdef RGFW_ADVANCED_SMOOTH_RESIZE + i64 counter_value; + XID counter; + #endif +#endif /* RGFW_X11 */ +#if defined(RGFW_WAYLAND) + struct wl_display* wl_display; + struct wl_surface* surface; + struct wl_buffer* wl_buffer; + struct wl_keyboard* keyboard; + + struct wl_compositor* compositor; + struct xdg_surface* xdg_surface; + struct xdg_toplevel* xdg_toplevel; + struct zxdg_toplevel_decoration_v1* decoration; + struct xdg_wm_base* xdg_wm_base; + struct wl_shm* shm; + struct wl_seat *seat; + u8* buffer; + #if defined(RGFW_EGL) + struct wl_egl_window* eglWindow; + #endif + #if defined(RGFW_EGL) && !defined(RGFW_X11) + EGLSurface EGL_surface; + EGLDisplay EGL_display; + EGLContext EGL_context; + #elif defined(RGFW_OSMESA) && !defined(RGFW_X11) + OSMesaContext ctx; + #endif +#endif /* RGFW_WAYLAND */ +} RGFW_window_src; +#endif /* RGFW_UNIX */ +#if defined(RGFW_MACOS) +typedef struct RGFW_window_src { + void* window; +#if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) + void* ctx; /*!< source graphics context */ +#elif defined(RGFW_OSMESA) + OSMesaContext ctx; +#elif defined(RGFW_EGL) + EGLSurface EGL_surface; + EGLDisplay EGL_display; + EGLContext EGL_context; +#endif + + void* view; /* apple viewpoint thingy */ + void* mouse; +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) +#endif +} RGFW_window_src; +#elif defined(RGFW_WASM) +typedef struct RGFW_window_src { + #if defined(RGFW_WEBGPU) + WGPUInstance ctx; + WGPUDevice device; + WGPUQueue queue; + #elif defined(RGFW_OSMESA) + OSMesaContext ctx; + #else + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx; + #endif +} RGFW_window_src; +#endif + +/*! Optional arguments for making a windows */ +typedef RGFW_ENUM(u32, RGFW_windowFlags) { + RGFW_windowNoInitAPI = RGFW_BIT(0), /* do NOT init an API (including the software rendering buffer) (mostly for bindings. you can also use `#define RGFW_NO_API`) */ + RGFW_windowNoBorder = RGFW_BIT(1), /*!< the window doesn't have a border */ + RGFW_windowNoResize = RGFW_BIT(2), /*!< the window cannot be resized by the user */ + RGFW_windowAllowDND = RGFW_BIT(3), /*!< the window supports drag and drop */ + RGFW_windowHideMouse = RGFW_BIT(4), /*! the window should hide the mouse (can be toggled later on using `RGFW_window_mouseShow`) */ + RGFW_windowFullscreen = RGFW_BIT(5), /*!< the window is fullscreen by default */ + RGFW_windowTransparent = RGFW_BIT(6), /*!< the window is transparent (only properly works on X11 and MacOS, although it's meant for for windows) */ + RGFW_windowCenter = RGFW_BIT(7), /*! center the window on the screen */ + RGFW_windowOpenglSoftware = RGFW_BIT(8), /*! use OpenGL software rendering */ + RGFW_windowCocoaCHDirToRes = RGFW_BIT(9), /*! (cocoa only), change directory to resource folder */ + RGFW_windowScaleToMonitor = RGFW_BIT(10), /*! scale the window to the screen */ + RGFW_windowHide = RGFW_BIT(11), /*! the window is hidden */ + RGFW_windowMaximize = RGFW_BIT(12), + RGFW_windowCenterCursor = RGFW_BIT(13), + RGFW_windowFloating = RGFW_BIT(14), /*!< create a floating window */ + RGFW_windowFreeOnClose = RGFW_BIT(15), /*!< free (RGFW_window_close) the RGFW_window struct when the window is closed (by the end user) */ + RGFW_windowFocusOnShow = RGFW_BIT(16), /*!< focus the window when it's shown */ + RGFW_windowMinimize = RGFW_BIT(17), /*!< focus the window when it's shown */ + RGFW_windowFocus = RGFW_BIT(18), /*!< if the window is in focus */ + RGFW_windowedFullscreen = RGFW_windowNoBorder | RGFW_windowMaximize +}; + +typedef struct RGFW_window { + RGFW_window_src src; /*!< src window data */ + +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + u8* buffer; /*!< buffer for non-GPU systems (OSMesa, basic software rendering) */ + /* when rendering using RGFW_BUFFER, the buffer is in the RGBA format */ + RGFW_area bufferSize; +#endif + void* userPtr; /* ptr for usr data */ + + RGFW_event event; /*!< current event */ + + RGFW_rect r; /*!< the x, y, w and h of the struct */ + + RGFW_point _lastMousePoint; /*!< last cusor point (for raw mouse data) */ + + u32 _flags; /*!< windows flags (for RGFW to check) */ + RGFW_rect _oldRect; /*!< rect before fullscreen */ +} RGFW_window; /*!< window structure for managing the window */ + +#if defined(RGFW_X11) || defined(RGFW_MACOS) + typedef u64 RGFW_thread; /*!< thread type unix */ +#else + typedef void* RGFW_thread; /*!< thread type for windows */ +#endif + +/*! scale monitor to window size */ +RGFWDEF RGFW_bool RGFW_monitor_scaleToWindow(RGFW_monitor mon, RGFW_window* win); + +/** * @defgroup Window_management +* @{ */ + + +/*! + * the class name for X11 and WinAPI. apps with the same class will be grouped by the WM + * by default the class name will == the root window's name +*/ +RGFWDEF void RGFW_setClassName(const char* name); +RGFWDEF void RGFW_setXInstName(const char* name); /*!< X11 instance name (window name will by used by default) */ + +/*! (cocoa only) change directory to resource folder */ +RGFWDEF void RGFW_moveToMacOSResourceDir(void); + +/* NOTE: (windows) if the executable has an icon resource named RGFW_ICON, it will be set as the initial icon for the window */ + +RGFWDEF RGFW_window* RGFW_createWindow( + const char* name, /* name of the window */ + RGFW_rect rect, /* rect of window */ + RGFW_windowFlags flags /* extra arguments ((u32)0 means no flags used)*/ +); /*!< function to create a window and struct */ + +RGFWDEF RGFW_window* RGFW_createWindowPtr( + const char* name, /* name of the window */ + RGFW_rect rect, /* rect of window */ + RGFW_windowFlags flags, /* extra arguments (NULL / (u32)0 means no flags used) */ + RGFW_window* win /* ptr to the window struct you want to use */ +); /*!< function to create a window (without allocating a window struct) */ + +RGFWDEF void RGFW_window_initBuffer(RGFW_window* win); +RGFWDEF void RGFW_window_initBufferSize(RGFW_window* win, RGFW_area area); +RGFWDEF void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area); + +/*! set the window flags (will undo flags if they don't match the old ones) */ +RGFWDEF void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags); + +/*! get the size of the screen to an area struct */ +RGFWDEF RGFW_area RGFW_getScreenSize(void); + + +/*! + this function checks an *individual* event (and updates window structure attributes) + this means, using this function without a while loop may cause event lag + + ex. + + while (RGFW_window_checkEvent(win) != NULL) [this keeps checking events until it reaches the last one] + + this function is optional if you choose to use event callbacks, + although you still need some way to tell RGFW to process events eg. `RGFW_window_checkEvents` +*/ + +RGFWDEF RGFW_event* RGFW_window_checkEvent(RGFW_window* win); /*!< check current event (returns a pointer to win->event or NULL if there is no event)*/ + +/*! + for RGFW_window_eventWait and RGFW_window_checkEvents + waitMS -> Allows the function to keep checking for events even after `RGFW_window_checkEvent == NULL` + if waitMS == 0, the loop will not wait for events + if waitMS > 0, the loop will wait that many miliseconds after there are no more events until it returns + if waitMS == -1 or waitMS == the max size of an unsigned 32-bit int, the loop will not return until it gets another event +*/ +typedef RGFW_ENUM(i32, RGFW_eventWait) { + RGFW_eventNoWait = 0, + RGFW_eventWaitNext = -1 +}; + +/*! sleep until RGFW gets an event or the timer ends (defined by OS) */ +RGFWDEF void RGFW_window_eventWait(RGFW_window* win, i32 waitMS); + +/*! + check all the events until there are none left. + This should only be used if you're using callbacks only +*/ +RGFWDEF void RGFW_window_checkEvents(RGFW_window* win, i32 waitMS); + +/*! + tell RGFW_window_eventWait to stop waiting (to be ran from another thread) +*/ +RGFWDEF void RGFW_stopCheckEvents(void); + +/*! window managment functions */ +RGFWDEF void RGFW_window_close(RGFW_window* win); /*!< close the window and free leftover data */ + +/*! move a window to a given point */ +RGFWDEF void RGFW_window_move(RGFW_window* win, + RGFW_point v /*!< new pos */ +); + +#ifndef RGFW_NO_MONITOR + /*! move window to a specific monitor */ + RGFWDEF void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor m /* monitor */); +#endif + +/*! resize window to a current size/area */ +RGFWDEF void RGFW_window_resize(RGFW_window* win, /*!< source window */ + RGFW_area a /*!< new size */ +); + +/*! set window aspect ratio */ +RGFWDEF void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a); +/*! set the minimum dimensions of a window */ +RGFWDEF void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a); +/*! set the maximum dimensions of a window */ +RGFWDEF void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a); + +RGFWDEF void RGFW_window_focus(RGFW_window* win); /*!< sets the focus to this window */ +RGFWDEF RGFW_bool RGFW_window_isInFocus(RGFW_window* win); /*!< checks the focus to this window */ +RGFWDEF void RGFW_window_raise(RGFW_window* win); /*!< raise the window (to the top) */ +RGFWDEF void RGFW_window_maximize(RGFW_window* win); /*!< maximize the window */ +RGFWDEF void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen); /*!< turn fullscreen on / off for a window */ +RGFWDEF void RGFW_window_center(RGFW_window* win); /*!< center the window */ +RGFWDEF void RGFW_window_minimize(RGFW_window* win); /*!< minimize the window (in taskbar (per OS))*/ +RGFWDEF void RGFW_window_restore(RGFW_window* win); /*!< restore the window from minimized (per OS)*/ +RGFWDEF void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating); /*!< make the window a floating window */ +RGFWDEF void RGFW_window_setOpacity(RGFW_window* win, u8 opacity); /*!< sets the opacity of a window */ + +/*! if the window should have a border or not (borderless) based on bool value of `border` */ +RGFWDEF void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border); +RGFWDEF RGFW_bool RGFW_window_borderless(RGFW_window* win); + +/*! turn on / off dnd (RGFW_windowAllowDND stil must be passed to the window)*/ +RGFWDEF void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow); +/*! check if DND is allowed */ +RGFWDEF RGFW_bool RGFW_window_allowsDND(RGFW_window* win); + + +#ifndef RGFW_NO_PASSTHROUGH + /*! turn on / off mouse passthrough */ + RGFWDEF void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough); +#endif + +/*! rename window to a given string */ +RGFWDEF void RGFW_window_setName(RGFW_window* win, + const char* name +); + +RGFWDEF RGFW_bool RGFW_window_setIcon(RGFW_window* win, /*!< source window */ + u8* icon /*!< icon bitmap */, + RGFW_area a /*!< width and height of the bitmap */, + i32 channels /*!< how many channels the bitmap has (rgb : 3, rgba : 4) */ +); /*!< image MAY be resized by default, set both the taskbar and window icon */ + +typedef RGFW_ENUM(u8, RGFW_icon) { + RGFW_iconTaskbar = RGFW_BIT(0), + RGFW_iconWindow = RGFW_BIT(1), + RGFW_iconBoth = RGFW_iconTaskbar | RGFW_iconWindow +}; +RGFWDEF RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* icon, RGFW_area a, i32 channels, u8 type); + +/*!< sets mouse to RGFW_mouse icon (loaded from a bitmap struct) */ +RGFWDEF void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse); + +/*!< sets the mouse to a standard API cursor (based on RGFW_MOUSE, as seen at the end of the RGFW_HEADER part of this file) */ +RGFWDEF RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse); + +RGFWDEF RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win); /*!< sets the mouse to the default mouse icon */ +/* + Locks cursor at the center of the window + win->event.point becomes raw mouse movement data + + this is useful for a 3D camera +*/ +RGFWDEF void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area); +/*! stop holding the mouse and let it move freely */ +RGFWDEF void RGFW_window_mouseUnhold(RGFW_window* win); + +/*! hide the window */ +RGFWDEF void RGFW_window_hide(RGFW_window* win); +/*! show the window */ +RGFWDEF void RGFW_window_show(RGFW_window* win); + +/* + makes it so `RGFW_window_shouldClose` returns true or overrides a window close + by modifying window flags +*/ +RGFWDEF void RGFW_window_setShouldClose(RGFW_window* win, RGFW_bool shouldClose); + +/*! where the mouse is on the screen */ +RGFWDEF RGFW_point RGFW_getGlobalMousePoint(void); + +/*! where the mouse is on the window */ +RGFWDEF RGFW_point RGFW_window_getMousePoint(RGFW_window* win); + +/*! show the mouse or hide the mouse */ +RGFWDEF void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show); +/*! if the mouse is hidden */ +RGFWDEF RGFW_bool RGFW_window_mouseHidden(RGFW_window* win); +/*! move the mouse to a given point */ +RGFWDEF void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v); + +/*! if the window should close (RGFW_close was sent or escape was pressed) */ +RGFWDEF RGFW_bool RGFW_window_shouldClose(RGFW_window* win); +/*! if the window is fullscreen */ +RGFWDEF RGFW_bool RGFW_window_isFullscreen(RGFW_window* win); +/*! if the window is hidden */ +RGFWDEF RGFW_bool RGFW_window_isHidden(RGFW_window* win); +/*! if the window is minimized */ +RGFWDEF RGFW_bool RGFW_window_isMinimized(RGFW_window* win); +/*! if the window is maximized */ +RGFWDEF RGFW_bool RGFW_window_isMaximized(RGFW_window* win); +/*! if the window is floating */ +RGFWDEF RGFW_bool RGFW_window_isFloating(RGFW_window* win); +/** @} */ + +/** * @defgroup Monitor +* @{ */ + +#ifndef RGFW_NO_MONITOR +/* + scale the window to the monitor. + This is run by default if the user uses the arg `RGFW_scaleToMonitor` during window creation +*/ +RGFWDEF void RGFW_window_scaleToMonitor(RGFW_window* win); +/*! get the struct of the window's monitor */ +RGFWDEF RGFW_monitor RGFW_window_getMonitor(RGFW_window* win); +#endif + +/** @} */ + +/** * @defgroup Input +* @{ */ + +/*! if window == NULL, it checks if the key is pressed globally. Otherwise, it checks only if the key is pressed while the window in focus. */ +RGFWDEF RGFW_bool RGFW_isPressed(RGFW_window* win, RGFW_key key); /*!< if key is pressed (key code)*/ + +RGFWDEF RGFW_bool RGFW_wasPressed(RGFW_window* win, RGFW_key key); /*!< if key was pressed (checks previous state only) (key code) */ + +RGFWDEF RGFW_bool RGFW_isHeld(RGFW_window* win, RGFW_key key); /*!< if key is held (key code) */ +RGFWDEF RGFW_bool RGFW_isReleased(RGFW_window* win, RGFW_key key); /*!< if key is released (key code) */ + +/* if a key is pressed and then released, pretty much the same as RGFW_isReleased */ +RGFWDEF RGFW_bool RGFW_isClicked(RGFW_window* win, RGFW_key key /*!< key code */); + +/*! if a mouse button is pressed */ +RGFWDEF RGFW_bool RGFW_isMousePressed(RGFW_window* win, RGFW_mouseButton button /*!< mouse button code */ ); +/*! if a mouse button is held */ +RGFWDEF RGFW_bool RGFW_isMouseHeld(RGFW_window* win, RGFW_mouseButton button /*!< mouse button code */ ); +/*! if a mouse button was released */ +RGFWDEF RGFW_bool RGFW_isMouseReleased(RGFW_window* win, RGFW_mouseButton button /*!< mouse button code */ ); +/*! if a mouse button was pressed (checks previous state only) */ +RGFWDEF RGFW_bool RGFW_wasMousePressed(RGFW_window* win, RGFW_mouseButton button /*!< mouse button code */ ); +/** @} */ + +/** * @defgroup Clipboard +* @{ */ +typedef ptrdiff_t RGFW_ssize_t; + +RGFWDEF const char* RGFW_readClipboard(size_t* size); /*!< read clipboard data */ +/*! read clipboard data or send a NULL str to just get the length of the clipboard data */ +RGFWDEF RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity); +RGFWDEF void RGFW_writeClipboard(const char* text, u32 textLen); /*!< write text to the clipboard */ +/** @} */ + + + +/** * @defgroup error handling +* @{ */ +typedef RGFW_ENUM(u8, RGFW_debugType) { + RGFW_typeError = 0, RGFW_typeWarning, RGFW_typeInfo +}; + +typedef RGFW_ENUM(u8, RGFW_errorCode) { + RGFW_noError = 0, /*!< no error */ + RGFW_errOpenglContext, RGFW_errEGLContext, /*!< error with the OpenGL context */ + RGFW_errWayland, + RGFW_errDirectXContext, + RGFW_errIOKit, + RGFW_errClipboard, + RGFW_errFailedFuncLoad, + RGFW_errBuffer, + RGFW_infoMonitor, RGFW_infoWindow, RGFW_infoBuffer, RGFW_infoGlobal, RGFW_infoOpenGL, + RGFW_warningWayland, RGFW_warningOpenGL +}; + +typedef struct RGFW_debugContext { RGFW_window* win; RGFW_monitor monitor; u32 srcError; } RGFW_debugContext; + +#if defined(__cplusplus) && !defined(__APPLE__) +#define RGFW_DEBUG_CTX(win, err) {win, { 0 }, err} +#define RGFW_DEBUG_CTX_MON(monitor) {_RGFW.root, monitor, 0} +#else +#define RGFW_DEBUG_CTX(win, err) (RGFW_debugContext){win, (RGFW_monitor){ 0 }, err} +#define RGFW_DEBUG_CTX_MON(monitor) (RGFW_debugContext){_RGFW.root, monitor, 0} +#endif + +typedef void (* RGFW_debugfunc)(RGFW_debugType type, RGFW_errorCode err, RGFW_debugContext ctx, const char* msg); +RGFWDEF RGFW_debugfunc RGFW_setDebugCallback(RGFW_debugfunc func); +RGFWDEF void RGFW_sendDebugInfo(RGFW_debugType type, RGFW_errorCode err, RGFW_debugContext ctx, const char* msg); +/** @} */ + +/** + + + event callbacks. + These are completely optional, so you can use the normal + RGFW_checkEvent() method if you prefer that + +* @defgroup Callbacks +* @{ +*/ + +/*! RGFW_windowMoved, the window and its new rect value */ +typedef void (* RGFW_windowMovedfunc)(RGFW_window* win, RGFW_rect r); +/*! RGFW_windowResized, the window and its new rect value */ +typedef void (* RGFW_windowResizedfunc)(RGFW_window* win, RGFW_rect r); +/*! RGFW_windowRestored, the window and its new rect value */ +typedef void (* RGFW_windowRestoredfunc)(RGFW_window* win, RGFW_rect r); +/*! RGFW_windowMaximized, the window and its new rect value */ +typedef void (* RGFW_windowMaximizedfunc)(RGFW_window* win, RGFW_rect r); +/*! RGFW_windowMinimized, the window and its new rect value */ +typedef void (* RGFW_windowMinimizedfunc)(RGFW_window* win, RGFW_rect r); +/*! RGFW_quit, the window that was closed */ +typedef void (* RGFW_windowQuitfunc)(RGFW_window* win); +/*! RGFW_focusIn / RGFW_focusOut, the window who's focus has changed and if its in focus */ +typedef void (* RGFW_focusfunc)(RGFW_window* win, RGFW_bool inFocus); +/*! RGFW_mouseEnter / RGFW_mouseLeave, the window that changed, the point of the mouse (enter only) and if the mouse has entered */ +typedef void (* RGFW_mouseNotifyfunc)(RGFW_window* win, RGFW_point point, RGFW_bool status); +/*! RGFW_mousePosChanged, the window that the move happened on, and the new point of the mouse */ +typedef void (* RGFW_mousePosfunc)(RGFW_window* win, RGFW_point point, RGFW_point vector); +/*! RGFW_DNDInit, the window, the point of the drop on the windows */ +typedef void (* RGFW_dndInitfunc)(RGFW_window* win, RGFW_point point); +/*! RGFW_windowRefresh, the window that needs to be refreshed */ +typedef void (* RGFW_windowRefreshfunc)(RGFW_window* win); +/*! RGFW_keyPressed / RGFW_keyReleased, the window that got the event, the mapped key, the physical key, the string version, the state of the mod keys, if it was a press (else it's a release) */ +typedef void (* RGFW_keyfunc)(RGFW_window* win, u8 key, u8 keyChar, RGFW_keymod keyMod, RGFW_bool pressed); +/*! RGFW_mouseButtonPressed / RGFW_mouseButtonReleased, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ +typedef void (* RGFW_mouseButtonfunc)(RGFW_window* win, RGFW_mouseButton button, double scroll, RGFW_bool pressed); +/*! RGFW_gamepadButtonPressed, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ +typedef void (* RGFW_gamepadButtonfunc)(RGFW_window* win, u16 gamepad, u8 button, RGFW_bool pressed); +/*! RGFW_gamepadAxisMove, the window that got the event, the gamepad in question, the axis values and the axis count */ +typedef void (* RGFW_gamepadAxisfunc)(RGFW_window* win, u16 gamepad, RGFW_point axis[2], u8 axisesCount, u8 whichAxis); +/*! RGFW_gamepadConnected / RGFW_gamepadDisconnected, the window that got the event, the gamepad in question, if the controller was connected (else it was disconnected) */ +typedef void (* RGFW_gamepadfunc)(RGFW_window* win, u16 gamepad, RGFW_bool connected); +/*! RGFW_dnd, the window that had the drop, the drop data and the number of files dropped */ +typedef void (* RGFW_dndfunc)(RGFW_window* win, char** droppedFiles, size_t droppedFilesCount); +/*! RGFW_scaleUpdated, the window the event was sent to, content scaleX, content scaleY */ +typedef void (* RGFW_scaleUpdatedfunc)(RGFW_window* win, float scaleX, float scaleY); + +/*! set callback for a window move event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_windowMovedfunc RGFW_setWindowMovedCallback(RGFW_windowMovedfunc func); +/*! set callback for a window resize event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_windowResizedfunc RGFW_setWindowResizedCallback(RGFW_windowResizedfunc func); +/*! set callback for a window quit event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_windowQuitfunc RGFW_setWindowQuitCallback(RGFW_windowQuitfunc func); +/*! set callback for a mouse move event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_mousePosfunc RGFW_setMousePosCallback(RGFW_mousePosfunc func); +/*! set callback for a window refresh event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_windowRefreshfunc RGFW_setWindowRefreshCallback(RGFW_windowRefreshfunc func); +/*! set callback for a window focus change event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_focusfunc RGFW_setFocusCallback(RGFW_focusfunc func); +/*! set callback for a mouse notify event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_mouseNotifyfunc RGFW_setMouseNotifyCallback(RGFW_mouseNotifyfunc func); +/*! set callback for a drop event event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_dndfunc RGFW_setDndCallback(RGFW_dndfunc func); +/*! set callback for a start of a drop event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_dndInitfunc RGFW_setDndInitCallback(RGFW_dndInitfunc func); +/*! set callback for a key (press / release) event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_keyfunc RGFW_setKeyCallback(RGFW_keyfunc func); +/*! set callback for a mouse button (press / release) event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_mouseButtonfunc RGFW_setMouseButtonCallback(RGFW_mouseButtonfunc func); +/*! set callback for a controller button (press / release) event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_gamepadButtonfunc RGFW_setGamepadButtonCallback(RGFW_gamepadButtonfunc func); +/*! set callback for a gamepad axis move event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_gamepadAxisfunc RGFW_setGamepadAxisCallback(RGFW_gamepadAxisfunc func); +/*! set callback for when a controller is connected or disconnected. Returns the previous callback function (if it was set) */ +RGFWDEF RGFW_gamepadfunc RGFW_setGamepadCallback(RGFW_gamepadfunc func); +/*! set call back for when window is maximized. Returns the previous callback function (if it was set) */ +RGFWDEF RGFW_windowResizedfunc RGFW_setWindowMaximizedCallback(RGFW_windowResizedfunc func); +/*! set call back for when window is minimized. Returns the previous callback function (if it was set) */ +RGFWDEF RGFW_windowResizedfunc RGFW_setWindowMinimizedCallback(RGFW_windowResizedfunc func); +/*! set call back for when window is restored. Returns the previous callback function (if it was set) */ +RGFWDEF RGFW_windowResizedfunc RGFW_setWindowRestoredCallback(RGFW_windowResizedfunc func); +/*! set callback for when the DPI changes. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_scaleUpdatedfunc RGFW_setScaleUpdatedCallback(RGFW_scaleUpdatedfunc func); +/** @} */ + +/** * @defgroup Threads +* @{ */ + +#ifndef RGFW_NO_THREADS +/*! threading functions */ + +/*! NOTE! (for X11/linux) : if you define a window in a thread, it must be run after the original thread's window is created or else there will be a memory error */ +/* + I'd suggest you use sili's threading functions instead + if you're going to use sili + which is a good idea generally +*/ + +#if defined(__unix__) || defined(__APPLE__) || defined(RGFW_WASM) || defined(RGFW_CUSTOM_BACKEND) + typedef void* (* RGFW_threadFunc_ptr)(void*); +#else + typedef DWORD (__stdcall *RGFW_threadFunc_ptr) (LPVOID lpThreadParameter); +#endif + +RGFWDEF RGFW_thread RGFW_createThread(RGFW_threadFunc_ptr ptr, void* args); /*!< create a thread */ +RGFWDEF void RGFW_cancelThread(RGFW_thread thread); /*!< cancels a thread */ +RGFWDEF void RGFW_joinThread(RGFW_thread thread); /*!< join thread to current thread */ +RGFWDEF void RGFW_setThreadPriority(RGFW_thread thread, u8 priority); /*!< sets the priority priority */ +#endif + +/** @} */ + +/** * @defgroup gamepad +* @{ */ + +typedef RGFW_ENUM(u8, RGFW_gamepadType) { + RGFW_gamepadMicrosoft = 0, RGFW_gamepadSony, RGFW_gamepadNintendo, RGFW_gamepadLogitech, RGFW_gamepadUnknown +}; + +/*! gamepad count starts at 0*/ +RGFWDEF u32 RGFW_isPressedGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button); +RGFWDEF u32 RGFW_isReleasedGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button); +RGFWDEF u32 RGFW_isHeldGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button); +RGFWDEF u32 RGFW_wasPressedGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button); +RGFWDEF RGFW_point RGFW_getGamepadAxis(RGFW_window* win, u16 controller, u16 whichAxis); +RGFWDEF const char* RGFW_getGamepadName(RGFW_window* win, u16 controller); +RGFWDEF size_t RGFW_getGamepadCount(RGFW_window* win); +RGFWDEF RGFW_gamepadType RGFW_getGamepadType(RGFW_window* win, u16 controller); + +/** @} */ + +/** * @defgroup graphics_API +* @{ */ + +/*!< make the window the current opengl drawing context + + NOTE: + if you want to switch the graphics context's thread, + you have to run RGFW_window_makeCurrent(NULL); on the old thread + then RGFW_window_makeCurrent(valid_window) on the new thread +*/ +RGFWDEF void RGFW_window_makeCurrent(RGFW_window* win); + +/*! get current RGFW window graphics context */ +RGFWDEF RGFW_window* RGFW_getCurrent(void); + +/* supports openGL, directX, OSMesa, EGL and software rendering */ +RGFWDEF void RGFW_window_swapBuffers(RGFW_window* win); /*!< swap the rendering buffer */ +RGFWDEF void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval); +/*!< render the software rendering buffer (this is called by RGFW_window_swapInterval) */ +RGFWDEF void RGFW_window_swapBuffers_software(RGFW_window* win); + +typedef void (*RGFW_proc)(void); /* function pointer equivalent of void* */ + +/*! native API functions */ +#if defined(RGFW_OPENGL) || defined(RGFW_EGL) +/*!< create an opengl context for the RGFW window, run by createWindow by default (unless the RGFW_windowNoInitAPI is included) */ +RGFWDEF void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software); +/*!< called by `RGFW_window_close` by default (unless the RGFW_windowNoInitAPI is set) */ +RGFWDEF void RGFW_window_freeOpenGL(RGFW_window* win); + +/*! OpenGL init hints */ +typedef RGFW_ENUM(u8, RGFW_glHints) { + RGFW_glStencil = 0, /*!< set stencil buffer bit size (8 by default) */ + RGFW_glSamples, /*!< set number of sampiling buffers (4 by default) */ + RGFW_glStereo, /*!< use GL_STEREO (GL_FALSE by default) */ + RGFW_glAuxBuffers, /*!< number of aux buffers (0 by default) */ + RGFW_glDoubleBuffer, /*!< request double buffering */ + RGFW_glRed, RGFW_glGreen, RGFW_glBlue, RGFW_glAlpha, /*!< set RGBA bit sizes */ + RGFW_glDepth, + RGFW_glAccumRed, RGFW_glAccumGreen, RGFW_glAccumBlue,RGFW_glAccumAlpha, /*!< set accumulated RGBA bit sizes */ + RGFW_glSRGB, /*!< request sRGA */ + RGFW_glRobustness, /*!< request a robust context */ + RGFW_glDebug, /*!< request opengl debugging */ + RGFW_glNoError, /*!< request no opengl errors */ + RGFW_glReleaseBehavior, + RGFW_glProfile, + RGFW_glMajor, RGFW_glMinor, + RGFW_glFinalHint = 32, /*!< the final hint (not for setting) */ + RGFW_releaseFlush = 0, RGFW_glReleaseNone, /* RGFW_glReleaseBehavior options */ + RGFW_glCore = 0, RGFW_glCompatibility /*!< RGFW_glProfile options */ +}; +RGFWDEF void RGFW_setGLHint(RGFW_glHints hint, i32 value); +RGFWDEF RGFW_proc RGFW_getProcAddress(const char* procname); /*!< get native opengl proc address */ +RGFWDEF void RGFW_window_makeCurrent_OpenGL(RGFW_window* win); /*!< to be called by RGFW_window_makeCurrent */ +RGFWDEF void RGFW_window_swapBuffers_OpenGL(RGFW_window* win); /*!< swap opengl buffer (only) called by RGFW_window_swapInterval */ +void* RGFW_getCurrent_OpenGL(void); /*!< get the current context (OpenGL backend (GLX) (WGL) (EGL) (cocoa) (webgl))*/ +#endif +#ifdef RGFW_VULKAN + #if defined(RGFW_WAYLAND) && defined(RGFW_X11) + #define VK_USE_PLATFORM_WAYLAND_KHR + #define VK_USE_PLATFORM_XLIB_KHR + #define RGFW_VK_SURFACE ((RGFW_usingWayland()) ? ("VK_KHR_wayland_surface") : ("VK_KHR_xlib_surface")) + #elif defined(RGFW_WAYLAND) + #define VK_USE_PLATFORM_WAYLAND_KHR + #define VK_USE_PLATFORM_XLIB_KHR + #define RGFW_VK_SURFACE "VK_KHR_wayland_surface" + #elif defined(RGFW_X11) + #define VK_USE_PLATFORM_XLIB_KHR + #define RGFW_VK_SURFACE "VK_KHR_xlib_surface" + #elif defined(RGFW_WINDOWS) + #define VK_USE_PLATFORM_WIN32_KHR + #define OEMRESOURCE + #define RGFW_VK_SURFACE "VK_KHR_win32_surface" + #elif defined(RGFW_MACOS) && !defined(RGFW_MACOS_X11) + #define VK_USE_PLATFORM_MACOS_MVK + #define RGFW_VK_SURFACE "VK_MVK_macos_surface" + #else + #define RGFW_VK_SURFACE NULL + #endif + +/* if you don't want to use the above macros */ +RGFWDEF const char** RGFW_getVKRequiredInstanceExtensions(size_t* count); /*!< gets (static) extension array (and size (which will be 2)) */ + +#include + +RGFWDEF VkResult RGFW_window_createVKSurface(RGFW_window* win, VkInstance instance, VkSurfaceKHR* surface); +RGFWDEF RGFW_bool RGFW_getVKPresentationSupport(VkInstance instance, VkPhysicalDevice physicalDevice, u32 queueFamilyIndex); +#endif +#ifdef RGFW_DIRECTX +#ifndef RGFW_WINDOWS + #undef RGFW_DIRECTX +#else + #define OEMRESOURCE + #include + + #ifndef __cplusplus + #define __uuidof(T) IID_##T + #endif +RGFWDEF int RGFW_window_createDXSwapChain(RGFW_window* win, IDXGIFactory* pFactory, IUnknown* pDevice, IDXGISwapChain** swapchain); +#endif +#endif + +/** @} */ + +/** * @defgroup Supporting +* @{ */ + +/*! optional init/deinit function */ +RGFWDEF i32 RGFW_init(void); /*!< is called by default when the first window is created by default */ +RGFWDEF void RGFW_deinit(void); /*!< is called by default when the last open window is closed */ + +RGFWDEF double RGFW_getTime(void); /*!< get time in seconds since RGFW_setTime, which ran when the first window is open */ +RGFWDEF u64 RGFW_getTimeNS(void); /*!< get time in nanoseconds RGFW_setTime, which ran when the first window is open */ +RGFWDEF void RGFW_sleep(u64 milisecond); /*!< sleep for a set time */ +RGFWDEF void RGFW_setTime(double time); /*!< set timer in seconds */ +RGFWDEF u64 RGFW_getTimerValue(void); /*!< get API timer value */ +RGFWDEF u64 RGFW_getTimerFreq(void); /*!< get API time freq */ + +/*< updates fps / sets fps to cap (must by ran manually by the user at the end of a frame), returns current fps */ +RGFWDEF u32 RGFW_checkFPS(double startTime, u32 frameCount, u32 fpsCap); + +/*!< change which window is the root window */ +RGFWDEF void RGFW_setRootWindow(RGFW_window* win); +RGFWDEF RGFW_window* RGFW_getRootWindow(void); + +/*! standard event queue, used for injecting events and returning source API callback events like any other queue check */ +/* these are all used internally by RGFW */ +void RGFW_eventQueuePush(RGFW_event event); +RGFW_event* RGFW_eventQueuePop(RGFW_window* win); + +/*! + key codes and mouse icon enums +*/ +#undef RGFW_key +typedef RGFW_ENUM(u8, RGFW_key) { + RGFW_keyNULL = 0, + RGFW_escape = '\033', + RGFW_backtick = '`', + RGFW_0 = '0', + RGFW_1 = '1', + RGFW_2 = '2', + RGFW_3 = '3', + RGFW_4 = '4', + RGFW_5 = '5', + RGFW_6 = '6', + RGFW_7 = '7', + RGFW_8 = '8', + RGFW_9 = '9', + + RGFW_minus = '-', + RGFW_equals = '=', + RGFW_backSpace = '\b', + RGFW_tab = '\t', + RGFW_space = ' ', + + RGFW_a = 'a', + RGFW_b = 'b', + RGFW_c = 'c', + RGFW_d = 'd', + RGFW_e = 'e', + RGFW_f = 'f', + RGFW_g = 'g', + RGFW_h = 'h', + RGFW_i = 'i', + RGFW_j = 'j', + RGFW_k = 'k', + RGFW_l = 'l', + RGFW_m = 'm', + RGFW_n = 'n', + RGFW_o = 'o', + RGFW_p = 'p', + RGFW_q = 'q', + RGFW_r = 'r', + RGFW_s = 's', + RGFW_t = 't', + RGFW_u = 'u', + RGFW_v = 'v', + RGFW_w = 'w', + RGFW_x = 'x', + RGFW_y = 'y', + RGFW_z = 'z', + + RGFW_period = '.', + RGFW_comma = ',', + RGFW_slash = '/', + RGFW_bracket = '{', + RGFW_closeBracket = '}', + RGFW_semicolon = ';', + RGFW_apostrophe = '\'', + RGFW_backSlash = '\\', + RGFW_return = '\n', + + RGFW_delete = '\177', /* 127 */ + + RGFW_F1, + RGFW_F2, + RGFW_F3, + RGFW_F4, + RGFW_F5, + RGFW_F6, + RGFW_F7, + RGFW_F8, + RGFW_F9, + RGFW_F10, + RGFW_F11, + RGFW_F12, + + RGFW_capsLock, + RGFW_shiftL, + RGFW_controlL, + RGFW_altL, + RGFW_superL, + RGFW_shiftR, + RGFW_controlR, + RGFW_altR, + RGFW_superR, + RGFW_up, + RGFW_down, + RGFW_left, + RGFW_right, + + RGFW_insert, + RGFW_end, + RGFW_home, + RGFW_pageUp, + RGFW_pageDown, + + RGFW_numLock, + RGFW_KP_Slash, + RGFW_multiply, + RGFW_KP_Minus, + RGFW_KP_1, + RGFW_KP_2, + RGFW_KP_3, + RGFW_KP_4, + RGFW_KP_5, + RGFW_KP_6, + RGFW_KP_7, + RGFW_KP_8, + RGFW_KP_9, + RGFW_KP_0, + RGFW_KP_Period, + RGFW_KP_Return, + RGFW_scrollLock, + RGFW_keyLast = 256 /* padding for alignment ~(175 by default) */ + }; + +RGFWDEF u32 RGFW_apiKeyToRGFW(u32 keycode); + +typedef RGFW_ENUM(u8, RGFW_mouseIcons) { + RGFW_mouseNormal = 0, + RGFW_mouseArrow, + RGFW_mouseIbeam, + RGFW_mouseCrosshair, + RGFW_mousePointingHand, + RGFW_mouseResizeEW, + RGFW_mouseResizeNS, + RGFW_mouseResizeNWSE, + RGFW_mouseResizeNESW, + RGFW_mouseResizeAll, + RGFW_mouseNotAllowed, + RGFW_mouseIconFinal = 16 /* padding for alignment */ +}; + +/** @} */ + +#endif /* RGFW_HEADER */ +#if defined(RGFW_X11) || defined(RGFW_WAYLAND) + #define RGFW_OS_BASED_VALUE(l, w, m, h) l +#elif defined(RGFW_WINDOWS) + #define RGFW_OS_BASED_VALUE(l, w, m, h) w +#elif defined(RGFW_MACOS) + #define RGFW_OS_BASED_VALUE(l, w, m, h) m +#elif defined(RGFW_WASM) + #define RGFW_OS_BASED_VALUE(l, w, m, h) h +#endif + + +#ifdef RGFW_IMPLEMENTATION +RGFW_bool RGFW_useWaylandBool = 1; +void RGFW_useWayland(RGFW_bool wayland) { RGFW_useWaylandBool = wayland; } +RGFW_bool RGFW_usingWayland(void) { return RGFW_useWaylandBool; } + +#if !defined(RGFW_NO_X11) && defined(RGFW_WAYLAND) +#define RGFW_GOTO_WAYLAND(fallback) if (RGFW_useWaylandBool && fallback == 0) goto wayland +#else +#define RGFW_GOTO_WAYLAND(fallback) +#endif + +char* RGFW_clipboard_data; +void RGFW_clipboard_switch(char* newstr); +void RGFW_clipboard_switch(char* newstr) { + if (RGFW_clipboard_data != NULL) + RGFW_FREE(RGFW_clipboard_data); + RGFW_clipboard_data = newstr; +} + +#define RGFW_CHECK_CLIPBOARD() \ + if (size <= 0 && RGFW_clipboard_data != NULL) \ + return (const char*)RGFW_clipboard_data; \ + else if (size <= 0) \ + return "\0"; + +const char* RGFW_readClipboard(size_t* len) { + RGFW_ssize_t size = RGFW_readClipboardPtr(NULL, 0); + RGFW_CHECK_CLIPBOARD(); + char* str = (char*)RGFW_ALLOC((size_t)size); + RGFW_ASSERT(str != NULL); + str[0] = '\0'; + + size = RGFW_readClipboardPtr(str, (size_t)size); + + RGFW_CHECK_CLIPBOARD(); + + if (len != NULL) *len = (size_t)size; + + RGFW_clipboard_switch(str); + return (const char*)str; +} + +RGFW_debugfunc RGFW_debugCallback = NULL; +RGFW_debugfunc RGFW_setDebugCallback(RGFW_debugfunc func) { + RGFW_debugfunc RGFW_debugCallbackPrev = RGFW_debugCallback; + RGFW_debugCallback = func; + return RGFW_debugCallbackPrev; +} + +#ifdef RGFW_DEBUG +#include +#endif + +void RGFW_sendDebugInfo(RGFW_debugType type, RGFW_errorCode err, RGFW_debugContext ctx, const char* msg) { + if (RGFW_debugCallback) RGFW_debugCallback(type, err, ctx, msg); + #ifdef RGFW_DEBUG + switch (type) { + case RGFW_typeInfo: printf("RGFW INFO (%i %i): %s", type, err, msg); break; + case RGFW_typeError: printf("RGFW DEBUG (%i %i): %s", type, err, msg); break; + case RGFW_typeWarning: printf("RGFW WARNING (%i %i): %s", type, err, msg); break; + default: break; + } + + switch (err) { + #ifdef RGFW_BUFFER + case RGFW_errBuffer: case RGFW_infoBuffer: printf(" buffer size: %i %i\n", ctx.win->bufferSize.w, ctx.win->bufferSize.h); break; + #endif + case RGFW_infoMonitor: printf(": scale (%s):\n rect: {%i, %i, %i, %i}\n physical size:%f %f\n scale: %f %f\n pixelRatio: %f\n refreshRate: %i\n depth: %i\n", ctx.monitor.name, ctx.monitor.x, ctx.monitor.y, ctx.monitor.mode.area.w, ctx.monitor.mode.area.h, ctx.monitor.physW, ctx.monitor.physH, ctx.monitor.scaleX, ctx.monitor.scaleY, ctx.monitor.pixelRatio, ctx.monitor.mode.refreshRate, ctx.monitor.mode.red + ctx.monitor.mode.green + ctx.monitor.mode.blue); break; + case RGFW_infoWindow: printf(" with rect of {%i, %i, %i, %i} \n", ctx.win->r.x, ctx.win->r.y,ctx. win->r.w, ctx.win->r.h); break; + case RGFW_errDirectXContext: printf(" srcError %i\n", ctx.srcError); break; + default: printf("\n"); + } + #endif +} + +u64 RGFW_timerOffset = 0; +void RGFW_setTime(double time) { + RGFW_timerOffset = RGFW_getTimerValue() - (u64)(time * (double)RGFW_getTimerFreq()); +} + +double RGFW_getTime(void) { + return (double) ((double)(RGFW_getTimerValue() - RGFW_timerOffset) / (double)RGFW_getTimerFreq()); +} + +u64 RGFW_getTimeNS(void) { + return (u64)(((double)((RGFW_getTimerValue() - RGFW_timerOffset)) * 1e9) / (double)RGFW_getTimerFreq()); +} + +/* +RGFW_IMPLEMENTATION starts with generic RGFW defines + +This is the start of keycode data + + Why not use macros instead of the numbers itself? + Windows -> Not all scancodes keys are macros + Linux -> Only symcodes are values, (XK_0 - XK_1, XK_a - XK_z) are larger than 0xFF00, I can't find any way to work with them without making the array an unreasonable size + MacOS -> windows and linux already don't have keycodes as macros, so there's no point +*/ + + + +/* + the c++ compiler doesn't support setting up an array like, + we'll have to do it during runtime using a function & this messy setup +*/ + +#ifndef RGFW_CUSTOM_BACKEND + +#if !defined(__cplusplus) && !defined(RGFW_C89) +#define RGFW_NEXT , +#define RGFW_MAP +#else +#define RGFW_NEXT ; +#define RGFW_MAP RGFW_keycodes +#endif + +u8 RGFW_keycodes [RGFW_OS_BASED_VALUE(256, 512, 128, 256)] = { +#if defined(__cplusplus) || defined(RGFW_C89) + 0 +}; +void RGFW_init_keys(void); +void RGFW_init_keys(void) { +#endif + RGFW_MAP [RGFW_OS_BASED_VALUE(49, 0x029, 50, DOM_VK_BACK_QUOTE)] = RGFW_backtick RGFW_NEXT + + RGFW_MAP [RGFW_OS_BASED_VALUE(19, 0x00B, 29, DOM_VK_0)] = RGFW_0 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(10, 0x002, 18, DOM_VK_1)] = RGFW_1 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(11, 0x003, 19, DOM_VK_2)] = RGFW_2 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(12, 0x004, 20, DOM_VK_3)] = RGFW_3 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(13, 0x005, 21, DOM_VK_4)] = RGFW_4 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(14, 0x006, 23, DOM_VK_5)] = RGFW_5 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(15, 0x007, 22, DOM_VK_6)] = RGFW_6 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(16, 0x008, 26, DOM_VK_7)] = RGFW_7 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(17, 0x009, 28, DOM_VK_8)] = RGFW_8 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(18, 0x00A, 25, DOM_VK_9)] = RGFW_9, + RGFW_MAP [RGFW_OS_BASED_VALUE(65, 0x039, 49, DOM_VK_SPACE)] = RGFW_space, + RGFW_MAP [RGFW_OS_BASED_VALUE(38, 0x01E, 0, DOM_VK_A)] = RGFW_a RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(56, 0x030, 11, DOM_VK_B)] = RGFW_b RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(54, 0x02E, 8, DOM_VK_C)] = RGFW_c RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(40, 0x020, 2, DOM_VK_D)] = RGFW_d RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(26, 0x012, 14, DOM_VK_E)] = RGFW_e RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(41, 0x021, 3, DOM_VK_F)] = RGFW_f RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(42, 0x022, 5, DOM_VK_G)] = RGFW_g RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(43, 0x023, 4, DOM_VK_H)] = RGFW_h RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(31, 0x017, 34, DOM_VK_I)] = RGFW_i RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(44, 0x024, 38, DOM_VK_J)] = RGFW_j RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(45, 0x025, 40, DOM_VK_K)] = RGFW_k RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(46, 0x026, 37, DOM_VK_L)] = RGFW_l RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(58, 0x032, 46, DOM_VK_M)] = RGFW_m RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(57, 0x031, 45, DOM_VK_N)] = RGFW_n RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(32, 0x018, 31, DOM_VK_O)] = RGFW_o RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(33, 0x019, 35, DOM_VK_P)] = RGFW_p RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(24, 0x010, 12, DOM_VK_Q)] = RGFW_q RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(27, 0x013, 15, DOM_VK_R)] = RGFW_r RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(39, 0x01F, 1, DOM_VK_S)] = RGFW_s RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(28, 0x014, 17, DOM_VK_T)] = RGFW_t RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(30, 0x016, 32, DOM_VK_U)] = RGFW_u RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(55, 0x02F, 9, DOM_VK_V)] = RGFW_v RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(25, 0x011, 13, DOM_VK_W)] = RGFW_w RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(53, 0x02D, 7, DOM_VK_X)] = RGFW_x RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(29, 0x015, 16, DOM_VK_Y)] = RGFW_y RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(52, 0x02C, 6, DOM_VK_Z)] = RGFW_z, + RGFW_MAP [RGFW_OS_BASED_VALUE(60, 0x034, 47, DOM_VK_PERIOD)] = RGFW_period RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(59, 0x033, 43, DOM_VK_COMMA)] = RGFW_comma RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(61, 0x035, 44, DOM_VK_SLASH)] = RGFW_slash RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(34, 0x01A, 33, DOM_VK_OPEN_BRACKET)] = RGFW_bracket RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(35, 0x01B, 30, DOM_VK_CLOSE_BRACKET)] = RGFW_closeBracket RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(47, 0x027, 41, DOM_VK_SEMICOLON)] = RGFW_semicolon RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(48, 0x028, 39, DOM_VK_QUOTE)] = RGFW_apostrophe RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(51, 0x02B, 42, DOM_VK_BACK_SLASH)] = RGFW_backSlash, + RGFW_MAP [RGFW_OS_BASED_VALUE(36, 0x01C, 36, DOM_VK_RETURN)] = RGFW_return RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(119, 0x153, 118, DOM_VK_DELETE)] = RGFW_delete RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(77, 0x145, 72, DOM_VK_NUM_LOCK)] = RGFW_numLock RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(106, 0x135, 82, DOM_VK_DIVIDE)] = RGFW_KP_Slash RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(63, 0x037, 76, DOM_VK_MULTIPLY)] = RGFW_multiply RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(82, 0x04A, 67, DOM_VK_SUBTRACT)] = RGFW_KP_Minus RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(87, 0x04F, 84, DOM_VK_NUMPAD1)] = RGFW_KP_1 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(88, 0x050, 85, DOM_VK_NUMPAD2)] = RGFW_KP_2 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(89, 0x051, 86, DOM_VK_NUMPAD3)] = RGFW_KP_3 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(83, 0x04B, 87, DOM_VK_NUMPAD4)] = RGFW_KP_4 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(84, 0x04C, 88, DOM_VK_NUMPAD5)] = RGFW_KP_5 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(85, 0x04D, 89, DOM_VK_NUMPAD6)] = RGFW_KP_6 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(79, 0x047, 90, DOM_VK_NUMPAD7)] = RGFW_KP_7 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(80, 0x048, 92, DOM_VK_NUMPAD8)] = RGFW_KP_8 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(81, 0x049, 93, DOM_VK_NUMPAD9)] = RGFW_KP_9 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(90, 0x052, 83, DOM_VK_NUMPAD0)] = RGFW_KP_0 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(91, 0x053, 65, DOM_VK_DECIMAL)] = RGFW_KP_Period RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(104, 0x11C, 77, 0)] = RGFW_KP_Return, + RGFW_MAP [RGFW_OS_BASED_VALUE(20, 0x00C, 27, DOM_VK_HYPHEN_MINUS)] = RGFW_minus RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(21, 0x00D, 24, DOM_VK_EQUALS)] = RGFW_equals RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(22, 0x00E, 51, DOM_VK_BACK_SPACE)] = RGFW_backSpace RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(23, 0x00F, 48, DOM_VK_TAB)] = RGFW_tab RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(66, 0x03A, 57, DOM_VK_CAPS_LOCK)] = RGFW_capsLock RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(50, 0x02A, 56, DOM_VK_SHIFT)] = RGFW_shiftL RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(37, 0x01D, 59, DOM_VK_CONTROL)] = RGFW_controlL RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(64, 0x038, 58, DOM_VK_ALT)] = RGFW_altL RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(133, 0x15B, 55, DOM_VK_WIN)] = RGFW_superL, + #if !defined(RGFW_MACOS) && !defined(RGFW_WASM) + RGFW_MAP [RGFW_OS_BASED_VALUE(105, 0x11D, 59, 0)] = RGFW_controlR RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(135, 0x15C, 55, 0)] = RGFW_superR, + RGFW_MAP [RGFW_OS_BASED_VALUE(62, 0x036, 56, 0)] = RGFW_shiftR RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(108, 0x138, 58, 0)] = RGFW_altR, + #endif + RGFW_MAP [RGFW_OS_BASED_VALUE(67, 0x03B, 127, DOM_VK_F1)] = RGFW_F1 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(68, 0x03C, 121, DOM_VK_F2)] = RGFW_F2 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(69, 0x03D, 100, DOM_VK_F3)] = RGFW_F3 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(70, 0x03E, 119, DOM_VK_F4)] = RGFW_F4 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(71, 0x03F, 97, DOM_VK_F5)] = RGFW_F5 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(72, 0x040, 98, DOM_VK_F6)] = RGFW_F6 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(73, 0x041, 99, DOM_VK_F7)] = RGFW_F7 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(74, 0x042, 101, DOM_VK_F8)] = RGFW_F8 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(75, 0x043, 102, DOM_VK_F9)] = RGFW_F9 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(76, 0x044, 110, DOM_VK_F10)] = RGFW_F10 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(95, 0x057, 104, DOM_VK_F11)] = RGFW_F11 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(96, 0x058, 112, DOM_VK_F12)] = RGFW_F12 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(111, 0x148, 126, DOM_VK_UP)] = RGFW_up RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(116, 0x150, 125, DOM_VK_DOWN)] = RGFW_down RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(113, 0x14B, 123, DOM_VK_LEFT)] = RGFW_left RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(114, 0x14D, 124, DOM_VK_RIGHT)] = RGFW_right RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(118, 0x152, 115, DOM_VK_INSERT)] = RGFW_insert RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(115, 0x14F, 120, DOM_VK_END)] = RGFW_end RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(112, 0x149, 117, DOM_VK_PAGE_UP)] = RGFW_pageUp RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(117, 0x151, 122, DOM_VK_PAGE_DOWN)] = RGFW_pageDown RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(9, 0x001, 53, DOM_VK_ESCAPE)] = RGFW_escape RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(110, 0x147, 116, DOM_VK_HOME)] = RGFW_home RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(78, 0x046, 107, DOM_VK_SCROLL_LOCK)] = RGFW_scrollLock RGFW_NEXT +#if defined(__cplusplus) || defined(RGFW_C89) +} +#else +}; +#endif + +#undef RGFW_NEXT +#undef RGFW_MAP + +u32 RGFW_apiKeyToRGFW(u32 keycode) { + #if defined(__cplusplus) || defined(RGFW_C89) + if (RGFW_keycodes[RGFW_OS_BASED_VALUE(49, 0x029, 50, DOM_VK_BACK_QUOTE)] != RGFW_backtick) { + RGFW_init_keys(); + } + #endif + + /* make sure the key isn't out of bounds */ + if (keycode > sizeof(RGFW_keycodes) / sizeof(u8)) + return 0; + + return RGFW_keycodes[keycode]; +} +#endif /* RGFW_CUSTOM_BACKEND */ + +typedef struct { + RGFW_bool current : 1; + RGFW_bool prev : 1; +} RGFW_keyState; + +RGFW_keyState RGFW_keyboard[RGFW_keyLast] = { {0, 0} }; + +RGFWDEF void RGFW_resetKey(void); +void RGFW_resetKey(void) { + size_t len = RGFW_keyLast; /*!< last_key == length */ + + size_t i; /*!< reset each previous state */ + for (i = 0; i < len; i++) + RGFW_keyboard[i].prev = 0; +} + +/* + this is the end of keycode data +*/ + +/* gamepad data */ +RGFW_keyState RGFW_gamepadPressed[4][32]; /*!< if a key is currently pressed or not (per gamepad) */ +RGFW_point RGFW_gamepadAxes[4][4]; /*!< if a key is currently pressed or not (per gamepad) */ + +RGFW_gamepadType RGFW_gamepads_type[4]; /*!< if a key is currently pressed or not (per gamepad) */ +i32 RGFW_gamepads[4] = {0, 0, 0, 0}; /*!< limit of 4 gamepads at a time */ +char RGFW_gamepads_name[4][128]; /*!< gamepad names */ +u16 RGFW_gamepadCount = 0; /*!< the actual amount of gamepads */ + +/* + event callback defines start here +*/ + + +/* + These exist to avoid the + if (func == NULL) check + for (allegedly) better performance + + RGFW_EMPTY_DEF exists to prevent the missing-prototypes warning +*/ +static void RGFW_windowMovedfuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } +static void RGFW_windowResizedfuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } +static void RGFW_windowRestoredfuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } +static void RGFW_windowMinimizedfuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } +static void RGFW_windowMaximizedfuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } +static void RGFW_windowQuitfuncEMPTY(RGFW_window* win) { RGFW_UNUSED(win); } +static void RGFW_focusfuncEMPTY(RGFW_window* win, RGFW_bool inFocus) {RGFW_UNUSED(win); RGFW_UNUSED(inFocus);} +static void RGFW_mouseNotifyfuncEMPTY(RGFW_window* win, RGFW_point point, RGFW_bool status) {RGFW_UNUSED(win); RGFW_UNUSED(point); RGFW_UNUSED(status);} +static void RGFW_mousePosfuncEMPTY(RGFW_window* win, RGFW_point point, RGFW_point vector) {RGFW_UNUSED(win); RGFW_UNUSED(point); RGFW_UNUSED(vector);} +static void RGFW_dndInitfuncEMPTY(RGFW_window* win, RGFW_point point) {RGFW_UNUSED(win); RGFW_UNUSED(point);} +static void RGFW_windowRefreshfuncEMPTY(RGFW_window* win) {RGFW_UNUSED(win); } +static void RGFW_keyfuncEMPTY(RGFW_window* win, RGFW_key key, u8 keyChar, RGFW_keymod keyMod, RGFW_bool pressed) {RGFW_UNUSED(win); RGFW_UNUSED(key); RGFW_UNUSED(keyChar); RGFW_UNUSED(keyMod); RGFW_UNUSED(pressed);} +static void RGFW_mouseButtonfuncEMPTY(RGFW_window* win, RGFW_mouseButton button, double scroll, RGFW_bool pressed) {RGFW_UNUSED(win); RGFW_UNUSED(button); RGFW_UNUSED(scroll); RGFW_UNUSED(pressed);} +static void RGFW_gamepadButtonfuncEMPTY(RGFW_window* win, u16 gamepad, u8 button, RGFW_bool pressed) {RGFW_UNUSED(win); RGFW_UNUSED(gamepad); RGFW_UNUSED(button); RGFW_UNUSED(pressed); } +static void RGFW_gamepadAxisfuncEMPTY(RGFW_window* win, u16 gamepad, RGFW_point axis[2], u8 axisesCount, u8 whichAxis) {RGFW_UNUSED(win); RGFW_UNUSED(gamepad); RGFW_UNUSED(axis); RGFW_UNUSED(axisesCount); RGFW_UNUSED(whichAxis); } +static void RGFW_gamepadfuncEMPTY(RGFW_window* win, u16 gamepad, RGFW_bool connected) {RGFW_UNUSED(win); RGFW_UNUSED(gamepad); RGFW_UNUSED(connected);} +static void RGFW_dndfuncEMPTY(RGFW_window* win, char** droppedFiles, size_t droppedFilesCount) {RGFW_UNUSED(win); RGFW_UNUSED(droppedFiles); RGFW_UNUSED(droppedFilesCount);} +static void RGFW_scaleUpdatedfuncEMPTY(RGFW_window* win, float scaleX, float scaleY) {RGFW_UNUSED(win); RGFW_UNUSED(scaleX); RGFW_UNUSED(scaleY); } + +#define RGFW_CALLBACK_DEFINE(x, x2) \ +RGFW_##x##func RGFW_##x##Callback = RGFW_##x##funcEMPTY; \ +RGFW_##x##func RGFW_set##x2##Callback(RGFW_##x##func func) { \ + RGFW_##x##func prev = RGFW_##x##Callback; \ + RGFW_##x##Callback = func; \ + return prev; \ +} +RGFW_CALLBACK_DEFINE(windowMaximized, WindowMaximized) +RGFW_CALLBACK_DEFINE(windowMinimized, WindowMinimized) +RGFW_CALLBACK_DEFINE(windowRestored, WindowRestored) +RGFW_CALLBACK_DEFINE(windowMoved, WindowMoved) +RGFW_CALLBACK_DEFINE(windowResized, WindowResized) +RGFW_CALLBACK_DEFINE(windowQuit, WindowQuit) +RGFW_CALLBACK_DEFINE(mousePos, MousePos) +RGFW_CALLBACK_DEFINE(windowRefresh, WindowRefresh) +RGFW_CALLBACK_DEFINE(focus, Focus) +RGFW_CALLBACK_DEFINE(mouseNotify, MouseNotify) +RGFW_CALLBACK_DEFINE(dnd, Dnd) +RGFW_CALLBACK_DEFINE(dndInit, DndInit) +RGFW_CALLBACK_DEFINE(key, Key) +RGFW_CALLBACK_DEFINE(mouseButton, MouseButton) +RGFW_CALLBACK_DEFINE(gamepadButton, GamepadButton) +RGFW_CALLBACK_DEFINE(gamepadAxis, GamepadAxis) +RGFW_CALLBACK_DEFINE(gamepad, Gamepad) +RGFW_CALLBACK_DEFINE(scaleUpdated, ScaleUpdated) +#undef RGFW_CALLBACK_DEFINE + +void RGFW_window_checkEvents(RGFW_window* win, i32 waitMS) { + RGFW_window_eventWait(win, waitMS); + + while (RGFW_window_checkEvent(win) != NULL && RGFW_window_shouldClose(win) == 0) { + if (win->event.type == RGFW_quit) return; + } + + #ifdef RGFW_WASM /* WASM needs to run the sleep function for asyncify */ + RGFW_sleep(0); + #endif +} + +void RGFW_window_checkMode(RGFW_window* win); +void RGFW_window_checkMode(RGFW_window* win) { + if (RGFW_window_isMinimized(win)) { + win->_flags |= RGFW_windowMinimize; + RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMinimized, ._win = win}); + RGFW_windowMinimizedCallback(win, win->r); + } else if (RGFW_window_isMaximized(win)) { + win->_flags |= RGFW_windowMaximize; + RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMaximized, ._win = win}); + RGFW_windowMaximizedCallback(win, win->r); + } else if (((win->_flags & RGFW_windowMinimize) && !RGFW_window_isMaximized(win)) || + (win->_flags & RGFW_windowMaximize && !RGFW_window_isMaximized(win))) { + win->_flags &= ~(u32)RGFW_windowMinimize; + if (RGFW_window_isMaximized(win) == RGFW_FALSE) win->_flags &= ~(u32)RGFW_windowMaximize; + RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowRestored, ._win = win}); + RGFW_windowRestoredCallback(win, win->r); + } +} + +/* +no more event call back defines +*/ + +#define SET_ATTRIB(a, v) { \ + RGFW_ASSERT(((size_t) index + 1) < sizeof(attribs) / sizeof(attribs[0])); \ + attribs[index++] = a; \ + attribs[index++] = v; \ +} + +#define RGFW_EVENT_PASSED RGFW_BIT(24) /* if a queued event was passed */ +#define RGFW_EVENT_QUIT RGFW_BIT(25) /* the window close button was pressed */ +#define RGFW_HOLD_MOUSE RGFW_BIT(26) /*!< hold the moues still */ +#define RGFW_MOUSE_LEFT RGFW_BIT(27) /* if mouse left the window */ +#define RGFW_WINDOW_ALLOC RGFW_BIT(28) /* if window was allocated by RGFW */ +#define RGFW_BUFFER_ALLOC RGFW_BIT(29) /* if window.buffer was allocated by RGFW */ +#define RGFW_WINDOW_INIT RGFW_BIT(30) /* if window.buffer was allocated by RGFW */ +#define RGFW_INTERNAL_FLAGS (RGFW_EVENT_QUIT | RGFW_EVENT_PASSED | RGFW_HOLD_MOUSE | RGFW_MOUSE_LEFT | RGFW_WINDOW_ALLOC | RGFW_BUFFER_ALLOC | RGFW_windowFocus) + +RGFW_window* RGFW_createWindow(const char* name, RGFW_rect rect, RGFW_windowFlags flags) { + RGFW_window* win = (RGFW_window*)RGFW_ALLOC(sizeof(RGFW_window)); + RGFW_ASSERT(win != NULL); + win->_flags = RGFW_WINDOW_ALLOC; + return RGFW_createWindowPtr(name, rect, flags, win); +} + +#if defined(RGFW_USE_XDL) && defined(RGFW_X11) + #define XDL_IMPLEMENTATION + #include "XDL.h" +#endif + +#define RGFW_MAX_EVENTS 32 +typedef struct RGFW_globalStruct { + RGFW_window* root; + RGFW_window* current; + i32 windowCount; + i32 eventLen; + i32 eventIndex; + + #ifdef RGFW_X11 + Display* display; + Window helperWindow; + char* clipboard; /* for writing to the clipboard selection */ + size_t clipboard_len; + #endif + #ifdef RGFW_WAYLAND + struct wl_display* wl_display; + #endif + #if defined(RGFW_X11) || defined(RGFW_WINDOWS) + RGFW_mouse* hiddenMouse; + #endif + RGFW_event events[RGFW_MAX_EVENTS]; + +} RGFW_globalStruct; +#ifndef RGFW_C89 +RGFW_globalStruct _RGFW = {.root = NULL, .current = NULL, .windowCount = -1, .eventLen = 0, .eventIndex = 0}; +#else +RGFW_globalStruct _RGFW = {NULL, NULL, -1, 0, 0}; +#endif + +void RGFW_eventQueuePush(RGFW_event event) { + if (_RGFW.eventLen >= RGFW_MAX_EVENTS) return; + _RGFW.events[_RGFW.eventLen] = event; + _RGFW.eventLen++; +} + +RGFW_event* RGFW_eventQueuePop(RGFW_window* win) { + if (_RGFW.eventLen == 0) return NULL; + + RGFW_event* ev = (RGFW_event*)&_RGFW.events[_RGFW.eventIndex]; + + _RGFW.eventLen--; + if (_RGFW.eventLen && _RGFW.eventIndex < (_RGFW.eventLen - 1)) + _RGFW.eventIndex++; + else if (_RGFW.eventLen == 0) + _RGFW.eventIndex = 0; + + if (ev->_win != win && ev->_win != NULL) { + RGFW_eventQueuePush(*ev); + return NULL; + } + + ev->droppedFiles = win->event.droppedFiles; + return ev; +} + +RGFW_event* RGFW_window_checkEventCore(RGFW_window* win); +RGFW_event* RGFW_window_checkEventCore(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + if (win->event.type == 0 && _RGFW.eventLen == 0) + RGFW_resetKey(); + + if (win->event.type == RGFW_quit && win->_flags & RGFW_windowFreeOnClose) { + static RGFW_event ev; + ev = win->event; + RGFW_window_close(win); + return &ev; + } + + if (win->event.type != RGFW_DNDInit) win->event.type = 0; + + /* check queued events */ + RGFW_event* ev = RGFW_eventQueuePop(win); + if (ev != NULL) { + if (ev->type == RGFW_quit) RGFW_window_setShouldClose(win, RGFW_TRUE); + win->event = *ev; + } + else return NULL; + + return &win->event; +} + + +RGFWDEF void RGFW_window_basic_init(RGFW_window* win, RGFW_rect rect, RGFW_windowFlags flags); +void RGFW_setRootWindow(RGFW_window* win) { _RGFW.root = win; } +RGFW_window* RGFW_getRootWindow(void) { return _RGFW.root; } + +/* do a basic initialization for RGFW_window, this is to standard it for each OS */ +void RGFW_window_basic_init(RGFW_window* win, RGFW_rect rect, RGFW_windowFlags flags) { + RGFW_UNUSED(flags); + if (_RGFW.windowCount == -1) RGFW_init(); + _RGFW.windowCount++; + + /* rect based the requested flags */ + if (_RGFW.root == NULL) { + RGFW_setRootWindow(win); + RGFW_setTime(0); + } + + if (!(win->_flags & RGFW_WINDOW_ALLOC)) win->_flags = 0; + + /* set and init the new window's data */ + win->r = rect; + win->event.droppedFilesCount = 0; + + win->_flags = 0 | (win->_flags & RGFW_WINDOW_ALLOC); + win->_flags |= flags; + win->event.keyMod = 0; + win->_lastMousePoint = RGFW_POINT(0, 0); + + win->event.droppedFiles = (char**)RGFW_ALLOC(RGFW_MAX_PATH * RGFW_MAX_DROPS); + RGFW_ASSERT(win->event.droppedFiles != NULL); + + u32 i; + for (i = 0; i < RGFW_MAX_DROPS; i++) + win->event.droppedFiles[i] = (char*)(win->event.droppedFiles + RGFW_MAX_DROPS + (i * RGFW_MAX_PATH)); +} + +void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags flags) { + RGFW_windowFlags cmpFlags = win->_flags; + if (win->_flags & RGFW_WINDOW_INIT) cmpFlags = 0; + + #ifndef RGFW_NO_MONITOR + if (flags & RGFW_windowScaleToMonitor) RGFW_window_scaleToMonitor(win); + #endif + + if (flags & RGFW_windowCenter) RGFW_window_center(win); + if (flags & RGFW_windowCenterCursor) + RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); + if (flags & RGFW_windowNoBorder) RGFW_window_setBorder(win, 0); + else RGFW_window_setBorder(win, 1); + if (flags & RGFW_windowFullscreen) RGFW_window_setFullscreen(win, RGFW_TRUE); + else if (cmpFlags & RGFW_windowFullscreen) RGFW_window_setFullscreen(win, 0); + if (flags & RGFW_windowMaximize) RGFW_window_maximize(win); + else if (cmpFlags & RGFW_windowMaximize) RGFW_window_restore(win); + if (flags & RGFW_windowMinimize) RGFW_window_minimize(win); + else if (cmpFlags & RGFW_windowMinimize) RGFW_window_restore(win); + if (flags & RGFW_windowHideMouse) RGFW_window_showMouse(win, 0); + else if (cmpFlags & RGFW_windowHideMouse) RGFW_window_showMouse(win, 1); + if (flags & RGFW_windowHide) RGFW_window_hide(win); + else if (cmpFlags & RGFW_windowHide) RGFW_window_show(win); + if (flags & RGFW_windowCocoaCHDirToRes) RGFW_moveToMacOSResourceDir(); + if (flags & RGFW_windowFloating) RGFW_window_setFloating(win, 1); + else if (cmpFlags & RGFW_windowFloating) RGFW_window_setFloating(win, 0); + if (flags & RGFW_windowFocus) RGFW_window_focus(win); + + if (flags & RGFW_windowNoResize) { + RGFW_window_setMaxSize(win, RGFW_AREA(win->r.w, win->r.h)); + RGFW_window_setMinSize(win, RGFW_AREA(win->r.w, win->r.h)); + } else if (cmpFlags & RGFW_windowNoResize) { + RGFW_window_setMaxSize(win, RGFW_AREA(0, 0)); + RGFW_window_setMinSize(win, RGFW_AREA(0, 0)); + } + + win->_flags = flags | (win->_flags & RGFW_INTERNAL_FLAGS); +} + +RGFW_bool RGFW_window_isInFocus(RGFW_window* win) { +#ifdef RGFW_WASM + return RGFW_TRUE; +#else + return RGFW_BOOL(win->_flags & RGFW_windowFocus); +#endif +} + +void RGFW_window_initBuffer(RGFW_window* win) { + RGFW_area area = RGFW_getScreenSize(); + if ((win->_flags & RGFW_windowNoResize)) + area = RGFW_AREA(win->r.w, win->r.h); + + RGFW_window_initBufferSize(win, area); +} + +void RGFW_window_initBufferSize(RGFW_window* win, RGFW_area area) { +#if defined(RGFW_BUFFER) || defined(RGFW_OSMESA) + win->_flags |= RGFW_BUFFER_ALLOC; + #ifndef RGFW_WINDOWS + u8* buffer = (u8*)RGFW_ALLOC(area.w * area.h * 4); + RGFW_ASSERT(buffer != NULL); + + RGFW_window_initBufferPtr(win, buffer, area); + #else /* windows's bitmap allocs memory for us */ + RGFW_window_initBufferPtr(win, (u8*)NULL, area); + #endif +#else + RGFW_UNUSED(win); RGFW_UNUSED(area); +#endif +} + +#ifdef RGFW_MACOS +RGFWDEF void RGFW_window_cocoaSetLayer(RGFW_window* win, void* layer); +RGFWDEF void* RGFW_cocoaGetLayer(void); +#endif + +const char* RGFW_className = NULL; +void RGFW_setClassName(const char* name) { RGFW_className = name; } + +#ifndef RGFW_X11 +void RGFW_setXInstName(const char* name) { RGFW_UNUSED(name); } +#endif + +RGFW_keyState RGFW_mouseButtons[RGFW_mouseFinal] = { {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + +RGFW_bool RGFW_isMousePressed(RGFW_window* win, RGFW_mouseButton button) { + return RGFW_mouseButtons[button].current && (win == NULL || RGFW_window_isInFocus(win)); +} +RGFW_bool RGFW_wasMousePressed(RGFW_window* win, RGFW_mouseButton button) { + return RGFW_mouseButtons[button].prev && (win != NULL || RGFW_window_isInFocus(win)); +} +RGFW_bool RGFW_isMouseHeld(RGFW_window* win, RGFW_mouseButton button) { + return (RGFW_isMousePressed(win, button) && RGFW_wasMousePressed(win, button)); +} +RGFW_bool RGFW_isMouseReleased(RGFW_window* win, RGFW_mouseButton button) { + return (!RGFW_isMousePressed(win, button) && RGFW_wasMousePressed(win, button)); +} + +RGFW_point RGFW_window_getMousePoint(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + return win->_lastMousePoint; +} + +RGFW_bool RGFW_isPressed(RGFW_window* win, RGFW_key key) { + return RGFW_keyboard[key].current && (win == NULL || RGFW_window_isInFocus(win)); +} + +RGFW_bool RGFW_wasPressed(RGFW_window* win, RGFW_key key) { + return RGFW_keyboard[key].prev && (win == NULL || RGFW_window_isInFocus(win)); +} + +RGFW_bool RGFW_isHeld(RGFW_window* win, RGFW_key key) { + return (RGFW_isPressed(win, key) && RGFW_wasPressed(win, key)); +} + +RGFW_bool RGFW_isClicked(RGFW_window* win, RGFW_key key) { + return (RGFW_wasPressed(win, key) && !RGFW_isPressed(win, key)); +} + +RGFW_bool RGFW_isReleased(RGFW_window* win, RGFW_key key) { + return (!RGFW_isPressed(win, key) && RGFW_wasPressed(win, key)); +} + +void RGFW_window_makeCurrent(RGFW_window* win) { + _RGFW.current = win; +#if defined(RGFW_OPENGL) || defined(RGFW_EGL) + RGFW_window_makeCurrent_OpenGL(win); +#endif +} + +RGFW_window* RGFW_getCurrent(void) { + return _RGFW.current; +} + +void RGFW_window_swapBuffers(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_window_swapBuffers_software(win); +#if defined(RGFW_OPENGL) || defined(RGFW_EGL) + RGFW_window_swapBuffers_OpenGL(win); +#endif +} + +RGFWDEF void RGFW_setBit(u32* data, u32 bit, RGFW_bool value); +void RGFW_setBit(u32* data, u32 bit, RGFW_bool value) { + if (value) + *data |= bit; + else if (!value && (*(data) & bit)) + *data ^= bit; +} + +void RGFW_window_center(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_area screenR = RGFW_getScreenSize(); + RGFW_window_move(win, RGFW_POINT((i32)(screenR.w - (u32)win->r.w) / 2, (screenR.h - (u32)win->r.h) / 2)); +} + +RGFW_bool RGFW_monitor_scaleToWindow(RGFW_monitor mon, RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + RGFW_monitorMode mode; + mode.area = RGFW_AREA(win->r.w, win->r.h); + return RGFW_monitor_requestMode(mon, mode, RGFW_monitorScale); +} + +void RGFW_splitBPP(u32 bpp, RGFW_monitorMode* mode); +void RGFW_splitBPP(u32 bpp, RGFW_monitorMode* mode) { + if (bpp == 32) bpp = 24; + mode->red = mode->green = mode->blue = (u8)(bpp / 3); + + u32 delta = bpp - (mode->red * 3); /* handle leftovers */ + if (delta >= 1) mode->green = mode->green + 1; + if (delta == 2) mode->red = mode->red + 1; +} + +RGFW_bool RGFW_monitorModeCompare(RGFW_monitorMode mon, RGFW_monitorMode mon2, RGFW_modeRequest request) { + return (((mon.area.w == mon2.area.w && mon.area.h == mon2.area.h) || !(request & RGFW_monitorScale)) && + ((mon.refreshRate == mon2.refreshRate) || !(request & RGFW_monitorRefresh)) && + ((mon.red == mon2.red && mon.green == mon2.green && mon.blue == mon2.blue) || !(request & RGFW_monitorRGB))); +} + +RGFW_bool RGFW_window_shouldClose(RGFW_window* win) { + return (win == NULL || (win->_flags & RGFW_EVENT_QUIT)|| RGFW_isPressed(win, RGFW_escape)); +} + +void RGFW_window_setShouldClose(RGFW_window* win, RGFW_bool shouldClose) { + if (shouldClose) { + win->_flags |= RGFW_EVENT_QUIT; + RGFW_windowQuitCallback(win); + } else { + win->_flags &= ~(u32)RGFW_EVENT_QUIT; + } +} + +#ifndef RGFW_NO_MONITOR +void RGFW_window_scaleToMonitor(RGFW_window* win) { + RGFW_monitor monitor = RGFW_window_getMonitor(win); + if (monitor.scaleX == 0 && monitor.scaleY == 0) + return; + + RGFW_window_resize(win, RGFW_AREA((u32)(monitor.scaleX * (float)win->r.w), (u32)(monitor.scaleY * (float)win->r.h))); +} + +void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor m) { + RGFW_window_move(win, RGFW_POINT(m.x + win->r.x, m.y + win->r.y)); +} +#endif + +RGFW_bool RGFW_window_setIcon(RGFW_window* win, u8* icon, RGFW_area a, i32 channels) { + return RGFW_window_setIconEx(win, icon, a, channels, RGFW_iconBoth); +} + +RGFWDEF void RGFW_captureCursor(RGFW_window* win, RGFW_rect); +RGFWDEF void RGFW_releaseCursor(RGFW_window* win); + +void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area) { + if ((win->_flags & RGFW_HOLD_MOUSE)) + return; + + if (!area.w && !area.h) + area = RGFW_AREA(win->r.w / 2, win->r.h / 2); + + win->_flags |= RGFW_HOLD_MOUSE; + RGFW_captureCursor(win, win->r); + RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); +} + +void RGFW_window_mouseUnhold(RGFW_window* win) { + win->_flags &= ~(u32)RGFW_HOLD_MOUSE; + RGFW_releaseCursor(win); +} + +u32 RGFW_checkFPS(double startTime, u32 frameCount, u32 fpsCap) { + double deltaTime = RGFW_getTime() - startTime; + if (deltaTime == 0) return 0; + + double fps = (frameCount / deltaTime); /* the numer of frames over the time it took for them to render */ + if (fpsCap && fps > fpsCap) { + double frameTime = (double)frameCount / (double)fpsCap; /* how long it should take to finish the frames */ + double sleepTime = frameTime - deltaTime; /* subtract how long it should have taken with how long it did take */ + + if (sleepTime > 0) RGFW_sleep((u32)(sleepTime * 1000)); + } + + return (u32) fps; +} + +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) +void RGFW_RGB_to_BGR(RGFW_window* win, u8* data) { + #if !defined(RGFW_BUFFER_BGR) && !defined(RGFW_OSMESA) + u32 x, y; + for (y = 0; y < (u32)win->r.h; y++) { + for (x = 0; x < (u32)win->r.w; x++) { + u32 index = (y * 4 * win->bufferSize.w) + x * 4; + + u8 red = data[index]; + data[index] = win->buffer[index + 2]; + data[index + 2] = red; + } + } + #elif defined(RGFW_OSMESA) + u32 y; + for(y = 0; y < (u32)win->r.h; y++){ + u32 index_from = (y + (win->bufferSize.h - win->r.h)) * 4 * win->bufferSize.w; + u32 index_to = y * 4 * win->bufferSize.w; + memcpy(&data[index_to], &data[index_from], 4 * win->bufferSize.w); + } + #else + RGFW_UNUSED(win); RGFW_UNUSED(data); + #endif +} +#endif + +u32 RGFW_isPressedGamepad(RGFW_window* win, u8 c, RGFW_gamepadCodes button) { + RGFW_UNUSED(win); + return RGFW_gamepadPressed[c][button].current; +} +u32 RGFW_wasPressedGamepad(RGFW_window* win, u8 c, RGFW_gamepadCodes button) { + RGFW_UNUSED(win); + return RGFW_gamepadPressed[c][button].prev; +} +u32 RGFW_isReleasedGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button) { + RGFW_UNUSED(win); + return !RGFW_isPressedGamepad(win, controller, button) && RGFW_wasPressedGamepad(win, controller, button); +} +u32 RGFW_isHeldGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button) { + RGFW_UNUSED(win); + return RGFW_isPressedGamepad(win, controller, button) && RGFW_wasPressedGamepad(win, controller, button); +} + +RGFW_point RGFW_getGamepadAxis(RGFW_window* win, u16 controller, u16 whichAxis) { + RGFW_UNUSED(win); + return RGFW_gamepadAxes[controller][whichAxis]; +} +const char* RGFW_getGamepadName(RGFW_window* win, u16 controller) { + RGFW_UNUSED(win); + return (const char*)RGFW_gamepads_name[controller]; +} + +size_t RGFW_getGamepadCount(RGFW_window* win) { + RGFW_UNUSED(win); + return RGFW_gamepadCount; +} + +RGFW_gamepadType RGFW_getGamepadType(RGFW_window* win, u16 controller) { + RGFW_UNUSED(win); + return RGFW_gamepads_type[controller]; +} + +RGFWDEF void RGFW_updateKeyMod(RGFW_window* win, RGFW_keymod mod, RGFW_bool value); +void RGFW_updateKeyMod(RGFW_window* win, RGFW_keymod mod, RGFW_bool value) { + if (value) win->event.keyMod |= mod; + else win->event.keyMod &= ~mod; +} + +RGFWDEF void RGFW_updateKeyModsPro(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll); +void RGFW_updateKeyModsPro(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll) { + RGFW_updateKeyMod(win, RGFW_modCapsLock, capital); + RGFW_updateKeyMod(win, RGFW_modNumLock, numlock); + RGFW_updateKeyMod(win, RGFW_modControl, control); + RGFW_updateKeyMod(win, RGFW_modAlt, alt); + RGFW_updateKeyMod(win, RGFW_modShift, shift); + RGFW_updateKeyMod(win, RGFW_modSuper, super); + RGFW_updateKeyMod(win, RGFW_modScrollLock, scroll); +} + +RGFWDEF void RGFW_updateKeyMods(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool scroll); +void RGFW_updateKeyMods(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool scroll) { + RGFW_updateKeyModsPro(win, capital, numlock, + RGFW_isPressed(win, RGFW_controlL) || RGFW_isPressed(win, RGFW_controlR), + RGFW_isPressed(win, RGFW_altL) || RGFW_isPressed(win, RGFW_altR), + RGFW_isPressed(win, RGFW_shiftL) || RGFW_isPressed(win, RGFW_shiftR), + RGFW_isPressed(win, RGFW_superL) || RGFW_isPressed(win, RGFW_superR), + scroll); +} + +RGFWDEF void RGFW_window_showMouseFlags(RGFW_window* win, RGFW_bool show); +void RGFW_window_showMouseFlags(RGFW_window* win, RGFW_bool show) { + if (show && (win->_flags & RGFW_windowHideMouse)) + win->_flags ^= RGFW_windowHideMouse; + else if (!show && !(win->_flags & RGFW_windowHideMouse)) + win->_flags |= RGFW_windowHideMouse; +} + +RGFW_bool RGFW_window_mouseHidden(RGFW_window* win) { + return (RGFW_bool)RGFW_BOOL(win->_flags & RGFW_windowHideMouse); +} + +RGFW_bool RGFW_window_borderless(RGFW_window* win) { + return (RGFW_bool)RGFW_BOOL(win->_flags & RGFW_windowNoBorder); +} + +RGFW_bool RGFW_window_isFullscreen(RGFW_window* win){ return RGFW_BOOL(win->_flags & RGFW_windowFullscreen); } +RGFW_bool RGFW_window_allowsDND(RGFW_window* win) { return RGFW_BOOL(win->_flags & RGFW_windowAllowDND); } + +#ifndef RGFW_WINDOWS +void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow) { + RGFW_setBit(&win->_flags, RGFW_windowAllowDND, allow); +} +#endif + +#if defined(RGFW_X11) || defined(RGFW_MACOS) || defined(RGFW_WASM) || defined(RGFW_WAYLAND) +#ifndef __USE_POSIX199309 + #define __USE_POSIX199309 +#endif +#include +struct timespec; +#endif + +#if defined(RGFW_X11) || defined(RGFW_WINDOWS) +void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show) { + RGFW_window_showMouseFlags(win, show); + if (show == 0) + RGFW_window_setMouse(win, _RGFW.hiddenMouse); + else + RGFW_window_setMouseDefault(win); +} +#endif + +#ifndef RGFW_MACOS +void RGFW_moveToMacOSResourceDir(void) { } +#endif + +/* + graphics API specific code (end of generic code) + starts here +*/ + + +/* + OpenGL defines start here (Normal, EGL, OSMesa) +*/ + +#if defined(RGFW_OPENGL) || defined(RGFW_EGL) + +#ifdef RGFW_WINDOWS + #define WIN32_LEAN_AND_MEAN + #define OEMRESOURCE + #include +#endif + +#if !defined(__APPLE__) && !defined(RGFW_NO_GL_HEADER) + #include +#elif defined(__APPLE__) + #ifndef GL_SILENCE_DEPRECATION + #define GL_SILENCE_DEPRECATION + #endif + #include + #include +#endif + +/* EGL, normal OpenGL only */ +#ifndef RGFW_EGL +i32 RGFW_GL_HINTS[RGFW_glFinalHint] = {8, +#else +i32 RGFW_GL_HINTS[RGFW_glFinalHint] = {0, +#endif + 0, 0, 0, 1, 8, 8, 8, 8, 24, 0, 0, 0, 0, 0, 0, 0, 0, RGFW_glReleaseNone, RGFW_glCore, 0, 0}; + +void RGFW_setGLHint(RGFW_glHints hint, i32 value) { + if (hint < RGFW_glFinalHint && hint) RGFW_GL_HINTS[hint] = value; +} + +/* OPENGL normal only (no EGL / OSMesa) */ +#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) && !defined(RGFW_CUSTOM_BACKEND) && !defined(RGFW_WASM) + +#define RGFW_GL_RENDER_TYPE RGFW_OS_BASED_VALUE(GLX_X_VISUAL_TYPE, 0x2003, 73, 0) + #define RGFW_GL_ALPHA_SIZE RGFW_OS_BASED_VALUE(GLX_ALPHA_SIZE, 0x201b, 11, 0) + #define RGFW_GL_DEPTH_SIZE RGFW_OS_BASED_VALUE(GLX_DEPTH_SIZE, 0x2022, 12, 0) + #define RGFW_GL_DOUBLEBUFFER RGFW_OS_BASED_VALUE(GLX_DOUBLEBUFFER, 0x2011, 5, 0) + #define RGFW_GL_STENCIL_SIZE RGFW_OS_BASED_VALUE(GLX_STENCIL_SIZE, 0x2023, 13, 0) + #define RGFW_GL_SAMPLES RGFW_OS_BASED_VALUE(GLX_SAMPLES, 0x2042, 55, 0) + #define RGFW_GL_STEREO RGFW_OS_BASED_VALUE(GLX_STEREO, 0x2012, 6, 0) + #define RGFW_GL_AUX_BUFFERS RGFW_OS_BASED_VALUE(GLX_AUX_BUFFERS, 0x2024, 7, 0) + +#if defined(RGFW_X11) || defined(RGFW_WINDOWS) + #define RGFW_GL_DRAW RGFW_OS_BASED_VALUE(GLX_X_RENDERABLE, 0x2001, 0, 0) + #define RGFW_GL_DRAW_TYPE RGFW_OS_BASED_VALUE(GLX_RENDER_TYPE, 0x2013, 0, 0) + #define RGFW_GL_FULL_FORMAT RGFW_OS_BASED_VALUE(GLX_TRUE_COLOR, 0x2027, 0, 0) + #define RGFW_GL_RED_SIZE RGFW_OS_BASED_VALUE(GLX_RED_SIZE, 0x2015, 0, 0) + #define RGFW_GL_GREEN_SIZE RGFW_OS_BASED_VALUE(GLX_GREEN_SIZE, 0x2017, 0, 0) + #define RGFW_GL_BLUE_SIZE RGFW_OS_BASED_VALUE(GLX_BLUE_SIZE, 0x2019, 0, 0) + #define RGFW_GL_USE_RGBA RGFW_OS_BASED_VALUE(GLX_RGBA_BIT, 0x202B, 0, 0) + #define RGFW_GL_ACCUM_RED_SIZE RGFW_OS_BASED_VALUE(14, 0x201E, 0, 0) + #define RGFW_GL_ACCUM_GREEN_SIZE RGFW_OS_BASED_VALUE(15, 0x201F, 0, 0) + #define RGFW_GL_ACCUM_BLUE_SIZE RGFW_OS_BASED_VALUE(16, 0x2020, 0, 0) + #define RGFW_GL_ACCUM_ALPHA_SIZE RGFW_OS_BASED_VALUE(17, 0x2021, 0, 0) + #define RGFW_GL_SRGB RGFW_OS_BASED_VALUE(0x20b2, 0x3089, 0, 0) + #define RGFW_GL_NOERROR RGFW_OS_BASED_VALUE(0x31b3, 0x31b3, 0, 0) + #define RGFW_GL_FLAGS RGFW_OS_BASED_VALUE(GLX_CONTEXT_FLAGS_ARB, 0x2094, 0, 0) + #define RGFW_GL_RELEASE_BEHAVIOR RGFW_OS_BASED_VALUE(GLX_CONTEXT_RELEASE_BEHAVIOR_ARB, 0x2097 , 0, 0) + #define RGFW_GL_CONTEXT_RELEASE RGFW_OS_BASED_VALUE(GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB, 0x2098, 0, 0) + #define RGFW_GL_CONTEXT_NONE RGFW_OS_BASED_VALUE(GLX_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB, 0x0000, 0, 0) + #define RGFW_GL_FLAGS RGFW_OS_BASED_VALUE(GLX_CONTEXT_FLAGS_ARB, 0x2094, 0, 0) + #define RGFW_GL_DEBUG_BIT RGFW_OS_BASED_VALUE(GLX_CONTEXT_FLAGS_ARB, 0x2094, 0, 0) + #define RGFW_GL_ROBUST_BIT RGFW_OS_BASED_VALUE(GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB, 0x00000004, 0, 0) +#endif + +#ifdef RGFW_WINDOWS + #define WGL_SUPPORT_OPENGL_ARB 0x2010 + #define WGL_COLOR_BITS_ARB 0x2014 + #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_SAMPLE_BUFFERS_ARB 0x2041 + #define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9 + #define WGL_PIXEL_TYPE_ARB 0x2013 + #define WGL_TYPE_RGBA_ARB 0x202B + + #define WGL_TRANSPARENT_ARB 0x200A +#endif + +/* The window'ing api needs to know how to render the data we (or opengl) give it + MacOS and Windows do this using a structure called a "pixel format" + X11 calls it a "Visual" + This function returns the attributes for the format we want */ +i32* RGFW_initFormatAttribs(u32 useSoftware); +i32* RGFW_initFormatAttribs(u32 useSoftware) { + RGFW_UNUSED(useSoftware); + static i32 attribs[] = { + #if defined(RGFW_X11) || defined(RGFW_WINDOWS) + RGFW_GL_RENDER_TYPE, + RGFW_GL_FULL_FORMAT, + RGFW_GL_DRAW, 1, + RGFW_GL_DRAW_TYPE , RGFW_GL_USE_RGBA, + #endif + + #ifdef RGFW_X11 + GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT, + #endif + + #ifdef RGFW_MACOS + 72, + 8, 24, + #endif + + #ifdef RGFW_WINDOWS + WGL_SUPPORT_OPENGL_ARB, 1, + WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + WGL_COLOR_BITS_ARB, 32, + #endif + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + size_t index = (sizeof(attribs) / sizeof(attribs[0])) - 27; + + #define RGFW_GL_ADD_ATTRIB(attrib, attVal) \ + if (attVal) { \ + attribs[index] = attrib;\ + attribs[index + 1] = attVal;\ + index += 2;\ + } + + #if defined(RGFW_MACOS) && defined(RGFW_COCOA_GRAPHICS_SWITCHING) + RGFW_GL_ADD_ATTRIB(96, kCGLPFASupportsAutomaticGraphicsSwitching); + #endif + + RGFW_GL_ADD_ATTRIB(RGFW_GL_DOUBLEBUFFER, 1); + + RGFW_GL_ADD_ATTRIB(RGFW_GL_ALPHA_SIZE, RGFW_GL_HINTS[RGFW_glAlpha]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_DEPTH_SIZE, RGFW_GL_HINTS[RGFW_glDepth]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_STENCIL_SIZE, RGFW_GL_HINTS[RGFW_glStencil]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_STEREO, RGFW_GL_HINTS[RGFW_glStereo]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_AUX_BUFFERS, RGFW_GL_HINTS[RGFW_glAuxBuffers]); + + #if defined(RGFW_X11) || defined(RGFW_WINDOWS) + RGFW_GL_ADD_ATTRIB(RGFW_GL_RED_SIZE, RGFW_GL_HINTS[RGFW_glRed]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_GREEN_SIZE, RGFW_GL_HINTS[RGFW_glBlue]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_BLUE_SIZE, RGFW_GL_HINTS[RGFW_glGreen]); + #endif + + #if defined(RGFW_X11) || defined(RGFW_WINDOWS) + RGFW_GL_ADD_ATTRIB(RGFW_GL_ACCUM_RED_SIZE, RGFW_GL_HINTS[RGFW_glAccumRed]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_ACCUM_GREEN_SIZE, RGFW_GL_HINTS[RGFW_glAccumBlue]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_ACCUM_BLUE_SIZE, RGFW_GL_HINTS[RGFW_glAccumGreen]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_ACCUM_ALPHA_SIZE, RGFW_GL_HINTS[RGFW_glAccumAlpha]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_SRGB, RGFW_GL_HINTS[RGFW_glSRGB]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_NOERROR, RGFW_GL_HINTS[RGFW_glNoError]); + + if (RGFW_GL_HINTS[RGFW_glReleaseBehavior] == RGFW_releaseFlush) { + RGFW_GL_ADD_ATTRIB(RGFW_GL_RELEASE_BEHAVIOR, RGFW_GL_CONTEXT_RELEASE); + } else if (RGFW_GL_HINTS[RGFW_glReleaseBehavior] == RGFW_glReleaseNone) { + RGFW_GL_ADD_ATTRIB(RGFW_GL_RELEASE_BEHAVIOR, RGFW_GL_CONTEXT_NONE); + } + + i32 flags = 0; + if (RGFW_GL_HINTS[RGFW_glDebug]) flags |= RGFW_GL_DEBUG_BIT; + if (RGFW_GL_HINTS[RGFW_glRobustness]) flags |= RGFW_GL_ROBUST_BIT; + RGFW_GL_ADD_ATTRIB(RGFW_GL_FLAGS, flags); + #else + i32 accumSize = (i32)(RGFW_GL_HINTS[RGFW_glAccumRed] + RGFW_GL_HINTS[RGFW_glAccumGreen] + RGFW_GL_HINTS[RGFW_glAccumBlue] + RGFW_GL_HINTS[RGFW_glAccumAlpha]) / 4; + RGFW_GL_ADD_ATTRIB(14, accumSize); + #endif + + #ifndef RGFW_X11 + RGFW_GL_ADD_ATTRIB(RGFW_GL_SAMPLES, RGFW_GL_HINTS[RGFW_glSamples]); + #endif + + #ifdef RGFW_MACOS + if (useSoftware) { + RGFW_GL_ADD_ATTRIB(70, kCGLRendererGenericFloatID); + } else { + attribs[index] = RGFW_GL_RENDER_TYPE; + index += 1; + } + #endif + + #ifdef RGFW_MACOS + /* macOS has the surface attribs and the opengl attribs connected for some reason + maybe this is to give macOS more control to limit openGL/the opengl version? */ + + attribs[index] = 99; + attribs[index + 1] = 0x1000; + + + if (RGFW_GL_HINTS[RGFW_glMajor] >= 4 || RGFW_GL_HINTS[RGFW_glMajor] >= 3) { + attribs[index + 1] = (i32) ((RGFW_GL_HINTS[RGFW_glMajor] >= 4) ? 0x4100 : 0x3200); + } + #endif + + RGFW_GL_ADD_ATTRIB(0, 0); + + return attribs; +} + +/* EGL only (no OSMesa nor normal OPENGL) */ +#elif defined(RGFW_EGL) + +#include + +#if defined(RGFW_LINK_EGL) + typedef EGLBoolean(EGLAPIENTRY* PFN_eglInitialize)(EGLDisplay, EGLint*, EGLint*); + + PFNEGLINITIALIZEPROC eglInitializeSource; + PFNEGLGETCONFIGSPROC eglGetConfigsSource; + PFNEGLCHOOSECONFIgamepadROC eglChooseConfigSource; + PFNEGLCREATEWINDOWSURFACEPROC eglCreateWindowSurfaceSource; + PFNEGLCREATECONTEXTPROC eglCreateContextSource; + PFNEGLMAKECURRENTPROC eglMakeCurrentSource; + PFNEGLGETDISPLAYPROC eglGetDisplaySource; + PFNEGLSWAPBUFFERSPROC eglSwapBuffersSource; + PFNEGLSWAPINTERVALPROC eglSwapIntervalSource; + PFNEGLBINDAPIPROC eglBindAPISource; + PFNEGLDESTROYCONTEXTPROC eglDestroyContextSource; + PFNEGLTERMINATEPROC eglTerminateSource; + PFNEGLDESTROYSURFACEPROC eglDestroySurfaceSource; + + #define eglInitialize eglInitializeSource + #define eglGetConfigs eglGetConfigsSource + #define eglChooseConfig eglChooseConfigSource + #define eglCreateWindowSurface eglCreateWindowSurfaceSource + #define eglCreateContext eglCreateContextSource + #define eglMakeCurrent eglMakeCurrentSource + #define eglGetDisplay eglGetDisplaySource + #define eglSwapBuffers eglSwapBuffersSource + #define eglSwapInterval eglSwapIntervalSource + #define eglBindAPI eglBindAPISource + #define eglDestroyContext eglDestroyContextSource + #define eglTerminate eglTerminateSource + #define eglDestroySurface eglDestroySurfaceSource; +#endif + + +#define EGL_SURFACE_MAJOR_VERSION_KHR 0x3098 +#define EGL_SURFACE_MINOR_VERSION_KHR 0x30fb + +#ifndef RGFW_GL_ADD_ATTRIB +#define RGFW_GL_ADD_ATTRIB(attrib, attVal) \ + if (attVal) { \ + attribs[index] = attrib;\ + attribs[index + 1] = attVal;\ + index += 2;\ + } +#endif + + +void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { + RGFW_UNUSED(software); +#if defined(RGFW_LINK_EGL) + eglInitializeSource = (PFNEGLINITIALIZEPROC) eglGetProcAddress("eglInitialize"); + eglGetConfigsSource = (PFNEGLGETCONFIGSPROC) eglGetProcAddress("eglGetConfigs"); + eglChooseConfigSource = (PFNEGLCHOOSECONFIgamepadROC) eglGetProcAddress("eglChooseConfig"); + eglCreateWindowSurfaceSource = (PFNEGLCREATEWINDOWSURFACEPROC) eglGetProcAddress("eglCreateWindowSurface"); + eglCreateContextSource = (PFNEGLCREATECONTEXTPROC) eglGetProcAddress("eglCreateContext"); + eglMakeCurrentSource = (PFNEGLMAKECURRENTPROC) eglGetProcAddress("eglMakeCurrent"); + eglGetDisplaySource = (PFNEGLGETDISPLAYPROC) eglGetProcAddress("eglGetDisplay"); + eglSwapBuffersSource = (PFNEGLSWAPBUFFERSPROC) eglGetProcAddress("eglSwapBuffers"); + eglSwapIntervalSource = (PFNEGLSWAPINTERVALPROC) eglGetProcAddress("eglSwapInterval"); + eglBindAPISource = (PFNEGLBINDAPIPROC) eglGetProcAddress("eglBindAPI"); + eglDestroyContextSource = (PFNEGLDESTROYCONTEXTPROC) eglGetProcAddress("eglDestroyContext"); + eglTerminateSource = (PFNEGLTERMINATEPROC) eglGetProcAddress("eglTerminate"); + eglDestroySurfaceSource = (PFNEGLDESTROYSURFACEPROC) eglGetProcAddress("eglDestroySurface"); + + RGFW_ASSERT(eglInitializeSource != NULL && + eglGetConfigsSource != NULL && + eglChooseConfigSource != NULL && + eglCreateWindowSurfaceSource != NULL && + eglCreateContextSource != NULL && + eglMakeCurrentSource != NULL && + eglGetDisplaySource != NULL && + eglSwapBuffersSource != NULL && + eglSwapIntervalsSource != NULL && + eglBindAPISource != NULL && + eglDestroyContextSource != NULL && + eglTerminateSource != NULL && + eglDestroySurfaceSource != NULL); +#endif /* RGFW_LINK_EGL */ + +#ifdef RGFW_WAYLAND + if (RGFW_useWaylandBool) + win->src.eglWindow = wl_egl_window_create(win->src.surface, win->r.w, win->r.h); +#endif + + #ifdef RGFW_WINDOWS + win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.hdc); + #elif defined(RGFW_MACOS) + win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType)0); + #elif defined(RGFW_WAYLAND) + if (RGFW_useWaylandBool) + win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.wl_display); + else + win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.display); + #else + win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.display); + #endif + + EGLint major, minor; + + eglInitialize(win->src.EGL_display, &major, &minor); + + #ifndef EGL_OPENGL_ES1_BIT + #define EGL_OPENGL_ES1_BIT 0x1 + #endif + + EGLint egl_config[24] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, + #ifdef RGFW_OPENGL_ES1 + EGL_OPENGL_ES1_BIT, + #elif defined(RGFW_OPENGL_ES3) + EGL_OPENGL_ES3_BIT, + #elif defined(RGFW_OPENGL_ES2) + EGL_OPENGL_ES2_BIT, + #else + EGL_OPENGL_BIT, + #endif + EGL_NONE, EGL_NONE + }; + + { + size_t index = 7; + EGLint* attribs = egl_config; + + RGFW_GL_ADD_ATTRIB(EGL_RED_SIZE, RGFW_GL_HINTS[RGFW_glRed]); + RGFW_GL_ADD_ATTRIB(EGL_GREEN_SIZE, RGFW_GL_HINTS[RGFW_glBlue]); + RGFW_GL_ADD_ATTRIB(EGL_BLUE_SIZE, RGFW_GL_HINTS[RGFW_glGreen]); + RGFW_GL_ADD_ATTRIB(EGL_ALPHA_SIZE, RGFW_GL_HINTS[RGFW_glAlpha]); + RGFW_GL_ADD_ATTRIB(EGL_DEPTH_SIZE, RGFW_GL_HINTS[RGFW_glDepth]); + + if (RGFW_GL_HINTS[RGFW_glSRGB]) + RGFW_GL_ADD_ATTRIB(0x3089, RGFW_GL_HINTS[RGFW_glSRGB]); + + RGFW_GL_ADD_ATTRIB(EGL_NONE, EGL_NONE); + } + + EGLConfig config; + EGLint numConfigs; + eglChooseConfig(win->src.EGL_display, egl_config, &config, 1, &numConfigs); + + #if defined(RGFW_MACOS) + void* layer = RGFW_cocoaGetLayer(); + + RGFW_window_cocoaSetLayer(win, layer); + + win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) layer, NULL); + #elif defined(RGFW_WINDOWS) + win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.window, NULL); + #elif defined(RGFW_WAYLAND) + if (RGFW_useWaylandBool) + win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.eglWindow, NULL); + else + win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.window, NULL); + #else + win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.window, NULL); + #endif + + EGLint attribs[12]; + size_t index = 0; + +#ifdef RGFW_OPENGL_ES1 + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_CLIENT_VERSION, 1); +#elif defined(RGFW_OPENGL_ES2) + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_CLIENT_VERSION, 2); +#elif defined(RGFW_OPENGL_ES3) + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_CLIENT_VERSION, 3); +#endif + + RGFW_GL_ADD_ATTRIB(EGL_STENCIL_SIZE, RGFW_GL_HINTS[RGFW_glStencil]); + RGFW_GL_ADD_ATTRIB(EGL_SAMPLES, RGFW_GL_HINTS[RGFW_glSamples]); + + if (RGFW_GL_HINTS[RGFW_glDoubleBuffer] == 0) + RGFW_GL_ADD_ATTRIB(EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER); + + if (RGFW_GL_HINTS[RGFW_glMajor]) { + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_MAJOR_VERSION, RGFW_GL_HINTS[RGFW_glMajor]); + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_MINOR_VERSION, RGFW_GL_HINTS[RGFW_glMinor]); + + if (RGFW_GL_HINTS[RGFW_glProfile] == RGFW_glCore) { + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT); + } + else { + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT); + } + } + + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_ROBUST_ACCESS, RGFW_GL_HINTS[RGFW_glRobustness]); + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_DEBUG, RGFW_GL_HINTS[RGFW_glDebug]); + if (RGFW_GL_HINTS[RGFW_glReleaseBehavior] == RGFW_releaseFlush) { + RGFW_GL_ADD_ATTRIB(0x2097, 0x2098); + } else { + RGFW_GL_ADD_ATTRIB(0x2097, 0x0000); + } + + RGFW_GL_ADD_ATTRIB(EGL_NONE, EGL_NONE); + + #if defined(RGFW_OPENGL_ES1) || defined(RGFW_OPENGL_ES2) || defined(RGFW_OPENGL_ES3) + eglBindAPI(EGL_OPENGL_ES_API); + #else + eglBindAPI(EGL_OPENGL_API); + #endif + + win->src.EGL_context = eglCreateContext(win->src.EGL_display, config, EGL_NO_CONTEXT, attribs); + + if (win->src.EGL_context == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEGLContext, RGFW_DEBUG_CTX(win, 0), "failed to create an EGL opengl context"); + return; + } + + eglMakeCurrent(win->src.EGL_display, win->src.EGL_surface, win->src.EGL_surface, win->src.EGL_context); + eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "EGL opengl context initalized"); +} + +void RGFW_window_freeOpenGL(RGFW_window* win) { + if (win->src.EGL_display == NULL) return; + + eglDestroySurface(win->src.EGL_display, win->src.EGL_surface); + eglDestroyContext(win->src.EGL_display, win->src.EGL_context); + eglTerminate(win->src.EGL_display); + win->src.EGL_display = NULL; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "EGL opengl context freed"); +} + +void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { + if (win == NULL) + eglMakeCurrent(_RGFW.root->src.EGL_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + else { + eglMakeCurrent(win->src.EGL_display, win->src.EGL_surface, win->src.EGL_surface, win->src.EGL_context); + } +} + +void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); } + +void* RGFW_getCurrent_OpenGL(void) { return eglGetCurrentContext(); } + +#ifdef RGFW_APPLE +void* RGFWnsglFramework = NULL; +#elif defined(RGFW_WINDOWS) +HMODULE RGFW_wgl_dll = NULL; +#endif + +RGFW_proc RGFW_getProcAddress(const char* procname) { + #if defined(RGFW_WINDOWS) + RGFW_proc proc = (RGFW_proc) GetProcAddress(RGFW_wgl_dll, procname); + + if (proc) + return proc; + #endif + + return (RGFW_proc) eglGetProcAddress(procname); +} + +void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { + RGFW_ASSERT(win != NULL); + + eglSwapInterval(win->src.EGL_display, swapInterval); + +} + +#endif /* RGFW_EGL */ + +/* + end of RGFW_EGL defines +*/ +#endif /* end of RGFW_GL (OpenGL, EGL, OSMesa )*/ + +/* + RGFW_VULKAN defines +*/ +#ifdef RGFW_VULKAN +#ifdef RGFW_MACOS +#include +#endif + +const char** RGFW_getVKRequiredInstanceExtensions(size_t* count) { + static const char* arr[2] = {VK_KHR_SURFACE_EXTENSION_NAME}; + arr[1] = RGFW_VK_SURFACE; + if (count != NULL) *count = 2; + + return (const char**)arr; +} + +VkResult RGFW_window_createVKSurface(RGFW_window* win, VkInstance instance, VkSurfaceKHR* surface) { + RGFW_ASSERT(win != NULL); RGFW_ASSERT(instance); + RGFW_ASSERT(surface != NULL); + + *surface = VK_NULL_HANDLE; + +#ifdef RGFW_X11 + RGFW_GOTO_WAYLAND(0); + VkXlibSurfaceCreateInfoKHR x11 = { VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, 0, 0, (Display*) win->src.display, (Window) win->src.window }; + return vkCreateXlibSurfaceKHR(instance, &x11, NULL, surface); +#endif +#if defined(RGFW_WAYLAND) +wayland: + VkWaylandSurfaceCreateInfoKHR wayland = { VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, 0, 0, (struct wl_display*) win->src.wl_display, (struct wl_surface*) win->src.surface }; + return vkCreateWaylandSurfaceKHR(instance, &wayland, NULL, surface); +#elif defined(RGFW_WINDOWS) + VkWin32SurfaceCreateInfoKHR win32 = { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, 0, 0, GetModuleHandle(NULL), (HWND)win->src.window }; + + return vkCreateWin32SurfaceKHR(instance, &win32, NULL, surface); +#elif defined(RGFW_MACOS) && !defined(RGFW_MACOS_X11) + void* contentView = ((void* (*)(id, SEL))objc_msgSend)((id)win->src.window, sel_getUid("contentView")); + VkMacOSSurfaceCreateFlagsMVK macos = { VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK, 0, 0, win->src.display, (void*)contentView }; + + return vkCreateMacOSSurfaceMVK(instance, &macos, NULL, surface); +#endif +} + + +RGFW_bool RGFW_getVKPresentationSupport(VkInstance instance, VkPhysicalDevice physicalDevice, u32 queueFamilyIndex) { + RGFW_ASSERT(instance); + if (_RGFW.windowCount == -1) RGFW_init(); +#ifdef RGFW_X11 + RGFW_GOTO_WAYLAND(0); + Visual* visual = DefaultVisual(_RGFW.display, DefaultScreen(_RGFW.display)); + if (_RGFW.root) + visual = _RGFW.root->src.visual.visual; + + RGFW_bool out = vkGetPhysicalDeviceXlibPresentationSupportKHR(physicalDevice, queueFamilyIndex, _RGFW.display, XVisualIDFromVisual(visual)); + return out; +#endif +#if defined(RGFW_WAYLAND) +wayland: + RGFW_bool wlout = vkGetPhysicalDeviceWaylandPresentationSupportKHR(physicalDevice, queueFamilyIndex, _RGFW.wl_display); + return wlout; +#elif defined(RGFW_WINDOWS) +#elif defined(RGFW_MACOS) && !defined(RGFW_MACOS_X11) + return RGFW_FALSE; /* TODO */ +#endif +} +#endif /* end of RGFW_vulkan */ + +/* +This is where OS specific stuff starts +*/ + + +#if (defined(RGFW_WAYLAND) || defined(RGFW_X11)) && !defined(RGFW_NO_LINUX) + int RGFW_eventWait_forceStop[] = {0, 0, 0}; /* for wait events */ + + #if defined(__linux__) + #include + #include + #include + #include + + u32 RGFW_linux_updateGamepad(RGFW_window* win); + u32 RGFW_linux_updateGamepad(RGFW_window* win) { + /* check for new gamepads */ + static const char* str[] = {"/dev/input/js0", "/dev/input/js1", "/dev/input/js2", "/dev/input/js3", "/dev/input/js4", "/dev/input/js5"}; + static u8 RGFW_rawGamepads[6]; + { + u16 i; + for (i = 0; i < 6; i++) { + u16 index = RGFW_gamepadCount; + if (RGFW_rawGamepads[i]) { + struct input_id device_info; + if (ioctl(RGFW_rawGamepads[i], EVIOCGID, &device_info) == -2) { + if (errno == ENODEV) { + RGFW_rawGamepads[i] = 0; + } + } + continue; + } + + i32 js = open(str[i], O_RDONLY); + + if (js <= 0) + break; + + if (RGFW_gamepadCount >= 4) { + close(js); + break; + } + + RGFW_rawGamepads[i] = 1; + + int axes, buttons; + if (ioctl(js, JSIOCGAXES, &axes) < 0 || ioctl(js, JSIOCGBUTTONS, &buttons) < 0) { + close(js); + continue; + } + + if (buttons <= 5 || buttons >= 30) { + close(js); + continue; + } + + RGFW_gamepadCount++; + + RGFW_gamepads[index] = js; + + ioctl(js, JSIOCGNAME(sizeof(RGFW_gamepads_name[index])), RGFW_gamepads_name[index]); + RGFW_gamepads_name[index][sizeof(RGFW_gamepads_name[index]) - 1] = 0; + + u8 j; + for (j = 0; j < 16; j++) + RGFW_gamepadPressed[index][j] = (RGFW_keyState){0, 0}; + + win->event.type = RGFW_gamepadConnected; + + RGFW_gamepads_type[index] = RGFW_gamepadUnknown; + if (RGFW_STRSTR(RGFW_gamepads_name[index], "Microsoft") || RGFW_STRSTR(RGFW_gamepads_name[index], "X-Box")) + RGFW_gamepads_type[index] = RGFW_gamepadMicrosoft; + else if (RGFW_STRSTR(RGFW_gamepads_name[index], "PlayStation") || RGFW_STRSTR(RGFW_gamepads_name[index], "PS3") || RGFW_STRSTR(RGFW_gamepads_name[index], "PS4") || RGFW_STRSTR(RGFW_gamepads_name[index], "PS5")) + RGFW_gamepads_type[index] = RGFW_gamepadSony; + else if (RGFW_STRSTR(RGFW_gamepads_name[index], "Nintendo")) + RGFW_gamepads_type[index] = RGFW_gamepadNintendo; + else if (RGFW_STRSTR(RGFW_gamepads_name[index], "Logitech")) + RGFW_gamepads_type[index] = RGFW_gamepadLogitech; + + win->event.gamepad = index; + RGFW_gamepadCallback(win, index, 1); + return 1; + } + } + /* check gamepad events */ + u8 i; + + for (i = 0; i < RGFW_gamepadCount; i++) { + struct js_event e; + if (RGFW_gamepads[i] == 0) + continue; + + i32 flags = fcntl(RGFW_gamepads[i], F_GETFL, 0); + fcntl(RGFW_gamepads[i], F_SETFL, flags | O_NONBLOCK); + + ssize_t bytes; + while ((bytes = read(RGFW_gamepads[i], &e, sizeof(e))) > 0) { + switch (e.type) { + case JS_EVENT_BUTTON: { + size_t typeIndex = 0; + if (RGFW_gamepads_type[i] == RGFW_gamepadMicrosoft) typeIndex = 1; + else if (RGFW_gamepads_type[i] == RGFW_gamepadLogitech) typeIndex = 2; + + win->event.type = e.value ? RGFW_gamepadButtonPressed : RGFW_gamepadButtonReleased; + u8 RGFW_linux2RGFW[3][RGFW_gamepadR3 + 8] = {{ /* ps */ + RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadY, RGFW_gamepadX, RGFW_gamepadL1, RGFW_gamepadR1, RGFW_gamepadL2, RGFW_gamepadR2, + RGFW_gamepadSelect, RGFW_gamepadStart, RGFW_gamepadHome, RGFW_gamepadL3, RGFW_gamepadR3, RGFW_gamepadUp, RGFW_gamepadDown, RGFW_gamepadLeft, RGFW_gamepadRight, + },{ /* xbox */ + RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadX, RGFW_gamepadY, RGFW_gamepadL1, RGFW_gamepadR1, RGFW_gamepadSelect, RGFW_gamepadStart, + RGFW_gamepadHome, RGFW_gamepadL3, RGFW_gamepadR3, 255, 255, RGFW_gamepadUp, RGFW_gamepadDown, RGFW_gamepadLeft, RGFW_gamepadRight + },{ /* Logitech */ + RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadX, RGFW_gamepadY, RGFW_gamepadL1, RGFW_gamepadR1, RGFW_gamepadL2, RGFW_gamepadR2, + RGFW_gamepadSelect, RGFW_gamepadStart, RGFW_gamepadHome, RGFW_gamepadL3, RGFW_gamepadR3, RGFW_gamepadUp, RGFW_gamepadDown, RGFW_gamepadLeft, RGFW_gamepadRight + } + }; + + win->event.button = RGFW_linux2RGFW[typeIndex][e.number]; + win->event.gamepad = i; + if (win->event.button == 255) break; + + RGFW_gamepadPressed[i][win->event.button].prev = RGFW_gamepadPressed[i][win->event.button].current; + RGFW_gamepadPressed[i][win->event.button].current = RGFW_BOOL(e.value); + RGFW_gamepadButtonCallback(win, i, win->event.button, RGFW_BOOL(e.value)); + + return 1; + } + case JS_EVENT_AXIS: { + size_t axis = e.number / 2; + if (axis == 2) axis = 1; + + ioctl(RGFW_gamepads[i], JSIOCGAXES, &win->event.axisesCount); + win->event.axisesCount = 2; + + if (axis < 3) { + if (e.number == 0 || e.number == 3) + RGFW_gamepadAxes[i][axis].x = (i32)((e.value / 32767.0f) * 100); + else if (e.number == 1 || e.number == 4) { + RGFW_gamepadAxes[i][axis].y = (i32)((e.value / 32767.0f) * 100); + } + } + + win->event.axis[axis] = RGFW_gamepadAxes[i][axis]; + win->event.type = RGFW_gamepadAxisMove; + win->event.gamepad = i; + win->event.whichAxis = (u8)axis; + RGFW_gamepadAxisCallback(win, i, win->event.axis, win->event.axisesCount, win->event.whichAxis); + return 1; + } + default: break; + } + } + if (bytes == -1 && errno == ENODEV) { + RGFW_gamepadCount--; + close(RGFW_gamepads[i]); + RGFW_gamepads[i] = 0; + + win->event.type = RGFW_gamepadDisconnected; + win->event.gamepad = i; + RGFW_gamepadCallback(win, i, 0); + return 1; + } + } + return 0; + } + + #endif +#endif + + + +/* + + Start of Wayland defines + + +*/ + +#ifdef RGFW_WAYLAND +/* +Wayland TODO: (out of date) +- fix RGFW_keyPressed lock state + + RGFW_windowMoved, the window was moved (by the user) + RGFW_windowResized the window was resized (by the user), [on WASM this means the browser was resized] + RGFW_windowRefresh The window content needs to be refreshed + + RGFW_DND a file has been dropped into the window + RGFW_DNDInit + +- window args: + #define RGFW_windowNoResize the window cannot be resized by the user + #define RGFW_windowAllowDND the window supports drag and drop + #define RGFW_scaleToMonitor scale the window to the screen + +- other missing functions functions ("TODO wayland") (~30 functions) +- fix buffer rendering weird behavior +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +RGFW_window* RGFW_key_win = NULL; + +/* wayland global garbage (wayland bad, X11 is fine (ish) (not really)) */ +#include "xdg-shell.h" +#include "xdg-decoration-unstable-v1.h" + +struct xkb_context *xkb_context; +struct xkb_keymap *keymap = NULL; +struct xkb_state *xkb_state = NULL; +enum zxdg_toplevel_decoration_v1_mode client_preferred_mode, RGFW_current_mode; +struct zxdg_decoration_manager_v1 *decoration_manager = NULL; + +struct wl_cursor_theme* RGFW_wl_cursor_theme = NULL; +struct wl_surface* RGFW_cursor_surface = NULL; +struct wl_cursor_image* RGFW_cursor_image = NULL; + +void xdg_wm_base_ping_handler(void *data, + struct xdg_wm_base *wm_base, uint32_t serial) +{ + RGFW_UNUSED(data); + xdg_wm_base_pong(wm_base, serial); +} + +const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = xdg_wm_base_ping_handler, +}; + +RGFW_bool RGFW_wl_configured = 0; + +void xdg_surface_configure_handler(void *data, + struct xdg_surface *xdg_surface, uint32_t serial) +{ + RGFW_UNUSED(data); + xdg_surface_ack_configure(xdg_surface, serial); + RGFW_wl_configured = 1; +} + +const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_configure_handler, +}; + +void xdg_toplevel_configure_handler(void *data, + struct xdg_toplevel *toplevel, int32_t width, int32_t height, + struct wl_array *states) +{ + RGFW_UNUSED(data); RGFW_UNUSED(toplevel); RGFW_UNUSED(states); + RGFW_UNUSED(width); RGFW_UNUSED(height); +} + +void xdg_toplevel_close_handler(void *data, + struct xdg_toplevel *toplevel) +{ + RGFW_UNUSED(data); + RGFW_window* win = (RGFW_window*)xdg_toplevel_get_user_data(toplevel); + if (win == NULL) + win = RGFW_key_win; + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_quit, ._win = win}); + RGFW_windowQuitCallback(win); +} + +void shm_format_handler(void *data, + struct wl_shm *shm, uint32_t format) +{ + RGFW_UNUSED(data); RGFW_UNUSED(shm); RGFW_UNUSED(format); +} + +const struct wl_shm_listener shm_listener = { + .format = shm_format_handler, +}; + +const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_configure_handler, + .close = xdg_toplevel_close_handler, +}; + +RGFW_window* RGFW_mouse_win = NULL; + +void pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(serial); RGFW_UNUSED(surface_x); RGFW_UNUSED(surface_y); + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + RGFW_mouse_win = win; + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseEnter, + .point = RGFW_POINT(wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)), + ._win = win}); + + RGFW_mouseNotifyCallback(win, win->event.point, RGFW_TRUE); +} +void pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(serial); RGFW_UNUSED(surface); + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + if (RGFW_mouse_win == win) + RGFW_mouse_win = NULL; + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseLeave, + .point = win->event.point, + ._win = win}); + + RGFW_mouseNotifyCallback(win, win->event.point, RGFW_FALSE); +} +void pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(x); RGFW_UNUSED(y); + + RGFW_ASSERT(RGFW_mouse_win != NULL); + RGFW_eventQueuePush((RGFW_event){.type = RGFW_mousePosChanged, + .point = RGFW_POINT(wl_fixed_to_double(x), wl_fixed_to_double(y)), + ._win = RGFW_mouse_win}); + + RGFW_mousePosCallback(RGFW_mouse_win, RGFW_POINT(wl_fixed_to_double(x), wl_fixed_to_double(y)), RGFW_mouse_win->event.vector); +} +void pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(serial); + RGFW_ASSERT(RGFW_mouse_win != NULL); + + u32 b = (button - 0x110) + 1; + + /* flip right and middle button codes */ + if (b == 2) b = 3; + else if (b == 3) b = 2; + + RGFW_mouseButtons[b].prev = RGFW_mouseButtons[b].current; + RGFW_mouseButtons[b].current = RGFW_BOOL(state); + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonPressed + RGFW_BOOL(state), + .button = (u8)b, + ._win = RGFW_mouse_win}); + RGFW_mouseButtonCallback(RGFW_mouse_win, (u8)b, 0, RGFW_BOOL(state)); +} +void pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(axis); + RGFW_ASSERT(RGFW_mouse_win != NULL); + + double scroll = wl_fixed_to_double(value); + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonPressed, + .button = RGFW_mouseScrollUp + (scroll < 0), + .scroll = scroll, + ._win = RGFW_mouse_win}); + + RGFW_mouseButtonCallback(RGFW_mouse_win, RGFW_mouseScrollUp + (scroll < 0), scroll, 1); +} + +void RGFW_doNothing(void) { } + +void keyboard_keymap (void *data, struct wl_keyboard *keyboard, uint32_t format, int32_t fd, uint32_t size) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(format); + + char *keymap_string = mmap (NULL, size, PROT_READ, MAP_SHARED, fd, 0); + xkb_keymap_unref (keymap); + keymap = xkb_keymap_new_from_string (xkb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + + munmap (keymap_string, size); + close (fd); + xkb_state_unref (xkb_state); + xkb_state = xkb_state_new (keymap); +} +void keyboard_enter (void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(keys); + + RGFW_key_win = (RGFW_window*)wl_surface_get_user_data(surface); + + RGFW_key_win->_flags |= RGFW_windowFocus; + RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusIn, ._win = RGFW_key_win}); + RGFW_focusCallback(RGFW_key_win, RGFW_TRUE); +} +void keyboard_leave (void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); + + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + if (RGFW_key_win == win) + RGFW_key_win = NULL; + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusOut, ._win = win}); + win->_flags &= ~(u32)RGFW_windowFocus; + RGFW_focusCallback(win, RGFW_FALSE); +} +void keyboard_key (void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); + + if (RGFW_key_win == NULL) return; + + xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, key + 8); + + u32 RGFWkey = RGFW_apiKeyToRGFW(key + 8); + RGFW_keyboard[RGFWkey].prev = RGFW_keyboard[RGFWkey].current; + RGFW_keyboard[RGFWkey].current = RGFW_BOOL(state); + + RGFW_eventQueuePush((RGFW_event){.type = (u8)(RGFW_keyPressed + state), + .key = (u8)RGFWkey, + .keyChar = (u8)keysym, + .repeat = RGFW_isHeld(RGFW_key_win, (u8)RGFWkey), + ._win = RGFW_key_win}); + + RGFW_updateKeyMods(RGFW_key_win, RGFW_BOOL(xkb_keymap_mod_get_index(keymap, "Lock")), RGFW_BOOL(xkb_keymap_mod_get_index(keymap, "Mod2")), RGFW_BOOL(xkb_keymap_mod_get_index(keymap, "ScrollLock"))); + RGFW_keyCallback(RGFW_key_win, (u8)RGFWkey, (u8)keysym, RGFW_key_win->event.keyMod, RGFW_BOOL(state)); +} +void keyboard_modifiers (void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); + xkb_state_update_mask (xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); +} +struct wl_keyboard_listener keyboard_listener = {&keyboard_keymap, &keyboard_enter, &keyboard_leave, &keyboard_key, &keyboard_modifiers, (void (*)(void *, struct wl_keyboard *, +int, int))&RGFW_doNothing}; + +void seat_capabilities (void *data, struct wl_seat *seat, uint32_t capabilities) { + RGFW_UNUSED(data); + struct wl_pointer_listener pointer_listener = (struct wl_pointer_listener){&pointer_enter, &pointer_leave, &pointer_motion, &pointer_button, &pointer_axis, (void (*)(void *, struct wl_pointer *))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t, uint32_t))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t, int32_t))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t, int32_t))&RGFW_doNothing}; + + if (capabilities & WL_SEAT_CAPABILITY_POINTER) { + struct wl_pointer *pointer = wl_seat_get_pointer (seat); + wl_pointer_add_listener (pointer, &pointer_listener, NULL); + } + if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { + struct wl_keyboard *keyboard = wl_seat_get_keyboard (seat); + wl_keyboard_add_listener (keyboard, &keyboard_listener, NULL); + } +} +struct wl_seat_listener seat_listener = {&seat_capabilities, (void (*)(void *, struct wl_seat *, const char *))&RGFW_doNothing}; + +void wl_global_registry_handler(void *data, + struct wl_registry *registry, uint32_t id, const char *interface, + uint32_t version) +{ + RGFW_window* win = (RGFW_window*)data; + RGFW_UNUSED(version); + if (RGFW_STRNCMP(interface, "wl_compositor", 16) == 0) { + win->src.compositor = wl_registry_bind(registry, + id, &wl_compositor_interface, 4); + } else if (RGFW_STRNCMP(interface, "xdg_wm_base", 12) == 0) { + win->src.xdg_wm_base = wl_registry_bind(registry, + id, &xdg_wm_base_interface, 1); + } else if (RGFW_STRNCMP(interface, zxdg_decoration_manager_v1_interface.name, 255) == 0) { + decoration_manager = wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1); + } else if (RGFW_STRNCMP(interface, "wl_shm", 7) == 0) { + win->src.shm = wl_registry_bind(registry, + id, &wl_shm_interface, 1); + wl_shm_add_listener(win->src.shm, &shm_listener, NULL); + } else if (RGFW_STRNCMP(interface,"wl_seat", 8) == 0) { + win->src.seat = wl_registry_bind(registry, id, &wl_seat_interface, 1); + wl_seat_add_listener(win->src.seat, &seat_listener, NULL); + } +} + +void wl_global_registry_remove(void *data, struct wl_registry *registry, uint32_t name) { RGFW_UNUSED(data); RGFW_UNUSED(registry); RGFW_UNUSED(name); } +const struct wl_registry_listener registry_listener = { + .global = wl_global_registry_handler, + .global_remove = wl_global_registry_remove, +}; + +void decoration_handle_configure(void *data, + struct zxdg_toplevel_decoration_v1 *decoration, + enum zxdg_toplevel_decoration_v1_mode mode) { + RGFW_UNUSED(data); RGFW_UNUSED(decoration); + RGFW_current_mode = mode; +} + +const struct zxdg_toplevel_decoration_v1_listener decoration_listener = { + .configure = decoration_handle_configure, +}; + +void randname(char *buf) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + long r = ts.tv_nsec; + + int i; + for (i = 0; i < 6; ++i) { + buf[i] = (char)('A'+(r&15)+(r&16)*2); + r >>= 5; + } +} + +size_t wl_stringlen(char* name) { + size_t i = 0; + while (name[i]) { i++; } + return i; +} + +int anonymous_shm_open(void) { + char name[] = "/RGFW-wayland-XXXXXX"; + int retries = 100; + + do { + randname(name + wl_stringlen(name) - 6); + + --retries; + /* shm_open guarantees that O_CLOEXEC is set */ + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + + return -1; +} + +int create_shm_file(off_t size) { + int fd = anonymous_shm_open(); + if (fd < 0) { + return fd; + } + + if (ftruncate(fd, size) < 0) { + close(fd); + return -1; + } + + return fd; +} + +void wl_surface_frame_done(void *data, struct wl_callback *cb, uint32_t time) { + RGFW_UNUSED(data); RGFW_UNUSED(cb); RGFW_UNUSED(time); + + #ifdef RGFW_BUFFER + RGFW_window* win = (RGFW_window*)data; + wl_surface_attach(win->src.surface, win->src.wl_buffer, 0, 0); + wl_surface_damage_buffer(win->src.surface, 0, 0, win->r.w, win->r.h); + wl_surface_commit(win->src.surface); + #endif +} + +const struct wl_callback_listener wl_surface_frame_listener = { + .done = wl_surface_frame_done, +}; +#endif /* RGFW_WAYLAND */ +/* + End of Wayland defines +*/ + +/* + + +Start of Linux / Unix defines + + +*/ + +#ifdef RGFW_UNIX +#if !defined(RGFW_NO_X11_CURSOR) && defined(RGFW_X11) +#include +#endif + +#include + +#ifndef RGFW_NO_DPI +#include +#include +#endif + +#include +#include +#include +#include + +#include /* for converting keycode to string */ +#include /* for hiding */ +#include +#include +#include + +#include /* for data limits (mainly used in drag and drop functions) */ +#include + +/* atoms needed for drag and drop */ +Atom XdndAware, XtextPlain, XtextUriList; +Atom RGFW_XUTF8_STRING = 0; + +Atom wm_delete_window = 0, RGFW_XCLIPBOARD = 0; + +#if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) + typedef XcursorImage* (*PFN_XcursorImageCreate)(int, int); + typedef void (*PFN_XcursorImageDestroy)(XcursorImage*); + typedef Cursor(*PFN_XcursorImageLoadCursor)(Display*, const XcursorImage*); +#endif +#ifdef RGFW_OPENGL + typedef GLXContext(*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); +#endif + +#if !defined(RGFW_NO_X11_XI_PRELOAD) + typedef int (* PFN_XISelectEvents)(Display*,Window,XIEventMask*,int); + PFN_XISelectEvents XISelectEventsSRC = NULL; + #define XISelectEvents XISelectEventsSRC + + void* X11Xihandle = NULL; +#endif + +#if !defined(RGFW_NO_X11_EXT_PRELOAD) + typedef void (* PFN_XSyncIntToValue)(XSyncValue*, int); + PFN_XSyncIntToValue XSyncIntToValueSRC = NULL; + #define XSyncIntToValue XSyncIntToValueSRC + + typedef Status (* PFN_XSyncSetCounter)(Display*, XSyncCounter, XSyncValue); + PFN_XSyncSetCounter XSyncSetCounterSRC = NULL; + #define XSyncSetCounter XSyncSetCounterSRC + + typedef XSyncCounter (* PFN_XSyncCreateCounter)(Display*, XSyncValue); + PFN_XSyncCreateCounter XSyncCreateCounterSRC = NULL; + #define XSyncCreateCounter XSyncCreateCounterSRC + + typedef void (* PFN_XShapeCombineMask)(Display*,Window,int,int,int,Pixmap,int); + PFN_XShapeCombineMask XShapeCombineMaskSRC; + #define XShapeCombineMask XShapeCombineMaskSRC + + typedef void (* PFN_XShapeCombineRegion)(Display*,Window,int,int,int,Region,int); + PFN_XShapeCombineRegion XShapeCombineRegionSRC; + #define XShapeCombineRegion XShapeCombineRegionSRC + void* X11XEXThandle = NULL; +#endif + +#if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) + PFN_XcursorImageLoadCursor XcursorImageLoadCursorSRC = NULL; + PFN_XcursorImageCreate XcursorImageCreateSRC = NULL; + PFN_XcursorImageDestroy XcursorImageDestroySRC = NULL; + + #define XcursorImageLoadCursor XcursorImageLoadCursorSRC + #define XcursorImageCreate XcursorImageCreateSRC + #define XcursorImageDestroy XcursorImageDestroySRC + + void* X11Cursorhandle = NULL; +#endif + +const char* RGFW_instName = NULL; +void RGFW_setXInstName(const char* name) { + RGFW_instName = name; +} + +#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) +RGFW_proc RGFW_getProcAddress(const char* procname) { return (RGFW_proc) glXGetProcAddress((GLubyte*) procname); } +#endif + +void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area) { + RGFW_GOTO_WAYLAND(0); + +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + win->buffer = (u8*)buffer; + win->bufferSize = area; + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoBuffer, RGFW_DEBUG_CTX(win, 0), "createing a 4 channel buffer"); + #ifdef RGFW_X11 + #ifdef RGFW_OSMESA + win->src.ctx = OSMesaCreateContext(OSMESA_BGRA, NULL); + OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); + OSMesaPixelStore(OSMESA_Y_UP, 0); + #endif + + win->src.bitmap = XCreateImage( + win->src.display, win->src.visual.visual, (u32)win->src.visual.depth, + ZPixmap, 0, NULL, area.w, area.h, 32, 0 + ); + #endif + #ifdef RGFW_WAYLAND + wayland: {} + u32 size = (u32)(win->r.w * win->r.h * 4); + int fd = create_shm_file(size); + if (fd < 0) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, RGFW_DEBUG_CTX(win, (u32)fd),"Failed to create a buffer."); + exit(1); + } + + win->src.buffer = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (win->src.buffer == MAP_FAILED) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, RGFW_DEBUG_CTX(win, 0), "mmap failed!"); + close(fd); + exit(1); + } + + win->_flags |= RGFW_BUFFER_ALLOC; + + struct wl_shm_pool* pool = wl_shm_create_pool(win->src.shm, fd, (i32)size); + win->src.wl_buffer = wl_shm_pool_create_buffer(pool, 0, win->r.w, win->r.h, win->r.w * 4, + WL_SHM_FORMAT_ARGB8888); + wl_shm_pool_destroy(pool); + + close(fd); + + wl_surface_attach(win->src.surface, win->src.wl_buffer, 0, 0); + wl_surface_commit(win->src.surface); + + u8 color[] = {0x00, 0x00, 0x00, 0xFF}; + + size_t i; + for (i = 0; i < area.w * area.h * 4; i += 4) { + RGFW_MEMCPY(&win->buffer[i], color, 4); + } + + RGFW_MEMCPY(win->src.buffer, win->buffer, (size_t)(win->r.w * win->r.h * 4)); + + #if defined(RGFW_OSMESA) + win->src.ctx = OSMesaCreateContext(OSMESA_BGRA, NULL); + OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); + OSMesaPixelStore(OSMESA_Y_UP, 0); + #endif + #endif +#else + #ifdef RGFW_WAYLAND + wayland:{} + #endif + + RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); +#endif +} + +#define RGFW_LOAD_ATOM(name) \ + static Atom name = 0; \ + if (name == 0) name = XInternAtom(_RGFW.display, #name, False); + +void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { + RGFW_setBit(&win->_flags, RGFW_windowNoBorder, !border); + + RGFW_GOTO_WAYLAND(0); + #ifdef RGFW_X11 + RGFW_LOAD_ATOM(_MOTIF_WM_HINTS); + + struct __x11WindowHints { + unsigned long flags, functions, decorations, status; + long input_mode; + } hints; + hints.flags = 2; + hints.decorations = border; + + XChangeProperty(win->src.display, win->src.window, _MOTIF_WM_HINTS, _MOTIF_WM_HINTS, 32, + PropModeReplace, (u8*)&hints, 5 + ); + + if (RGFW_window_isHidden(win) == 0) { + RGFW_window_hide(win); + RGFW_window_show(win); + } + + #endif + #ifdef RGFW_WAYLAND + wayland: + RGFW_UNUSED(win); RGFW_UNUSED(border); + #endif +} + +void RGFW_releaseCursor(RGFW_window* win) { +RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + XUngrabPointer(win->src.display, CurrentTime); + + /* disable raw input */ + unsigned char mask[] = { 0 }; + XIEventMask em; + em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; + + XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); +#endif +#ifdef RGFW_WAYLAND + wayland: + RGFW_UNUSED(win); +#endif +} + +void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { +RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + /* enable raw input */ + unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; + XISetMask(mask, XI_RawMotion); + + XIEventMask em; + em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; + + XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); + + XGrabPointer(win->src.display, win->src.window, True, PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); + RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (i32)(r.w / 2), win->r.y + (i32)(r.h / 2))); +#endif +#ifdef RGFW_WAYLAND + wayland: + RGFW_UNUSED(win); RGFW_UNUSED(r); +#endif +} + +#define RGFW_LOAD_LIBRARY(x, lib) if (x == NULL) x = dlopen(lib, RTLD_LAZY | RTLD_LOCAL) +#define RGFW_PROC_DEF(proc, name) if (name##SRC == NULL && proc != NULL) { \ + void* ptr = dlsym(proc, #name); \ + if (ptr != NULL) memcpy(&name##SRC, &ptr, sizeof(PFN_##name)); \ +} + + + +void RGFW_window_getVisual(RGFW_window* win, RGFW_bool software) { +#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) + i32* visual_attribs = RGFW_initFormatAttribs(software); + i32 fbcount; + GLXFBConfig* fbc = glXChooseFBConfig(win->src.display, DefaultScreen(win->src.display), visual_attribs, &fbcount); + + i32 best_fbc = -1; + i32 best_depth = 0; + i32 best_samples = 0; + + if (fbcount == 0) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to find any valid GLX visual configs"); + return; + } + + i32 i; + for (i = 0; i < fbcount; i++) { + XVisualInfo* vi = glXGetVisualFromFBConfig(win->src.display, fbc[i]); + if (vi == NULL) + continue; + + i32 samp_buf, samples; + glXGetFBConfigAttrib(win->src.display, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf); + glXGetFBConfigAttrib(win->src.display, fbc[i], GLX_SAMPLES, &samples); + + if (best_fbc == -1) best_fbc = i; + if ((!(win->_flags & RGFW_windowTransparent) || vi->depth == 32) && best_depth == 0) { + best_fbc = i; + best_depth = vi->depth; + } + if ((!(win->_flags & RGFW_windowTransparent) || vi->depth == 32) && samples <= RGFW_GL_HINTS[RGFW_glSamples] && samples > best_samples) { + best_fbc = i; + best_depth = vi->depth; + best_samples = samples; + } + XFree(vi); + } + + if (best_fbc == -1) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to get a valid GLX visual"); + return; + } + + win->src.bestFbc = fbc[best_fbc]; + XVisualInfo* vi = glXGetVisualFromFBConfig(win->src.display, win->src.bestFbc); + if (vi->depth != 32 && (win->_flags & RGFW_windowTransparent)) + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, RGFW_DEBUG_CTX(win, 0), "Failed to to find a matching visual with a 32-bit depth"); + + if (best_samples < RGFW_GL_HINTS[RGFW_glSamples]) + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, RGFW_DEBUG_CTX(win, 0), "Failed to load matching sampiling"); + + XFree(fbc); + win->src.visual = *vi; +#else + RGFW_UNUSED(software); + win->src.visual.visual = DefaultVisual(win->src.display, DefaultScreen(win->src.display)); + win->src.visual.depth = DefaultDepth(win->src.display, DefaultScreen(win->src.display)); + if (win->_flags & RGFW_windowTransparent) { + XMatchVisualInfo(win->src.display, DefaultScreen(win->src.display), 32, TrueColor, &win->src.visual); /*!< for RGBA backgrounds */ + if (win->src.visual.depth != 32) + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, RGFW_DEBUG_CTX(win, 0), "Failed to load a 32-bit depth"); + } +#endif +} + +#ifndef RGFW_EGL +void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { + RGFW_UNUSED(software); +#ifdef RGFW_OPENGL + i32 context_attribs[7] = { 0, 0, 0, 0, 0, 0, 0 }; + context_attribs[0] = GLX_CONTEXT_PROFILE_MASK_ARB; + if (RGFW_GL_HINTS[RGFW_glProfile] == RGFW_glCore) + context_attribs[1] = GLX_CONTEXT_CORE_PROFILE_BIT_ARB; + else + context_attribs[1] = GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; + + if (RGFW_GL_HINTS[RGFW_glMinor] || RGFW_GL_HINTS[RGFW_glMajor]) { + context_attribs[2] = GLX_CONTEXT_MAJOR_VERSION_ARB; + context_attribs[3] = RGFW_GL_HINTS[RGFW_glMajor]; + context_attribs[4] = GLX_CONTEXT_MINOR_VERSION_ARB; + context_attribs[5] = RGFW_GL_HINTS[RGFW_glMinor]; + } + + glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0; + glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc) + glXGetProcAddressARB((GLubyte*) "glXCreateContextAttribsARB"); + + GLXContext ctx = NULL; + if (_RGFW.root != NULL && _RGFW.root != win) { + ctx = _RGFW.root->src.ctx; + RGFW_window_makeCurrent_OpenGL(_RGFW.root); + } + + if (glXCreateContextAttribsARB == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "failed to load proc address 'glXCreateContextAttribsARB', loading a generic opengl context"); + win->src.ctx = glXCreateContext(win->src.display, &win->src.visual, ctx, True); + } + else { + win->src.ctx = glXCreateContextAttribsARB(win->src.display, win->src.bestFbc, ctx, True, context_attribs); + XSync(win->src.display, False); + if (win->src.ctx == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "failed to create an opengl context with AttribsARB, loading a generic opengl context"); + win->src.ctx = glXCreateContext(win->src.display, &win->src.visual, ctx, True); + } + } + + glXMakeCurrent(win->src.display, (Drawable) win->src.window, (GLXContext) win->src.ctx); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context initalized"); +#else + RGFW_UNUSED(win); RGFW_UNUSED(software); +#endif +} + +void RGFW_window_freeOpenGL(RGFW_window* win) { +#ifdef RGFW_OPENGL + if (win->src.ctx == NULL) return; + glXDestroyContext(win->src.display, win->src.ctx); + win->src.ctx = NULL; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context freed"); +#else +RGFW_UNUSED(win); +#endif +} +#endif + + +i32 RGFW_init(void) { + RGFW_GOTO_WAYLAND(1); +#ifdef RGFW_X11 + if (_RGFW.windowCount != -1) return 0; + #ifdef RGFW_USE_XDL + XDL_init(); + #endif + + #if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) + #if defined(__CYGWIN__) + RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor-1.so"); + #elif defined(__OpenBSD__) || defined(__NetBSD__) + RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor.so"); + #else + RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor.so.1"); + #endif + RGFW_PROC_DEF(X11Cursorhandle, XcursorImageCreate); + RGFW_PROC_DEF(X11Cursorhandle, XcursorImageDestroy); + RGFW_PROC_DEF(X11Cursorhandle, XcursorImageLoadCursor); + #endif + + #if !defined(RGFW_NO_X11_XI_PRELOAD) + #if defined(__CYGWIN__) + RGFW_LOAD_LIBRARY(X11Xihandle, "libXi-6.so"); + #elif defined(__OpenBSD__) || defined(__NetBSD__) + RGFW_LOAD_LIBRARY(X11Xihandle, "libXi.so"); + #else + RGFW_LOAD_LIBRARY(X11Xihandle, "libXi.so.6"); + #endif + RGFW_PROC_DEF(X11Xihandle, XISelectEvents); + #endif + + #if !defined(RGFW_NO_X11_EXT_PRELOAD) + #if defined(__CYGWIN__) + RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext-6.so"); + #elif defined(__OpenBSD__) || defined(__NetBSD__) + RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext.so"); + #else + RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext.so.6"); + #endif + RGFW_PROC_DEF(X11XEXThandle, XSyncCreateCounter); + RGFW_PROC_DEF(X11XEXThandle, XSyncIntToValue); + RGFW_PROC_DEF(X11XEXThandle, XSyncSetCounter); + RGFW_PROC_DEF(X11XEXThandle, XShapeCombineRegion); + RGFW_PROC_DEF(X11XEXThandle, XShapeCombineMask); + #endif + + XInitThreads(); /*!< init X11 threading */ + _RGFW.display = XOpenDisplay(0); + XSetWindowAttributes wa; + wa.event_mask = PropertyChangeMask; + _RGFW.helperWindow = XCreateWindow(_RGFW.display, XDefaultRootWindow(_RGFW.display), 0, 0, 1, 1, 0, 0, + InputOnly, DefaultVisual(_RGFW.display, DefaultScreen(_RGFW.display)), CWEventMask, &wa); + + _RGFW.windowCount = 0; + u8 RGFW_blk[] = { 0, 0, 0, 0 }; + _RGFW.hiddenMouse = RGFW_loadMouse(RGFW_blk, RGFW_AREA(1, 1), 4); + _RGFW.clipboard = NULL; + + XkbComponentNamesRec rec; + XkbDescPtr desc = XkbGetMap(_RGFW.display, 0, XkbUseCoreKbd); + XkbDescPtr evdesc; + u8 old[sizeof(RGFW_keycodes) / sizeof(RGFW_keycodes[0])]; + + XkbGetNames(_RGFW.display, XkbKeyNamesMask, desc); + + memset(&rec, 0, sizeof(rec)); + rec.keycodes = (char*)"evdev"; + evdesc = XkbGetKeyboardByName(_RGFW.display, XkbUseCoreKbd, &rec, XkbGBN_KeyNamesMask, XkbGBN_KeyNamesMask, False); + /* memo: RGFW_keycodes[x11 keycode] = rgfw keycode */ + if(evdesc != NULL && desc != NULL){ + for(int i = 0; i < (int)sizeof(RGFW_keycodes) / (int)sizeof(RGFW_keycodes[0]); i++){ + old[i] = RGFW_keycodes[i]; + RGFW_keycodes[i] = 0; + } + for(int i = evdesc->min_key_code; i <= evdesc->max_key_code; i++){ + for(int j = desc->min_key_code; j <= desc->max_key_code; j++){ + if(strncmp(evdesc->names->keys[i].name, desc->names->keys[j].name, XkbKeyNameLength) == 0){ + RGFW_keycodes[j] = old[i]; + break; + } + } + } + XkbFreeKeyboard(desc, 0, True); + XkbFreeKeyboard(evdesc, 0, True); + } +#endif +#ifdef RGFW_WAYLAND +wayland: + _RGFW.wl_display = wl_display_connect(NULL); +#endif + _RGFW.windowCount = 0; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context initialized"); + return 0; +} + + +RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { + RGFW_window_basic_init(win, rect, flags); + +#ifdef RGFW_WAYLAND + win->src.compositor = NULL; +#endif + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + i64 event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | FocusChangeMask | LeaveWindowMask | EnterWindowMask | ExposureMask; /*!< X11 events accepted */ + + win->src.display = XOpenDisplay(NULL); + RGFW_window_getVisual(win, RGFW_BOOL(flags & RGFW_windowOpenglSoftware)); + + /* make X window attrubutes */ + XSetWindowAttributes swa; + Colormap cmap; + swa.colormap = cmap = XCreateColormap(win->src.display, + DefaultRootWindow(win->src.display), + win->src.visual.visual, AllocNone); + + swa.background_pixmap = None; + swa.border_pixel = 0; + swa.event_mask = event_mask; + + swa.background_pixel = 0; + + /* create the window */ + win->src.window = XCreateWindow(win->src.display, DefaultRootWindow(win->src.display), win->r.x, win->r.y, (u32)win->r.w, (u32)win->r.h, + 0, win->src.visual.depth, InputOutput, win->src.visual.visual, + CWColormap | CWBorderPixel | CWBackPixel | CWEventMask, &swa); + + XFreeColors(win->src.display, cmap, NULL, 0, 0); + + win->src.gc = XCreateGC(win->src.display, win->src.window, 0, NULL); + + /* In your .desktop app, if you set the property + StartupWMClass=RGFW that will assoicate the launcher icon + with your application - robrohan */ + if (RGFW_className == NULL) + RGFW_className = (char*)name; + + XClassHint hint; + hint.res_class = (char*)RGFW_className; + if (RGFW_instName == NULL) hint.res_name = (char*)name; + else hint.res_name = (char*)RGFW_instName; + XSetClassHint(win->src.display, win->src.window, &hint); + + #ifndef RGFW_NO_MONITOR + if (flags & RGFW_windowScaleToMonitor) + RGFW_window_scaleToMonitor(win); + #endif + XSelectInput(win->src.display, (Drawable) win->src.window, event_mask); /*!< tell X11 what events we want */ + + /* make it so the user can't close the window until the program does */ + if (wm_delete_window == 0) { + wm_delete_window = XInternAtom(win->src.display, "WM_DELETE_WINDOW", False); + RGFW_XUTF8_STRING = XInternAtom(win->src.display, "UTF8_STRING", False); + RGFW_XCLIPBOARD = XInternAtom(win->src.display, "CLIPBOARD", False); + } + + XSetWMProtocols(win->src.display, (Drawable) win->src.window, &wm_delete_window, 1); + /* set the background */ + RGFW_window_setName(win, name); + + XMoveWindow(win->src.display, (Drawable) win->src.window, win->r.x, win->r.y); /*!< move the window to it's proper cords */ + + if (flags & RGFW_windowAllowDND) { /* init drag and drop atoms and turn on drag and drop for this window */ + win->_flags |= RGFW_windowAllowDND; + + /* actions */ + XtextUriList = XInternAtom(win->src.display, "text/uri-list", False); + XtextPlain = XInternAtom(win->src.display, "text/plain", False); + XdndAware = XInternAtom(win->src.display, "XdndAware", False); + const u8 version = 5; + + XChangeProperty(win->src.display, win->src.window, + XdndAware, 4, 32, + PropModeReplace, &version, 1); /*!< turns on drag and drop */ + } + +#ifdef RGFW_ADVANCED_SMOOTH_RESIZE + RGFW_LOAD_ATOM(_NET_WM_SYNC_REQUEST_COUNTER) + RGFW_LOAD_ATOM(_NET_WM_SYNC_REQUEST) + Atom protcols[2] = {_NET_WM_SYNC_REQUEST, wm_delete_window}; + XSetWMProtocols(win->src.display, win->src.window, protcols, 2); + + XSyncValue initial_value; + XSyncIntToValue(&initial_value, 0); + win->src.counter = XSyncCreateCounter(win->src.display, initial_value); + + XChangeProperty(win->src.display, win->src.window, _NET_WM_SYNC_REQUEST_COUNTER, XA_CARDINAL, 32, PropModeReplace, (uint8_t*)&win->src.counter, 1); +#endif + + if ((flags & RGFW_windowNoInitAPI) == 0) { + RGFW_window_initOpenGL(win, RGFW_BOOL(flags & RGFW_windowOpenglSoftware)); + RGFW_window_initBuffer(win); + } + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); + RGFW_window_setMouseDefault(win); + RGFW_window_setFlags(win, flags); + + RGFW_window_show(win); + return win; /*return newly created window */ +#endif +#ifdef RGFW_WAYLAND + wayland: + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningWayland, RGFW_DEBUG_CTX(win, 0), "RGFW Wayland support is experimental"); + + win->src.wl_display = _RGFW.wl_display; + if (win->src.wl_display == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errWayland, RGFW_DEBUG_CTX(win, 0), "Failed to load Wayland display"); + #ifdef RGFW_X11 + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningWayland, RGFW_DEBUG_CTX(win, 0), "Falling back to X11"); + RGFW_useWayland(0); + return RGFW_createWindowPtr(name, rect, flags, win); + #endif + return NULL; + } + + + #ifdef RGFW_X11 + win->src.window = _RGFW.helperWindow; + XMapWindow(win->src.display, win->src.window); + XFlush(win->src.display); + if (wm_delete_window == 0) { + wm_delete_window = XInternAtom(win->src.display, "WM_DELETE_WINDOW", False); + RGFW_XUTF8_STRING = XInternAtom(win->src.display, "UTF8_STRING", False); + RGFW_XCLIPBOARD = XInternAtom(win->src.display, "CLIPBOARD", False); + } + #endif + + struct wl_registry *registry = wl_display_get_registry(win->src.wl_display); + wl_registry_add_listener(registry, ®istry_listener, win); + + wl_display_roundtrip(win->src.wl_display); + wl_display_dispatch(win->src.wl_display); + + if (win->src.compositor == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errWayland, RGFW_DEBUG_CTX(win, 0), "Can't find compositor."); + return NULL; + } + + if (RGFW_wl_cursor_theme == NULL) { + RGFW_wl_cursor_theme = wl_cursor_theme_load(NULL, 24, win->src.shm); + RGFW_cursor_surface = wl_compositor_create_surface(win->src.compositor); + + struct wl_cursor* cursor = wl_cursor_theme_get_cursor(RGFW_wl_cursor_theme, "left_ptr"); + RGFW_cursor_image = cursor->images[0]; + struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(RGFW_cursor_image); + + wl_surface_attach(RGFW_cursor_surface, cursor_buffer, 0, 0); + wl_surface_commit(RGFW_cursor_surface); + } + + xdg_wm_base_add_listener(win->src.xdg_wm_base, &xdg_wm_base_listener, NULL); + + xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + win->src.surface = wl_compositor_create_surface(win->src.compositor); + wl_surface_set_user_data(win->src.surface, win); + + win->src.xdg_surface = xdg_wm_base_get_xdg_surface(win->src.xdg_wm_base, win->src.surface); + xdg_surface_add_listener(win->src.xdg_surface, &xdg_surface_listener, NULL); + + xdg_wm_base_set_user_data(win->src.xdg_wm_base, win); + + win->src.xdg_toplevel = xdg_surface_get_toplevel(win->src.xdg_surface); + xdg_toplevel_set_user_data(win->src.xdg_toplevel, win); + xdg_toplevel_add_listener(win->src.xdg_toplevel, &xdg_toplevel_listener, NULL); + + xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->r.w, win->r.h); + + if (!(flags & RGFW_windowNoBorder)) { + win->src.decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( + decoration_manager, win->src.xdg_toplevel); + } + + wl_display_roundtrip(win->src.wl_display); + + wl_surface_commit(win->src.surface); + RGFW_window_show(win); + + /* wait for the surface to be configured */ + while (wl_display_dispatch(win->src.wl_display) != -1 && !RGFW_wl_configured) { } + + if ((flags & RGFW_windowNoInitAPI) == 0) { + RGFW_window_initOpenGL(win, RGFW_BOOL(flags & RGFW_windowOpenglSoftware)); + RGFW_window_initBuffer(win); + } + struct wl_callback* callback = wl_surface_frame(win->src.surface); + wl_callback_add_listener(callback, &wl_surface_frame_listener, win); + wl_surface_commit(win->src.surface); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); + + #ifndef RGFW_NO_MONITOR + if (flags & RGFW_windowScaleToMonitor) + RGFW_window_scaleToMonitor(win); + #endif + + RGFW_window_setMouseDefault(win); + RGFW_window_setFlags(win, flags); + return win; /* return newly created window */ +#endif +} + +RGFW_area RGFW_getScreenSize(void) { + RGFW_GOTO_WAYLAND(1); + RGFW_init(); + + #ifdef RGFW_X11 + Screen* scrn = DefaultScreenOfDisplay(_RGFW.display); + return RGFW_AREA(scrn->width, scrn->height); + #endif + #ifdef RGFW_WAYLAND + wayland: return RGFW_AREA(_RGFW.root->r.w, _RGFW.root->r.h); /* TODO */ + #endif +} + +RGFW_point RGFW_getGlobalMousePoint(void) { + RGFW_init(); + RGFW_point RGFWMouse; + + i32 x, y; + u32 z; + Window window1, window2; + XQueryPointer(_RGFW.display, XDefaultRootWindow(_RGFW.display), &window1, &window2, &RGFWMouse.x, &RGFWMouse.y, &x, &y, &z); + + return RGFWMouse; +} + +RGFWDEF void RGFW_XHandleClipboardSelection(XEvent* event); +void RGFW_XHandleClipboardSelection(XEvent* event) { + RGFW_LOAD_ATOM(ATOM_PAIR); + RGFW_LOAD_ATOM(MULTIPLE); + RGFW_LOAD_ATOM(TARGETS); + RGFW_LOAD_ATOM(SAVE_TARGETS); + + const XSelectionRequestEvent* request = &event->xselectionrequest; + const Atom formats[] = { RGFW_XUTF8_STRING, XA_STRING }; + const int formatCount = sizeof(formats) / sizeof(formats[0]); + + if (request->target == TARGETS) { + const Atom targets[] = { TARGETS, MULTIPLE, RGFW_XUTF8_STRING, XA_STRING }; + + XChangeProperty(_RGFW.display, request->requestor, request->property, + XA_ATOM, 32, PropModeReplace, (u8*) targets, sizeof(targets) / sizeof(Atom)); + } else if (request->target == MULTIPLE) { + Atom* targets = NULL; + + Atom actualType = 0; + int actualFormat = 0; + unsigned long count = 0, bytesAfter = 0; + + XGetWindowProperty(_RGFW.display, request->requestor, request->property, 0, LONG_MAX, + False, ATOM_PAIR, &actualType, &actualFormat, &count, &bytesAfter, (u8**) &targets); + + unsigned long i; + for (i = 0; i < (u32)count; i += 2) { + if (targets[i] == RGFW_XUTF8_STRING || targets[i] == XA_STRING) + XChangeProperty(_RGFW.display, request->requestor, targets[i + 1], targets[i], + 8, PropModeReplace, (const unsigned char *)_RGFW.clipboard, (i32)_RGFW.clipboard_len); + else + targets[i + 1] = None; + } + + XChangeProperty(_RGFW.display, + request->requestor, request->property, ATOM_PAIR, 32, + PropModeReplace, (u8*) targets, (i32)count); + + XFlush(_RGFW.display); + XFree(targets); + } else if (request->target == SAVE_TARGETS) + XChangeProperty(_RGFW.display, request->requestor, request->property, 0, 32, PropModeReplace, NULL, 0); + else { + int i; + for (i = 0; i < formatCount; i++) { + if (request->target != formats[i]) + continue; + XChangeProperty(_RGFW.display, request->requestor, request->property, request->target, + 8, PropModeReplace, (u8*) _RGFW.clipboard, (i32)_RGFW.clipboard_len); + } + } + + XEvent reply = { SelectionNotify }; + reply.xselection.property = request->property; + reply.xselection.display = request->display; + reply.xselection.requestor = request->requestor; + reply.xselection.selection = request->selection; + reply.xselection.target = request->target; + reply.xselection.time = request->time; + + XSendEvent(_RGFW.display, request->requestor, False, 0, &reply); +} + +char* RGFW_strtok(char* str, const char* delimStr); +char* RGFW_strtok(char* str, const char* delimStr) { + static char* static_str = NULL; + + if (str != NULL) + static_str = str; + + if (static_str == NULL) { + return NULL; + } + + while (*static_str != '\0') { + RGFW_bool delim = 0; + const char* d; + for (d = delimStr; *d != '\0'; d++) { + if (*static_str == *d) { + delim = 1; + break; + } + } + if (!delim) + break; + static_str++; + } + + if (*static_str == '\0') + return NULL; + + char* token_start = static_str; + while (*static_str != '\0') { + int delim = 0; + const char* d; + for (d = delimStr; *d != '\0'; d++) { + if (*static_str == *d) { + delim = 1; + break; + } + } + + if (delim) { + *static_str = '\0'; + static_str++; + break; + } + static_str++; + } + + return token_start; +} + +i32 RGFW_XHandleClipboardSelectionHelper(void); + +RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { + RGFW_XHandleClipboardSelectionHelper(); + + if (win == NULL || ((win->_flags & RGFW_windowFreeOnClose) && (win->_flags & RGFW_EVENT_QUIT))) return NULL; + RGFW_event* ev = RGFW_window_checkEventCore(win); + if (ev) return ev; + + #if defined(__linux__) && !defined(RGFW_NO_LINUX) + if (RGFW_linux_updateGamepad(win)) return &win->event; + #endif + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + RGFW_LOAD_ATOM(XdndTypeList); + RGFW_LOAD_ATOM(XdndSelection); + RGFW_LOAD_ATOM(XdndEnter); + RGFW_LOAD_ATOM(XdndPosition); + RGFW_LOAD_ATOM(XdndStatus); + RGFW_LOAD_ATOM(XdndLeave); + RGFW_LOAD_ATOM(XdndDrop); + RGFW_LOAD_ATOM(XdndFinished); + RGFW_LOAD_ATOM(XdndActionCopy); + RGFW_LOAD_ATOM(_NET_WM_SYNC_REQUEST); + RGFW_LOAD_ATOM(WM_PROTOCOLS); + XPending(win->src.display); + + XEvent E; /*!< raw X11 event */ + + /* if there is no unread qued events, get a new one */ + if ((QLength(win->src.display) || XEventsQueued(win->src.display, QueuedAlready) + XEventsQueued(win->src.display, QueuedAfterReading)) + && win->event.type != RGFW_quit + ) + XNextEvent(win->src.display, &E); + else { + return NULL; + } + + win->event.type = 0; + + /* xdnd data */ + static Window source = 0; + static long version = 0; + static i32 format = 0; + + XEvent reply = { ClientMessage }; + + switch (E.type) { + case KeyPress: + case KeyRelease: { + win->event.repeat = RGFW_FALSE; + /* check if it's a real key release */ + if (E.type == KeyRelease && XEventsQueued(win->src.display, QueuedAfterReading)) { /* get next event if there is one */ + XEvent NE; + XPeekEvent(win->src.display, &NE); + + if (E.xkey.time == NE.xkey.time && E.xkey.keycode == NE.xkey.keycode) /* check if the current and next are both the same */ + win->event.repeat = RGFW_TRUE; + } + + /* set event key data */ + win->event.key = (u8)RGFW_apiKeyToRGFW(E.xkey.keycode); + KeySym sym = (KeySym)XkbKeycodeToKeysym(win->src.display, (KeyCode)E.xkey.keycode, 0, (KeyCode)E.xkey.state & ShiftMask ? 1 : 0); + + if ((E.xkey.state & LockMask) && sym >= XK_a && sym <= XK_z) + sym = (E.xkey.state & ShiftMask) ? sym + 32 : sym - 32; + if ((u8)sym != (u32)sym) + sym = 0; + + win->event.keyChar = (u8)sym; + + RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; + + /* get keystate data */ + win->event.type = (E.type == KeyPress) ? RGFW_keyPressed : RGFW_keyReleased; + + XKeyboardState keystate; + XGetKeyboardControl(win->src.display, &keystate); + + RGFW_keyboard[win->event.key].current = (E.type == KeyPress); + + XkbStateRec state; + XkbGetState(win->src.display, XkbUseCoreKbd, &state); + RGFW_updateKeyMods(win, (state.locked_mods & LockMask), (state.locked_mods & Mod2Mask), (state.locked_mods & Mod3Mask)); + + RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, (E.type == KeyPress)); + break; + } + case ButtonPress: + case ButtonRelease: + if (E.xbutton.button > RGFW_mouseFinal) { /* skip this event */ + XFlush(win->src.display); + return RGFW_window_checkEvent(win); + } + + win->event.type = RGFW_mouseButtonPressed + (E.type == ButtonRelease); /* the events match */ + win->event.button = (u8)(E.xbutton.button - 1); + switch(win->event.button) { + case RGFW_mouseScrollUp: + win->event.scroll = 1; + break; + case RGFW_mouseScrollDown: + win->event.scroll = -1; + break; + default: break; + } + + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + + if (win->event.repeat == RGFW_FALSE) + win->event.repeat = RGFW_isPressed(win, win->event.key); + + RGFW_mouseButtons[win->event.button].current = (E.type == ButtonPress); + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, (E.type == ButtonPress)); + break; + + case MotionNotify: + win->event.point.x = E.xmotion.x; + win->event.point.y = E.xmotion.y; + + win->event.vector.x = win->event.point.x - win->_lastMousePoint.x; + win->event.vector.y = win->event.point.y - win->_lastMousePoint.y; + win->_lastMousePoint = win->event.point; + + win->event.type = RGFW_mousePosChanged; + RGFW_mousePosCallback(win, win->event.point, win->event.vector); + break; + + case GenericEvent: { + /* MotionNotify is used for mouse events if the mouse isn't held */ + if (!(win->_flags & RGFW_HOLD_MOUSE)) { + XFreeEventData(win->src.display, &E.xcookie); + break; + } + + XGetEventData(win->src.display, &E.xcookie); + if (E.xcookie.evtype == XI_RawMotion) { + XIRawEvent *raw = (XIRawEvent *)E.xcookie.data; + if (raw->valuators.mask_len == 0) { + XFreeEventData(win->src.display, &E.xcookie); + break; + } + + double deltaX = 0.0f; + double deltaY = 0.0f; + + /* check if relative motion data exists where we think it does */ + if (XIMaskIsSet(raw->valuators.mask, 0) != 0) + deltaX += raw->raw_values[0]; + if (XIMaskIsSet(raw->valuators.mask, 1) != 0) + deltaY += raw->raw_values[1]; + + win->event.vector = RGFW_POINT((i32)deltaX, (i32)deltaY); + win->event.point.x = win->_lastMousePoint.x + win->event.vector.x; + win->event.point.y = win->_lastMousePoint.y + win->event.vector.y; + win->_lastMousePoint = win->event.point; + + RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); + + win->event.type = RGFW_mousePosChanged; + RGFW_mousePosCallback(win, win->event.point, win->event.vector); + } + + XFreeEventData(win->src.display, &E.xcookie); + break; + } + + case Expose: { + win->event.type = RGFW_windowRefresh; + RGFW_windowRefreshCallback(win); + +#ifdef RGFW_ADVANCED_SMOOTH_RESIZE + XSyncValue value; + XSyncIntToValue(&value, (i32)win->src.counter_value); + XSyncSetCounter(win->src.display, win->src.counter, value); +#endif + break; + } + case MapNotify: case UnmapNotify: RGFW_window_checkMode(win); break; + case ClientMessage: { + /* if the client closed the window */ + if (E.xclient.data.l[0] == (long)wm_delete_window) { + win->event.type = RGFW_quit; + RGFW_window_setShouldClose(win, RGFW_TRUE); + RGFW_windowQuitCallback(win); + break; + } +#ifdef RGFW_ADVANCED_SMOOTH_RESIZE + if (E.xclient.message_type == WM_PROTOCOLS && (Atom)E.xclient.data.l[0] == _NET_WM_SYNC_REQUEST) { + RGFW_windowRefreshCallback(win); + win->src.counter_value = 0; + win->src.counter_value |= E.xclient.data.l[2]; + win->src.counter_value |= (E.xclient.data.l[3] << 32); + + XSyncValue value; + XSyncIntToValue(&value, (i32)win->src.counter_value); + XSyncSetCounter(win->src.display, win->src.counter, value); + break; + } +#endif + if ((win->_flags & RGFW_windowAllowDND) == 0) + break; + + reply.xclient.window = source; + reply.xclient.format = 32; + reply.xclient.data.l[0] = (long)win->src.window; + reply.xclient.data.l[1] = 0; + reply.xclient.data.l[2] = None; + + if (E.xclient.message_type == XdndEnter) { + if (version > 5) + break; + + unsigned long count; + Atom* formats; + Atom real_formats[6]; + Bool list = E.xclient.data.l[1] & 1; + + source = (unsigned long int)E.xclient.data.l[0]; + version = E.xclient.data.l[1] >> 24; + format = None; + if (list) { + Atom actualType; + i32 actualFormat; + unsigned long bytesAfter; + + XGetWindowProperty( + win->src.display, source, XdndTypeList, + 0, LONG_MAX, False, 4, + &actualType, &actualFormat, &count, &bytesAfter, (u8**)&formats + ); + } else { + count = 0; + + size_t i; + for (i = 2; i < 5; i++) { + if (E.xclient.data.l[i] != None) { + real_formats[count] = (unsigned long int)E.xclient.data.l[i]; + count += 1; + } + } + + formats = real_formats; + } + + size_t i; + for (i = 0; i < count; i++) { + if (formats[i] == XtextUriList || formats[i] == XtextPlain) { + format = (int)formats[i]; + break; + } + } + + if (list) { + XFree(formats); + } + + break; + } + + if (E.xclient.message_type == XdndPosition) { + const i32 xabs = (E.xclient.data.l[2] >> 16) & 0xffff; + const i32 yabs = (E.xclient.data.l[2]) & 0xffff; + Window dummy; + i32 xpos, ypos; + + if (version > 5) + break; + + XTranslateCoordinates( + win->src.display, XDefaultRootWindow(win->src.display), win->src.window, + xabs, yabs, &xpos, &ypos, &dummy + ); + + win->event.point.x = xpos; + win->event.point.y = ypos; + + reply.xclient.window = source; + reply.xclient.message_type = XdndStatus; + + if (format) { + reply.xclient.data.l[1] = 1; + if (version >= 2) + reply.xclient.data.l[4] = (long)XdndActionCopy; + } + + XSendEvent(win->src.display, source, False, NoEventMask, &reply); + XFlush(win->src.display); + break; + } + if (E.xclient.message_type != XdndDrop) + break; + + if (version > 5) + break; + + size_t i; + for (i = 0; i < win->event.droppedFilesCount; i++) + win->event.droppedFiles[i][0] = '\0'; + + win->event.droppedFilesCount = 0; + + + win->event.type = RGFW_DNDInit; + + if (format) { + Time time = (version >= 1) + ? (Time)E.xclient.data.l[2] + : CurrentTime; + + XConvertSelection( + win->src.display, XdndSelection, (Atom)format, + XdndSelection, win->src.window, time + ); + } else if (version >= 2) { + XEvent new_reply = { ClientMessage }; + + XSendEvent(win->src.display, source, False, NoEventMask, &new_reply); + XFlush(win->src.display); + } + + RGFW_dndInitCallback(win, win->event.point); + } break; + case SelectionRequest: + RGFW_XHandleClipboardSelection(&E); + XFlush(win->src.display); + return RGFW_window_checkEvent(win); + case SelectionNotify: { + /* this is only for checking for xdnd drops */ + if (E.xselection.property != XdndSelection || !(win->_flags & RGFW_windowAllowDND)) + break; + char* data; + unsigned long result; + + Atom actualType; + i32 actualFormat; + unsigned long bytesAfter; + + XGetWindowProperty(win->src.display, E.xselection.requestor, E.xselection.property, 0, LONG_MAX, False, E.xselection.target, &actualType, &actualFormat, &result, &bytesAfter, (u8**) &data); + + if (result == 0) + break; + + const char* prefix = (const char*)"file://"; + + char* line; + + win->event.droppedFilesCount = 0; + win->event.type = RGFW_DND; + + while ((line = (char*)RGFW_strtok(data, "\r\n"))) { + char path[RGFW_MAX_PATH]; + + data = NULL; + + if (line[0] == '#') + continue; + + char* l; + for (l = line; 1; l++) { + if ((l - line) > 7) + break; + else if (*l != prefix[(l - line)]) + break; + else if (*l == '\0' && prefix[(l - line)] == '\0') { + line += 7; + while (*line != '/') + line++; + break; + } else if (*l == '\0') + break; + } + + win->event.droppedFilesCount++; + + size_t index = 0; + while (*line) { + if (line[0] == '%' && line[1] && line[2]) { + const char digits[3] = { line[1], line[2], '\0' }; + path[index] = (char) RGFW_STRTOL(digits, NULL, 16); + line += 2; + } else + path[index] = *line; + + index++; + line++; + } + path[index] = '\0'; + RGFW_MEMCPY(win->event.droppedFiles[win->event.droppedFilesCount - 1], path, index + 1); + } + + RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); + if (data) + XFree(data); + + if (version >= 2) { + XEvent new_reply = { ClientMessage }; + new_reply.xclient.format = 32; + new_reply.xclient.message_type = XdndFinished; + new_reply.xclient.data.l[1] = (long int)result; + new_reply.xclient.data.l[2] = (long int)XdndActionCopy; + XSendEvent(win->src.display, source, False, NoEventMask, &new_reply); + XFlush(win->src.display); + } + break; + } + case FocusIn: + if ((win->_flags & RGFW_windowFullscreen)) + XMapRaised(win->src.display, win->src.window); + + win->_flags |= RGFW_windowFocus; + win->event.type = RGFW_focusIn; + RGFW_focusCallback(win, 1); + break; + case FocusOut: + if ((win->_flags & RGFW_windowFullscreen)) + RGFW_window_minimize(win); + + win->_flags &= ~(u32)RGFW_windowFocus; + win->event.type = RGFW_focusOut; + RGFW_focusCallback(win, 0); + break; + case PropertyNotify: RGFW_window_checkMode(win); break; + case EnterNotify: { + win->event.type = RGFW_mouseEnter; + win->event.point.x = E.xcrossing.x; + win->event.point.y = E.xcrossing.y; + RGFW_mouseNotifyCallback(win, win->event.point, 1); + break; + } + + case LeaveNotify: { + win->event.type = RGFW_mouseLeave; + RGFW_mouseNotifyCallback(win, win->event.point, 0); + break; + } + + case ConfigureNotify: { + /* detect resize */ + RGFW_window_checkMode(win); + if (E.xconfigure.width != win->r.w || E.xconfigure.height != win->r.h) { + win->event.type = RGFW_windowResized; + win->r = RGFW_RECT(win->r.x, win->r.y, E.xconfigure.width, E.xconfigure.height); + RGFW_windowResizedCallback(win, win->r); + break; + } + + /* detect move */ + if (E.xconfigure.x != win->r.x || E.xconfigure.y != win->r.y) { + win->event.type = RGFW_windowMoved; + win->r = RGFW_RECT(E.xconfigure.x, E.xconfigure.y, win->r.w, win->r.h); + RGFW_windowMovedCallback(win, win->r); + break; + } + + break; + } + default: + XFlush(win->src.display); + return RGFW_window_checkEvent(win); + } + XFlush(win->src.display); + if (win->event.type) return &win->event; + else return NULL; +#endif +#ifdef RGFW_WAYLAND + wayland: + if ((win->_flags & RGFW_windowHide) == 0) + wl_display_roundtrip(win->src.wl_display); + return NULL; +#endif +} + +void RGFW_window_move(RGFW_window* win, RGFW_point v) { + RGFW_ASSERT(win != NULL); + win->r.x = v.x; + win->r.y = v.y; + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + XMoveWindow(win->src.display, win->src.window, v.x, v.y); +#endif +#ifdef RGFW_WAYLAND + wayland: + RGFW_ASSERT(win != NULL); + + if (win->src.compositor) { + struct wl_pointer *pointer = wl_seat_get_pointer(win->src.seat); + if (!pointer) { + return; + } + + wl_display_flush(win->src.wl_display); + } +#endif +} + + +void RGFW_window_resize(RGFW_window* win, RGFW_area a) { + RGFW_ASSERT(win != NULL); + win->r.w = (i32)a.w; + win->r.h = (i32)a.h; + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + XResizeWindow(win->src.display, win->src.window, a.w, a.h); + + if ((win->_flags & RGFW_windowNoResize)) { + XSizeHints sh; + sh.flags = (1L << 4) | (1L << 5); + sh.min_width = sh.max_width = (i32)a.w; + sh.min_height = sh.max_height = (i32)a.h; + + XSetWMSizeHints(win->src.display, (Drawable) win->src.window, &sh, XA_WM_NORMAL_HINTS); + } +#endif +#ifdef RGFW_WAYLAND + wayland: + if (win->src.compositor) { + xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->r.w, win->r.h); + #ifdef RGFW_OPENGL + wl_egl_window_resize(win->src.eglWindow, (i32)a.w, (i32)a.h, 0, 0); + #endif + } +#endif +} + +void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { + RGFW_ASSERT(win != NULL); + + if (a.w == 0 && a.h == 0) + return; + + XSizeHints hints; + long flags; + + XGetWMNormalHints(win->src.display, win->src.window, &hints, &flags); + + hints.flags |= PAspect; + + hints.min_aspect.x = hints.max_aspect.x = (i32)a.w; + hints.min_aspect.y = hints.max_aspect.y = (i32)a.h; + + XSetWMNormalHints(win->src.display, win->src.window, &hints); +} + +void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { + RGFW_ASSERT(win != NULL); + + if (a.w == 0 && a.h == 0) + return; + + XSizeHints hints; + long flags; + + XGetWMNormalHints(win->src.display, win->src.window, &hints, &flags); + + hints.flags |= PMinSize; + + hints.min_width = (i32)a.w; + hints.min_height = (i32)a.h; + + XSetWMNormalHints(win->src.display, win->src.window, &hints); +} + +void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { + RGFW_ASSERT(win != NULL); + + if (a.w == 0 && a.h == 0) + a = RGFW_getScreenSize(); + + XSizeHints hints; + long flags; + + XGetWMNormalHints(win->src.display, win->src.window, &hints, &flags); + + hints.flags |= PMaxSize; + + hints.max_width = (i32)a.w; + hints.max_height = (i32)a.h; + + XSetWMNormalHints(win->src.display, win->src.window, &hints); +} + +void RGFW_toggleXMaximized(RGFW_window* win, RGFW_bool maximized); +void RGFW_toggleXMaximized(RGFW_window* win, RGFW_bool maximized) { + RGFW_ASSERT(win != NULL); + RGFW_LOAD_ATOM(_NET_WM_STATE); + RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_VERT); + RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ); + + XEvent xev = {0}; + xev.type = ClientMessage; + xev.xclient.window = win->src.window; + xev.xclient.message_type = _NET_WM_STATE; + xev.xclient.format = 32; + xev.xclient.data.l[0] = maximized; + xev.xclient.data.l[1] = (long int)_NET_WM_STATE_MAXIMIZED_HORZ; + xev.xclient.data.l[2] = (long int)_NET_WM_STATE_MAXIMIZED_VERT; + xev.xclient.data.l[3] = 0; + xev.xclient.data.l[4] = 0; + + XSendEvent(win->src.display, DefaultRootWindow(win->src.display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); +} + +void RGFW_window_maximize(RGFW_window* win) { + win->_oldRect = win->r; + RGFW_toggleXMaximized(win, 1); +} + +void RGFW_window_focus(RGFW_window* win) { + RGFW_ASSERT(win); + + XWindowAttributes attr; + XGetWindowAttributes(win->src.display, win->src.window, &attr); + if (attr.map_state != IsViewable) return; + + XSetInputFocus(win->src.display, win->src.window, RevertToPointerRoot, CurrentTime); + XFlush(win->src.display); +} + +void RGFW_window_raise(RGFW_window* win) { + RGFW_ASSERT(win); + XRaiseWindow(win->src.display, win->src.window); + XMapRaised(win->src.display, win->src.window); +} + +void RGFW_window_setXAtom(RGFW_window* win, Atom netAtom, RGFW_bool fullscreen); +void RGFW_window_setXAtom(RGFW_window* win, Atom netAtom, RGFW_bool fullscreen) { + RGFW_ASSERT(win != NULL); + RGFW_LOAD_ATOM(_NET_WM_STATE); + + XEvent xev = {0}; + xev.xclient.type = ClientMessage; + xev.xclient.serial = 0; + xev.xclient.send_event = True; + xev.xclient.message_type = _NET_WM_STATE; + xev.xclient.window = win->src.window; + xev.xclient.format = 32; + xev.xclient.data.l[0] = fullscreen; + xev.xclient.data.l[1] = (long int)netAtom; + xev.xclient.data.l[2] = 0; + + XSendEvent(win->src.display, DefaultRootWindow(win->src.display), False, SubstructureNotifyMask | SubstructureRedirectMask, &xev); +} + +void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { + RGFW_ASSERT(win != NULL); + if (fullscreen) { + win->_flags |= RGFW_windowFullscreen; + win->_oldRect = win->r; + } + else win->_flags &= ~(u32)RGFW_windowFullscreen; + + RGFW_LOAD_ATOM(_NET_WM_STATE_FULLSCREEN); + + RGFW_window_setXAtom(win, _NET_WM_STATE_FULLSCREEN, fullscreen); + + XRaiseWindow(win->src.display, win->src.window); + XMapRaised(win->src.display, win->src.window); +} + +void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { + RGFW_ASSERT(win != NULL); + + RGFW_LOAD_ATOM(_NET_WM_STATE_ABOVE); + RGFW_window_setXAtom(win, _NET_WM_STATE_ABOVE, floating); +} + +void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { + RGFW_ASSERT(win != NULL); + const u32 value = (u32) (0xffffffffu * (double) opacity); + RGFW_LOAD_ATOM(NET_WM_WINDOW_OPACITY); + XChangeProperty(win->src.display, win->src.window, + NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) &value, 1); +} + +void RGFW_window_minimize(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + if (RGFW_window_isMaximized(win)) return; + + win->_oldRect = win->r; + XIconifyWindow(win->src.display, win->src.window, DefaultScreen(win->src.display)); + XFlush(win->src.display); +} + +void RGFW_window_restore(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_toggleXMaximized(win, 0); + + win->r = win->_oldRect; + RGFW_window_move(win, RGFW_POINT(win->r.x, win->r.y)); + RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h)); + + RGFW_window_show(win); + XFlush(win->src.display); +} + +RGFW_bool RGFW_window_isFloating(RGFW_window* win) { + RGFW_LOAD_ATOM(_NET_WM_STATE); + RGFW_LOAD_ATOM(_NET_WM_STATE_ABOVE); + + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + Atom* prop_return = NULL; + + int status = XGetWindowProperty(win->src.display, win->src.window, _NET_WM_STATE, 0, (~0L), False, XA_ATOM, + &actual_type, &actual_format, &nitems, &bytes_after, + (unsigned char **)&prop_return); + + if (status != Success || actual_type != XA_ATOM) + return RGFW_FALSE; + + unsigned long i; + for (i = 0; i < nitems; i++) + if (prop_return[i] == _NET_WM_STATE_ABOVE) return RGFW_TRUE; + + if (prop_return) + XFree(prop_return); + + return RGFW_FALSE; +} + +void RGFW_window_setName(RGFW_window* win, const char* name) { + RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(0); + #ifdef RGFW_X11 + XStoreName(win->src.display, win->src.window, name); + + RGFW_LOAD_ATOM(_NET_WM_NAME); + XChangeProperty( + win->src.display, win->src.window, _NET_WM_NAME, RGFW_XUTF8_STRING, + 8, PropModeReplace, (u8*)name, 256 + ); + #endif + #ifdef RGFW_WAYLAND + wayland: + if (win->src.compositor) + xdg_toplevel_set_title(win->src.xdg_toplevel, name); + #endif +} + +#ifndef RGFW_NO_PASSTHROUGH +void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { + RGFW_ASSERT(win != NULL); + if (passthrough) { + Region region = XCreateRegion(); + XShapeCombineRegion(win->src.display, win->src.window, ShapeInput, 0, 0, region, ShapeSet); + XDestroyRegion(region); + + return; + } + + XShapeCombineMask(win->src.display, win->src.window, ShapeInput, 0, 0, None, ShapeSet); +} +#endif /* RGFW_NO_PASSTHROUGH */ + +RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* icon, RGFW_area a, i32 channels, u8 type) { + RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + RGFW_LOAD_ATOM(_NET_WM_ICON); + if (icon == NULL || (channels != 3 && channels != 4)) { + RGFW_bool res = (RGFW_bool)XChangeProperty( + win->src.display, win->src.window, _NET_WM_ICON, XA_CARDINAL, 32, + PropModeReplace, (u8*)NULL, 0 + ); + return res; + } + + i32 count = (i32)(2 + (a.w * a.h)); + + unsigned long* data = (unsigned long*) RGFW_ALLOC((u32)count * sizeof(unsigned long)); + RGFW_ASSERT(data != NULL); + + data[0] = (unsigned long)a.w; + data[1] = (unsigned long)a.h; + + unsigned long* target = &data[2]; + u32 x, y; + + for (x = 0; x < a.w; x++) { + for (y = 0; y < a.h; y++) { + size_t i = y * a.w + x; + u32 alpha = (channels == 4) ? icon[i * 4 + 3] : 0xFF; + + target[i] = (unsigned long)((icon[i * 4 + 0]) << 16) | + (unsigned long)((icon[i * 4 + 1]) << 8) | + (unsigned long)((icon[i * 4 + 2]) << 0) | + (unsigned long)(alpha << 24); + } + } + + RGFW_bool res = RGFW_TRUE; + if (type & RGFW_iconTaskbar) { + res = (RGFW_bool)XChangeProperty( + win->src.display, win->src.window, _NET_WM_ICON, XA_CARDINAL, 32, + PropModeReplace, (u8*)data, count + ); + } + + if (type & RGFW_iconWindow) { + XWMHints wm_hints; + wm_hints.flags = IconPixmapHint; + + i32 depth = DefaultDepth(win->src.display, DefaultScreen(win->src.display)); + XImage *image = XCreateImage(win->src.display, DefaultVisual(win->src.display, DefaultScreen(win->src.display)), + (u32)depth, ZPixmap, 0, (char *)target, a.w, a.h, 32, 0); + + wm_hints.icon_pixmap = XCreatePixmap(win->src.display, win->src.window, a.w, a.h, (u32)depth); + XPutImage(win->src.display, wm_hints.icon_pixmap, DefaultGC(win->src.display, DefaultScreen(win->src.display)), image, 0, 0, 0, 0, a.w, a.h); + image->data = NULL; + XDestroyImage(image); + + XSetWMHints(win->src.display, win->src.window, &wm_hints); + } + + RGFW_FREE(data); + XFlush(win->src.display); + return RGFW_BOOL(res); +#endif +#ifdef RGFW_WAYLAND + wayland: + return RGFW_FALSE; +#endif +} + +RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { + RGFW_ASSERT(icon); + RGFW_ASSERT(channels == 3 || channels == 4); + RGFW_GOTO_WAYLAND(0); + +#ifdef RGFW_X11 +#ifndef RGFW_NO_X11_CURSOR + RGFW_init(); + XcursorImage* native = XcursorImageCreate((i32)a.w, (i32)a.h); + native->xhot = 0; + native->yhot = 0; + + XcursorPixel* target = native->pixels; + size_t x, y; + for (x = 0; x < a.w; x++) { + for (y = 0; y < a.h; y++) { + size_t i = y * a.w + x; + u32 alpha = (channels == 4) ? icon[i * 4 + 3] : 0xFF; + + target[i] = (u32)((icon[i * 4 + 0]) << 16) + | (u32)((icon[i * 4 + 1]) << 8) + | (u32)((icon[i * 4 + 2]) << 0) + | (u32)(alpha << 24); + } + } + + Cursor cursor = XcursorImageLoadCursor(_RGFW.display, native); + XcursorImageDestroy(native); + + return (void*)cursor; +#else + RGFW_UNUSED(image); RGFW_UNUSED(a.w); RGFW_UNUSED(channels); + return NULL; +#endif +#endif +#ifdef RGFW_WAYLAND + wayland: + RGFW_UNUSED(icon); RGFW_UNUSED(a); RGFW_UNUSED(channels); + return NULL; /* TODO */ +#endif +} + +void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { +RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + RGFW_ASSERT(win && mouse); + XDefineCursor(win->src.display, win->src.window, (Cursor)mouse); +#endif +#ifdef RGFW_WAYLAND + wayland: + RGFW_UNUSED(win); RGFW_UNUSED(mouse); +#endif +} + +void RGFW_freeMouse(RGFW_mouse* mouse) { +RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + RGFW_ASSERT(mouse); + XFreeCursor(_RGFW.display, (Cursor)mouse); +#endif +#ifdef RGFW_WAYLAND + wayland: + RGFW_UNUSED(mouse); +#endif +} + +void RGFW_window_moveMouse(RGFW_window* win, RGFW_point p) { +RGFW_GOTO_WAYLAND(1); +#ifdef RGFW_X11 + RGFW_ASSERT(win != NULL); + + XEvent event; + XQueryPointer(win->src.display, DefaultRootWindow(win->src.display), + &event.xbutton.root, &event.xbutton.window, + &event.xbutton.x_root, &event.xbutton.y_root, + &event.xbutton.x, &event.xbutton.y, + &event.xbutton.state); + + win->_lastMousePoint = RGFW_POINT(p.x - win->r.x, p.y - win->r.y); + if (event.xbutton.x == p.x && event.xbutton.y == p.y) + return; + + XWarpPointer(win->src.display, None, win->src.window, 0, 0, 0, 0, (int) p.x - win->r.x, (int) p.y - win->r.y); +#endif +#ifdef RGFW_WAYLAND + wayland: + RGFW_UNUSED(win); RGFW_UNUSED(p); +#endif +} + +RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { + return RGFW_window_setMouseStandard(win, RGFW_mouseArrow); +} + +RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { + RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + static const u8 mouseIconSrc[16] = { XC_arrow, XC_left_ptr, XC_xterm, XC_crosshair, XC_hand2, XC_sb_h_double_arrow, XC_sb_v_double_arrow, XC_bottom_left_corner, XC_bottom_right_corner, XC_fleur, XC_X_cursor}; + + if (mouse > (sizeof(mouseIconSrc) / sizeof(u8))) + return RGFW_FALSE; + + mouse = mouseIconSrc[mouse]; + + Cursor cursor = XCreateFontCursor(win->src.display, mouse); + XDefineCursor(win->src.display, win->src.window, (Cursor) cursor); + + XFreeCursor(win->src.display, (Cursor) cursor); + return RGFW_TRUE; +#endif +#ifdef RGFW_WAYLAND + wayland: { } + static const char* iconStrings[16] = { "left_ptr", "left_ptr", "text", "cross", "pointer", "e-resize", "n-resize", "nw-resize", "ne-resize", "all-resize", "not-allowed" }; + + struct wl_cursor* wlcursor = wl_cursor_theme_get_cursor(RGFW_wl_cursor_theme, iconStrings[mouse]); + RGFW_cursor_image = wlcursor->images[0]; + struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(RGFW_cursor_image); + + wl_surface_attach(RGFW_cursor_surface, cursor_buffer, 0, 0); + wl_surface_commit(RGFW_cursor_surface); + return RGFW_TRUE; + +#endif +} + +void RGFW_window_hide(RGFW_window* win) { + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + XUnmapWindow(win->src.display, win->src.window); +#endif +#ifdef RGFW_WAYLAND + wayland: + wl_surface_attach(win->src.surface, NULL, 0, 0); + wl_surface_commit(win->src.surface); + win->_flags |= RGFW_windowHide; +#endif +} + +void RGFW_window_show(RGFW_window* win) { + win->_flags &= ~(u32)RGFW_windowHide; + if (win->_flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + XMapWindow(win->src.display, win->src.window); +#endif +#ifdef RGFW_WAYLAND + wayland: + /* wl_surface_attach(win->src.surface, win->rc., 0, 0); */ + wl_surface_commit(win->src.surface); +#endif +} + +RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { + RGFW_GOTO_WAYLAND(1); + #ifdef RGFW_X11 + RGFW_init(); + if (XGetSelectionOwner(_RGFW.display, RGFW_XCLIPBOARD) == _RGFW.helperWindow) { + if (str != NULL) + RGFW_STRNCPY(str, _RGFW.clipboard, _RGFW.clipboard_len); + return (RGFW_ssize_t)_RGFW.clipboard_len; + } + + XEvent event; + int format; + unsigned long N, sizeN; + char* data; + Atom target; + + RGFW_LOAD_ATOM(XSEL_DATA); + + XConvertSelection(_RGFW.display, RGFW_XCLIPBOARD, RGFW_XUTF8_STRING, XSEL_DATA, _RGFW.helperWindow, CurrentTime); + XSync(_RGFW.display, 0); + while (1) { + XNextEvent(_RGFW.display, &event); + if (event.type != SelectionNotify) continue; + + if (event.xselection.selection != RGFW_XCLIPBOARD || event.xselection.property == 0) + return -1; + break; + } + + XGetWindowProperty(event.xselection.display, event.xselection.requestor, + event.xselection.property, 0L, (~0L), 0, AnyPropertyType, &target, + &format, &sizeN, &N, (u8**) &data); + + RGFW_ssize_t size; + if (sizeN > strCapacity && str != NULL) + size = -1; + + if ((target == RGFW_XUTF8_STRING || target == XA_STRING) && str != NULL) { + RGFW_MEMCPY(str, data, sizeN); + str[sizeN] = '\0'; + XFree(data); + } else if (str != NULL) size = -1; + + XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property); + size = (RGFW_ssize_t)sizeN; + + return size; + #endif + #if defined(RGFW_WAYLAND) + wayland: return 0; + #endif +} + +i32 RGFW_XHandleClipboardSelectionHelper(void) { +#ifdef RGFW_X11 + RGFW_LOAD_ATOM(SAVE_TARGETS); + + XEvent event; + XPending(_RGFW.display); + + if (QLength(_RGFW.display) || XEventsQueued(_RGFW.display, QueuedAlready) + XEventsQueued(_RGFW.display, QueuedAfterReading)) + XNextEvent(_RGFW.display, &event); + else + return 0; + + switch (event.type) { + case SelectionRequest: + RGFW_XHandleClipboardSelection(&event); + return 0; + case SelectionNotify: + if (event.xselection.target == SAVE_TARGETS) + return 0; + break; + default: break; + } + + return 0; +#else + return 1; +#endif +} + +void RGFW_writeClipboard(const char* text, u32 textLen) { + RGFW_GOTO_WAYLAND(1); + #ifdef RGFW_X11 + RGFW_LOAD_ATOM(SAVE_TARGETS); + RGFW_init(); + + /* request ownership of the clipboard section and request to convert it, this means its our job to convert it */ + XSetSelectionOwner(_RGFW.display, RGFW_XCLIPBOARD, _RGFW.helperWindow, CurrentTime); + if (XGetSelectionOwner(_RGFW.display, RGFW_XCLIPBOARD) != _RGFW.helperWindow) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, RGFW_DEBUG_CTX(_RGFW.root, 0), "X11 failed to become owner of clipboard selection"); + return; + } + + if (_RGFW.clipboard) + RGFW_FREE(_RGFW.clipboard); + + _RGFW.clipboard = (char*)RGFW_ALLOC(textLen); + RGFW_ASSERT(_RGFW.clipboard != NULL); + + RGFW_STRNCPY(_RGFW.clipboard, text, textLen); + _RGFW.clipboard_len = textLen; + #endif + #ifdef RGFW_WAYLAND + wayland: + RGFW_UNUSED(text); RGFW_UNUSED(textLen); + #endif +} + +RGFW_bool RGFW_window_isHidden(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + XWindowAttributes windowAttributes; + XGetWindowAttributes(win->src.display, win->src.window, &windowAttributes); + + return (windowAttributes.map_state == IsUnmapped && !RGFW_window_isMinimized(win)); +} + +RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_LOAD_ATOM(WM_STATE); + + Atom actual_type; + i32 actual_format; + unsigned long nitems, bytes_after; + unsigned char* prop_data; + + i32 status = XGetWindowProperty(win->src.display, win->src.window, WM_STATE, 0, 2, False, + AnyPropertyType, &actual_type, &actual_format, + &nitems, &bytes_after, &prop_data); + + if (status == Success && nitems >= 1 && prop_data == (unsigned char*)IconicState) { + XFree(prop_data); + return RGFW_TRUE; + } + + if (prop_data != NULL) + XFree(prop_data); + + XWindowAttributes windowAttributes; + XGetWindowAttributes(win->src.display, win->src.window, &windowAttributes); + return windowAttributes.map_state != IsViewable; +} + +RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_LOAD_ATOM(_NET_WM_STATE); + RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_VERT); + RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ); + + Atom actual_type; + i32 actual_format; + unsigned long nitems, bytes_after; + unsigned char* prop_data; + + i32 status = XGetWindowProperty(win->src.display, win->src.window, _NET_WM_STATE, 0, 1024, False, + XA_ATOM, &actual_type, &actual_format, + &nitems, &bytes_after, &prop_data); + + if (status != Success) { + if (prop_data != NULL) + XFree(prop_data); + + return RGFW_FALSE; + } + + u64 i; + for (i = 0; i < nitems; ++i) { + if (prop_data[i] == _NET_WM_STATE_MAXIMIZED_VERT || + prop_data[i] == _NET_WM_STATE_MAXIMIZED_HORZ) { + XFree(prop_data); + return RGFW_TRUE; + } + } + + if (prop_data != NULL) + XFree(prop_data); + + return RGFW_FALSE; +} + +#ifndef RGFW_NO_DPI +u32 RGFW_XCalculateRefreshRate(XRRModeInfo mi); +u32 RGFW_XCalculateRefreshRate(XRRModeInfo mi) { + if (mi.hTotal == 0 || mi.vTotal == 0) return 0; + return (u32) RGFW_ROUND((double) mi.dotClock / ((double) mi.hTotal * (double) mi.vTotal)); +} +#endif + + +static float XGetSystemContentDPI(Display* display, i32 screen) { + float dpi = 96.0f; + + #ifndef RGFW_NO_DPI + RGFW_UNUSED(screen); + char* rms = XResourceManagerString(display); + XrmDatabase db = NULL; + if (rms) db = XrmGetStringDatabase(rms); + + if (rms && db) { + XrmValue value; + char* type = NULL; + + if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value) && type && RGFW_STRNCMP(type, "String", 7) == 0) + dpi = (float)RGFW_ATOF(value.addr); + XrmDestroyDatabase(db); + } + #else + dpi = RGFW_ROUND(DisplayWidth(display, screen) / (DisplayWidthMM(display, screen) / 25.4)); + #endif + + return dpi; +} + +RGFW_monitor RGFW_XCreateMonitor(i32 screen); +RGFW_monitor RGFW_XCreateMonitor(i32 screen) { + RGFW_monitor monitor; + RGFW_init(); + Display* display = _RGFW.display; + + if (screen == -1) screen = DefaultScreen(display); + + Screen* scrn = DefaultScreenOfDisplay(display); + RGFW_area size = RGFW_AREA(scrn->width, scrn->height); + + monitor.x = 0; + monitor.y = 0; + monitor.mode.area = RGFW_AREA(size.w, size.h); + monitor.physW = (float)DisplayWidthMM(display, screen) / 25.4f; + monitor.physH = (float)DisplayHeightMM(display, screen) / 25.4f; + + RGFW_splitBPP((u32)DefaultDepth(display, DefaultScreen(display)), &monitor.mode); + + char* name = XDisplayName((const char*)display); + RGFW_MEMCPY(monitor.name, name, 128); + + float dpi = XGetSystemContentDPI(display, screen); + monitor.pixelRatio = dpi >= 192.0f ? 2 : 1; + monitor.scaleX = (float) (dpi) / 96.0f; + monitor.scaleY = (float) (dpi) / 96.0f; + + #ifndef RGFW_NO_DPI + XRRScreenResources* sr = XRRGetScreenResourcesCurrent(display, RootWindow(display, screen)); + monitor.mode.refreshRate = RGFW_XCalculateRefreshRate(sr->modes[screen]); + + XRRCrtcInfo* ci = NULL; + int crtc = screen; + + if (sr->ncrtc > crtc) { + ci = XRRGetCrtcInfo(display, sr, sr->crtcs[crtc]); + } + #endif + + #ifndef RGFW_NO_DPI + XRROutputInfo* info = XRRGetOutputInfo (display, sr, sr->outputs[screen]); + + if (info == NULL || ci == NULL) { + XRRFreeScreenResources(sr); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); + return monitor; + } + + + float physW = (float)info->mm_width / 25.4f; + float physH = (float)info->mm_height / 25.4f; + + RGFW_MEMCPY(monitor.name, info->name, 128); + + if ((u8)physW && (u8)physH) { + monitor.physW = physW; + monitor.physH = physH; + } + + monitor.x = ci->x; + monitor.y = ci->y; + + if (ci->width && ci->height) { + monitor.mode.area.w = (u32)ci->width; + monitor.mode.area.h = (u32)ci->height; + } + #endif + + #ifndef RGFW_NO_DPI + XRRFreeCrtcInfo(ci); + XRRFreeScreenResources(sr); + #endif + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); + return monitor; +} + +RGFW_monitor* RGFW_getMonitors(size_t* len) { + static RGFW_monitor monitors[7]; + + RGFW_GOTO_WAYLAND(1); + #ifdef RGFW_X11 + RGFW_init(); + + Display* display = _RGFW.display; + i32 max = ScreenCount(display); + + i32 i; + for (i = 0; i < max && i < 6; i++) + monitors[i] = RGFW_XCreateMonitor(i); + + if (len != NULL) *len = (size_t)((max <= 6) ? (max) : (6)); + + return monitors; + #endif + #ifdef RGFW_WAYLAND + wayland: return monitors; /* TODO WAYLAND */ + #endif +} + +RGFW_monitor RGFW_getPrimaryMonitor(void) { + RGFW_GOTO_WAYLAND(1); + #ifdef RGFW_X11 + return RGFW_XCreateMonitor(-1); + #endif + #ifdef RGFW_WAYLAND + wayland: return (RGFW_monitor){ 0 }; /* TODO WAYLAND */ + #endif +} + +RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { + RGFW_GOTO_WAYLAND(1); +#ifdef RGFW_X11 + #ifndef RGFW_NO_DPI + RGFW_init(); + XRRScreenResources* screenRes = XRRGetScreenResources(_RGFW.display, DefaultRootWindow(_RGFW.display)); + if (screenRes == NULL) return RGFW_FALSE; + + int i; + for (i = 0; i < screenRes->ncrtc; i++) { + XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(_RGFW.display, screenRes, screenRes->crtcs[i]); + if (!crtcInfo) continue; + + if (mon.x == crtcInfo->x && mon.y == crtcInfo->y && (u32)mon.mode.area.w == crtcInfo->width && (u32)mon.mode.area.h == crtcInfo->height) { + RRMode rmode = None; + int index; + for (index = 0; index < screenRes->nmode; index++) { + RGFW_monitorMode foundMode; + foundMode.area = RGFW_AREA(screenRes->modes[index].width, screenRes->modes[index].height); + foundMode.refreshRate = RGFW_XCalculateRefreshRate(screenRes->modes[index]); + RGFW_splitBPP((u32)DefaultDepth(_RGFW.display, DefaultScreen(_RGFW.display)), &foundMode); + + if (RGFW_monitorModeCompare(mode, foundMode, request)) { + rmode = screenRes->modes[index].id; + + RROutput output = screenRes->outputs[i]; + XRROutputInfo* info = XRRGetOutputInfo(_RGFW.display, screenRes, output); + if (info) { + XRRSetCrtcConfig(_RGFW.display, screenRes, screenRes->crtcs[i], + CurrentTime, 0, 0, rmode, RR_Rotate_0, &output, 1); + XRRFreeOutputInfo(info); + XRRFreeCrtcInfo(crtcInfo); + XRRFreeScreenResources(screenRes); + return RGFW_TRUE; + } + } + } + + XRRFreeCrtcInfo(crtcInfo); + XRRFreeScreenResources(screenRes); + return RGFW_FALSE; + } + + XRRFreeCrtcInfo(crtcInfo); + } + + XRRFreeScreenResources(screenRes); + return RGFW_FALSE; + #endif +#endif +#ifdef RGFW_WAYLAND +wayland: +#endif + return RGFW_FALSE; +} + +RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(1); +#ifdef RGFW_X11 + XWindowAttributes attrs; + if (!XGetWindowAttributes(win->src.display, win->src.window, &attrs)) { + return (RGFW_monitor){0}; + } + + i32 i; + for (i = 0; i < ScreenCount(win->src.display) && i < 6; i++) { + Screen* screen = ScreenOfDisplay(win->src.display, i); + if (attrs.x >= 0 && attrs.x < XWidthOfScreen(screen) && + attrs.y >= 0 && attrs.y < XHeightOfScreen(screen)) + return RGFW_XCreateMonitor(i); + } +#endif +#ifdef RGFW_WAYLAND +wayland: +#endif + return (RGFW_monitor){0}; + +} + +#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) +void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { + if (win == NULL) + glXMakeCurrent(NULL, (Drawable)NULL, (GLXContext) NULL); + else + glXMakeCurrent(win->src.display, (Drawable) win->src.window, (GLXContext) win->src.ctx); +} +void* RGFW_getCurrent_OpenGL(void) { return glXGetCurrentContext(); } +void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { glXSwapBuffers(win->src.display, win->src.window); } +#endif + +void RGFW_window_swapBuffers_software(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(0); +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + #ifdef RGFW_X11 + win->src.bitmap->data = (char*) win->buffer; + RGFW_RGB_to_BGR(win, (u8*)win->src.bitmap->data); + XPutImage(win->src.display, win->src.window, win->src.gc, win->src.bitmap, 0, 0, 0, 0, win->bufferSize.w, win->bufferSize.h); + win->src.bitmap->data = NULL; + return; + #endif + #ifdef RGFW_WAYLAND + wayland: + #if !defined(RGFW_BUFFER_BGR) && !defined(RGFW_OSMESA) + RGFW_RGB_to_BGR(win, win->src.buffer); + #else + size_t y; + for (y = 0; y < win->r.h; y++) { + u32 index = (y * 4 * win->r.w); + u32 index2 = (y * 4 * win->bufferSize.w); + RGFW_MEMCPY(&win->src.buffer[index], &win->buffer[index2], win->r.w * 4); + } + #endif + + wl_surface_frame_done(win, NULL, 0); + wl_surface_commit(win->src.surface); + #endif +#else +#ifdef RGFW_WAYLAND + wayland: +#endif + RGFW_UNUSED(win); +#endif +} + +#if !defined(RGFW_EGL) + +void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { + RGFW_ASSERT(win != NULL); + + #if defined(RGFW_OPENGL) + // cached pfn to avoid calling glXGetProcAddress more than once + static PFNGLXSWAPINTERVALEXTPROC pfn = (PFNGLXSWAPINTERVALEXTPROC)123; + static int (*pfn2)(int) = NULL; + + if (pfn == (PFNGLXSWAPINTERVALEXTPROC)123) { + pfn = ((PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddress((GLubyte*) "glXSwapIntervalEXT")); + if (pfn == NULL) { + const char* array[] = {"GLX_MESA_swap_control", "GLX_SGI_swap_control"}; + u32 i; + for (i = 0; i < sizeof(array) / sizeof(char*) && pfn2 == NULL; i++) + pfn2 = ((int(*)(int))glXGetProcAddress((GLubyte*) array[i])); + + if (pfn2 != NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to load swap interval function, fallingback to the native swapinterval function"); + } else { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to load swap interval function"); + } + } + } + if (pfn != NULL) + pfn(win->src.display, win->src.window, swapInterval); + else if (pfn2 != NULL) { + pfn2(swapInterval); + } + #else + RGFW_UNUSED(swapInterval); + #endif +} +#endif + +void RGFW_deinit(void) { + if (_RGFW.windowCount == -1) return; + #define RGFW_FREE_LIBRARY(x) if (x != NULL) dlclose(x); x = NULL; +#ifdef RGFW_X11 + /* to save the clipboard on the x server after the window is closed */ + RGFW_LOAD_ATOM(CLIPBOARD_MANAGER); + RGFW_LOAD_ATOM(SAVE_TARGETS); + if (XGetSelectionOwner(_RGFW.display, RGFW_XCLIPBOARD) == _RGFW.helperWindow) { + XConvertSelection(_RGFW.display, CLIPBOARD_MANAGER, SAVE_TARGETS, None, _RGFW.helperWindow, CurrentTime); + while (RGFW_XHandleClipboardSelectionHelper()); + } + if (_RGFW.clipboard) { + RGFW_FREE(_RGFW.clipboard); + _RGFW.clipboard = NULL; + } + + RGFW_freeMouse(_RGFW.hiddenMouse); + XCloseDisplay(_RGFW.display); /*!< kill connection to the x server */ + + #if !defined(RGFW_NO_X11_CURSOR_PRELOAD) && !defined(RGFW_NO_X11_CURSOR) + RGFW_FREE_LIBRARY(X11Cursorhandle); + #endif + #if !defined(RGFW_NO_X11_XI_PRELOAD) + RGFW_FREE_LIBRARY(X11Xihandle); + #endif + + #ifdef RGFW_USE_XDL + XDL_close(); + #endif + + #if !defined(RGFW_NO_X11_EXT_PRELOAD) + RGFW_FREE_LIBRARY(X11XEXThandle); + #endif +#endif +#ifdef RGFW_WAYLAND + wl_display_disconnect(_RGFW.wl_display); +#endif + #ifndef RGFW_NO_LINUX + if (RGFW_eventWait_forceStop[0] || RGFW_eventWait_forceStop[1]){ + close(RGFW_eventWait_forceStop[0]); + close(RGFW_eventWait_forceStop[1]); + } + + u8 i; + for (i = 0; i < RGFW_gamepadCount; i++) { + if(RGFW_gamepads[i]) + close(RGFW_gamepads[i]); + } + #endif + + _RGFW.root = NULL; + _RGFW.windowCount = -1; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context deinitialized"); +} + +void RGFW_window_close(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + if ((win->_flags & RGFW_windowNoInitAPI) == 0) RGFW_window_freeOpenGL(win); + + #ifdef RGFW_X11 + RGFW_GOTO_WAYLAND(0); + + /* ungrab pointer if it was grabbed */ + if (win->_flags & RGFW_HOLD_MOUSE) + XUngrabPointer(win->src.display, CurrentTime); + + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + if (win->buffer != NULL) { + if ((win->_flags & RGFW_BUFFER_ALLOC)) + RGFW_FREE(win->buffer); + XDestroyImage((XImage*) win->src.bitmap); + } + #endif + + XFreeGC(win->src.display, win->src.gc); + XDestroyWindow(win->src.display, (Drawable) win->src.window); /*!< close the window */ + win->src.window = 0; + XCloseDisplay(win->src.display); + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a window was freed"); + _RGFW.windowCount--; + if (_RGFW.windowCount == 0) RGFW_deinit(); + + RGFW_clipboard_switch(NULL); + RGFW_FREE(win->event.droppedFiles); + if ((win->_flags & RGFW_WINDOW_ALLOC)) { + RGFW_FREE(win); + win = NULL; + } + return; + #endif + + #ifdef RGFW_WAYLAND + wayland: + + #ifdef RGFW_X11 + XDestroyWindow(win->src.display, (Drawable) win->src.window); + #endif + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a window was freed"); + _RGFW.windowCount--; + if (_RGFW.windowCount == 0) RGFW_deinit(); + + xdg_toplevel_destroy(win->src.xdg_toplevel); + xdg_surface_destroy(win->src.xdg_surface); + wl_surface_destroy(win->src.surface); + + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + wl_buffer_destroy(win->src.wl_buffer); + if ((win->_flags & RGFW_BUFFER_ALLOC)) + RGFW_FREE(win->buffer); + + munmap(win->src.buffer, (size_t)(win->r.w * win->r.h * 4)); + #endif + + RGFW_clipboard_switch(NULL); + RGFW_FREE(win->event.droppedFiles); + if ((win->_flags & RGFW_WINDOW_ALLOC)) { + RGFW_FREE(win); + win = NULL; + } + #endif +} + + +/* + End of X11 linux / wayland / unix defines +*/ + +#include +#include +#include + +void RGFW_stopCheckEvents(void) { + + RGFW_eventWait_forceStop[2] = 1; + while (1) { + const char byte = 0; + const ssize_t result = write(RGFW_eventWait_forceStop[1], &byte, 1); + if (result == 1 || result == -1) + break; + } +} + +void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { + if (waitMS == 0) return; + + u8 i; + if (RGFW_eventWait_forceStop[0] == 0 || RGFW_eventWait_forceStop[1] == 0) { + if (pipe(RGFW_eventWait_forceStop) != -1) { + fcntl(RGFW_eventWait_forceStop[0], F_GETFL, 0); + fcntl(RGFW_eventWait_forceStop[0], F_GETFD, 0); + fcntl(RGFW_eventWait_forceStop[1], F_GETFL, 0); + fcntl(RGFW_eventWait_forceStop[1], F_GETFD, 0); + } + } + + struct pollfd fds[] = { + #ifdef RGFW_WAYLAND + { wl_display_get_fd(win->src.wl_display), POLLIN, 0 }, + #else + { ConnectionNumber(win->src.display), POLLIN, 0 }, + #endif + #ifdef RGFW_X11 + { ConnectionNumber(_RGFW.display), POLLIN, 0 }, + #endif + { RGFW_eventWait_forceStop[0], POLLIN, 0 }, + #if defined(__linux__) + { -1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0} + #endif + }; + + u8 index = 2; +#ifdef RGFW_X11 + index++; +#endif + + #if defined(__linux__) || defined(__NetBSD__) + for (i = 0; i < RGFW_gamepadCount; i++) { + if (RGFW_gamepads[i] == 0) + continue; + + fds[index].fd = RGFW_gamepads[i]; + index++; + } + #endif + + + u64 start = RGFW_getTimeNS(); + + + #ifdef RGFW_WAYLAND + while (wl_display_dispatch(win->src.wl_display) <= 0 + #else + while (XPending(win->src.display) == 0 + #endif + #ifdef RGFW_X11 + && XPending(_RGFW.display) == 0 + #endif + ) { + if (poll(fds, index, waitMS) <= 0) + break; + + if (waitMS != RGFW_eventWaitNext) + waitMS -= (i32)(RGFW_getTimeNS() - start) / (i32)1e+6; + } + + /* drain any data in the stop request */ + if (RGFW_eventWait_forceStop[2]) { + char data[64]; + (void)!read(RGFW_eventWait_forceStop[0], data, sizeof(data)); + + RGFW_eventWait_forceStop[2] = 0; + } +} + +i32 RGFW_getClock(void); +i32 RGFW_getClock(void) { + static i32 clock = -1; + if (clock != -1) return clock; + + #if defined(_POSIX_MONOTONIC_CLOCK) + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) + clock = CLOCK_MONOTONIC; + #else + clock = CLOCK_REALTIME; + #endif + + return clock; +} + +u64 RGFW_getTimerFreq(void) { return 1000000000LLU; } +u64 RGFW_getTimerValue(void) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return (u64)ts.tv_sec * RGFW_getTimerFreq() + (u64)ts.tv_nsec; +} +#endif /* end of wayland or X11 defines */ + + + +/* + + Start of Windows defines + + +*/ + +#ifdef RGFW_WINDOWS +#define WIN32_LEAN_AND_MEAN +#define OEMRESOURCE +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifndef WM_DPICHANGED +#define WM_DPICHANGED 0x02E0 +#endif + +#ifndef RGFW_NO_XINPUT + typedef DWORD (WINAPI * PFN_XInputGetState)(DWORD,XINPUT_STATE*); + PFN_XInputGetState XInputGetStateSRC = NULL; + #define XInputGetState XInputGetStateSRC + + typedef DWORD (WINAPI * PFN_XInputGetKeystroke)(DWORD, DWORD, PXINPUT_KEYSTROKE); + PFN_XInputGetKeystroke XInputGetKeystrokeSRC = NULL; + #define XInputGetKeystroke XInputGetKeystrokeSRC + + HMODULE RGFW_XInput_dll = NULL; +#endif + +char* RGFW_createUTF8FromWideStringWin32(const WCHAR* source); + +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 + +typedef int (*PFN_wglGetSwapIntervalEXT)(void); +PFN_wglGetSwapIntervalEXT wglGetSwapIntervalEXTSrc = NULL; +#define wglGetSwapIntervalEXT wglGetSwapIntervalEXTSrc + + +void* RGFWgamepadApi = NULL; + +/* these two wgl functions need to be preloaded */ +typedef HGLRC (WINAPI *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hdc, HGLRC hglrc, const int *attribList); +PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL; + +#ifndef RGFW_EGL + HMODULE RGFW_wgl_dll = NULL; +#endif + +#ifndef RGFW_NO_LOAD_WGL + typedef HGLRC(WINAPI* PFN_wglCreateContext)(HDC); + typedef BOOL(WINAPI* PFN_wglDeleteContext)(HGLRC); + typedef PROC(WINAPI* PFN_wglGetProcAddress)(LPCSTR); + typedef BOOL(WINAPI* PFN_wglMakeCurrent)(HDC, HGLRC); + typedef HDC(WINAPI* PFN_wglGetCurrentDC)(void); + typedef HGLRC(WINAPI* PFN_wglGetCurrentContext)(void); + typedef BOOL(WINAPI* PFN_wglShareLists)(HGLRC, HGLRC); + + PFN_wglCreateContext wglCreateContextSRC; + PFN_wglDeleteContext wglDeleteContextSRC; + PFN_wglGetProcAddress wglGetProcAddressSRC; + PFN_wglMakeCurrent wglMakeCurrentSRC; + PFN_wglGetCurrentDC wglGetCurrentDCSRC; + PFN_wglGetCurrentContext wglGetCurrentContextSRC; + PFN_wglShareLists wglShareListsSRC; + + #define wglCreateContext wglCreateContextSRC + #define wglDeleteContext wglDeleteContextSRC + #define wglGetProcAddress wglGetProcAddressSRC + #define wglMakeCurrent wglMakeCurrentSRC + #define wglGetCurrentDC wglGetCurrentDCSRC + #define wglGetCurrentContext wglGetCurrentContextSRC + #define wglShareLists wglShareListsSRC +#endif + +#ifdef RGFW_OPENGL + +RGFW_proc RGFW_getProcAddress(const char* procname) { + RGFW_proc proc = (RGFW_proc)wglGetProcAddress(procname); + if (proc) + return proc; + + return (RGFW_proc) GetProcAddress(RGFW_wgl_dll, procname); +} + +typedef HRESULT (APIENTRY* PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc, const int* piAttribIList, const FLOAT* pfAttribFList, UINT nMaxFormats, int* piFormats, UINT* nNumFormats); +PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = NULL; + +typedef BOOL(APIENTRY* PFNWGLSWAPINTERVALEXTPROC)(int interval); +PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; +#endif + +#ifndef RGFW_NO_DWM +HMODULE RGFW_dwm_dll = NULL; +typedef struct { DWORD dwFlags; int fEnable; HRGN hRgnBlur; int fTransitionOnMaximized;} DWM_BLURBEHIND; +typedef HRESULT (WINAPI * PFN_DwmEnableBlurBehindWindow)(HWND, const DWM_BLURBEHIND*); +PFN_DwmEnableBlurBehindWindow DwmEnableBlurBehindWindowSRC = NULL; +#endif +void RGFW_win32_makeWindowTransparent(RGFW_window* win); +void RGFW_win32_makeWindowTransparent(RGFW_window* win) { + if (!(win->_flags & RGFW_windowTransparent)) return; + + #ifndef RGFW_NO_DWM + if (DwmEnableBlurBehindWindowSRC != NULL) { + DWM_BLURBEHIND bb = {0, 0, 0, 0}; + bb.dwFlags = 0x1; + bb.fEnable = TRUE; + bb.hRgnBlur = NULL; + DwmEnableBlurBehindWindowSRC(win->src.window, &bb); + + } else + #endif + { + SetWindowLong(win->src.window, GWL_EXSTYLE, WS_EX_LAYERED); + SetLayeredWindowAttributes(win->src.window, 0, 128, LWA_ALPHA); + } +} + +LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); +LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + RGFW_window* win = (RGFW_window*)GetPropW(hWnd, L"RGFW"); + if (win == NULL) return DefWindowProcW(hWnd, message, wParam, lParam); + + RECT windowRect; + GetWindowRect(hWnd, &windowRect); + + switch (message) { + case WM_CLOSE: + case WM_QUIT: + RGFW_eventQueuePush((RGFW_event){.type = RGFW_quit, ._win = win}); + RGFW_windowQuitCallback(win); + return 0; + case WM_ACTIVATE: { + RGFW_bool inFocus = RGFW_BOOL(LOWORD(wParam) != WA_INACTIVE); + if (inFocus) win->_flags |= RGFW_windowFocus; + else win->_flags &= ~ (u32)RGFW_windowFocus; + RGFW_eventQueuePush((RGFW_event){.type = (RGFW_eventType)((u8)RGFW_focusOut - inFocus), ._win = win}); + + RGFW_focusCallback(win, inFocus); + + if ((win->_flags & RGFW_windowFullscreen) == 0) + return DefWindowProcW(hWnd, message, wParam, lParam); + + win->_flags &= ~(u32)RGFW_EVENT_PASSED; + if (inFocus == RGFW_FALSE) RGFW_window_minimize(win); + else RGFW_window_setFullscreen(win, 1); + return DefWindowProcW(hWnd, message, wParam, lParam); + } + case WM_MOVE: + win->r.x = windowRect.left; + win->r.y = windowRect.top; + RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMoved, ._win = win}); + RGFW_windowMovedCallback(win, win->r); + return DefWindowProcW(hWnd, message, wParam, lParam); + case WM_SIZE: { + if (win->src.aspectRatio.w != 0 && win->src.aspectRatio.h != 0) { + double aspectRatio = (double)win->src.aspectRatio.w / win->src.aspectRatio.h; + + int width = windowRect.right - windowRect.left; + int height = windowRect.bottom - windowRect.top; + int newHeight = (int)(width / aspectRatio); + int newWidth = (int)(height * aspectRatio); + + if (win->r.w > windowRect.right - windowRect.left || + win->r.h > (i32)((u32)(windowRect.bottom - windowRect.top) - win->src.hOffset)) + { + if (newHeight > height) windowRect.right = windowRect.left + newWidth; + else windowRect.bottom = windowRect.top + newHeight; + } else { + if (newHeight < height) windowRect.right = windowRect.left + newWidth; + else windowRect.bottom = windowRect.top + newHeight; + } + + RGFW_window_resize(win, RGFW_AREA((windowRect.right - windowRect.left), + (u32)(windowRect.bottom - windowRect.top) - (u32)win->src.hOffset)); + } + + win->r.w = windowRect.right - windowRect.left; + win->r.h = (windowRect.bottom - windowRect.top) - (i32)win->src.hOffset; + RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowResized, ._win = win}); + RGFW_windowResizedCallback(win, win->r); + RGFW_window_checkMode(win); + return DefWindowProcW(hWnd, message, wParam, lParam); + } + #ifndef RGFW_NO_MONITOR + case WM_DPICHANGED: { + if (win->_flags & RGFW_windowScaleToMonitor) RGFW_window_scaleToMonitor(win); + + const float scaleX = HIWORD(wParam) / (float) 96; + const float scaleY = LOWORD(wParam) / (float) 96; + RGFW_scaleUpdatedCallback(win, scaleX, scaleY); + RGFW_eventQueuePush((RGFW_event){.type = RGFW_scaleUpdated, .scaleX = scaleX, .scaleY = scaleY , ._win = win}); + return DefWindowProcW(hWnd, message, wParam, lParam); + } + #endif + case WM_GETMINMAXINFO: { + MINMAXINFO* mmi = (MINMAXINFO*) lParam; + mmi->ptMinTrackSize.x = (LONG)win->src.minSize.w; + mmi->ptMinTrackSize.y = (LONG)(win->src.minSize.h + win->src.hOffset); + if (win->src.maxSize.w == 0 && win->src.maxSize.h == 0) + return DefWindowProcW(hWnd, message, wParam, lParam); + + mmi->ptMaxTrackSize.x = (LONG)win->src.maxSize.w; + mmi->ptMaxTrackSize.y = (LONG)(win->src.maxSize.h + win->src.hOffset); + return DefWindowProcW(hWnd, message, wParam, lParam); + } + case WM_PAINT: { + PAINTSTRUCT ps; + BeginPaint(hWnd, &ps); + RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowRefresh, ._win = win}); + RGFW_windowRefreshCallback(win); + EndPaint(hWnd, &ps); + + return DefWindowProcW(hWnd, message, wParam, lParam); + } + #if(_WIN32_WINNT >= 0x0600) + case WM_DWMCOMPOSITIONCHANGED: + case WM_DWMCOLORIZATIONCOLORCHANGED: + RGFW_win32_makeWindowTransparent(win); + break; + #endif +/* based on sokol_app.h */ +#ifdef RGFW_ADVANCED_SMOOTH_RESIZE + case WM_ENTERSIZEMOVE: SetTimer(win->src.window, 1, USER_TIMER_MINIMUM, NULL); break; + case WM_EXITSIZEMOVE: KillTimer(win->src.window, 1); break; + case WM_TIMER: RGFW_windowRefreshCallback(win); break; +#endif + case WM_NCLBUTTONDOWN: { + /* workaround for half-second pause when starting to move window + see: https://gamedev.net/forums/topic/672094-keeping-things-moving-during-win32-moveresize-events/5254386/ + */ + POINT point = { 0, 0 }; + if (SendMessage(win->src.window, WM_NCHITTEST, wParam, lParam) != HTCAPTION || GetCursorPos(&point) == FALSE) + break; + + ScreenToClient(win->src.window, &point); + PostMessage(win->src.window, WM_MOUSEMOVE, 0, ((uint32_t)point.x)|(((uint32_t)point.y) << 16)); + break; + } + default: break; + } + return DefWindowProcW(hWnd, message, wParam, lParam); +} + +#ifndef RGFW_NO_DPI + HMODULE RGFW_Shcore_dll = NULL; + typedef HRESULT (WINAPI *PFN_GetDpiForMonitor)(HMONITOR,MONITOR_DPI_TYPE,UINT*,UINT*); + PFN_GetDpiForMonitor GetDpiForMonitorSRC = NULL; + #define GetDpiForMonitor GetDpiForMonitorSRC +#endif + +#if !defined(RGFW_NO_LOAD_WINMM) && !defined(RGFW_NO_WINMM) + HMODULE RGFW_winmm_dll = NULL; + typedef u32 (WINAPI * PFN_timeBeginPeriod)(u32); + typedef PFN_timeBeginPeriod PFN_timeEndPeriod; + PFN_timeBeginPeriod timeBeginPeriodSRC, timeEndPeriodSRC; + #define timeBeginPeriod timeBeginPeriodSRC + #define timeEndPeriod timeEndPeriodSRC +#elif !defined(RGFW_NO_WINMM) + __declspec(dllimport) u32 __stdcall timeBeginPeriod(u32 uPeriod); + __declspec(dllimport) u32 __stdcall timeEndPeriod(u32 uPeriod); +#endif +#define RGFW_PROC_DEF(proc, name) if (name##SRC == NULL && proc != NULL) name##SRC = (PFN_##name)(RGFW_proc)GetProcAddress((proc), (#name)); + +#ifndef RGFW_NO_XINPUT +void RGFW_loadXInput(void); +void RGFW_loadXInput(void) { + u32 i; + static const char* names[] = {"xinput1_4.dll", "xinput9_1_0.dll", "xinput1_2.dll", "xinput1_1.dll"}; + + for (i = 0; i < sizeof(names) / sizeof(const char*) && (XInputGetStateSRC == NULL || XInputGetKeystrokeSRC != NULL); i++) { + RGFW_XInput_dll = LoadLibraryA(names[i]); + RGFW_PROC_DEF(RGFW_XInput_dll, XInputGetState); + RGFW_PROC_DEF(RGFW_XInput_dll, XInputGetKeystroke); + } + + if (XInputGetStateSRC == NULL) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errFailedFuncLoad, RGFW_DEBUG_CTX(.win = _RGFW.root, .srcError = 0), "Failed to load XInputGetState"); + if (XInputGetKeystrokeSRC == NULL) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errFailedFuncLoad, RGFW_DEBUG_CTX(.win = _RGFW.root, .srcError = 0), "Failed to load XInputGetKeystroke"); +} +#endif + +void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area){ +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + win->buffer = buffer; + win->bufferSize = area; + + BITMAPV5HEADER bi = { 0 }; + ZeroMemory(&bi, sizeof(bi)); + bi.bV5Size = sizeof(bi); + bi.bV5Width = (i32)area.w; + bi.bV5Height = -((LONG) area.h); + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_RGB; + + win->src.bitmap = CreateDIBSection(win->src.hdc, + (BITMAPINFO*) &bi, DIB_RGB_COLORS, + (void**) &win->src.bitmapBits, + NULL, (DWORD) 0); + + if (win->buffer == NULL) + win->buffer = win->src.bitmapBits; + + win->src.hdcMem = CreateCompatibleDC(win->src.hdc); + SelectObject(win->src.hdcMem, win->src.bitmap); + + #if defined(RGFW_OSMESA) + win->src.ctx = OSMesaCreateContext(OSMESA_BGRA, NULL); + OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); + OSMesaPixelStore(OSMESA_Y_UP, 0); + #endif + #else + RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); /*!< if buffer rendering is not being used */ + #endif +} + +void RGFW_releaseCursor(RGFW_window* win) { + RGFW_UNUSED(win); + ClipCursor(NULL); + const RAWINPUTDEVICE id = { 0x01, 0x02, RIDEV_REMOVE, NULL }; + RegisterRawInputDevices(&id, 1, sizeof(id)); +} + +void RGFW_captureCursor(RGFW_window* win, RGFW_rect rect) { + RGFW_UNUSED(win); RGFW_UNUSED(rect); + + RECT clipRect; + GetClientRect(win->src.window, &clipRect); + ClientToScreen(win->src.window, (POINT*) &clipRect.left); + ClientToScreen(win->src.window, (POINT*) &clipRect.right); + ClipCursor(&clipRect); + + const RAWINPUTDEVICE id = { 0x01, 0x02, 0, win->src.window }; + RegisterRawInputDevices(&id, 1, sizeof(id)); +} + +#define RGFW_LOAD_LIBRARY(x, lib) if (x == NULL) x = LoadLibraryA(lib) + +#ifdef RGFW_DIRECTX +int RGFW_window_createDXSwapChain(RGFW_window* win, IDXGIFactory* pFactory, IUnknown* pDevice, IDXGISwapChain** swapchain) { + RGFW_ASSERT(win && pFactory && pDevice && swapchain); + + static DXGI_SWAP_CHAIN_DESC swapChainDesc = { 0 }; + swapChainDesc.BufferCount = 2; + swapChainDesc.BufferDesc.Width = win->r.w; + swapChainDesc.BufferDesc.Height = win->r.h; + swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapChainDesc.OutputWindow = (HWND)win->src.window; + swapChainDesc.SampleDesc.Count = 1; + swapChainDesc.SampleDesc.Quality = 0; + swapChainDesc.Windowed = TRUE; + swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + + HRESULT hr = pFactory->lpVtbl->CreateSwapChain(pFactory, (IUnknown*)pDevice, &swapChainDesc, swapchain); + if (FAILED(hr)) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errDirectXContext, RGFW_DEBUG_CTX(.win = win, .srcError = hr), "Failed to create DirectX swap chain!"); + return -2; + } + + return 0; +} +#endif + +void RGFW_win32_loadOpenGLFuncs(HWND dummyWin); +void RGFW_win32_loadOpenGLFuncs(HWND dummyWin) { +#ifdef RGFW_OPENGL + if (wglSwapIntervalEXT != NULL && wglChoosePixelFormatARB != NULL && wglChoosePixelFormatARB != NULL) + return; + + HDC dummy_dc = GetDC(dummyWin); + u32 pfd_flags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + + PIXELFORMATDESCRIPTOR pfd = {sizeof(pfd), 1, pfd_flags, PFD_TYPE_RGBA, 32, 8, PFD_MAIN_PLANE, 32, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 32, 8, 0, PFD_MAIN_PLANE, 0, 0, 0, 0}; + + int dummy_pixel_format = ChoosePixelFormat(dummy_dc, &pfd); + SetPixelFormat(dummy_dc, dummy_pixel_format, &pfd); + + HGLRC dummy_context = wglCreateContext(dummy_dc); + wglMakeCurrent(dummy_dc, dummy_context); + + wglCreateContextAttribsARB = ((PFNWGLCREATECONTEXTATTRIBSARBPROC(WINAPI *)(const char*)) wglGetProcAddress)("wglCreateContextAttribsARB"); + wglChoosePixelFormatARB = ((PFNWGLCHOOSEPIXELFORMATARBPROC(WINAPI *)(const char*)) wglGetProcAddress)("wglChoosePixelFormatARB"); + + wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)(RGFW_proc)wglGetProcAddress("wglSwapIntervalEXT"); + if (wglSwapIntervalEXT == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to load swap interval function"); + } + + wglMakeCurrent(dummy_dc, 0); + wglDeleteContext(dummy_context); + ReleaseDC(dummyWin, dummy_dc); +#else + RGFW_UNUSED(dummyWin); +#endif +} + +#ifndef RGFW_EGL +void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { +#ifdef RGFW_OPENGL + PIXELFORMATDESCRIPTOR pfd; + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.iLayerType = PFD_MAIN_PLANE; + pfd.cColorBits = 32; + pfd.cAlphaBits = 8; + pfd.cDepthBits = 24; + pfd.cStencilBits = (BYTE)RGFW_GL_HINTS[RGFW_glStencil]; + pfd.cAuxBuffers = (BYTE)RGFW_GL_HINTS[RGFW_glAuxBuffers]; + if (RGFW_GL_HINTS[RGFW_glStereo]) pfd.dwFlags |= PFD_STEREO; + + /* try to create the pixel format we want for opengl and then try to create an opengl context for the specified version */ + if (software) + pfd.dwFlags |= PFD_GENERIC_FORMAT | PFD_GENERIC_ACCELERATED; + + /* get pixel format, default to a basic pixel format */ + int pixel_format = ChoosePixelFormat(win->src.hdc, &pfd); + if (wglChoosePixelFormatARB != NULL) { + i32* pixel_format_attribs = (i32*)RGFW_initFormatAttribs(software); + + int new_pixel_format; + UINT num_formats; + wglChoosePixelFormatARB(win->src.hdc, pixel_format_attribs, 0, 1, &new_pixel_format, &num_formats); + if (!num_formats) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to create a pixel format for WGL"); + else pixel_format = new_pixel_format; + } + + PIXELFORMATDESCRIPTOR suggested; + if (!DescribePixelFormat(win->src.hdc, pixel_format, sizeof(suggested), &suggested) || + !SetPixelFormat(win->src.hdc, pixel_format, &pfd)) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to set the WGL pixel format"); + + if (wglCreateContextAttribsARB != NULL) { + /* create opengl/WGL context for the specified version */ + u32 index = 0; + i32 attribs[40]; + + if (RGFW_GL_HINTS[RGFW_glProfile]== RGFW_glCore) { + SET_ATTRIB(WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB); + } + else { + SET_ATTRIB(WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB); + } + + if (RGFW_GL_HINTS[RGFW_glMinor] || RGFW_GL_HINTS[RGFW_glMajor]) { + SET_ATTRIB(WGL_CONTEXT_MAJOR_VERSION_ARB, RGFW_GL_HINTS[RGFW_glMajor]); + SET_ATTRIB(WGL_CONTEXT_MINOR_VERSION_ARB, RGFW_GL_HINTS[RGFW_glMinor]); + } + + SET_ATTRIB(0, 0); + + win->src.ctx = (HGLRC)wglCreateContextAttribsARB(win->src.hdc, NULL, attribs); + } else { /* fall back to a default context (probably opengl 2 or something) */ + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to create an accelerated OpenGL Context"); + win->src.ctx = wglCreateContext(win->src.hdc); + } + + ReleaseDC(win->src.window, win->src.hdc); + win->src.hdc = GetDC(win->src.window); + wglMakeCurrent(win->src.hdc, win->src.ctx); + + if (_RGFW.root != win) + wglShareLists(_RGFW.root->src.ctx, win->src.ctx); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context initalized"); +#else + RGFW_UNUSED(win); RGFW_UNUSED(software); +#endif +} + +void RGFW_window_freeOpenGL(RGFW_window* win) { +#ifdef RGFW_OPENGL + if (win->src.ctx == NULL) return; + wglDeleteContext((HGLRC) win->src.ctx); /*!< delete opengl context */ + win->src.ctx = NULL; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context freed"); +#else + RGFW_UNUSED(win); +#endif +} +#endif + + +i32 RGFW_init(void) { + #ifndef RGFW_NO_XINPUT + if (RGFW_XInput_dll == NULL) + RGFW_loadXInput(); + #endif + +#ifndef RGFW_NO_DPI + #if (_WIN32_WINNT >= 0x0600) + SetProcessDPIAware(); + #endif +#endif + + #ifndef RGFW_NO_WINMM + #ifndef RGFW_NO_LOAD_WINMM + RGFW_LOAD_LIBRARY(RGFW_winmm_dll, "winmm.dll"); + RGFW_PROC_DEF(RGFW_winmm_dll, timeBeginPeriod); + RGFW_PROC_DEF(RGFW_winmm_dll, timeEndPeriod); + #endif + timeBeginPeriod(1); + #endif + + #ifndef RGFW_NO_DWM + RGFW_LOAD_LIBRARY(RGFW_dwm_dll, "dwmapi.dll"); + RGFW_PROC_DEF(RGFW_dwm_dll, DwmEnableBlurBehindWindow); + #endif + + RGFW_LOAD_LIBRARY(RGFW_wgl_dll, "opengl32.dll"); + #ifndef RGFW_NO_LOAD_WGL + RGFW_PROC_DEF(RGFW_wgl_dll, wglCreateContext); + RGFW_PROC_DEF(RGFW_wgl_dll, wglDeleteContext); + RGFW_PROC_DEF(RGFW_wgl_dll, wglDeleteContext); + RGFW_PROC_DEF(RGFW_wgl_dll, wglGetProcAddress); + RGFW_PROC_DEF(RGFW_wgl_dll, wglMakeCurrent); + RGFW_PROC_DEF(RGFW_wgl_dll, wglGetCurrentDC); + RGFW_PROC_DEF(RGFW_wgl_dll, wglGetCurrentContext); + RGFW_PROC_DEF(RGFW_wgl_dll, wglShareLists); + #endif + + u8 RGFW_blk[] = { 0, 0, 0, 0 }; + _RGFW.hiddenMouse = RGFW_loadMouse(RGFW_blk, RGFW_AREA(1, 1), 4); + + _RGFW.windowCount = 0; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context initialized"); + return 1; +} + +RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { + if (name[0] == 0) name = (char*) " "; + + RGFW_window_basic_init(win, rect, flags); + + win->src.hIconSmall = win->src.hIconBig = NULL; + win->src.maxSize = RGFW_AREA(0, 0); + win->src.minSize = RGFW_AREA(0, 0); + win->src.aspectRatio = RGFW_AREA(0, 0); + + HINSTANCE inh = GetModuleHandleA(NULL); + + #ifndef __cplusplus + WNDCLASSW Class = { 0 }; /*!< Setup the Window class. */ + #else + WNDCLASSW Class = { }; + #endif + + if (RGFW_className == NULL) + RGFW_className = (char*)name; + + wchar_t wide_class[256]; + MultiByteToWideChar(CP_UTF8, 0, RGFW_className, -1, wide_class, 255); + + Class.lpszClassName = wide_class; + Class.hInstance = inh; + Class.hCursor = LoadCursor(NULL, IDC_ARROW); + Class.lpfnWndProc = WndProcW; + Class.cbClsExtra = sizeof(RGFW_window*); + + Class.hIcon = (HICON)LoadImageA(GetModuleHandleW(NULL), "RGFW_ICON", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); + if (Class.hIcon == NULL) + Class.hIcon = (HICON)LoadImageA(NULL, (LPCSTR)IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); + + RegisterClassW(&Class); + + DWORD window_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN; + + RECT windowRect, clientRect; + + if (!(flags & RGFW_windowNoBorder)) { + window_style |= WS_CAPTION | WS_SYSMENU | WS_BORDER | WS_MINIMIZEBOX | WS_THICKFRAME; + + if (!(flags & RGFW_windowNoResize)) + window_style |= WS_SIZEBOX | WS_MAXIMIZEBOX; + } else + window_style |= WS_POPUP | WS_VISIBLE | WS_SYSMENU; + + wchar_t wide_name[256]; + MultiByteToWideChar(CP_UTF8, 0, name, -1, wide_name, 255); + HWND dummyWin = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->r.x, win->r.y, win->r.w, win->r.h, 0, 0, inh, 0); + + GetWindowRect(dummyWin, &windowRect); + GetClientRect(dummyWin, &clientRect); + + RGFW_win32_loadOpenGLFuncs(dummyWin); + DestroyWindow(dummyWin); + + win->src.hOffset = (u32)(windowRect.bottom - windowRect.top) - (u32)(clientRect.bottom - clientRect.top); + win->src.window = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->r.x, win->r.y, win->r.w, win->r.h + (i32)win->src.hOffset, 0, 0, inh, 0); + SetPropW(win->src.window, L"RGFW", win); + RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h)); /* so WM_GETMINMAXINFO gets called again */ + + if (flags & RGFW_windowAllowDND) { + win->_flags |= RGFW_windowAllowDND; + RGFW_window_setDND(win, 1); + } + win->src.hdc = GetDC(win->src.window); + + if ((flags & RGFW_windowNoInitAPI) == 0) { + RGFW_window_initOpenGL(win, RGFW_BOOL(flags & RGFW_windowOpenglSoftware)); + RGFW_window_initBuffer(win); + } + + RGFW_window_setFlags(win, flags); + RGFW_win32_makeWindowTransparent(win); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); + RGFW_window_show(win); + + return win; +} + +void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { + RGFW_setBit(&win->_flags, RGFW_windowNoBorder, !border); + LONG style = GetWindowLong(win->src.window, GWL_STYLE); + + + if (border == 0) { + SetWindowLong(win->src.window, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW); + SetWindowPos( + win->src.window, HWND_TOP, 0, 0, 0, 0, + SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE + ); + } + else { + style |= WS_OVERLAPPEDWINDOW; + if (win->_flags & RGFW_windowNoResize) style &= ~WS_MAXIMIZEBOX; + SetWindowPos( + win->src.window, HWND_TOP, 0, 0, 0, 0, + SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE + ); + } +} + +void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow) { + RGFW_setBit(&win->_flags, RGFW_windowAllowDND, allow); + DragAcceptFiles(win->src.window, allow); +} + +RGFW_area RGFW_getScreenSize(void) { + HDC dc = GetDC(NULL); + RGFW_area area = RGFW_AREA(GetDeviceCaps(dc, HORZRES), GetDeviceCaps(dc, VERTRES)); + ReleaseDC(NULL, dc); + return area; +} + +RGFW_point RGFW_getGlobalMousePoint(void) { + POINT p; + GetCursorPos(&p); + + return RGFW_POINT(p.x, p.y); +} + +void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { + RGFW_ASSERT(win != NULL); + win->src.aspectRatio = a; +} + +void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { + RGFW_ASSERT(win != NULL); + win->src.minSize = a; +} + +void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { + RGFW_ASSERT(win != NULL); + win->src.maxSize = a; +} + +void RGFW_window_focus(RGFW_window* win) { + RGFW_ASSERT(win); + SetForegroundWindow(win->src.window); + SetFocus(win->src.window); +} + +void RGFW_window_raise(RGFW_window* win) { + RGFW_ASSERT(win); + BringWindowToTop(win->src.window); + SetWindowPos(win->src.window, HWND_TOP, win->r.x, win->r.y, win->r.w, win->r.h, SWP_NOSIZE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); +} + +void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { + RGFW_ASSERT(win != NULL); + + if (fullscreen == RGFW_FALSE) { + RGFW_window_setBorder(win, 1); + SetWindowPos(win->src.window, HWND_NOTOPMOST, win->_oldRect.x, win->_oldRect.y, win->_oldRect.w, win->_oldRect.h + (i32)win->src.hOffset, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + + win->_flags &= ~(u32)RGFW_windowFullscreen; + win->r = win->_oldRect; + return; + } + + win->_oldRect = win->r; + win->_flags |= RGFW_windowFullscreen; + + RGFW_monitor mon = RGFW_window_getMonitor(win); + RGFW_window_setBorder(win, 0); + + SetWindowPos(win->src.window, HWND_TOPMOST, 0, 0, (i32)mon.mode.area.w, (i32)mon.mode.area.h, SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW); + RGFW_monitor_scaleToWindow(mon, win); + + win->r = RGFW_RECT(0, 0, mon.mode.area.w, mon.mode.area.h); +} + +void RGFW_window_maximize(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_window_hide(win); + ShowWindow(win->src.window, SW_MAXIMIZE); +} + +void RGFW_window_minimize(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + ShowWindow(win->src.window, SW_MINIMIZE); +} + +void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { + RGFW_ASSERT(win != NULL); + if (floating) SetWindowPos(win->src.window, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + else SetWindowPos(win->src.window, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); +} + +void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { + SetWindowLong(win->src.window, GWL_EXSTYLE, WS_EX_LAYERED); + SetLayeredWindowAttributes(win->src.window, 0, opacity, LWA_ALPHA); +} + +void RGFW_window_restore(RGFW_window* win) { RGFW_window_show(win); } + +RGFW_bool RGFW_window_isFloating(RGFW_window* win) { + return (GetWindowLongPtr(win->src.window, GWL_EXSTYLE) & WS_EX_TOPMOST) != 0; +} + +u8 RGFW_xinput2RGFW[] = { + RGFW_gamepadA, /* or PS X button */ + RGFW_gamepadB, /* or PS circle button */ + RGFW_gamepadX, /* or PS square button */ + RGFW_gamepadY, /* or PS triangle button */ + RGFW_gamepadR1, /* right bumper */ + RGFW_gamepadL1, /* left bump */ + RGFW_gamepadL2, /* left trigger */ + RGFW_gamepadR2, /* right trigger */ + 0, 0, 0, 0, 0, 0, 0, 0, + RGFW_gamepadUp, /* dpad up */ + RGFW_gamepadDown, /* dpad down */ + RGFW_gamepadLeft, /* dpad left */ + RGFW_gamepadRight, /* dpad right */ + RGFW_gamepadStart, /* start button */ + RGFW_gamepadSelect,/* select button */ + RGFW_gamepadL3, + RGFW_gamepadR3, +}; +i32 RGFW_checkXInput(RGFW_window* win, RGFW_event* e); +i32 RGFW_checkXInput(RGFW_window* win, RGFW_event* e) { + #ifndef RGFW_NO_XINPUT + + RGFW_UNUSED(win); + u16 i; + for (i = 0; i < 4; i++) { + XINPUT_KEYSTROKE keystroke; + + if (XInputGetKeystroke == NULL) + return 0; + + DWORD result = XInputGetKeystroke((DWORD)i, 0, &keystroke); + + if ((keystroke.Flags & XINPUT_KEYSTROKE_REPEAT) == 0 && result != ERROR_EMPTY) { + if (result != ERROR_SUCCESS) + return 0; + + if (keystroke.VirtualKey > VK_PAD_RTHUMB_PRESS) + continue; + + /* gamepad + 1 = RGFW_gamepadButtonReleased */ + e->type = RGFW_gamepadButtonPressed + !(keystroke.Flags & XINPUT_KEYSTROKE_KEYDOWN); + e->button = RGFW_xinput2RGFW[keystroke.VirtualKey - 0x5800]; + RGFW_gamepadPressed[i][e->button].prev = RGFW_gamepadPressed[i][e->button].current; + RGFW_gamepadPressed[i][e->button].current = RGFW_BOOL(keystroke.Flags & XINPUT_KEYSTROKE_KEYDOWN); + + RGFW_gamepadButtonCallback(win, i, e->button, e->type == RGFW_gamepadButtonPressed); + return 1; + } + + XINPUT_STATE state; + if (XInputGetState == NULL || + XInputGetState((DWORD) i, &state) == ERROR_DEVICE_NOT_CONNECTED + ) { + if (RGFW_gamepads[i] == 0) + continue; + + RGFW_gamepads[i] = 0; + RGFW_gamepadCount--; + + win->event.type = RGFW_gamepadDisconnected; + win->event.gamepad = (u16)i; + RGFW_gamepadCallback(win, i, 0); + return 1; + } + + if (RGFW_gamepads[i] == 0) { + RGFW_gamepads[i] = 1; + RGFW_gamepadCount++; + + char str[] = "Microsoft X-Box (XInput device)"; + RGFW_MEMCPY(RGFW_gamepads_name[i], str, sizeof(str)); + RGFW_gamepads_name[i][sizeof(RGFW_gamepads_name[i]) - 1] = '\0'; + win->event.type = RGFW_gamepadConnected; + win->event.gamepad = i; + RGFW_gamepads_type[i] = RGFW_gamepadMicrosoft; + + RGFW_gamepadCallback(win, i, 1); + return 1; + } + +#define INPUT_DEADZONE ( 0.24f * (float)(0x7FFF) ) /* Default to 24% of the +/- 32767 range. This is a reasonable default value but can be altered if needed. */ + + if ((state.Gamepad.sThumbLX < INPUT_DEADZONE && + state.Gamepad.sThumbLX > -INPUT_DEADZONE) && + (state.Gamepad.sThumbLY < INPUT_DEADZONE && + state.Gamepad.sThumbLY > -INPUT_DEADZONE)) + { + state.Gamepad.sThumbLX = 0; + state.Gamepad.sThumbLY = 0; + } + + if ((state.Gamepad.sThumbRX < INPUT_DEADZONE && + state.Gamepad.sThumbRX > -INPUT_DEADZONE) && + (state.Gamepad.sThumbRY < INPUT_DEADZONE && + state.Gamepad.sThumbRY > -INPUT_DEADZONE)) + { + state.Gamepad.sThumbRX = 0; + state.Gamepad.sThumbRY = 0; + } + + e->axisesCount = 2; + RGFW_point axis1 = RGFW_POINT(((float)state.Gamepad.sThumbLX / 32768.0f) * 100, ((float)state.Gamepad.sThumbLY / -32768.0f) * 100); + RGFW_point axis2 = RGFW_POINT(((float)state.Gamepad.sThumbRX / 32768.0f) * 100, ((float)state.Gamepad.sThumbRY / -32768.0f) * 100); + + if (axis1.x != e->axis[0].x || axis1.y != e->axis[0].y){ + win->event.whichAxis = 0; + + e->type = RGFW_gamepadAxisMove; + e->axis[0] = axis1; + RGFW_gamepadAxes[i][0] = e->axis[0]; + + RGFW_gamepadAxisCallback(win, e->gamepad, e->axis, e->axisesCount, e->whichAxis); + return 1; + } + + if (axis2.x != e->axis[1].x || axis2.y != e->axis[1].y) { + win->event.whichAxis = 1; + e->type = RGFW_gamepadAxisMove; + e->axis[1] = axis2; + RGFW_gamepadAxes[i][1] = e->axis[1]; + + RGFW_gamepadAxisCallback(win, e->gamepad, e->axis, e->axisesCount, e->whichAxis); + return 1; + } + } + + #endif + + return 0; +} + +void RGFW_stopCheckEvents(void) { + PostMessageW(_RGFW.root->src.window, WM_NULL, 0, 0); +} + +void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { + RGFW_UNUSED(win); + MsgWaitForMultipleObjects(0, NULL, FALSE, (DWORD)waitMS, QS_ALLINPUT); +} + +RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { + if (win == NULL || ((win->_flags & RGFW_windowFreeOnClose) && (win->_flags & RGFW_EVENT_QUIT))) return NULL; + RGFW_event* ev = RGFW_window_checkEventCore(win); + if (ev) return ev; + + static HDROP drop; + if (win->event.type == RGFW_DNDInit) { + if (win->event.droppedFilesCount) { + u32 i; + for (i = 0; i < win->event.droppedFilesCount; i++) + win->event.droppedFiles[i][0] = '\0'; + } + + win->event.droppedFilesCount = 0; + win->event.droppedFilesCount = DragQueryFileW(drop, 0xffffffff, NULL, 0); + + u32 i; + for (i = 0; i < win->event.droppedFilesCount; i++) { + UINT length = DragQueryFileW(drop, i, NULL, 0); + if (length == 0) + continue; + + WCHAR buffer[RGFW_MAX_PATH * 2]; + if (length > (RGFW_MAX_PATH * 2) - 1) + length = RGFW_MAX_PATH * 2; + + DragQueryFileW(drop, i, buffer, length + 1); + + char* str = RGFW_createUTF8FromWideStringWin32(buffer); + if (str != NULL) + RGFW_MEMCPY(win->event.droppedFiles[i], str, length + 1); + + win->event.droppedFiles[i][RGFW_MAX_PATH - 1] = '\0'; + } + + DragFinish(drop); + RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); + + win->event.type = RGFW_DND; + return &win->event; + } + + if (RGFW_checkXInput(win, &win->event)) + return &win->event; + + static BYTE keyboardState[256]; + GetKeyboardState(keyboardState); + + MSG msg; + if (PeekMessageA(&msg, NULL, 0u, 0u, PM_REMOVE)) { + if (msg.hwnd != win->src.window && msg.hwnd != NULL) { + TranslateMessage(&msg); + DispatchMessageA(&msg); + return RGFW_window_checkEvent(win); + } + } else { + return NULL; + } + + switch (msg.message) { + case WM_MOUSELEAVE: + win->event.type = RGFW_mouseLeave; + win->_flags |= RGFW_MOUSE_LEFT; + RGFW_mouseNotifyCallback(win, win->event.point, 0); + break; + case WM_SYSKEYUP: case WM_KEYUP: { + i32 scancode = (HIWORD(msg.lParam) & (KF_EXTENDED | 0xff)); + if (scancode == 0) + scancode = (i32)MapVirtualKeyW((UINT)msg.wParam, MAPVK_VK_TO_VSC); + + switch (scancode) { + case 0x54: scancode = 0x137; break; /* Alt+PrtS */ + case 0x146: scancode = 0x45; break; /* Ctrl+Pause */ + case 0x136: scancode = 0x36; break; /* CJK IME sets the extended bit for right Shift */ + default: break; + } + + win->event.key = (u8)RGFW_apiKeyToRGFW((u32) scancode); + + if (msg.wParam == VK_CONTROL) { + if (HIWORD(msg.lParam) & KF_EXTENDED) + win->event.key = RGFW_controlR; + else win->event.key = RGFW_controlL; + } + + wchar_t charBuffer; + ToUnicodeEx((UINT)msg.wParam, (UINT)scancode, keyboardState, (wchar_t*)&charBuffer, 1, 0, NULL); + + win->event.keyChar = (u8)charBuffer; + + RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; + win->event.type = RGFW_keyReleased; + RGFW_keyboard[win->event.key].current = 0; + + RGFW_updateKeyMods(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001), (GetKeyState(VK_SCROLL) & 0x0001)); + + RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, 0); + break; + } + case WM_SYSKEYDOWN: case WM_KEYDOWN: { + i32 scancode = (HIWORD(msg.lParam) & (KF_EXTENDED | 0xff)); + if (scancode == 0) + scancode = (i32)MapVirtualKeyW((u32)msg.wParam, MAPVK_VK_TO_VSC); + + switch (scancode) { + case 0x54: scancode = 0x137; break; /* Alt+PrtS */ + case 0x146: scancode = 0x45; break; /* Ctrl+Pause */ + case 0x136: scancode = 0x36; break; /* CJK IME sets the extended bit for right Shift */ + default: break; + } + + win->event.key = (u8)RGFW_apiKeyToRGFW((u32) scancode); + if (msg.wParam == VK_CONTROL) { + if (HIWORD(msg.lParam) & KF_EXTENDED) + win->event.key = RGFW_controlR; + else win->event.key = RGFW_controlL; + } + + wchar_t charBuffer; + ToUnicodeEx((UINT)msg.wParam, (UINT)scancode, keyboardState, &charBuffer, 1, 0, NULL); + win->event.keyChar = (u8)charBuffer; + + RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; + + win->event.type = RGFW_keyPressed; + win->event.repeat = RGFW_isPressed(win, win->event.key); + RGFW_keyboard[win->event.key].current = 1; + RGFW_updateKeyMods(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001), (GetKeyState(VK_SCROLL) & 0x0001)); + + RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, 1); + break; + } + case WM_MOUSEMOVE: { + if ((win->_flags & RGFW_HOLD_MOUSE)) + break; + + win->event.type = RGFW_mousePosChanged; + + i32 x = GET_X_LPARAM(msg.lParam); + i32 y = GET_Y_LPARAM(msg.lParam); + + RGFW_mousePosCallback(win, win->event.point, win->event.vector); + + if (win->_flags & RGFW_MOUSE_LEFT) { + win->_flags &= ~(u32)RGFW_MOUSE_LEFT; + win->event.type = RGFW_mouseEnter; + RGFW_mouseNotifyCallback(win, win->event.point, 1); + } + + win->event.point.x = x; + win->event.point.y = y; + win->_lastMousePoint = RGFW_POINT(x, y); + + break; + } + case WM_INPUT: { + if (!(win->_flags & RGFW_HOLD_MOUSE)) + break; + + unsigned size = sizeof(RAWINPUT); + static RAWINPUT raw = {0}; + + GetRawInputData((HRAWINPUT)msg.lParam, RID_INPUT, &raw, &size, sizeof(RAWINPUTHEADER)); + + if (raw.header.dwType != RIM_TYPEMOUSE || (raw.data.mouse.lLastX == 0 && raw.data.mouse.lLastY == 0) ) + break; + + if (raw.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { + POINT pos = {0, 0}; + int width, height; + + if (raw.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) { + pos.x += GetSystemMetrics(SM_XVIRTUALSCREEN); + pos.y += GetSystemMetrics(SM_YVIRTUALSCREEN); + width = GetSystemMetrics(SM_CXVIRTUALSCREEN); + height = GetSystemMetrics(SM_CYVIRTUALSCREEN); + } + else { + width = GetSystemMetrics(SM_CXSCREEN); + height = GetSystemMetrics(SM_CYSCREEN); + } + + pos.x += (int) (((float)raw.data.mouse.lLastX / 65535.f) * (float)width); + pos.y += (int) (((float)raw.data.mouse.lLastY / 65535.f) * (float)height); + ScreenToClient(win->src.window, &pos); + + win->event.vector.x = pos.x - win->_lastMousePoint.x; + win->event.vector.y = pos.y - win->_lastMousePoint.y; + } else { + win->event.vector.x = raw.data.mouse.lLastX; + win->event.vector.y = raw.data.mouse.lLastY; + } + + win->event.type = RGFW_mousePosChanged; + win->_lastMousePoint.x += win->event.vector.x; + win->_lastMousePoint.y += win->event.vector.y; + win->event.point = win->_lastMousePoint; + RGFW_mousePosCallback(win, win->event.point, win->event.vector); + break; + } + case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: case WM_XBUTTONDOWN: + if (msg.message == WM_XBUTTONDOWN) + win->event.button = RGFW_mouseMisc1 + (GET_XBUTTON_WPARAM(msg.wParam) == XBUTTON2); + else win->event.button = (msg.message == WM_LBUTTONDOWN) ? RGFW_mouseLeft : + (msg.message == WM_RBUTTONDOWN) ? RGFW_mouseRight : RGFW_mouseMiddle; + + win->event.type = RGFW_mouseButtonPressed; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 1; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); + break; + case WM_LBUTTONUP: case WM_RBUTTONUP: case WM_MBUTTONUP: case WM_XBUTTONUP: + if (msg.message == WM_XBUTTONUP) + win->event.button = RGFW_mouseMisc1 + (GET_XBUTTON_WPARAM(msg.wParam) == XBUTTON2); + else win->event.button = (msg.message == WM_LBUTTONUP) ? RGFW_mouseLeft : + (msg.message == WM_RBUTTONUP) ? RGFW_mouseRight : RGFW_mouseMiddle; + win->event.type = RGFW_mouseButtonReleased; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 0; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 0); + break; + case WM_MOUSEWHEEL: + if (msg.wParam > 0) + win->event.button = RGFW_mouseScrollUp; + else + win->event.button = RGFW_mouseScrollDown; + + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 1; + + win->event.scroll = (SHORT) HIWORD(msg.wParam) / (double) WHEEL_DELTA; + + win->event.type = RGFW_mouseButtonPressed; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); + break; + case WM_DROPFILES: { + win->event.type = RGFW_DNDInit; + + drop = (HDROP) msg.wParam; + POINT pt; + + /* Move the mouse to the position of the drop */ + DragQueryPoint(drop, &pt); + + win->event.point.x = pt.x; + win->event.point.y = pt.y; + + RGFW_dndInitCallback(win, win->event.point); + } + break; + default: + TranslateMessage(&msg); + DispatchMessageA(&msg); + return RGFW_window_checkEvent(win); + } + + TranslateMessage(&msg); + DispatchMessageA(&msg); + + return &win->event; +} + +RGFW_bool RGFW_window_isHidden(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + return IsWindowVisible(win->src.window) == 0 && !RGFW_window_isMinimized(win); +} + +RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + #ifndef __cplusplus + WINDOWPLACEMENT placement = { 0 }; + #else + WINDOWPLACEMENT placement = { }; + #endif + GetWindowPlacement(win->src.window, &placement); + return placement.showCmd == SW_SHOWMINIMIZED; +} + +RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + #ifndef __cplusplus + WINDOWPLACEMENT placement = { 0 }; + #else + WINDOWPLACEMENT placement = { }; + #endif + GetWindowPlacement(win->src.window, &placement); + return placement.showCmd == SW_SHOWMAXIMIZED || IsZoomed(win->src.window); +} + +typedef struct { int iIndex; HMONITOR hMonitor; RGFW_monitor* monitors; } RGFW_mInfo; +#ifndef RGFW_NO_MONITOR +RGFW_monitor win32CreateMonitor(HMONITOR src); +RGFW_monitor win32CreateMonitor(HMONITOR src) { + RGFW_monitor monitor; + MONITORINFOEX monitorInfo; + + monitorInfo.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfoA(src, (LPMONITORINFO)&monitorInfo); + + /* get the monitor's index */ + DISPLAY_DEVICEA dd; + dd.cb = sizeof(dd); + + DWORD deviceNum; + for (deviceNum = 0; EnumDisplayDevicesA(NULL, deviceNum, &dd, 0); deviceNum++) { + if (!(dd.StateFlags & DISPLAY_DEVICE_ACTIVE)) + continue; + + DEVMODE dm; + ZeroMemory(&dm, sizeof(dm)); + dm.dmSize = sizeof(dm); + + if (EnumDisplaySettingsA(dd.DeviceName, ENUM_CURRENT_SETTINGS, &dm)) { + monitor.mode.refreshRate = dm.dmDisplayFrequency; + RGFW_splitBPP(dm.dmBitsPerPel, &monitor.mode); + } + + DISPLAY_DEVICEA mdd; + mdd.cb = sizeof(mdd); + + if (EnumDisplayDevicesA(dd.DeviceName, (DWORD)deviceNum, &mdd, 0)) { + RGFW_MEMCPY(monitor.name, mdd.DeviceString, 128); + break; + } + } + + + + + monitor.x = monitorInfo.rcWork.left; + monitor.y = monitorInfo.rcWork.top; + monitor.mode.area.w = (u32)(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left); + monitor.mode.area.h = (u32)(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top); + + HDC hdc = CreateDC(monitorInfo.szDevice, NULL, NULL, NULL); + /* get pixels per inch */ + float dpiX = (float)GetDeviceCaps(hdc, LOGPIXELSX); + float dpiY = (float)GetDeviceCaps(hdc, LOGPIXELSX); + + monitor.scaleX = dpiX / 96.0f; + monitor.scaleY = dpiY / 96.0f; + monitor.pixelRatio = dpiX >= 192.0f ? 2.0f : 1.0f; + + monitor.physW = (float)GetDeviceCaps(hdc, HORZSIZE) / 25.4f; + monitor.physH = (float)GetDeviceCaps(hdc, VERTSIZE) / 25.4f; + DeleteDC(hdc); + + #ifndef RGFW_NO_DPI + RGFW_LOAD_LIBRARY(RGFW_Shcore_dll, "shcore.dll"); + RGFW_PROC_DEF(RGFW_Shcore_dll, GetDpiForMonitor); + + if (GetDpiForMonitor != NULL) { + u32 x, y; + GetDpiForMonitor(src, MDT_EFFECTIVE_DPI, &x, &y); + monitor.scaleX = (float) (x) / (float) 96.0f; + monitor.scaleY = (float) (y) / (float) 96.0f; + monitor.pixelRatio = dpiX >= 192.0f ? 2.0f : 1.0f; + } + #endif + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); + return monitor; +} +#endif /* RGFW_NO_MONITOR */ + +#ifndef RGFW_NO_MONITOR +BOOL CALLBACK GetMonitorHandle(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData); +BOOL CALLBACK GetMonitorHandle(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { + RGFW_UNUSED(hdcMonitor); + RGFW_UNUSED(lprcMonitor); + + RGFW_mInfo* info = (RGFW_mInfo*) dwData; + + + if (info->iIndex >= 6) + return FALSE; + + info->monitors[info->iIndex] = win32CreateMonitor(hMonitor); + info->iIndex++; + + return TRUE; +} + +RGFW_monitor RGFW_getPrimaryMonitor(void) { + #ifdef __cplusplus + return win32CreateMonitor(MonitorFromPoint({ 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); + #else + return win32CreateMonitor(MonitorFromPoint((POINT) { 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); + #endif +} + +RGFW_monitor* RGFW_getMonitors(size_t* len) { + static RGFW_monitor monitors[6]; + RGFW_mInfo info; + info.iIndex = 0; + info.monitors = monitors; + + EnumDisplayMonitors(NULL, NULL, GetMonitorHandle, (LPARAM) &info); + + if (len != NULL) *len = (size_t)info.iIndex; + return monitors; +} + +RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { + HMONITOR src = MonitorFromWindow(win->src.window, MONITOR_DEFAULTTOPRIMARY); + return win32CreateMonitor(src); +} + +RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { + HMONITOR src = MonitorFromPoint((POINT) { mon.x, mon.y }, MONITOR_DEFAULTTOPRIMARY); + + MONITORINFOEX monitorInfo; + monitorInfo.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfoA(src, (LPMONITORINFO)&monitorInfo); + + DISPLAY_DEVICE dd; + dd.cb = sizeof(dd); + + /* Enumerate display devices */ + DWORD deviceNum; + for (deviceNum = 0; EnumDisplayDevicesA(NULL, deviceNum, &dd, 0); deviceNum++) { + if (!(dd.StateFlags & DISPLAY_DEVICE_ACTIVE)) + continue; + + if (strcmp(dd.DeviceName, monitorInfo.szDevice) != 0) + continue; + + DEVMODE dm; + ZeroMemory(&dm, sizeof(dm)); + dm.dmSize = sizeof(dm); + + if (EnumDisplaySettingsA(dd.DeviceName, ENUM_CURRENT_SETTINGS, &dm)) { + if (request & RGFW_monitorScale) { + dm.dmFields |= DM_PELSWIDTH | DM_PELSHEIGHT; + dm.dmPelsWidth = mode.area.w; + dm.dmPelsHeight = mode.area.h; + } + + if (request & RGFW_monitorRefresh) { + dm.dmFields |= DM_DISPLAYFREQUENCY; + dm.dmDisplayFrequency = mode.refreshRate; + } + + if (request & RGFW_monitorRGB) { + dm.dmFields |= DM_BITSPERPEL; + dm.dmBitsPerPel = (DWORD)(mode.red + mode.green + mode.blue); + } + + if (ChangeDisplaySettingsEx(dd.DeviceName, &dm, NULL, CDS_TEST, NULL) == DISP_CHANGE_SUCCESSFUL) { + if (ChangeDisplaySettingsEx(dd.DeviceName, &dm, NULL, CDS_UPDATEREGISTRY, NULL) == DISP_CHANGE_SUCCESSFUL) + return RGFW_TRUE; + return RGFW_FALSE; + } else return RGFW_FALSE; + } + } + + return RGFW_FALSE; +} + +#endif +HICON RGFW_loadHandleImage(u8* src, i32 c, RGFW_area a, BOOL icon); +HICON RGFW_loadHandleImage(u8* src, i32 c, RGFW_area a, BOOL icon) { + size_t channels = (size_t)c; + + BITMAPV5HEADER bi; + ZeroMemory(&bi, sizeof(bi)); + bi.bV5Size = sizeof(bi); + bi.bV5Width = (i32)a.w; + bi.bV5Height = -((LONG) a.h); + bi.bV5Planes = 1; + bi.bV5BitCount = (WORD)(channels * 8); + bi.bV5Compression = BI_RGB; + HDC dc = GetDC(NULL); + u8* target = NULL; + + HBITMAP color = CreateDIBSection(dc, + (BITMAPINFO*) &bi, DIB_RGB_COLORS, (void**) &target, + NULL, (DWORD) 0); + + size_t x, y; + for (y = 0; y < a.h; y++) { + for (x = 0; x < a.w; x++) { + size_t index = (y * 4 * (size_t)a.w) + x * channels; + target[index] = src[index + 2]; + target[index + 1] = src[index + 1]; + target[index + 2] = src[index]; + target[index + 3] = src[index + 3]; + } + } + + ReleaseDC(NULL, dc); + + HBITMAP mask = CreateBitmap((i32)a.w, (i32)a.h, 1, 1, NULL); + + ICONINFO ii; + ZeroMemory(&ii, sizeof(ii)); + ii.fIcon = icon; + ii.xHotspot = a.w / 2; + ii.yHotspot = a.h / 2; + ii.hbmMask = mask; + ii.hbmColor = color; + + HICON handle = CreateIconIndirect(&ii); + + DeleteObject(color); + DeleteObject(mask); + + return handle; +} + +void* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { + HCURSOR cursor = (HCURSOR) RGFW_loadHandleImage(icon, channels, a, FALSE); + return cursor; +} + +void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { + RGFW_ASSERT(win && mouse); + SetClassLongPtrA(win->src.window, GCLP_HCURSOR, (LPARAM) mouse); + SetCursor((HCURSOR)mouse); +} + +void RGFW_freeMouse(RGFW_mouse* mouse) { + RGFW_ASSERT(mouse); + DestroyCursor((HCURSOR)mouse); +} + +RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { + return RGFW_window_setMouseStandard(win, RGFW_mouseArrow); +} + +RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { + RGFW_ASSERT(win != NULL); + + static const u32 mouseIconSrc[16] = {OCR_NORMAL, OCR_NORMAL, OCR_IBEAM, OCR_CROSS, OCR_HAND, OCR_SIZEWE, OCR_SIZENS, OCR_SIZENWSE, OCR_SIZENESW, OCR_SIZEALL, OCR_NO}; + if (mouse > (sizeof(mouseIconSrc) / sizeof(u32))) + return RGFW_FALSE; + + char* icon = MAKEINTRESOURCEA(mouseIconSrc[mouse]); + + SetClassLongPtrA(win->src.window, GCLP_HCURSOR, (LPARAM) LoadCursorA(NULL, icon)); + SetCursor(LoadCursorA(NULL, icon)); + return RGFW_TRUE; +} + +void RGFW_window_hide(RGFW_window* win) { + ShowWindow(win->src.window, SW_HIDE); +} + +void RGFW_window_show(RGFW_window* win) { + if (win->_flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); + ShowWindow(win->src.window, SW_RESTORE); +} + +#define RGFW_FREE_LIBRARY(x) if (x != NULL) FreeLibrary(x); x = NULL; +void RGFW_deinit(void) { + #ifndef RGFW_NO_XINPUT + RGFW_FREE_LIBRARY(RGFW_XInput_dll); + #endif + + #ifndef RGFW_NO_DPI + RGFW_FREE_LIBRARY(RGFW_Shcore_dll); + #endif + + #ifndef RGFW_NO_WINMM + timeEndPeriod(1); + #ifndef RGFW_NO_LOAD_WINMM + RGFW_FREE_LIBRARY(RGFW_winmm_dll); + #endif + #endif + + RGFW_FREE_LIBRARY(RGFW_wgl_dll); + _RGFW.root = NULL; + + RGFW_freeMouse(_RGFW.hiddenMouse); + _RGFW.windowCount = -1; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context deinitialized"); +} + + +void RGFW_window_close(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + #ifdef RGFW_BUFFER + DeleteDC(win->src.hdcMem); + DeleteObject(win->src.bitmap); + #endif + + if ((win->_flags & RGFW_windowNoInitAPI) == 0) RGFW_window_freeOpenGL(win); + RemovePropW(win->src.window, L"RGFW"); + ReleaseDC(win->src.window, win->src.hdc); /*!< delete device context */ + DestroyWindow(win->src.window); /*!< delete window */ + + if (win->src.hIconSmall) DestroyIcon(win->src.hIconSmall); + if (win->src.hIconBig) DestroyIcon(win->src.hIconBig); + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a window was freed"); + _RGFW.windowCount--; + if (_RGFW.windowCount == 0) RGFW_deinit(); + + RGFW_clipboard_switch(NULL); + RGFW_FREE(win->event.droppedFiles); + if ((win->_flags & RGFW_WINDOW_ALLOC)) { + RGFW_FREE(win); + win = NULL; + } +} + +void RGFW_window_move(RGFW_window* win, RGFW_point v) { + RGFW_ASSERT(win != NULL); + + win->r.x = v.x; + win->r.y = v.y; + SetWindowPos(win->src.window, HWND_TOP, win->r.x, win->r.y, 0, 0, SWP_NOSIZE); +} + +void RGFW_window_resize(RGFW_window* win, RGFW_area a) { + RGFW_ASSERT(win != NULL); + + win->r.w = (i32)a.w; + win->r.h = (i32)a.h; + SetWindowPos(win->src.window, HWND_TOP, 0, 0, win->r.w, win->r.h + (i32)win->src.hOffset, SWP_NOMOVE); +} + + +void RGFW_window_setName(RGFW_window* win, const char* name) { + RGFW_ASSERT(win != NULL); + + wchar_t wide_name[256]; + MultiByteToWideChar(CP_UTF8, 0, name, -1, wide_name, 256); + SetWindowTextW(win->src.window, wide_name); +} + +#ifndef RGFW_NO_PASSTHROUGH +void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { + RGFW_ASSERT(win != NULL); + COLORREF key = 0; + BYTE alpha = 0; + DWORD flags = 0; + i32 exStyle = GetWindowLongW(win->src.window, GWL_EXSTYLE); + + if (exStyle & WS_EX_LAYERED) + GetLayeredWindowAttributes(win->src.window, &key, &alpha, &flags); + + if (passthrough) + exStyle |= (WS_EX_TRANSPARENT | WS_EX_LAYERED); + else { + exStyle &= ~WS_EX_TRANSPARENT; + if (exStyle & WS_EX_LAYERED && !(flags & LWA_ALPHA)) + exStyle &= ~WS_EX_LAYERED; + } + + SetWindowLongW(win->src.window, GWL_EXSTYLE, exStyle); + + if (passthrough) + SetLayeredWindowAttributes(win->src.window, key, alpha, flags); +} +#endif + +RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* src, RGFW_area a, i32 channels, u8 type) { + RGFW_ASSERT(win != NULL); + #ifndef RGFW_WIN95 + RGFW_UNUSED(channels); + + if (win->src.hIconSmall && (type & RGFW_iconWindow)) DestroyIcon(win->src.hIconSmall); + if (win->src.hIconBig && (type & RGFW_iconTaskbar)) DestroyIcon(win->src.hIconBig); + + if (src == NULL) { + HICON defaultIcon = LoadIcon(NULL, IDI_APPLICATION); + if (type & RGFW_iconWindow) + SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)defaultIcon); + if (type & RGFW_iconTaskbar) + SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)defaultIcon); + return RGFW_TRUE; + } + + if (type & RGFW_iconWindow) { + win->src.hIconSmall = RGFW_loadHandleImage(src, channels, a, TRUE); + SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)win->src.hIconSmall); + } + if (type & RGFW_iconTaskbar) { + win->src.hIconBig = RGFW_loadHandleImage(src, channels, a, TRUE); + SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)win->src.hIconBig); + } + return RGFW_TRUE; + #else + RGFW_UNUSED(src); + RGFW_UNUSED(a); + RGFW_UNUSED(channels); + return RGFW_FALSE; + #endif +} + +RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { + /* Open the clipboard */ + if (OpenClipboard(NULL) == 0) + return -1; + + /* Get the clipboard data as a Unicode string */ + HANDLE hData = GetClipboardData(CF_UNICODETEXT); + if (hData == NULL) { + CloseClipboard(); + return -1; + } + + wchar_t* wstr = (wchar_t*) GlobalLock(hData); + + RGFW_ssize_t textLen = 0; + + { + setlocale(LC_ALL, "en_US.UTF-8"); + + textLen = (RGFW_ssize_t)wcstombs(NULL, wstr, 0) + 1; + if (str != NULL && (RGFW_ssize_t)strCapacity <= textLen - 1) + textLen = 0; + + if (str != NULL && textLen) { + if (textLen > 1) + wcstombs(str, wstr, (size_t)(textLen)); + + str[textLen] = '\0'; + } + } + + /* Release the clipboard data */ + GlobalUnlock(hData); + CloseClipboard(); + + return textLen; +} + +void RGFW_writeClipboard(const char* text, u32 textLen) { + HANDLE object; + WCHAR* buffer; + + object = GlobalAlloc(GMEM_MOVEABLE, (1 + textLen) * sizeof(WCHAR)); + if (!object) + return; + + buffer = (WCHAR*) GlobalLock(object); + if (!buffer) { + GlobalFree(object); + return; + } + + MultiByteToWideChar(CP_UTF8, 0, text, -1, buffer, (i32)textLen); + GlobalUnlock(object); + + if (!OpenClipboard(_RGFW.root->src.window)) { + GlobalFree(object); + return; + } + + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT, object); + CloseClipboard(); +} + +void RGFW_window_moveMouse(RGFW_window* win, RGFW_point p) { + RGFW_ASSERT(win != NULL); + win->_lastMousePoint = RGFW_POINT(p.x - win->r.x, p.y - win->r.y); + SetCursorPos(p.x, p.y); +} + +#ifdef RGFW_OPENGL +void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { + if (win == NULL) + wglMakeCurrent(NULL, NULL); + else + wglMakeCurrent(win->src.hdc, (HGLRC) win->src.ctx); +} +void* RGFW_getCurrent_OpenGL(void) { return wglGetCurrentContext(); } +void RGFW_window_swapBuffers_OpenGL(RGFW_window* win){ SwapBuffers(win->src.hdc); } +#endif + +#ifndef RGFW_EGL +void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { + RGFW_ASSERT(win != NULL); +#if defined(RGFW_OPENGL) + if (wglSwapIntervalEXT == NULL || wglSwapIntervalEXT(swapInterval) == FALSE) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to set swap interval"); +#else + RGFW_UNUSED(swapInterval); +#endif +} +#endif + +void RGFW_window_swapBuffers_software(RGFW_window* win) { +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + if (win->buffer != win->src.bitmapBits) + memcpy(win->src.bitmapBits, win->buffer, win->bufferSize.w * win->bufferSize.h * 4); + + RGFW_RGB_to_BGR(win, win->src.bitmapBits); + BitBlt(win->src.hdc, 0, 0, win->r.w, win->r.h, win->src.hdcMem, 0, 0, SRCCOPY); +#else + RGFW_UNUSED(win); +#endif +} + +char* RGFW_createUTF8FromWideStringWin32(const WCHAR* source) { + if (source == NULL) { + return NULL; + } + i32 size = WideCharToMultiByte(CP_UTF8, 0, source, -1, NULL, 0, NULL, NULL); + if (!size) { + return NULL; + } + + static char target[RGFW_MAX_PATH * 2]; + if (size > RGFW_MAX_PATH * 2) + size = RGFW_MAX_PATH * 2; + + target[size] = 0; + + if (!WideCharToMultiByte(CP_UTF8, 0, source, -1, target, size, NULL, NULL)) { + return NULL; + } + + return target; +} + +u64 RGFW_getTimerFreq(void) { + static u64 frequency = 0; + if (frequency == 0) QueryPerformanceFrequency((LARGE_INTEGER*)&frequency); + + return frequency; +} + +u64 RGFW_getTimerValue(void) { + u64 value; + QueryPerformanceCounter((LARGE_INTEGER*)&value); + return value; +} + +void RGFW_sleep(u64 ms) { + Sleep((u32)ms); +} + +#ifndef RGFW_NO_THREADS + +RGFW_thread RGFW_createThread(RGFW_threadFunc_ptr ptr, void* args) { return CreateThread(NULL, 0, ptr, args, 0, NULL); } +void RGFW_cancelThread(RGFW_thread thread) { CloseHandle((HANDLE) thread); } +void RGFW_joinThread(RGFW_thread thread) { WaitForSingleObject((HANDLE) thread, INFINITE); } +void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { SetThreadPriority((HANDLE) thread, priority); } + +#endif +#endif /* RGFW_WINDOWS */ + +/* + End of Windows defines +*/ + + + +/* + + Start of MacOS defines + + +*/ + +#if defined(RGFW_MACOS) +/* + based on silicon.h + start of cocoa wrapper +*/ + +#include +#include +#include +#include +#include +#include + +typedef CGRect NSRect; +typedef CGPoint NSPoint; +typedef CGSize NSSize; + +typedef const char* NSPasteboardType; +typedef unsigned long NSUInteger; +typedef long NSInteger; +typedef NSInteger NSModalResponse; + +#ifdef __arm64__ + /* ARM just uses objc_msgSend */ +#define abi_objc_msgSend_stret objc_msgSend +#define abi_objc_msgSend_fpret objc_msgSend +#else /* __i386__ */ + /* x86 just uses abi_objc_msgSend_fpret and (NSColor *)objc_msgSend_id respectively */ +#define abi_objc_msgSend_stret objc_msgSend_stret +#define abi_objc_msgSend_fpret objc_msgSend_fpret +#endif + +#define NSAlloc(nsclass) objc_msgSend_id((id)nsclass, sel_registerName("alloc")) +#define objc_msgSend_bool(x, y) ((BOOL (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_void(x, y) ((void (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_void_id(x, y, z) ((void (*)(id, SEL, id))objc_msgSend) ((id)x, (SEL)y, (id)z) +#define objc_msgSend_uint(x, y) ((NSUInteger (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_void_bool(x, y, z) ((void (*)(id, SEL, BOOL))objc_msgSend) ((id)(x), (SEL)y, (BOOL)z) +#define objc_msgSend_bool_void(x, y) ((BOOL (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_void_SEL(x, y, z) ((void (*)(id, SEL, SEL))objc_msgSend) ((id)(x), (SEL)y, (SEL)z) +#define objc_msgSend_id(x, y) ((id (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_id_id(x, y, z) ((id (*)(id, SEL, id))objc_msgSend) ((id)(x), (SEL)y, (id)z) +#define objc_msgSend_id_bool(x, y, z) ((BOOL (*)(id, SEL, id))objc_msgSend) ((id)(x), (SEL)y, (id)z) +#define objc_msgSend_int(x, y, z) ((id (*)(id, SEL, int))objc_msgSend) ((id)(x), (SEL)y, (int)z) +#define objc_msgSend_arr(x, y, z) ((id (*)(id, SEL, int))objc_msgSend) ((id)(x), (SEL)y, (int)z) +#define objc_msgSend_ptr(x, y, z) ((id (*)(id, SEL, void*))objc_msgSend) ((id)(x), (SEL)y, (void*)z) +#define objc_msgSend_class(x, y) ((id (*)(Class, SEL))objc_msgSend) ((Class)(x), (SEL)y) +#define objc_msgSend_class_char(x, y, z) ((id (*)(Class, SEL, char*))objc_msgSend) ((Class)(x), (SEL)y, (char*)z) + +id NSApp = NULL; + +#define NSRelease(obj) objc_msgSend_void((id)obj, sel_registerName("release")) +id NSString_stringWithUTF8String(const char* str); +id NSString_stringWithUTF8String(const char* str) { + return ((id(*)(id, SEL, const char*))objc_msgSend) + ((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), str); +} + +const char* NSString_to_char(id str); +const char* NSString_to_char(id str) { + return ((const char* (*)(id, SEL)) objc_msgSend) ((id)(id)str, sel_registerName("UTF8String")); +} + +void si_impl_func_to_SEL_with_name(const char* class_name, const char* register_name, void* function); +void si_impl_func_to_SEL_with_name(const char* class_name, const char* register_name, void* function) { + Class selected_class; + + if (RGFW_STRNCMP(class_name, "NSView", 6) == 0) { + selected_class = objc_getClass("ViewClass"); + } else if (RGFW_STRNCMP(class_name, "NSWindow", 8) == 0) { + selected_class = objc_getClass("WindowClass"); + } else { + selected_class = objc_getClass(class_name); + } + + class_addMethod(selected_class, sel_registerName(register_name), (IMP) function, 0); +} + +/* Header for the array. */ +typedef struct siArrayHeader { + size_t count; + /* TODO(EimaMei): Add a `type_width` later on. */ +} siArrayHeader; + +/* Gets the header of the siArray. */ +#define SI_ARRAY_HEADER(s) ((siArrayHeader*)s - 1) +#define si_array_len(array) (SI_ARRAY_HEADER(array)->count) +#define si_func_to_SEL(class_name, function) si_impl_func_to_SEL_with_name(class_name, #function":", (void*)function) +/* Creates an Objective-C method (SEL) from a regular C function with the option to set the register name.*/ +#define si_func_to_SEL_with_name(class_name, register_name, function) si_impl_func_to_SEL_with_name(class_name, register_name":", (void*)function) + +unsigned char* NSBitmapImageRep_bitmapData(id imageRep); +unsigned char* NSBitmapImageRep_bitmapData(id imageRep) { + return ((unsigned char* (*)(id, SEL))objc_msgSend) ((id)imageRep, sel_registerName("bitmapData")); +} + +typedef RGFW_ENUM(NSUInteger, NSBitmapFormat) { + NSBitmapFormatAlphaFirst = 1 << 0, /* 0 means is alpha last (RGBA, CMYKA, etc.) */ + NSBitmapFormatAlphaNonpremultiplied = 1 << 1, /* 0 means is premultiplied */ + NSBitmapFormatFloatingpointSamples = 1 << 2, /* 0 is integer */ + + NSBitmapFormatSixteenBitLittleEndian API_AVAILABLE(macos(10.10)) = (1 << 8), + NSBitmapFormatThirtyTwoBitLittleEndian API_AVAILABLE(macos(10.10)) = (1 << 9), + NSBitmapFormatSixteenBitBigEndian API_AVAILABLE(macos(10.10)) = (1 << 10), + NSBitmapFormatThirtyTwoBitBigEndian API_AVAILABLE(macos(10.10)) = (1 << 11) +}; + +id NSBitmapImageRep_initWithBitmapData(unsigned char** planes, NSInteger width, NSInteger height, NSInteger bps, NSInteger spp, bool alpha, bool isPlanar, const char* colorSpaceName, NSBitmapFormat bitmapFormat, NSInteger rowBytes, NSInteger pixelBits); +id NSBitmapImageRep_initWithBitmapData(unsigned char** planes, NSInteger width, NSInteger height, NSInteger bps, NSInteger spp, bool alpha, bool isPlanar, const char* colorSpaceName, NSBitmapFormat bitmapFormat, NSInteger rowBytes, NSInteger pixelBits) { + SEL func = sel_registerName("initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel:"); + + return (id) ((id(*)(id, SEL, unsigned char**, NSInteger, NSInteger, NSInteger, NSInteger, bool, bool, id, NSBitmapFormat, NSInteger, NSInteger))objc_msgSend) + (NSAlloc((id)objc_getClass("NSBitmapImageRep")), func, planes, width, height, bps, spp, alpha, isPlanar, NSString_stringWithUTF8String(colorSpaceName), bitmapFormat, rowBytes, pixelBits); +} + +id NSColor_colorWithSRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha); +id NSColor_colorWithSRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) { + void* nsclass = objc_getClass("NSColor"); + SEL func = sel_registerName("colorWithSRGBRed:green:blue:alpha:"); + return ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend) + ((id)nsclass, func, red, green, blue, alpha); +} + +#define NS_OPENGL_ENUM_DEPRECATED(minVers, maxVers) API_AVAILABLE(macos(minVers)) +typedef RGFW_ENUM(NSInteger, NSOpenGLContextParameter) { + NSOpenGLContextParameterSwapInterval NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 222, /* 1 param. 0 -> Don't sync, 1 -> Sync to vertical retrace */ + NSOpenGLContextParametectxaceOrder NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 235, /* 1 param. 1 -> Above Window (default), -1 -> Below Window */ + NSOpenGLContextParametectxaceOpacity NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 236, /* 1 param. 1-> Surface is opaque (default), 0 -> non-opaque */ + NSOpenGLContextParametectxaceBackingSize NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 304, /* 2 params. Width/height of surface backing size */ + NSOpenGLContextParameterReclaimResources NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 308, /* 0 params. */ + NSOpenGLContextParameterCurrentRendererID NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 309, /* 1 param. Retrieves the current renderer ID */ + NSOpenGLContextParameterGPUVertexProcessing NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 310, /* 1 param. Currently processing vertices with GPU (get) */ + NSOpenGLContextParameterGPUFragmentProcessing NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 311, /* 1 param. Currently processing fragments with GPU (get) */ + NSOpenGLContextParameterHasDrawable NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 314, /* 1 param. Boolean returned if drawable is attached */ + NSOpenGLContextParameterMPSwapsInFlight NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 315, /* 1 param. Max number of swaps queued by the MP GL engine */ + + NSOpenGLContextParameterSwapRectangle API_DEPRECATED("", macos(10.0, 10.14)) = 200, /* 4 params. Set or get the swap rectangle {x, y, w, h} */ + NSOpenGLContextParameterSwapRectangleEnable API_DEPRECATED("", macos(10.0, 10.14)) = 201, /* Enable or disable the swap rectangle */ + NSOpenGLContextParameterRasterizationEnable API_DEPRECATED("", macos(10.0, 10.14)) = 221, /* Enable or disable all rasterization */ + NSOpenGLContextParameterStateValidation API_DEPRECATED("", macos(10.0, 10.14)) = 301, /* Validate state for multi-screen functionality */ + NSOpenGLContextParametectxaceSurfaceVolatile API_DEPRECATED("", macos(10.0, 10.14)) = 306, /* 1 param. Surface volatile state */ +}; + +typedef RGFW_ENUM(NSInteger, NSWindowButton) { + NSWindowCloseButton = 0, + NSWindowMiniaturizeButton = 1, + NSWindowZoomButton = 2, + NSWindowToolbarButton = 3, + NSWindowDocumentIconButton = 4, + NSWindowDocumentVersionsButton = 6, + NSWindowFullScreenButton = 7, +}; +void NSOpenGLContext_setValues(id context, const int* vals, NSOpenGLContextParameter param); +void NSOpenGLContext_setValues(id context, const int* vals, NSOpenGLContextParameter param) { + ((void (*)(id, SEL, const int*, NSOpenGLContextParameter))objc_msgSend) + (context, sel_registerName("setValues:forParameter:"), vals, param); +} +void* NSOpenGLPixelFormat_initWithAttributes(const uint32_t* attribs); +void* NSOpenGLPixelFormat_initWithAttributes(const uint32_t* attribs) { + return (void*) ((id(*)(id, SEL, const uint32_t*))objc_msgSend) + (NSAlloc((id)objc_getClass("NSOpenGLPixelFormat")), sel_registerName("initWithAttributes:"), attribs); +} + +id NSPasteboard_generalPasteboard(void); +id NSPasteboard_generalPasteboard(void) { + return (id) objc_msgSend_id((id)objc_getClass("NSPasteboard"), sel_registerName("generalPasteboard")); +} + +id* cstrToNSStringArray(char** strs, size_t len); +id* cstrToNSStringArray(char** strs, size_t len) { + static id nstrs[6]; + size_t i; + for (i = 0; i < len; i++) + nstrs[i] = NSString_stringWithUTF8String(strs[i]); + + return nstrs; +} + +const char* NSPasteboard_stringForType(id pasteboard, NSPasteboardType dataType, size_t* len); +const char* NSPasteboard_stringForType(id pasteboard, NSPasteboardType dataType, size_t* len) { + SEL func = sel_registerName("stringForType:"); + id nsstr = NSString_stringWithUTF8String(dataType); + id nsString = ((id(*)(id, SEL, id))objc_msgSend)(pasteboard, func, nsstr); + const char* str = NSString_to_char(nsString); + if (len != NULL) + *len = (size_t)((NSUInteger(*)(id, SEL, int))objc_msgSend)(nsString, sel_registerName("maximumLengthOfBytesUsingEncoding:"), 4); + return str; +} + +id c_array_to_NSArray(void* array, size_t len); +id c_array_to_NSArray(void* array, size_t len) { + SEL func = sel_registerName("initWithObjects:count:"); + void* nsclass = objc_getClass("NSArray"); + return ((id (*)(id, SEL, void*, NSUInteger))objc_msgSend) + (NSAlloc(nsclass), func, array, len); +} + + +void NSregisterForDraggedTypes(id view, NSPasteboardType* newTypes, size_t len); +void NSregisterForDraggedTypes(id view, NSPasteboardType* newTypes, size_t len) { + id* ntypes = cstrToNSStringArray((char**)newTypes, len); + + id array = c_array_to_NSArray(ntypes, len); + objc_msgSend_void_id(view, sel_registerName("registerForDraggedTypes:"), array); + NSRelease(array); +} + +NSInteger NSPasteBoard_declareTypes(id pasteboard, NSPasteboardType* newTypes, size_t len, void* owner); +NSInteger NSPasteBoard_declareTypes(id pasteboard, NSPasteboardType* newTypes, size_t len, void* owner) { + id* ntypes = cstrToNSStringArray((char**)newTypes, len); + + SEL func = sel_registerName("declareTypes:owner:"); + + id array = c_array_to_NSArray(ntypes, len); + + NSInteger output = ((NSInteger(*)(id, SEL, id, void*))objc_msgSend) + (pasteboard, func, array, owner); + NSRelease(array); + + return output; +} + +#define NSRetain(obj) objc_msgSend_void((id)obj, sel_registerName("retain")) + +typedef enum NSApplicationActivationPolicy { + NSApplicationActivationPolicyRegular, + NSApplicationActivationPolicyAccessory, + NSApplicationActivationPolicyProhibited +} NSApplicationActivationPolicy; + +typedef RGFW_ENUM(u32, NSBackingStoreType) { + NSBackingStoreRetained = 0, + NSBackingStoreNonretained = 1, + NSBackingStoreBuffered = 2 +}; + +typedef RGFW_ENUM(u32, NSWindowStyleMask) { + NSWindowStyleMaskBorderless = 0, + NSWindowStyleMaskTitled = 1 << 0, + NSWindowStyleMaskClosable = 1 << 1, + NSWindowStyleMaskMiniaturizable = 1 << 2, + NSWindowStyleMaskResizable = 1 << 3, + NSWindowStyleMaskTexturedBackground = 1 << 8, /* deprecated */ + NSWindowStyleMaskUnifiedTitleAndToolbar = 1 << 12, + NSWindowStyleMaskFullScreen = 1 << 14, + NSWindowStyleMaskFullSizeContentView = 1 << 15, + NSWindowStyleMaskUtilityWindow = 1 << 4, + NSWindowStyleMaskDocModalWindow = 1 << 6, + NSWindowStyleMaskNonactivatingpanel = 1 << 7, + NSWindowStyleMaskHUDWindow = 1 << 13 +}; + +NSPasteboardType const NSPasteboardTypeString = "public.utf8-plain-text"; /* Replaces NSStringPasteboardType */ + + +typedef RGFW_ENUM(i32, NSDragOperation) { + NSDragOperationNone = 0, + NSDragOperationCopy = 1, + NSDragOperationLink = 2, + NSDragOperationGeneric = 4, + NSDragOperationPrivate = 8, + NSDragOperationMove = 16, + NSDragOperationDelete = 32, + NSDragOperationEvery = (int)ULONG_MAX +}; + +void* NSArray_objectAtIndex(id array, NSUInteger index) { + SEL func = sel_registerName("objectAtIndex:"); + return ((id(*)(id, SEL, NSUInteger))objc_msgSend)(array, func, index); +} + +id NSWindow_contentView(id window) { + SEL func = sel_registerName("contentView"); + return objc_msgSend_id(window, func); +} + +/* + End of cocoa wrapper +*/ + +#ifdef RGFW_OPENGL +CFBundleRef RGFWnsglFramework = NULL; + +RGFW_proc RGFW_getProcAddress(const char* procname) { + if (RGFWnsglFramework == NULL) + RGFWnsglFramework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); + + CFStringRef symbolName = CFStringCreateWithCString(kCFAllocatorDefault, procname, kCFStringEncodingASCII); + + RGFW_proc symbol = (RGFW_proc)CFBundleGetFunctionPointerForName(RGFWnsglFramework, symbolName); + + CFRelease(symbolName); + + return symbol; +} +#endif + +id NSWindow_delegate(RGFW_window* win) { + return (id) objc_msgSend_id((id)win->src.window, sel_registerName("delegate")); +} + +u32 RGFW_OnClose(id self) { + RGFW_window* win = NULL; + object_getInstanceVariable(self, (const char*)"RGFW_window", (void**)&win); + if (win == NULL) + return true; + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_quit, ._win = win}); + RGFW_windowQuitCallback(win); + + return false; +} + +/* NOTE(EimaMei): Fixes the constant clicking when the app is running under a terminal. */ +bool acceptsFirstResponder(void) { return true; } +bool performKeyEquivalent(id event) { RGFW_UNUSED(event); return true; } + +NSDragOperation draggingEntered(id self, SEL sel, id sender) { + RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); + + return NSDragOperationCopy; +} +NSDragOperation draggingUpdated(id self, SEL sel, id sender) { + RGFW_UNUSED(sel); + + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL || (!(win->_flags & RGFW_windowAllowDND))) + return 0; + + NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(sender, sel_registerName("draggingLocation")); + RGFW_eventQueuePush((RGFW_event){.type = RGFW_DNDInit, + .point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)), + ._win = win}); + + RGFW_dndInitCallback(win, win->event.point); + return NSDragOperationCopy; +} +bool prepareForDragOperation(id self) { + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) + return true; + + if (!(win->_flags & RGFW_windowAllowDND)) { + return false; + } + + return true; +} + +void RGFW__osxDraggingEnded(id self, SEL sel, id sender); +void RGFW__osxDraggingEnded(id self, SEL sel, id sender) { RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); return; } + +/* NOTE(EimaMei): Usually, you never need 'id self, SEL cmd' for C -> Obj-C methods. This isn't the case. */ +bool performDragOperation(id self, SEL sel, id sender) { + RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); + + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + + if (win == NULL) + return false; + + /* id pasteBoard = objc_msgSend_id(sender, sel_registerName("draggingPasteboard")); */ + + id pasteBoard = objc_msgSend_id(sender, sel_registerName("draggingPasteboard")); + + /* Get the types of data available on the pasteboard */ + id types = objc_msgSend_id(pasteBoard, sel_registerName("types")); + + /* Get the string type for file URLs */ + id fileURLsType = objc_msgSend_class_char(objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "NSFilenamesPboardType"); + + /* Check if the pasteboard contains file URLs */ + if (objc_msgSend_id_bool(types, sel_registerName("containsObject:"), fileURLsType) == 0) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, RGFW_DEBUG_CTX(win, 0), "No files found on the pasteboard."); + return 0; + } + + id fileURLs = objc_msgSend_id_id(pasteBoard, sel_registerName("propertyListForType:"), fileURLsType); + int count = ((int (*)(id, SEL))objc_msgSend)(fileURLs, sel_registerName("count")); + + if (count == 0) + return 0; + + int i; + for (i = 0; i < count; i++) { + id fileURL = objc_msgSend_arr(fileURLs, sel_registerName("objectAtIndex:"), i); + const char *filePath = ((const char* (*)(id, SEL))objc_msgSend)(fileURL, sel_registerName("UTF8String")); + RGFW_MEMCPY(win->event.droppedFiles[i], filePath, RGFW_MAX_PATH); + win->event.droppedFiles[i][RGFW_MAX_PATH - 1] = '\0'; + } + NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(sender, sel_registerName("draggingLocation")); + RGFW_eventQueuePush((RGFW_event){.type = RGFW_DND, + .point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)), + .droppedFilesCount = (size_t)count, + ._win = win}); + + RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); + + return false; +} + +#ifndef RGFW_NO_IOKIT +#include +#include + +IOHIDDeviceRef RGFW_osxControllers[4] = {NULL}; + +size_t findControllerIndex(IOHIDDeviceRef device) { + size_t i; + for (i = 0; i < 4; i++) + if (RGFW_osxControllers[i] == device) + return i; + return (size_t)-1; +} + +void RGFW__osxInputValueChangedCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value) { + RGFW_UNUSED(context); RGFW_UNUSED(result); RGFW_UNUSED(sender); + IOHIDElementRef element = IOHIDValueGetElement(value); + + IOHIDDeviceRef device = IOHIDElementGetDevice(element); + size_t index = findControllerIndex(device); + if (index == (size_t)-1) return; + + uint32_t usagePage = IOHIDElementGetUsagePage(element); + uint32_t usage = IOHIDElementGetUsage(element); + + CFIndex intValue = IOHIDValueGetIntegerValue(value); + + u8 RGFW_osx2RGFWSrc[2][RGFW_gamepadFinal] = {{ + 0, RGFW_gamepadSelect, RGFW_gamepadL3, RGFW_gamepadR3, RGFW_gamepadStart, + RGFW_gamepadUp, RGFW_gamepadRight, RGFW_gamepadDown, RGFW_gamepadLeft, + RGFW_gamepadL2, RGFW_gamepadR2, RGFW_gamepadL1, RGFW_gamepadR1, + RGFW_gamepadY, RGFW_gamepadB, RGFW_gamepadA, RGFW_gamepadX, RGFW_gamepadHome}, + {0, RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadR3, RGFW_gamepadX, + RGFW_gamepadY, RGFW_gamepadRight, RGFW_gamepadL1, RGFW_gamepadR1, + RGFW_gamepadL2, RGFW_gamepadR2, RGFW_gamepadDown, RGFW_gamepadStart, + RGFW_gamepadUp, RGFW_gamepadL3, RGFW_gamepadSelect, RGFW_gamepadStart, RGFW_gamepadHome} + }; + + u8* RGFW_osx2RGFW = RGFW_osx2RGFWSrc[0]; + if (RGFW_gamepads_type[index] == RGFW_gamepadMicrosoft) + RGFW_osx2RGFW = RGFW_osx2RGFWSrc[1]; + + switch (usagePage) { + case kHIDPage_Button: { + u8 button = 0; + if (usage < sizeof(RGFW_osx2RGFW)) + button = RGFW_osx2RGFW[usage]; + + RGFW_gamepadButtonCallback(_RGFW.root, (u16)index, button, (u8)intValue); + RGFW_gamepadPressed[index][button].prev = RGFW_gamepadPressed[index][button].current; + RGFW_gamepadPressed[index][button].current = RGFW_BOOL(intValue); + RGFW_eventQueuePush((RGFW_event){.type = intValue ? RGFW_gamepadButtonPressed: RGFW_gamepadButtonReleased, + .button = button, + .gamepad = (u16)index, + ._win = _RGFW.root}); + break; + } + case kHIDPage_GenericDesktop: { + CFIndex logicalMin = IOHIDElementGetLogicalMin(element); + CFIndex logicalMax = IOHIDElementGetLogicalMax(element); + + if (logicalMax <= logicalMin) return; + if (intValue < logicalMin) intValue = logicalMin; + if (intValue > logicalMax) intValue = logicalMax; + + i8 axisValue = (i8)(-100.0 + ((intValue - logicalMin) * 200.0) / (logicalMax - logicalMin)); + + u8 whichAxis = 0; + switch (usage) { + case kHIDUsage_GD_X: RGFW_gamepadAxes[index][0].x = axisValue; whichAxis = 0; break; + case kHIDUsage_GD_Y: RGFW_gamepadAxes[index][0].y = axisValue; whichAxis = 0; break; + case kHIDUsage_GD_Z: RGFW_gamepadAxes[index][1].x = axisValue; whichAxis = 1; break; + case kHIDUsage_GD_Rz: RGFW_gamepadAxes[index][1].y = axisValue; whichAxis = 1; break; + default: return; + } + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_gamepadAxisMove, + .gamepad = (u16)index, + .axis = {RGFW_gamepadAxes[index][0], RGFW_gamepadAxes[index][1], + RGFW_gamepadAxes[index][2], RGFW_gamepadAxes[index][3]}, + .whichAxis = whichAxis, + ._win = _RGFW.root}); + + RGFW_gamepadAxisCallback(_RGFW.root, (u16)index, RGFW_gamepadAxes[index], 2, whichAxis); + } + } +} + +void RGFW__osxDeviceAddedCallback(void* context, IOReturn result, void *sender, IOHIDDeviceRef device) { + RGFW_UNUSED(context); RGFW_UNUSED(result); RGFW_UNUSED(sender); + CFTypeRef usageRef = (CFTypeRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDPrimaryUsageKey)); + int usage = 0; + if (usageRef) + CFNumberGetValue((CFNumberRef)usageRef, kCFNumberIntType, (void*)&usage); + + if (usage != kHIDUsage_GD_Joystick && usage != kHIDUsage_GD_GamePad && usage != kHIDUsage_GD_MultiAxisController) { + return; + } + + size_t i; + for (i = 0; i < 4; i++) { + if (RGFW_osxControllers[i] != NULL) + continue; + + RGFW_osxControllers[i] = device; + + IOHIDDeviceRegisterInputValueCallback(device, RGFW__osxInputValueChangedCallback, NULL); + + CFStringRef deviceName = (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); + if (deviceName) + CFStringGetCString(deviceName, RGFW_gamepads_name[i], sizeof(RGFW_gamepads_name[i]), kCFStringEncodingUTF8); + + RGFW_gamepads_type[i] = RGFW_gamepadUnknown; + if (RGFW_STRSTR(RGFW_gamepads_name[i], "Microsoft") || RGFW_STRSTR(RGFW_gamepads_name[i], "X-Box") || RGFW_STRSTR(RGFW_gamepads_name[i], "Xbox")) + RGFW_gamepads_type[i] = RGFW_gamepadMicrosoft; + else if (RGFW_STRSTR(RGFW_gamepads_name[i], "PlayStation") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS3") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS4") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS5")) + RGFW_gamepads_type[i] = RGFW_gamepadSony; + else if (RGFW_STRSTR(RGFW_gamepads_name[i], "Nintendo")) + RGFW_gamepads_type[i] = RGFW_gamepadNintendo; + else if (RGFW_STRSTR(RGFW_gamepads_name[i], "Logitech")) + RGFW_gamepads_type[i] = RGFW_gamepadLogitech; + + RGFW_gamepads[i] = (u16)i; + RGFW_gamepadCount++; + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_gamepadConnected, + .gamepad = (u16)i, + ._win = _RGFW.root}); + + RGFW_gamepadCallback(_RGFW.root, (u16)i, 1); + break; + } +} + +void RGFW__osxDeviceRemovedCallback(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) { + RGFW_UNUSED(context); RGFW_UNUSED(result); RGFW_UNUSED(sender); RGFW_UNUSED(device); + CFNumberRef usageRef = (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDPrimaryUsageKey)); + int usage = 0; + if (usageRef) + CFNumberGetValue(usageRef, kCFNumberIntType, &usage); + + if (usage != kHIDUsage_GD_Joystick && usage != kHIDUsage_GD_GamePad && usage != kHIDUsage_GD_MultiAxisController) { + return; + } + + size_t index = findControllerIndex(device); + if (index != (size_t)-1) + RGFW_osxControllers[index] = NULL; + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_gamepadDisconnected, + .gamepad = (u16)index, + ._win = _RGFW.root}); + RGFW_gamepadCallback(_RGFW.root, (u16)index, 0); + + RGFW_gamepadCount--; +} + +RGFWDEF void RGFW_osxInitIOKit(void); +void RGFW_osxInitIOKit(void) { + IOHIDManagerRef hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + if (!hidManager) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errIOKit, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to create IOHIDManager."); + return; + } + + CFMutableDictionaryRef matchingDictionary = CFDictionaryCreateMutable( + kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks + ); + if (!matchingDictionary) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errIOKit, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to create matching dictionary for IOKit."); + CFRelease(hidManager); + return; + } + + CFDictionarySetValue( + matchingDictionary, + CFSTR(kIOHIDDeviceUsagePageKey), + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, (int[]){kHIDPage_GenericDesktop}) + ); + + IOHIDManagerSetDeviceMatching(hidManager, matchingDictionary); + + IOHIDManagerRegisterDeviceMatchingCallback(hidManager, RGFW__osxDeviceAddedCallback, NULL); + IOHIDManagerRegisterDeviceRemovalCallback(hidManager, RGFW__osxDeviceRemovedCallback, NULL); + + IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + + IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone); + + /* Execute the run loop once in order to register any initially-attached joysticks */ + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); +} +#endif + +void RGFW_moveToMacOSResourceDir(void) { + char resourcesPath[256]; + + CFBundleRef bundle = CFBundleGetMainBundle(); + if (!bundle) + return; + + CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(bundle); + CFStringRef last = CFURLCopyLastPathComponent(resourcesURL); + + if ( + CFStringCompare(CFSTR("Resources"), last, 0) != kCFCompareEqualTo || + CFURLGetFileSystemRepresentation(resourcesURL, true, (u8*) resourcesPath, 255) == 0 + ) { + CFRelease(last); + CFRelease(resourcesURL); + return; + } + + CFRelease(last); + CFRelease(resourcesURL); + + chdir(resourcesPath); +} + + +void RGFW__osxWindowDeminiaturize(id self, SEL sel) { + RGFW_UNUSED(sel); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + win->_flags |= RGFW_windowMinimize; + RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowRestored, ._win = win}); + RGFW_windowRestoredCallback(win, win->r); + +} +void RGFW__osxWindowMiniaturize(id self, SEL sel) { + RGFW_UNUSED(sel); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + win->_flags &= ~(u32)RGFW_windowMinimize; + RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMinimized, ._win = win}); + RGFW_windowMinimizedCallback(win, win->r); + +} + +void RGFW__osxWindowBecameKey(id self, SEL sel) { + RGFW_UNUSED(sel); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + win->_flags |= RGFW_windowFocus; + RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusIn, ._win = win}); + + RGFW_focusCallback(win, RGFW_TRUE); +} + +void RGFW__osxWindowResignKey(id self, SEL sel) { + RGFW_UNUSED(sel); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + win->_flags &= ~(u32)RGFW_windowFocus; + RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusOut, ._win = win}); + RGFW_focusCallback(win, RGFW_FALSE); +} + +NSSize RGFW__osxWindowResize(id self, SEL sel, NSSize frameSize) { + RGFW_UNUSED(sel); + + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return frameSize; + + win->r.w = (i32)frameSize.width; + win->r.h = (i32)frameSize.height; + + RGFW_monitor mon = RGFW_window_getMonitor(win); + if ((i32)mon.mode.area.w == win->r.w && (i32)mon.mode.area.h - 102 <= win->r.h) { + win->_flags |= RGFW_windowMaximize; + RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMaximized, ._win = win}); + RGFW_windowMaximizedCallback(win, win->r); + } else if (win->_flags & RGFW_windowMaximize) { + win->_flags &= ~(u32)RGFW_windowMaximize; + RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowRestored, ._win = win}); + RGFW_windowRestoredCallback(win, win->r); + + } + + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowResized, ._win = win}); + RGFW_windowResizedCallback(win, win->r); + return frameSize; +} + +void RGFW__osxWindowMove(id self, SEL sel) { + RGFW_UNUSED(sel); + + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.window, sel_registerName("frame")); + win->r.x = (i32) frame.origin.x; + win->r.y = (i32) frame.origin.y; + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMoved, ._win = win}); + RGFW_windowMovedCallback(win, win->r); +} + +void RGFW__osxViewDidChangeBackingProperties(id self, SEL _cmd) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + RGFW_monitor mon = RGFW_window_getMonitor(win); + RGFW_scaleUpdatedCallback(win, mon.scaleX, mon.scaleY); + RGFW_eventQueuePush((RGFW_event){.type = RGFW_scaleUpdated, .scaleX = mon.scaleX, .scaleY = mon.scaleY , ._win = win}); +} + +void RGFW__osxDrawRect(id self, SEL _cmd, CGRect rect) { + RGFW_UNUSED(rect); RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowRefresh, ._win = win}); + RGFW_windowRefreshCallback(win); +} + +void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area) { + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + win->buffer = buffer; + win->bufferSize = area; + win->_flags |= RGFW_BUFFER_ALLOC; + #ifdef RGFW_OSMESA + win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); + OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); + OSMesaPixelStore(OSMESA_Y_UP, 0); + #endif + #else + RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); /*!< if buffer rendering is not being used */ + #endif +} + +void RGFW_window_cocoaSetLayer(RGFW_window* win, void* layer) { + objc_msgSend_void_id((id)win->src.view, sel_registerName("setLayer"), (id)layer); +} + +void* RGFW_cocoaGetLayer(void) { + return objc_msgSend_class((id)objc_getClass("CAMetalLayer"), (SEL)sel_registerName("layer")); +} + +NSPasteboardType const NSPasteboardTypeURL = "public.url"; +NSPasteboardType const NSPasteboardTypeFileURL = "public.file-url"; + +id RGFW__osx_generateViewClass(const char* subclass, RGFW_window* win) { + Class customViewClass; + customViewClass = objc_allocateClassPair(objc_getClass(subclass), "RGFWCustomView", 0); + + class_addIvar( customViewClass, "RGFW_window", sizeof(RGFW_window*), (u8)rint(log2(sizeof(RGFW_window*))), "L"); + class_addMethod(customViewClass, sel_registerName("drawRect:"), (IMP)RGFW__osxDrawRect, "v@:{CGRect=ffff}"); + class_addMethod(customViewClass, sel_registerName("viewDidChangeBackingProperties"), (IMP)RGFW__osxViewDidChangeBackingProperties, ""); + + id customView = objc_msgSend_id(NSAlloc(customViewClass), sel_registerName("init")); + object_setInstanceVariable(customView, "RGFW_window", win); + + return customView; +} + +#ifndef RGFW_EGL +void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { +#ifdef RGFW_OPENGL + void* attrs = RGFW_initFormatAttribs(software); + void* format = NSOpenGLPixelFormat_initWithAttributes((uint32_t*)attrs); + + if (format == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to load pixel format for OpenGL"); + void* subAttrs = RGFW_initFormatAttribs(1); + format = NSOpenGLPixelFormat_initWithAttributes((uint32_t*)subAttrs); + + if (format == NULL) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "and loading software rendering OpenGL failed"); + else + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, RGFW_DEBUG_CTX(win, 0), "Switching to software rendering"); + } + + /* the pixel format can be passed directly to opengl context creation to create a context + this is because the format also includes information about the opengl version (which may be a bad thing) */ + + win->src.view = (id) ((id(*)(id, SEL, NSRect, uint32_t*))objc_msgSend) (RGFW__osx_generateViewClass("NSOpenGLView", win), + sel_registerName("initWithFrame:pixelFormat:"), (NSRect){{0, 0}, {win->r.w, win->r.h}}, (uint32_t*)format); + + objc_msgSend_void(win->src.view, sel_registerName("prepareOpenGL")); + win->src.ctx = objc_msgSend_id(win->src.view, sel_registerName("openGLContext")); + + if (win->_flags & RGFW_windowTransparent) { + i32 opacity = 0; + #define NSOpenGLCPSurfaceOpacity 236 + NSOpenGLContext_setValues((id)win->src.ctx, &opacity, NSOpenGLCPSurfaceOpacity); + } + + objc_msgSend_void(win->src.ctx, sel_registerName("makeCurrentContext")); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context initalized"); +#else + RGFW_UNUSED(win); RGFW_UNUSED(software); +#endif +} + +void RGFW_window_freeOpenGL(RGFW_window* win) { +#ifdef RGFW_OPENGL + if (win->src.ctx == NULL) return; + objc_msgSend_void(win->src.ctx, sel_registerName("release")); + win->src.ctx = NULL; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context freed"); +#else + RGFW_UNUSED(win); +#endif +} +#endif + + +i32 RGFW_init(void) { + /* NOTE(EimaMei): Why does Apple hate good code? Like wtf, who thought of methods being a great idea??? + Imagine a universe, where MacOS had a proper system API (we would probably have like 20% better performance). + */ + si_func_to_SEL_with_name("NSObject", "windowShouldClose", (void*)RGFW_OnClose); + + /* NOTE(EimaMei): Fixes the 'Boop' sfx from constantly playing each time you click a key. Only a problem when running in the terminal. */ + si_func_to_SEL("NSWindow", acceptsFirstResponder); + si_func_to_SEL("NSWindow", performKeyEquivalent); + + if (NSApp == NULL) { + NSApp = objc_msgSend_id((id)objc_getClass("NSApplication"), sel_registerName("sharedApplication")); + + ((void (*)(id, SEL, NSUInteger))objc_msgSend) + (NSApp, sel_registerName("setActivationPolicy:"), NSApplicationActivationPolicyRegular); + + #ifndef RGFW_NO_IOKIT + RGFW_osxInitIOKit(); + #endif + } + + _RGFW.windowCount = 0; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context initialized"); + return 0; +} + +RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { + static u8 RGFW_loaded = 0; + RGFW_window_basic_init(win, rect, flags); + + /* RR Create an autorelease pool */ + id pool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + pool = objc_msgSend_id(pool, sel_registerName("init")); + + RGFW_window_setMouseDefault(win); + + NSRect windowRect; + windowRect.origin.x = win->r.x; + windowRect.origin.y = win->r.y; + windowRect.size.width = win->r.w; + windowRect.size.height = win->r.h; + + NSBackingStoreType macArgs = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSBackingStoreBuffered | NSWindowStyleMaskTitled; + + if (!(flags & RGFW_windowNoResize)) + macArgs |= NSWindowStyleMaskResizable; + if (!(flags & RGFW_windowNoBorder)) + macArgs |= NSWindowStyleMaskTitled; + { + void* nsclass = objc_getClass("NSWindow"); + SEL func = sel_registerName("initWithContentRect:styleMask:backing:defer:"); + + win->src.window = ((id(*)(id, SEL, NSRect, NSWindowStyleMask, NSBackingStoreType, bool))objc_msgSend) + (NSAlloc(nsclass), func, windowRect, macArgs, macArgs, false); + } + + id str = NSString_stringWithUTF8String(name); + objc_msgSend_void_id((id)win->src.window, sel_registerName("setTitle:"), str); + + if ((flags & RGFW_windowNoInitAPI) == 0) { + RGFW_window_initOpenGL(win, RGFW_BOOL(flags & RGFW_windowOpenglSoftware)); + RGFW_window_initBuffer(win); + } + + #ifdef RGFW_OPENGL + else + #endif + { + NSRect contentRect = (NSRect){{0, 0}, {win->r.w, win->r.h}}; + win->src.view = ((id(*)(id, SEL, NSRect))objc_msgSend) (NSAlloc(objc_getClass("NSView")), sel_registerName("initWithFrame:"), contentRect); + } + + void* contentView = NSWindow_contentView((id)win->src.window); + objc_msgSend_void_bool(contentView, sel_registerName("setWantsLayer:"), true); + objc_msgSend_int((id)win->src.view, sel_registerName("setLayerContentsPlacement:"), 4); + objc_msgSend_void_id((id)win->src.window, sel_registerName("setContentView:"), win->src.view); + + if (flags & RGFW_windowTransparent) { + objc_msgSend_void_bool(win->src.window, sel_registerName("setOpaque:"), false); + + objc_msgSend_void_id((id)win->src.window, sel_registerName("setBackgroundColor:"), + NSColor_colorWithSRGB(0, 0, 0, 0)); + } + + Class delegateClass = objc_allocateClassPair(objc_getClass("NSObject"), "WindowDelegate", 0); + + class_addIvar( + delegateClass, "RGFW_window", + sizeof(RGFW_window*), (u8)rint(log2(sizeof(RGFW_window*))), + "L" + ); + + class_addMethod(delegateClass, sel_registerName("windowWillResize:toSize:"), (IMP) RGFW__osxWindowResize, "{NSSize=ff}@:{NSSize=ff}"); + class_addMethod(delegateClass, sel_registerName("windowWillMove:"), (IMP) RGFW__osxWindowMove, ""); + class_addMethod(delegateClass, sel_registerName("windowDidMove:"), (IMP) RGFW__osxWindowMove, ""); + class_addMethod(delegateClass, sel_registerName("windowDidMiniaturize:"), (IMP) RGFW__osxWindowMiniaturize, ""); + class_addMethod(delegateClass, sel_registerName("windowDidDeminiaturize:"), (IMP) RGFW__osxWindowDeminiaturize, ""); + class_addMethod(delegateClass, sel_registerName("windowDidBecomeKey:"), (IMP) RGFW__osxWindowBecameKey, ""); + class_addMethod(delegateClass, sel_registerName("windowDidResignKey:"), (IMP) RGFW__osxWindowResignKey, ""); + class_addMethod(delegateClass, sel_registerName("draggingEntered:"), (IMP)draggingEntered, "l@:@"); + class_addMethod(delegateClass, sel_registerName("draggingUpdated:"), (IMP)draggingUpdated, "l@:@"); + class_addMethod(delegateClass, sel_registerName("draggingExited:"), (IMP)RGFW__osxDraggingEnded, "v@:@"); + class_addMethod(delegateClass, sel_registerName("draggingEnded:"), (IMP)RGFW__osxDraggingEnded, "v@:@"); + class_addMethod(delegateClass, sel_registerName("prepareForDragOperation:"), (IMP)prepareForDragOperation, "B@:@"); + class_addMethod(delegateClass, sel_registerName("performDragOperation:"), (IMP)performDragOperation, "B@:@"); + + id delegate = objc_msgSend_id(NSAlloc(delegateClass), sel_registerName("init")); + + if (RGFW_COCOA_FRAME_NAME) + objc_msgSend_ptr(win->src.view, sel_registerName("setFrameAutosaveName:"), RGFW_COCOA_FRAME_NAME); + + object_setInstanceVariable(delegate, "RGFW_window", win); + + objc_msgSend_void_id((id)win->src.window, sel_registerName("setDelegate:"), delegate); + + if (flags & RGFW_windowAllowDND) { + win->_flags |= RGFW_windowAllowDND; + + NSPasteboardType types[] = {NSPasteboardTypeURL, NSPasteboardTypeFileURL, NSPasteboardTypeString}; + NSregisterForDraggedTypes((id)win->src.window, types, 3); + } + + RGFW_window_setFlags(win, flags); + + /* Show the window */ + objc_msgSend_void_bool(NSApp, sel_registerName("activateIgnoringOtherApps:"), true); + ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("makeKeyAndOrderFront:"), NULL); + RGFW_window_show(win); + + if (!RGFW_loaded) { + objc_msgSend_void(win->src.window, sel_registerName("makeMainWindow")); + + RGFW_loaded = 1; + } + + objc_msgSend_void(win->src.window, sel_registerName("makeKeyWindow")); + + objc_msgSend_void(NSApp, sel_registerName("finishLaunching")); + NSRetain(win->src.window); + NSRetain(NSApp); + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); + return win; +} + +void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { + NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.window, sel_registerName("frame")); + NSRect content = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.view, sel_registerName("frame")); + float offset = 0; + + RGFW_setBit(&win->_flags, RGFW_windowNoBorder, !border); + NSBackingStoreType storeType = NSWindowStyleMaskBorderless | NSWindowStyleMaskFullSizeContentView; + if (border) + storeType = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable; + if (!(win->_flags & RGFW_windowNoResize)) { + storeType |= NSWindowStyleMaskResizable; + } + + ((void (*)(id, SEL, NSBackingStoreType))objc_msgSend)((id)win->src.window, sel_registerName("setStyleMask:"), storeType); + + if (!border) { + id miniaturizeButton = objc_msgSend_int((id)win->src.window, sel_registerName("standardWindowButton:"), NSWindowMiniaturizeButton); + id titleBarView = objc_msgSend_id(miniaturizeButton, sel_registerName("superview")); + objc_msgSend_void_bool(titleBarView, sel_registerName("setHidden:"), true); + + offset = (float)(frame.size.height - content.size.height); + } + + RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h + offset)); + win->r.h -= (i32)offset; +} + +RGFW_area RGFW_getScreenSize(void) { + static CGDirectDisplayID display = 0; + + if (display == 0) + display = CGMainDisplayID(); + + return RGFW_AREA(CGDisplayPixelsWide(display), CGDisplayPixelsHigh(display)); +} + +RGFW_point RGFW_getGlobalMousePoint(void) { + RGFW_ASSERT(_RGFW.root != NULL); + + CGEventRef e = CGEventCreate(NULL); + CGPoint point = CGEventGetLocation(e); + CFRelease(e); + + return RGFW_POINT((u32) point.x, (u32) point.y); /*!< the point is loaded during event checks */ +} + +typedef RGFW_ENUM(u32, NSEventType) { /* various types of events */ + NSEventTypeLeftMouseDown = 1, + NSEventTypeLeftMouseUp = 2, + NSEventTypeRightMouseDown = 3, + NSEventTypeRightMouseUp = 4, + NSEventTypeMouseMoved = 5, + NSEventTypeLeftMouseDragged = 6, + NSEventTypeRightMouseDragged = 7, + NSEventTypeMouseEntered = 8, + NSEventTypeMouseExited = 9, + NSEventTypeKeyDown = 10, + NSEventTypeKeyUp = 11, + NSEventTypeFlagsChanged = 12, + NSEventTypeAppKitDefined = 13, + NSEventTypeSystemDefined = 14, + NSEventTypeApplicationDefined = 15, + NSEventTypePeriodic = 16, + NSEventTypeCursorUpdate = 17, + NSEventTypeScrollWheel = 22, + NSEventTypeTabletPoint = 23, + NSEventTypeTabletProximity = 24, + NSEventTypeOtherMouseDown = 25, + NSEventTypeOtherMouseUp = 26, + NSEventTypeOtherMouseDragged = 27, + /* The following event types are available on some hardware on 10.5.2 and later */ + NSEventTypeGesture API_AVAILABLE(macos(10.5)) = 29, + NSEventTypeMagnify API_AVAILABLE(macos(10.5)) = 30, + NSEventTypeSwipe API_AVAILABLE(macos(10.5)) = 31, + NSEventTypeRotate API_AVAILABLE(macos(10.5)) = 18, + NSEventTypeBeginGesture API_AVAILABLE(macos(10.5)) = 19, + NSEventTypeEndGesture API_AVAILABLE(macos(10.5)) = 20, + + NSEventTypeSmartMagnify API_AVAILABLE(macos(10.8)) = 32, + NSEventTypeQuickLook API_AVAILABLE(macos(10.8)) = 33, + + NSEventTypePressure API_AVAILABLE(macos(10.10.3)) = 34, + NSEventTypeDirectTouch API_AVAILABLE(macos(10.10)) = 37, + + NSEventTypeChangeMode API_AVAILABLE(macos(10.15)) = 38, +}; + +typedef unsigned long long NSEventMask; + +typedef enum NSEventModifierFlags { + NSEventModifierFlagCapsLock = 1 << 16, + NSEventModifierFlagShift = 1 << 17, + NSEventModifierFlagControl = 1 << 18, + NSEventModifierFlagOption = 1 << 19, + NSEventModifierFlagCommand = 1 << 20, + NSEventModifierFlagNumericPad = 1 << 21 +} NSEventModifierFlags; + +void RGFW_stopCheckEvents(void) { + id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); + + id e = (id) ((id(*)(Class, SEL, NSEventType, NSPoint, NSEventModifierFlags, void*, NSInteger, void**, short, NSInteger, NSInteger))objc_msgSend) + (objc_getClass("NSEvent"), sel_registerName("otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:"), + NSEventTypeApplicationDefined, (NSPoint){0, 0}, (NSEventModifierFlags)0, NULL, (NSInteger)0, NULL, 0, 0, 0); + + ((void (*)(id, SEL, id, bool))objc_msgSend) + (NSApp, sel_registerName("postEvent:atStart:"), e, 1); + + objc_msgSend_bool_void(eventPool, sel_registerName("drain")); +} + +void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { + RGFW_UNUSED(win); + + id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); + + void* date = (void*) ((id(*)(Class, SEL, double))objc_msgSend) + (objc_getClass("NSDate"), sel_registerName("dateWithTimeIntervalSinceNow:"), waitMS); + + SEL eventFunc = sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"); + id e = (id) ((id(*)(id, SEL, NSEventMask, void*, id, bool))objc_msgSend) + (NSApp, eventFunc, + ULONG_MAX, date, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); + + + if (e) { + ((void (*)(id, SEL, id, bool))objc_msgSend) + (NSApp, sel_registerName("postEvent:atStart:"), e, 1); + } + + objc_msgSend_bool_void(eventPool, sel_registerName("drain")); +} + +RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { + if (win == NULL || ((win->_flags & RGFW_windowFreeOnClose) && (win->_flags & RGFW_EVENT_QUIT))) return NULL; + + objc_msgSend_void((id)win->src.mouse, sel_registerName("set")); + RGFW_event* ev = RGFW_window_checkEventCore(win); + if (ev) { + ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); + return ev; + } + + id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); + + SEL eventFunc = sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"); + + void* date = NULL; + + id e = (id) ((id(*)(id, SEL, NSEventMask, void*, id, bool))objc_msgSend) + (NSApp, eventFunc, ULONG_MAX, date, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); + + if (e == NULL) { + objc_msgSend_bool_void(eventPool, sel_registerName("drain")); + objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), e); + ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); + return NULL; + } + + if (objc_msgSend_id(e, sel_registerName("window")) != win->src.window) { + ((void (*)(id, SEL, id, bool))objc_msgSend) + (NSApp, sel_registerName("postEvent:atStart:"), e, 0); + + objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), e); + objc_msgSend_bool_void(eventPool, sel_registerName("drain")); + ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); + return NULL; + } + + if (win->event.droppedFilesCount) { + u32 i; + for (i = 0; i < win->event.droppedFilesCount; i++) + win->event.droppedFiles[i][0] = '\0'; + } + + win->event.droppedFilesCount = 0; + win->event.type = 0; + + u32 type = (u32)objc_msgSend_uint(e, sel_registerName("type")); + switch (type) { + case NSEventTypeMouseEntered: { + win->event.type = RGFW_mouseEnter; + NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(e, sel_registerName("locationInWindow")); + + win->event.point = RGFW_POINT((i32) p.x, (i32) (win->r.h - p.y)); + RGFW_mouseNotifyCallback(win, win->event.point, 1); + break; + } + + case NSEventTypeMouseExited: + win->event.type = RGFW_mouseLeave; + RGFW_mouseNotifyCallback(win, win->event.point, 0); + break; + + case NSEventTypeKeyDown: { + u32 key = (u16) objc_msgSend_uint(e, sel_registerName("keyCode")); + + u32 mappedKey = (u32)*(((char*)(const char*) NSString_to_char(objc_msgSend_id(e, sel_registerName("charactersIgnoringModifiers"))))); + if (((u8)mappedKey) == 239) + mappedKey = 0; + + win->event.keyChar = (u8)mappedKey; + + win->event.key = (u8)RGFW_apiKeyToRGFW(key); + RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; + + win->event.type = RGFW_keyPressed; + win->event.repeat = RGFW_isPressed(win, win->event.key); + RGFW_keyboard[win->event.key].current = 1; + + RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, 1); + break; + } + + case NSEventTypeKeyUp: { + u32 key = (u16) objc_msgSend_uint(e, sel_registerName("keyCode")); + + + u32 mappedKey = (u32)*(((char*)(const char*) NSString_to_char(objc_msgSend_id(e, sel_registerName("charactersIgnoringModifiers"))))); + if (((u8)mappedKey) == 239) + mappedKey = 0; + + win->event.keyChar = (u8)mappedKey; + + win->event.key = (u8)RGFW_apiKeyToRGFW(key); + + RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; + + win->event.type = RGFW_keyReleased; + + RGFW_keyboard[win->event.key].current = 0; + RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, 0); + break; + } + + case NSEventTypeFlagsChanged: { + u32 flags = (u32)objc_msgSend_uint(e, sel_registerName("modifierFlags")); + RGFW_updateKeyModsPro(win, ((u32)(flags & NSEventModifierFlagCapsLock) % 255), ((flags & NSEventModifierFlagNumericPad) % 255), + ((flags & NSEventModifierFlagControl) % 255), ((flags & NSEventModifierFlagOption) % 255), + ((flags & NSEventModifierFlagShift) % 255), ((flags & NSEventModifierFlagCommand) % 255), 0); + u8 i; + for (i = 0; i < 9; i++) + RGFW_keyboard[i + RGFW_capsLock].prev = 0; + + for (i = 0; i < 5; i++) { + u32 shift = (1 << (i + 16)); + u32 key = i + RGFW_capsLock; + + if ((flags & shift) && !RGFW_wasPressed(win, (u8)key)) { + RGFW_keyboard[key].current = 1; + + if (key != RGFW_capsLock) + RGFW_keyboard[key+ 4].current = 1; + + win->event.type = RGFW_keyPressed; + win->event.key = (u8)key; + break; + } + + if (!(flags & shift) && RGFW_wasPressed(win, (u8)key)) { + RGFW_keyboard[key].current = 0; + + if (key != RGFW_capsLock) + RGFW_keyboard[key + 4].current = 0; + + win->event.type = RGFW_keyReleased; + win->event.key = (u8)key; + break; + } + } + + RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, win->event.type == RGFW_keyPressed); + + break; + } + case NSEventTypeLeftMouseDragged: + case NSEventTypeOtherMouseDragged: + case NSEventTypeRightMouseDragged: + case NSEventTypeMouseMoved: { + win->event.type = RGFW_mousePosChanged; + NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(e, sel_registerName("locationInWindow")); + win->event.point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); + + p.x = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaX")); + p.y = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaY")); + win->event.vector = RGFW_POINT((i32)p.x, (i32)p.y); + + win->_lastMousePoint = win->event.point; + RGFW_mousePosCallback(win, win->event.point, win->event.vector); + break; + } + case NSEventTypeLeftMouseDown: case NSEventTypeRightMouseDown: case NSEventTypeOtherMouseDown: { + u32 buttonNumber = (u32)objc_msgSend_uint(e, sel_registerName("buttonNumber")); + switch (buttonNumber) { + case 0: win->event.button = RGFW_mouseLeft; break; + case 1: win->event.button = RGFW_mouseRight; break; + case 2: win->event.button = RGFW_mouseMiddle; break; + default: win->event.button = (u8)buttonNumber; + } + + win->event.type = RGFW_mouseButtonPressed; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 1; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); + break; + } + case NSEventTypeLeftMouseUp: case NSEventTypeRightMouseUp: case NSEventTypeOtherMouseUp: { + u32 buttonNumber = (u32)objc_msgSend_uint(e, sel_registerName("buttonNumber")); + switch (buttonNumber) { + case 0: win->event.button = RGFW_mouseLeft; break; + case 1: win->event.button = RGFW_mouseRight; break; + case 2: win->event.button = RGFW_mouseMiddle; break; + default: win->event.button = (u8)buttonNumber; + } + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 0; + win->event.type = RGFW_mouseButtonReleased; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 0); + break; + } + case NSEventTypeScrollWheel: { + double deltaY = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaY")); + + if (deltaY > 0) { + win->event.button = RGFW_mouseScrollUp; + } + else if (deltaY < 0) { + win->event.button = RGFW_mouseScrollDown; + } + + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 1; + + win->event.scroll = deltaY; + + win->event.type = RGFW_mouseButtonPressed; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); + break; + } + + default: + objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), e); + ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); + return RGFW_window_checkEvent(win); + } + + objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), e); + ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); + objc_msgSend_bool_void(eventPool, sel_registerName("drain")); + return &win->event; +} + + +void RGFW_window_move(RGFW_window* win, RGFW_point v) { + RGFW_ASSERT(win != NULL); + + win->r.x = v.x; + win->r.y = v.y; + ((void(*)(id, SEL, NSRect, bool, bool))objc_msgSend) + ((id)win->src.window, sel_registerName("setFrame:display:animate:"), (NSRect){{win->r.x, win->r.y}, {win->r.w, win->r.h}}, true, true); +} + +void RGFW_window_resize(RGFW_window* win, RGFW_area a) { + RGFW_ASSERT(win != NULL); + + NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.window, sel_registerName("frame")); + NSRect content = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.view, sel_registerName("frame")); + float offset = (float)(frame.size.height - content.size.height); + + win->r.w = (i32)a.w; + win->r.h = (i32)a.h; + + ((void(*)(id, SEL, NSRect, bool, bool))objc_msgSend) + ((id)win->src.window, sel_registerName("setFrame:display:animate:"), (NSRect){{win->r.x, win->r.y}, {win->r.w, win->r.h + offset}}, true, true); +} + +void RGFW_window_focus(RGFW_window* win) { + RGFW_ASSERT(win); + objc_msgSend_void_bool(NSApp, sel_registerName("activateIgnoringOtherApps:"), true); + ((void (*)(id, SEL))objc_msgSend)((id)win->src.window, sel_registerName("makeKeyWindow")); +} + +void RGFW_window_raise(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("orderFront:"), (SEL)NULL); + objc_msgSend_void_id(win->src.window, sel_registerName("setLevel:"), kCGNormalWindowLevelKey); +} + +void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { + RGFW_ASSERT(win != NULL); + if (fullscreen && (win->_flags & RGFW_windowFullscreen)) return; + if (!fullscreen && !(win->_flags & RGFW_windowFullscreen)) return; + + if (fullscreen) { + win->_oldRect = win->r; + RGFW_monitor mon = RGFW_window_getMonitor(win); + win->r = RGFW_RECT(0, 0, mon.x, mon.y); + win->_flags |= RGFW_windowFullscreen; + RGFW_window_resize(win, RGFW_AREA(mon.mode.area.w, mon.mode.area.h)); + RGFW_window_move(win, RGFW_POINT(0, 0)); + } + objc_msgSend_void_SEL(win->src.window, sel_registerName("toggleFullScreen:"), NULL); + + if (!fullscreen) { + win->r = win->_oldRect; + win->_flags &= ~(u32)RGFW_windowFullscreen; + + RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h)); + RGFW_window_move(win, RGFW_POINT(win->r.x, win->r.y)); + } +} + +void RGFW_window_maximize(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + if (RGFW_window_isMaximized(win)) return; + + win->_flags |= RGFW_windowMaximize; + objc_msgSend_void_SEL(win->src.window, sel_registerName("zoom:"), NULL); +} + +void RGFW_window_minimize(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + objc_msgSend_void_SEL(win->src.window, sel_registerName("performMiniaturize:"), NULL); +} + +void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { + RGFW_ASSERT(win != NULL); + if (floating) objc_msgSend_void_id(win->src.window, sel_registerName("setLevel:"), kCGFloatingWindowLevelKey); + else objc_msgSend_void_id(win->src.window, sel_registerName("setLevel:"), kCGNormalWindowLevelKey); +} + +void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { + objc_msgSend_int(win->src.window, sel_registerName("setAlphaValue:"), opacity); + objc_msgSend_void_bool(win->src.window, sel_registerName("setOpaque:"), (opacity < (u8)255)); + + if (opacity) + objc_msgSend_void_id((id)win->src.window, sel_registerName("setBackgroundColor:"), NSColor_colorWithSRGB(0, 0, 0, opacity)); + +} + +void RGFW_window_restore(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + if (RGFW_window_isMaximized(win)) + objc_msgSend_void_SEL(win->src.window, sel_registerName("zoom:"), NULL); + + objc_msgSend_void_SEL(win->src.window, sel_registerName("deminiaturize:"), NULL); + RGFW_window_show(win); +} + +RGFW_bool RGFW_window_isFloating(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + int level = ((int (*)(id, SEL))objc_msgSend) ((id)(win->src.window), (SEL)sel_registerName("level")); + return level > kCGNormalWindowLevelKey; +} + +void RGFW_window_setName(RGFW_window* win, const char* name) { + RGFW_ASSERT(win != NULL); + + id str = NSString_stringWithUTF8String(name); + objc_msgSend_void_id((id)win->src.window, sel_registerName("setTitle:"), str); +} + +#ifndef RGFW_NO_PASSTHROUGH +void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { + objc_msgSend_void_bool(win->src.window, sel_registerName("setIgnoresMouseEvents:"), passthrough); +} +#endif + +void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { + if (a.w == 0 && a.h == 0) a = RGFW_AREA(1, 1); + + ((void (*)(id, SEL, NSSize))objc_msgSend) + ((id)win->src.window, sel_registerName("setContentAspectRatio:"), (NSSize){a.w, a.h}); +} + +void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { + ((void (*)(id, SEL, NSSize))objc_msgSend) + ((id)win->src.window, sel_registerName("setMinSize:"), (NSSize){a.w, a.h}); +} + +void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { + if (a.w == 0 && a.h == 0) { + a = RGFW_getScreenSize(); + } + + ((void (*)(id, SEL, NSSize))objc_msgSend) + ((id)win->src.window, sel_registerName("setMaxSize:"), (NSSize){a.w, a.h}); +} + +RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, RGFW_area area, i32 channels, u8 type) { + RGFW_ASSERT(win != NULL); + RGFW_UNUSED(type); + + if (data == NULL) { + objc_msgSend_void_id(NSApp, sel_registerName("setApplicationIconImage:"), NULL); + return RGFW_TRUE; + } + + /* code by EimaMei: Make a bitmap representation, then copy the loaded image into it. */ + id representation = NSBitmapImageRep_initWithBitmapData(NULL, area.w, area.h, 8, channels, (channels == 4), false, "NSCalibratedRGBColorSpace", 1 << 1, area.w * (u32)channels, 8 * (u32)channels); + RGFW_MEMCPY(NSBitmapImageRep_bitmapData(representation), data, area.w * area.h * (u32)channels); + + /* Add ze representation. */ + id dock_image = ((id(*)(id, SEL, NSSize))objc_msgSend) (NSAlloc((id)objc_getClass("NSImage")), sel_registerName("initWithSize:"), ((NSSize){area.w, area.h})); + + objc_msgSend_void_id(dock_image, sel_registerName("addRepresentation:"), representation); + + /* Finally, set the dock image to it. */ + objc_msgSend_void_id(NSApp, sel_registerName("setApplicationIconImage:"), dock_image); + /* Free the garbage. */ + NSRelease(dock_image); + NSRelease(representation); + + return RGFW_TRUE; +} + +id NSCursor_arrowStr(const char* str) { + void* nclass = objc_getClass("NSCursor"); + SEL func = sel_registerName(str); + return (id) objc_msgSend_id(nclass, func); +} + +RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { + if (icon == NULL) { + objc_msgSend_void(NSCursor_arrowStr("arrowCursor"), sel_registerName("set")); + return NULL; + } + + /* NOTE(EimaMei): Code by yours truly. */ + /* Make a bitmap representation, then copy the loaded image into it. */ + id representation = (id)NSBitmapImageRep_initWithBitmapData(NULL, a.w, a.h, 8, channels, (channels == 4), false, "NSCalibratedRGBColorSpace", 1 << 1, a.w * (u32)channels, 8 * (u32)channels); + RGFW_MEMCPY(NSBitmapImageRep_bitmapData(representation), icon, a.w * a.h * (u32)channels); + + /* Add ze representation. */ + id cursor_image = ((id(*)(id, SEL, NSSize))objc_msgSend) (NSAlloc((id)objc_getClass("NSImage")), sel_registerName("initWithSize:"), ((NSSize){a.w, a.h})); + + objc_msgSend_void_id(cursor_image, sel_registerName("addRepresentation:"), representation); + + /* Finally, set the cursor image. */ + id cursor = (id) ((id(*)(id, SEL, id, NSPoint))objc_msgSend) + (NSAlloc(objc_getClass("NSCursor")), sel_registerName("initWithImage:hotSpot:"), cursor_image, (NSPoint){0.0, 0.0}); + + /* Free the garbage. */ + NSRelease(cursor_image); + NSRelease(representation); + + return (void*)cursor; +} + +void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { + RGFW_ASSERT(win != NULL); RGFW_ASSERT(mouse); + CGDisplayShowCursor(kCGDirectMainDisplay); + objc_msgSend_void((id)mouse, sel_registerName("set")); + win->src.mouse = mouse; +} + +void RGFW_freeMouse(RGFW_mouse* mouse) { + RGFW_ASSERT(mouse); + NSRelease((id)mouse); +} + +RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { + return RGFW_window_setMouseStandard(win, RGFW_mouseArrow); +} + +void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show) { + RGFW_window_showMouseFlags(win, show); + if (show) CGDisplayShowCursor(kCGDirectMainDisplay); + else CGDisplayHideCursor(kCGDirectMainDisplay); +} + +RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 stdMouses) { + static const char* mouseIconSrc[16] = {"arrowCursor", "arrowCursor", "IBeamCursor", "crosshairCursor", "pointingHandCursor", "resizeLeftRightCursor", "resizeUpDownCursor", "_windowResizeNorthWestSouthEastCursor", "_windowResizeNorthEastSouthWestCursor", "closedHandCursor", "operationNotAllowedCursor"}; + if (stdMouses > ((sizeof(mouseIconSrc)) / (sizeof(char*)))) + return RGFW_FALSE; + + const char* mouseStr = mouseIconSrc[stdMouses]; + id mouse = NSCursor_arrowStr(mouseStr); + + if (mouse == NULL) + return RGFW_FALSE; + + RGFW_UNUSED(win); + CGDisplayShowCursor(kCGDirectMainDisplay); + objc_msgSend_void(mouse, sel_registerName("set")); + win->src.mouse = mouse; + + return RGFW_TRUE; +} + +void RGFW_releaseCursor(RGFW_window* win) { + RGFW_UNUSED(win); + CGAssociateMouseAndMouseCursorPosition(1); +} + +void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { + RGFW_UNUSED(win); + + CGWarpMouseCursorPosition((CGPoint){r.x + (r.w / 2), r.y + (r.h / 2)}); + CGAssociateMouseAndMouseCursorPosition(0); +} + +void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v) { + RGFW_UNUSED(win); + + win->_lastMousePoint = RGFW_POINT(v.x - win->r.x, v.y - win->r.y); + CGWarpMouseCursorPosition((CGPoint){v.x, v.y}); +} + + +void RGFW_window_hide(RGFW_window* win) { + objc_msgSend_void_bool(win->src.window, sel_registerName("setIsVisible:"), false); +} + +void RGFW_window_show(RGFW_window* win) { + if (win->_flags & RGFW_windowFocusOnShow) + ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("makeKeyAndOrderFront:"), NULL); + + ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("orderFront:"), NULL); + objc_msgSend_void_bool(win->src.window, sel_registerName("setIsVisible:"), true); +} + +RGFW_bool RGFW_window_isHidden(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + bool visible = objc_msgSend_bool(win->src.window, sel_registerName("isVisible")); + return visible == NO && !RGFW_window_isMinimized(win); +} + +RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + return objc_msgSend_bool(win->src.window, sel_registerName("isMiniaturized")) == YES; +} + +RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_bool b = (RGFW_bool)objc_msgSend_bool(win->src.window, sel_registerName("isZoomed")); + return b; +} + +id RGFW_getNSScreenForDisplayID(CGDirectDisplayID display) { + Class NSScreenClass = objc_getClass("NSScreen"); + + id screens = objc_msgSend_id(NSScreenClass, sel_registerName("screens")); + + NSUInteger count = (NSUInteger)objc_msgSend_uint(screens, sel_registerName("count")); + NSUInteger i; + for (i = 0; i < count; i++) { + id screen = ((id (*)(id, SEL, int))objc_msgSend) (screens, sel_registerName("objectAtIndex:"), (int)i); + id description = objc_msgSend_id(screen, sel_registerName("deviceDescription")); + id screenNumberKey = NSString_stringWithUTF8String("NSScreenNumber"); + id screenNumber = objc_msgSend_id_id(description, sel_registerName("objectForKey:"), screenNumberKey); + + if ((CGDirectDisplayID)objc_msgSend_uint(screenNumber, sel_registerName("unsignedIntValue")) == display) { + return screen; + } + } + + return NULL; +} + + +u32 RGFW_osx_getRefreshRate(CGDirectDisplayID display, CGDisplayModeRef mode) { + if (mode) { + u32 refreshRate = (u32)CGDisplayModeGetRefreshRate(mode); + if (refreshRate != 0) return refreshRate; + } + + CVDisplayLinkRef link; + CVDisplayLinkCreateWithCGDisplay(display, &link); + const CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link); + if (!(time.flags & kCVTimeIsIndefinite)) + return (u32) (time.timeScale / (double) time.timeValue); + + return 0; +} + +RGFW_monitor RGFW_NSCreateMonitor(CGDirectDisplayID display, id screen) { + RGFW_monitor monitor; + + const char name[] = "MacOS\0"; + RGFW_MEMCPY(monitor.name, name, 6); + + CGRect bounds = CGDisplayBounds(display); + monitor.x = (i32)bounds.origin.x; + monitor.y = (i32)bounds.origin.y; + monitor.mode.area = RGFW_AREA((int) bounds.size.width, (int) bounds.size.height); + + monitor.mode.red = 8; monitor.mode.green = 8; monitor.mode.blue = 8; + + CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display); + monitor.mode.refreshRate = RGFW_osx_getRefreshRate(display, mode); + CFRelease(mode); + + CGSize screenSizeMM = CGDisplayScreenSize(display); + monitor.physW = (float)screenSizeMM.width / 25.4f; + monitor.physH = (float)screenSizeMM.height / 25.4f; + + float ppi_width = (monitor.mode.area.w/monitor.physW); + float ppi_height = (monitor.mode.area.h/monitor.physH); + + monitor.pixelRatio = (float)((CGFloat (*)(id, SEL))abi_objc_msgSend_fpret) (screen, sel_registerName("backingScaleFactor")); + float dpi = 96.0f * monitor.pixelRatio; + + monitor.scaleX = ((i32)(((float) (ppi_width) / dpi) * 10.0f)) / 10.0f; + monitor.scaleY = ((i32)(((float) (ppi_height) / dpi) * 10.0f)) / 10.0f; + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); + return monitor; +} + + +RGFW_monitor* RGFW_getMonitors(size_t* len) { + static CGDirectDisplayID displays[7]; + u32 count; + + if (CGGetActiveDisplayList(6, displays, &count) != kCGErrorSuccess) + return NULL; + + if (count > 6) count = 6; + + static RGFW_monitor monitors[7]; + + u32 i; + for (i = 0; i < count; i++) + monitors[i] = RGFW_NSCreateMonitor(displays[i], RGFW_getNSScreenForDisplayID(displays[i])); + + if (len != NULL) *len = count; + return monitors; +} + +RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { + CGPoint point = { mon.x, mon.y }; + + CGDirectDisplayID display; + uint32_t displayCount = 0; + CGError err = CGGetDisplaysWithPoint(point, 1, &display, &displayCount); + if (err != kCGErrorSuccess || displayCount != 1) + return RGFW_FALSE; + + CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL); + + if (allModes == NULL) + return RGFW_FALSE; + + CFIndex i; + for (i = 0; i < CFArrayGetCount(allModes); i++) { + CGDisplayModeRef cmode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i); + + RGFW_monitorMode foundMode; + foundMode.area = RGFW_AREA(CGDisplayModeGetWidth(cmode), CGDisplayModeGetHeight(cmode)); + foundMode.refreshRate = RGFW_osx_getRefreshRate(display, cmode); + foundMode.red = 8; foundMode.green = 8; foundMode.blue = 8; + + if (RGFW_monitorModeCompare(mode, foundMode, request)) { + if (CGDisplaySetDisplayMode(display, cmode, NULL) == kCGErrorSuccess) { + CFRelease(allModes); + return RGFW_TRUE; + } + break; + } + } + + CFRelease(allModes); + + return RGFW_FALSE; +} + +RGFW_monitor RGFW_getPrimaryMonitor(void) { + CGDirectDisplayID primary = CGMainDisplayID(); + return RGFW_NSCreateMonitor(primary, RGFW_getNSScreenForDisplayID(primary)); +} + +RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { + id screen = objc_msgSend_id(win->src.window, sel_registerName("screen")); + id description = objc_msgSend_id(screen, sel_registerName("deviceDescription")); + id screenNumberKey = NSString_stringWithUTF8String("NSScreenNumber"); + id screenNumber = objc_msgSend_id_id(description, sel_registerName("objectForKey:"), screenNumberKey); + + CGDirectDisplayID display = (CGDirectDisplayID)objc_msgSend_uint(screenNumber, sel_registerName("unsignedIntValue")); + + return RGFW_NSCreateMonitor(display, screen); +} + +RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { + size_t clip_len; + char* clip = (char*)NSPasteboard_stringForType(NSPasteboard_generalPasteboard(), NSPasteboardTypeString, &clip_len); + if (clip == NULL) return -1; + + if (str != NULL) { + if (strCapacity < clip_len) + return 0; + + RGFW_MEMCPY(str, clip, clip_len); + + str[clip_len] = '\0'; + } + + return (RGFW_ssize_t)clip_len; +} + +void RGFW_writeClipboard(const char* text, u32 textLen) { + RGFW_UNUSED(textLen); + + NSPasteboardType array[] = { NSPasteboardTypeString, NULL }; + NSPasteBoard_declareTypes(NSPasteboard_generalPasteboard(), array, 1, NULL); + + SEL func = sel_registerName("setString:forType:"); + ((bool (*)(id, SEL, id, id))objc_msgSend) + (NSPasteboard_generalPasteboard(), func, NSString_stringWithUTF8String(text), NSString_stringWithUTF8String(NSPasteboardTypeString)); +} + + #ifdef RGFW_OPENGL + void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { + if (win != NULL) + objc_msgSend_void(win->src.ctx, sel_registerName("makeCurrentContext")); + else + objc_msgSend_id(objc_getClass("NSOpenGLContext"), sel_registerName("clearCurrentContext")); + } + void* RGFW_getCurrent_OpenGL(void) { + return objc_msgSend_id(objc_getClass("NSOpenGLContext"), sel_registerName("currentContext")); + } + + void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { + objc_msgSend_void(win->src.ctx, sel_registerName("flushBuffer")); + } + #endif + + #if !defined(RGFW_EGL) + + void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { + RGFW_ASSERT(win != NULL); + #if defined(RGFW_OPENGL) + + NSOpenGLContext_setValues((id)win->src.ctx, &swapInterval, 222); + #else + RGFW_UNUSED(swapInterval); + #endif + } + + #endif + +void RGFW_window_swapBuffers_software(RGFW_window* win) { +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + RGFW_RGB_to_BGR(win, win->buffer); + i32 channels = 4; + id image = ((id (*)(Class, SEL))objc_msgSend)(objc_getClass("NSImage"), sel_getUid("alloc")); + NSSize size = (NSSize){win->bufferSize.w, win->bufferSize.h}; + image = ((id (*)(id, SEL, NSSize))objc_msgSend)((id)image, sel_getUid("initWithSize:"), size); + + id rep = NSBitmapImageRep_initWithBitmapData(&win->buffer, win->r.w, win->r.h , 8, channels, (channels == 4), false, + "NSDeviceRGBColorSpace", 1 << 1, (u32)win->bufferSize.w * (u32)channels, 8 * (u32)channels); + ((void (*)(id, SEL, id))objc_msgSend)((id)image, sel_getUid("addRepresentation:"), rep); + + id contentView = ((id (*)(id, SEL))objc_msgSend)((id)win->src.window, sel_getUid("contentView")); + ((void (*)(id, SEL, BOOL))objc_msgSend)(contentView, sel_getUid("setWantsLayer:"), YES); + id layer = ((id (*)(id, SEL))objc_msgSend)(contentView, sel_getUid("layer")); + + ((void (*)(id, SEL, id))objc_msgSend)(layer, sel_getUid("setContents:"), (id)image); + ((void (*)(id, SEL, BOOL))objc_msgSend)(contentView, sel_getUid("setNeedsDisplay:"), YES); + + NSRelease(rep); + NSRelease(image); +#else + RGFW_UNUSED(win); +#endif +} + +void RGFW_deinit(void) { + _RGFW.windowCount = -1; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context deinitialized"); +} + +void RGFW_window_close(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + NSRelease(win->src.view); + if ((win->_flags & RGFW_windowNoInitAPI) == 0) RGFW_window_freeOpenGL(win); + + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + if ((win->_flags & RGFW_BUFFER_ALLOC)) + RGFW_FREE(win->buffer); + #endif + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context deinitialized"); + _RGFW.windowCount--; + if (_RGFW.windowCount == 0) RGFW_deinit(); + + RGFW_clipboard_switch(NULL); + RGFW_FREE(win->event.droppedFiles); + if ((win->_flags & RGFW_WINDOW_ALLOC)) { + RGFW_FREE(win); + win = NULL; + } +} + +u64 RGFW_getTimerFreq(void) { + static u64 freq = 0; + if (freq == 0) { + mach_timebase_info_data_t info; + mach_timebase_info(&info); + freq = (u64)((info.denom * 1e9) / info.numer); + } + + return freq; +} + +u64 RGFW_getTimerValue(void) { return (u64)mach_absolute_time(); } + +#endif /* RGFW_MACOS */ + +/* + End of MaOS defines +*/ + +/* + WASM defines +*/ + +#ifdef RGFW_WASM +EM_BOOL Emscripten_on_resize(int eventType, const EmscriptenUiEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowResized, ._win = _RGFW.root}); + RGFW_windowResizedCallback(_RGFW.root, RGFW_RECT(0, 0, e->windowInnerWidth, e->windowInnerHeight)); + return EM_TRUE; +} + +EM_BOOL Emscripten_on_fullscreenchange(int eventType, const EmscriptenFullscreenChangeEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + static u8 fullscreen = RGFW_FALSE; + static RGFW_rect ogRect; + + if (fullscreen == RGFW_FALSE) { + ogRect = _RGFW.root->r; + } + + fullscreen = !fullscreen; + RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowResized, ._win = _RGFW.root}); + _RGFW.root->r = RGFW_RECT(0, 0, e->screenWidth, e->screenHeight); + + EM_ASM("Module.canvas.focus();"); + + if (fullscreen == RGFW_FALSE) { + _RGFW.root->r = RGFW_RECT(0, 0, ogRect.w, ogRect.h); + /* emscripten_request_fullscreen("#canvas", 0); */ + } else { + #if __EMSCRIPTEN_major__ >= 1 && __EMSCRIPTEN_minor__ >= 29 && __EMSCRIPTEN_tiny__ >= 0 + EmscriptenFullscreenStrategy FSStrat = {0}; + FSStrat.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; /* EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT : EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; */ + FSStrat.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF; + FSStrat.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; + emscripten_request_fullscreen_strategy("#canvas", 1, &FSStrat); + #else + emscripten_request_fullscreen("#canvas", 1); + #endif + } + + emscripten_set_canvas_element_size("#canvas", _RGFW.root->r.w, _RGFW.root->r.h); + + RGFW_windowResizedCallback(_RGFW.root, _RGFW.root->r); + return EM_TRUE; +} + + + +EM_BOOL Emscripten_on_focusin(int eventType, const EmscriptenFocusEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusIn, ._win = _RGFW.root}); + _RGFW.root->_flags |= RGFW_windowFocus; + RGFW_focusCallback(_RGFW.root, 1); + return EM_TRUE; +} + +EM_BOOL Emscripten_on_focusout(int eventType, const EmscriptenFocusEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusOut, ._win = _RGFW.root}); + _RGFW.root->_flags &= ~(u32)RGFW_windowFocus; + RGFW_focusCallback(_RGFW.root, 0); + return EM_TRUE; +} + +EM_BOOL Emscripten_on_mousemove(int eventType, const EmscriptenMouseEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + RGFW_eventQueuePush((RGFW_event){.type = RGFW_mousePosChanged, + .point = RGFW_POINT(e->targetX, e->targetY), + .vector = RGFW_POINT(e->movementX, e->movementY), + ._win = _RGFW.root}); + + _RGFW.root->_lastMousePoint = RGFW_POINT(e->targetX, e->targetY); + RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(e->targetX, e->targetY), RGFW_POINT(e->movementX, e->movementY)); + return EM_TRUE; +} + +EM_BOOL Emscripten_on_mousedown(int eventType, const EmscriptenMouseEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + int button = e->button; + if (button > 2) + button += 2; + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonPressed, + .point = RGFW_POINT(e->targetX, e->targetY), + .vector = RGFW_POINT(e->movementX, e->movementY), + .button = (u8)button, + .scroll = 0, + ._win = _RGFW.root}); + RGFW_mouseButtons[button].prev = RGFW_mouseButtons[button].current; + RGFW_mouseButtons[button].current = 1; + + RGFW_mouseButtonCallback(_RGFW.root, button, 0, 1); + return EM_TRUE; +} + +EM_BOOL Emscripten_on_mouseup(int eventType, const EmscriptenMouseEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + int button = e->button; + if (button > 2) + button += 2; + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonReleased, + .point = RGFW_POINT(e->targetX, e->targetY), + .vector = RGFW_POINT(e->movementX, e->movementY), + .button = (u8)button, + .scroll = 0, + ._win = _RGFW.root}); + RGFW_mouseButtons[button].prev = RGFW_mouseButtons[button].current; + RGFW_mouseButtons[button].current = 0; + + RGFW_mouseButtonCallback(_RGFW.root, button, 0, 0); + return EM_TRUE; +} + +EM_BOOL Emscripten_on_wheel(int eventType, const EmscriptenWheelEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + int button = RGFW_mouseScrollUp + (e->deltaY < 0); + RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonPressed, + .button = (u8)button, + .scroll = (double)(e->deltaY < 0 ? 1 : -1), + ._win = _RGFW.root}); + RGFW_mouseButtons[button].prev = RGFW_mouseButtons[button].current; + RGFW_mouseButtons[button].current = 1; + RGFW_mouseButtonCallback(_RGFW.root, button, e->deltaY < 0 ? 1 : -1, 1); + + return EM_TRUE; +} + +EM_BOOL Emscripten_on_touchstart(int eventType, const EmscriptenTouchEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + size_t i; + for (i = 0; i < (size_t)e->numTouches; i++) { + RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonPressed, + .point = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), + .button = RGFW_mouseLeft, + ._win = _RGFW.root}); + + RGFW_mouseButtons[RGFW_mouseLeft].prev = RGFW_mouseButtons[RGFW_mouseLeft].current; + RGFW_mouseButtons[RGFW_mouseLeft].current = 1; + + _RGFW.root->_lastMousePoint = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY); + RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), _RGFW.root->event.vector); + RGFW_mouseButtonCallback(_RGFW.root, RGFW_mouseLeft, 0, 1); + } + + return EM_TRUE; +} +EM_BOOL Emscripten_on_touchmove(int eventType, const EmscriptenTouchEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + size_t i; + for (i = 0; i < (size_t)e->numTouches; i++) { + RGFW_eventQueuePush((RGFW_event){.type = RGFW_mousePosChanged, + .point = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), + .button = RGFW_mouseLeft, + ._win = _RGFW.root}); + + _RGFW.root->_lastMousePoint = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY); + RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), _RGFW.root->event.vector); + } + return EM_TRUE; +} + +EM_BOOL Emscripten_on_touchend(int eventType, const EmscriptenTouchEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + size_t i; + for (i = 0; i < (size_t)e->numTouches; i++) { + RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonReleased, + .point = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), + .button = RGFW_mouseLeft, + ._win = _RGFW.root}); + + RGFW_mouseButtons[RGFW_mouseLeft].prev = RGFW_mouseButtons[RGFW_mouseLeft].current; + RGFW_mouseButtons[RGFW_mouseLeft].current = 0; + + _RGFW.root->_lastMousePoint = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY); + RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), _RGFW.root->event.vector); + RGFW_mouseButtonCallback(_RGFW.root, RGFW_mouseLeft, 0, 0); + } + return EM_TRUE; +} + +EM_BOOL Emscripten_on_touchcancel(int eventType, const EmscriptenTouchEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); return EM_TRUE; } + +EM_BOOL Emscripten_on_gamepad(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + if (gamepadEvent->index >= 4) + return 0; + + size_t i = gamepadEvent->index; + if (gamepadEvent->connected) { + RGFW_MEMCPY(RGFW_gamepads_name[gamepadEvent->index], gamepadEvent->id, sizeof(RGFW_gamepads_name[gamepadEvent->index])); + RGFW_gamepads_type[i] = RGFW_gamepadUnknown; + if (RGFW_STRSTR(RGFW_gamepads_name[i], "Microsoft") || RGFW_STRSTR(RGFW_gamepads_name[i], "X-Box")) + RGFW_gamepads_type[i] = RGFW_gamepadMicrosoft; + else if (RGFW_STRSTR(RGFW_gamepads_name[i], "PlayStation") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS3") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS4") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS5")) + RGFW_gamepads_type[i] = RGFW_gamepadSony; + else if (RGFW_STRSTR(RGFW_gamepads_name[i], "Nintendo")) + RGFW_gamepads_type[i] = RGFW_gamepadNintendo; + else if (RGFW_STRSTR(RGFW_gamepads_name[i], "Logitech")) + RGFW_gamepads_type[i] = RGFW_gamepadLogitech; + RGFW_gamepadCount++; + } else { + RGFW_gamepadCount--; + } + + RGFW_eventQueuePush((RGFW_event){.type = (RGFW_eventType)(gamepadEvent->connected ? RGFW_gamepadConnected : RGFW_gamepadConnected), + .gamepad = (u16)gamepadEvent->index, + ._win = _RGFW.root}); + + RGFW_gamepadCallback(_RGFW.root, gamepadEvent->index, gamepadEvent->connected); + RGFW_gamepads[gamepadEvent->index] = gamepadEvent->connected; + + return 1; /* The event was consumed by the callback handler */ +} + +u32 RGFW_wASMPhysicalToRGFW(u32 hash) { + switch(hash) { /* 0x0000 */ + case 0x67243A2DU /* Escape */: return RGFW_escape; /* 0x0001 */ + case 0x67251058U /* Digit0 */: return RGFW_0; /* 0x0002 */ + case 0x67251059U /* Digit1 */: return RGFW_1; /* 0x0003 */ + case 0x6725105AU /* Digit2 */: return RGFW_2; /* 0x0004 */ + case 0x6725105BU /* Digit3 */: return RGFW_3; /* 0x0005 */ + case 0x6725105CU /* Digit4 */: return RGFW_4; /* 0x0006 */ + case 0x6725105DU /* Digit5 */: return RGFW_5; /* 0x0007 */ + case 0x6725105EU /* Digit6 */: return RGFW_6; /* 0x0008 */ + case 0x6725105FU /* Digit7 */: return RGFW_7; /* 0x0009 */ + case 0x67251050U /* Digit8 */: return RGFW_8; /* 0x000A */ + case 0x67251051U /* Digit9 */: return RGFW_9; /* 0x000B */ + case 0x92E14DD3U /* Minus */: return RGFW_minus; /* 0x000C */ + case 0x92E1FBACU /* Equal */: return RGFW_equals; /* 0x000D */ + case 0x36BF1CB5U /* Backspace */: return RGFW_backSpace; /* 0x000E */ + case 0x7B8E51E2U /* Tab */: return RGFW_tab; /* 0x000F */ + case 0x2C595B51U /* KeyQ */: return RGFW_q; /* 0x0010 */ + case 0x2C595B57U /* KeyW */: return RGFW_w; /* 0x0011 */ + case 0x2C595B45U /* KeyE */: return RGFW_e; /* 0x0012 */ + case 0x2C595B52U /* KeyR */: return RGFW_r; /* 0x0013 */ + case 0x2C595B54U /* KeyT */: return RGFW_t; /* 0x0014 */ + case 0x2C595B59U /* KeyY */: return RGFW_y; /* 0x0015 */ + case 0x2C595B55U /* KeyU */: return RGFW_u; /* 0x0016 */ + case 0x2C595B4FU /* KeyO */: return RGFW_o; /* 0x0018 */ + case 0x2C595B50U /* KeyP */: return RGFW_p; /* 0x0019 */ + case 0x45D8158CU /* BracketLeft */: return RGFW_closeBracket; /* 0x001A */ + case 0xDEEABF7CU /* BracketRight */: return RGFW_bracket; /* 0x001B */ + case 0x92E1C5D2U /* Enter */: return RGFW_return; /* 0x001C */ + case 0xE058958CU /* ControlLeft */: return RGFW_controlL; /* 0x001D */ + case 0x2C595B41U /* KeyA */: return RGFW_a; /* 0x001E */ + case 0x2C595B53U /* KeyS */: return RGFW_s; /* 0x001F */ + case 0x2C595B44U /* KeyD */: return RGFW_d; /* 0x0020 */ + case 0x2C595B46U /* KeyF */: return RGFW_f; /* 0x0021 */ + case 0x2C595B47U /* KeyG */: return RGFW_g; /* 0x0022 */ + case 0x2C595B48U /* KeyH */: return RGFW_h; /* 0x0023 */ + case 0x2C595B4AU /* KeyJ */: return RGFW_j; /* 0x0024 */ + case 0x2C595B4BU /* KeyK */: return RGFW_k; /* 0x0025 */ + case 0x2C595B4CU /* KeyL */: return RGFW_l; /* 0x0026 */ + case 0x2707219EU /* Semicolon */: return RGFW_semicolon; /* 0x0027 */ + case 0x92E0B58DU /* Quote */: return RGFW_apostrophe; /* 0x0028 */ + case 0x36BF358DU /* Backquote */: return RGFW_backtick; /* 0x0029 */ + case 0x26B1958CU /* ShiftLeft */: return RGFW_shiftL; /* 0x002A */ + case 0x36BF2438U /* Backslash */: return RGFW_backSlash; /* 0x002B */ + case 0x2C595B5AU /* KeyZ */: return RGFW_z; /* 0x002C */ + case 0x2C595B58U /* KeyX */: return RGFW_x; /* 0x002D */ + case 0x2C595B43U /* KeyC */: return RGFW_c; /* 0x002E */ + case 0x2C595B56U /* KeyV */: return RGFW_v; /* 0x002F */ + case 0x2C595B42U /* KeyB */: return RGFW_b; /* 0x0030 */ + case 0x2C595B4EU /* KeyN */: return RGFW_n; /* 0x0031 */ + case 0x2C595B4DU /* KeyM */: return RGFW_m; /* 0x0032 */ + case 0x92E1A1C1U /* Comma */: return RGFW_comma; /* 0x0033 */ + case 0x672FFAD4U /* Period */: return RGFW_period; /* 0x0034 */ + case 0x92E0A438U /* Slash */: return RGFW_slash; /* 0x0035 */ + case 0xC5A6BF7CU /* ShiftRight */: return RGFW_shiftR; + case 0x5D64DA91U /* NumpadMultiply */: return RGFW_multiply; + case 0xC914958CU /* AltLeft */: return RGFW_altL; /* 0x0038 */ + case 0x92E09CB5U /* Space */: return RGFW_space; /* 0x0039 */ + case 0xB8FAE73BU /* CapsLock */: return RGFW_capsLock; /* 0x003A */ + case 0x7174B789U /* F1 */: return RGFW_F1; /* 0x003B */ + case 0x7174B78AU /* F2 */: return RGFW_F2; /* 0x003C */ + case 0x7174B78BU /* F3 */: return RGFW_F3; /* 0x003D */ + case 0x7174B78CU /* F4 */: return RGFW_F4; /* 0x003E */ + case 0x7174B78DU /* F5 */: return RGFW_F5; /* 0x003F */ + case 0x7174B78EU /* F6 */: return RGFW_F6; /* 0x0040 */ + case 0x7174B78FU /* F7 */: return RGFW_F7; /* 0x0041 */ + case 0x7174B780U /* F8 */: return RGFW_F8; /* 0x0042 */ + case 0x7174B781U /* F9 */: return RGFW_F9; /* 0x0043 */ + case 0x7B8E57B0U /* F10 */: return RGFW_F10; /* 0x0044 */ + case 0xC925FCDFU /* Numpad7 */: return RGFW_multiply; /* 0x0047 */ + case 0xC925FCD0U /* Numpad8 */: return RGFW_KP_8; /* 0x0048 */ + case 0xC925FCD1U /* Numpad9 */: return RGFW_KP_9; /* 0x0049 */ + case 0x5EA3E8A4U /* NumpadSubtract */: return RGFW_minus; /* 0x004A */ + case 0xC925FCDCU /* Numpad4 */: return RGFW_KP_4; /* 0x004B */ + case 0xC925FCDDU /* Numpad5 */: return RGFW_KP_5; /* 0x004C */ + case 0xC925FCDEU /* Numpad6 */: return RGFW_KP_6; /* 0x004D */ + case 0xC925FCD9U /* Numpad1 */: return RGFW_KP_1; /* 0x004F */ + case 0xC925FCDAU /* Numpad2 */: return RGFW_KP_2; /* 0x0050 */ + case 0xC925FCDBU /* Numpad3 */: return RGFW_KP_3; /* 0x0051 */ + case 0xC925FCD8U /* Numpad0 */: return RGFW_KP_0; /* 0x0052 */ + case 0x95852DACU /* NumpadDecimal */: return RGFW_period; /* 0x0053 */ + case 0x7B8E57B1U /* F11 */: return RGFW_F11; /* 0x0057 */ + case 0x7B8E57B2U /* F12 */: return RGFW_F12; /* 0x0058 */ + case 0x7393FBACU /* NumpadEqual */: return RGFW_KP_Return; + case 0xB88EBF7CU /* AltRight */: return RGFW_altR; /* 0xE038 */ + case 0xC925873BU /* NumLock */: return RGFW_numLock; /* 0xE045 */ + case 0x2C595F45U /* Home */: return RGFW_home; /* 0xE047 */ + case 0xC91BB690U /* ArrowUp */: return RGFW_up; /* 0xE048 */ + case 0x672F9210U /* PageUp */: return RGFW_pageUp; /* 0xE049 */ + case 0x3799258CU /* ArrowLeft */: return RGFW_left; /* 0xE04B */ + case 0x4CE33F7CU /* ArrowRight */: return RGFW_right; /* 0xE04D */ + case 0x7B8E55DCU /* End */: return RGFW_end; /* 0xE04F */ + case 0x3799379EU /* ArrowDown */: return RGFW_down; /* 0xE050 */ + case 0xBA90179EU /* PageDown */: return RGFW_pageDown; /* 0xE051 */ + case 0x6723CB2CU /* Insert */: return RGFW_insert; /* 0xE052 */ + case 0x6725C50DU /* Delete */: return RGFW_delete; /* 0xE053 */ + case 0x6723658CU /* OSLeft */: return RGFW_superL; /* 0xE05B */ + case 0x39643F7CU /* MetaRight */: return RGFW_superR; /* 0xE05C */ + } + + return 0; +} + +void EMSCRIPTEN_KEEPALIVE RGFW_handleKeyEvent(char* key, char* code, RGFW_bool press) { + const char* iCode = code; + + u32 hash = 0; + while(*iCode) hash = ((hash ^ 0x7E057D79U) << 3) ^ (unsigned int)*iCode++; + + u32 physicalKey = RGFW_wASMPhysicalToRGFW(hash); + + u8 mappedKey = (u8)(*((u32*)key)); + + if (*((u16*)key) != mappedKey) { + mappedKey = 0; + if (*((u32*)key) == *((u32*)"Tab")) mappedKey = RGFW_tab; + } + + RGFW_eventQueuePush((RGFW_event){.type = (RGFW_eventType)(press ? RGFW_keyPressed : RGFW_keyReleased), + .key = (u8)physicalKey, + .keyChar = (u8)mappedKey, + .keyMod = _RGFW.root->event.keyMod, + ._win = _RGFW.root}); + + RGFW_keyboard[physicalKey].prev = RGFW_keyboard[physicalKey].current; + RGFW_keyboard[physicalKey].current = press; + + RGFW_keyCallback(_RGFW.root, physicalKey, mappedKey, _RGFW.root->event.keyMod, press); +} + +void EMSCRIPTEN_KEEPALIVE RGFW_handleKeyMods(RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll) { + RGFW_updateKeyModsPro(_RGFW.root, capital, numlock, control, alt, shift, super, scroll); +} + +void EMSCRIPTEN_KEEPALIVE Emscripten_onDrop(size_t count) { + if (!(_RGFW.root->_flags & RGFW_windowAllowDND)) + return; + + RGFW_eventQueuePush((RGFW_event){.type = RGFW_DND, + .droppedFilesCount = count, + ._win = _RGFW.root}); + RGFW_dndCallback(_RGFW.root, _RGFW.root->event.droppedFiles, count); +} + +RGFW_bool RGFW_stopCheckEvents_bool = RGFW_FALSE; +void RGFW_stopCheckEvents(void) { + RGFW_stopCheckEvents_bool = RGFW_TRUE; +} + +void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { + RGFW_UNUSED(win); + if (waitMS == 0) return; + + u32 start = (u32)(((u64)RGFW_getTimeNS()) / 1e+6); + + while ((_RGFW.eventLen == 0) && RGFW_stopCheckEvents_bool == RGFW_FALSE && (RGFW_getTimeNS() / 1e+6) - start < waitMS) + emscripten_sleep(0); + + RGFW_stopCheckEvents_bool = RGFW_FALSE; +} + +void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area){ + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + win->buffer = buffer; + win->bufferSize = area; + #ifdef RGFW_OSMESA + win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); + OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); + OSMesaPixelStore(OSMESA_Y_UP, 0); + #endif + #else + RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); /*!< if buffer rendering is not being used */ + #endif +} + +void EMSCRIPTEN_KEEPALIVE RGFW_makeSetValue(size_t index, char* file) { + /* This seems like a terrible idea, don't replicate this unless you hate yourself or the OS */ + /* TODO: find a better way to do this + */ + RGFW_MEMCPY((char*)_RGFW.root->event.droppedFiles[index], file, RGFW_MAX_PATH); +} + +#include +#include +#include +#include + +void EMSCRIPTEN_KEEPALIVE RGFW_mkdir(char* name) { mkdir(name, 0755); } + +void EMSCRIPTEN_KEEPALIVE RGFW_writeFile(const char *path, const char *data, size_t len) { + FILE* file = fopen(path, "w+"); + if (file == NULL) + return; + + fwrite(data, sizeof(char), len, file); + fclose(file); +} + +void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { +#if defined(RGFW_OPENGL) && !defined(RGFW_WEBGPU) && !defined(RGFW_OSMESA) && !defined(RGFW_BUFFER) + EmscriptenWebGLContextAttributes attrs; + attrs.alpha = RGFW_GL_HINTS[RGFW_glDepth]; + attrs.depth = RGFW_GL_HINTS[RGFW_glAlpha]; + attrs.stencil = RGFW_GL_HINTS[RGFW_glStencil]; + attrs.antialias = RGFW_GL_HINTS[RGFW_glSamples]; + attrs.premultipliedAlpha = EM_TRUE; + attrs.preserveDrawingBuffer = EM_FALSE; + + if (RGFW_GL_HINTS[RGFW_glDoubleBuffer] == 0) + attrs.renderViaOffscreenBackBuffer = 0; + else + attrs.renderViaOffscreenBackBuffer = RGFW_GL_HINTS[RGFW_glAuxBuffers]; + + attrs.failIfMajorPerformanceCaveat = EM_FALSE; + attrs.majorVersion = (RGFW_GL_HINTS[RGFW_glMajor] == 0) ? 1 : RGFW_GL_HINTS[RGFW_glMajor]; + attrs.minorVersion = RGFW_GL_HINTS[RGFW_glMinor]; + + attrs.enableExtensionsByDefault = EM_TRUE; + attrs.explicitSwapControl = EM_TRUE; + + emscripten_webgl_init_context_attributes(&attrs); + win->src.ctx = emscripten_webgl_create_context("#canvas", &attrs); + emscripten_webgl_make_context_current(win->src.ctx); + + #ifdef LEGACY_GL_EMULATION + EM_ASM("Module.useWebGL = true; GLImmediate.init();"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context initalized"); + #endif + glViewport(0, 0, win->r.w, win->r.h); +#endif +} + +void RGFW_window_freeOpenGL(RGFW_window* win) { +#if defined(RGFW_OPENGL) && !defined(RGFW_WEBGPU) && !defined(RGFW_OSMESA) && !defined(RGFW_OSMESA) + if (win->src.ctx == 0) return; + emscripten_webgl_destroy_context(win->src.ctx); + win->src.ctx = 0; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context freed"); +#elif defined(RGFW_OPENGL) && defined(RGFW_OSMESA) + if(win->src.ctx == 0) return; + OSMesaDestroyContext(win->src.ctx); + win->src.ctx = 0; +#else + RGFW_UNUSED(win); +#endif +} + +i32 RGFW_init(void) { _RGFW.windowCount = 0; RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context initialized"); return 0; } + +RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { + RGFW_window_basic_init(win, rect, flags); + RGFW_window_initOpenGL(win, 0); + + #if defined(RGFW_WEBGPU) + win->src.ctx = wgpuCreateInstance(NULL); + win->src.device = emscripten_webgpu_get_device(); + win->src.queue = wgpuDeviceGetQueue(win->src.device); + #endif + + emscripten_set_canvas_element_size("#canvas", rect.w, rect.h); + emscripten_set_window_title(name); + + /* load callbacks */ + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_resize); + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, EM_FALSE, Emscripten_on_fullscreenchange); + emscripten_set_mousemove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousemove); + emscripten_set_touchstart_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchstart); + emscripten_set_touchend_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchend); + emscripten_set_touchmove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchmove); + emscripten_set_touchcancel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchcancel); + emscripten_set_mousedown_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousedown); + emscripten_set_mouseup_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mouseup); + emscripten_set_wheel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_wheel); + emscripten_set_focusin_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusin); + emscripten_set_focusout_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusout); + emscripten_set_gamepadconnected_callback(NULL, 1, Emscripten_on_gamepad); + emscripten_set_gamepaddisconnected_callback(NULL, 1, Emscripten_on_gamepad); + + if (flags & RGFW_windowAllowDND) { + win->_flags |= RGFW_windowAllowDND; + } + + EM_ASM({ + window.addEventListener("keydown", + (event) => { + var key = stringToNewUTF8(event.key); var code = stringToNewUTF8(event.code); + Module._RGFW_handleKeyMods(event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("Control"), event.getModifierState("Alt"), event.getModifierState("Shift"), event.getModifierState("Meta"), event.getModifierState("ScrollLock")); + Module._RGFW_handleKeyEvent(key, code, 1); + _free(key); _free(code); + }, + true); + window.addEventListener("keyup", + (event) => { + var key = stringToNewUTF8(event.key); var code = stringToNewUTF8(event.code); + Module._RGFW_handleKeyMods(event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("Control"), event.getModifierState("Alt"), event.getModifierState("Shift"), event.getModifierState("Meta"), event.getModifierState("ScrollLock")); + Module._RGFW_handleKeyEvent(key, code, 0); + _free(key); _free(code); + }, + true); + }); + + EM_ASM({ + var canvas = document.getElementById('canvas'); + canvas.addEventListener('drop', function(e) { + e.preventDefault(); + if (e.dataTransfer.file < 0) + return; + + var filenamesArray = []; + var count = e.dataTransfer.files.length; + + /* Read and save the files to emscripten's files */ + var drop_dir = '.rgfw_dropped_files'; + Module._RGFW_mkdir(drop_dir); + + for (var i = 0; i < count; i++) { + var file = e.dataTransfer.files[i]; + + var path = '/' + drop_dir + '/' + file.name.replace("//", '_'); + var reader = new FileReader(); + + reader.onloadend = (e) => { + if (reader.readyState != 2) { + out('failed to read dropped file: '+file.name+': '+reader.error); + } + else { + var data = e.target.result; + + _RGFW_writeFile(path, new Uint8Array(data), file.size); + } + }; + + reader.readAsArrayBuffer(file); + /* This works weird on modern opengl */ + var filename = stringToNewUTF8(path); + + filenamesArray.push(filename); + + Module._RGFW_makeSetValue(i, filename); + } + + Module._Emscripten_onDrop(count); + + for (var i = 0; i < count; ++i) { + _free(filenamesArray[i]); + } + }, true); + + canvas.addEventListener('dragover', function(e) { e.preventDefault(); return false; }, true); + }); + + RGFW_window_setFlags(win, flags); + + if ((flags & RGFW_windowNoInitAPI) == 0) { + RGFW_window_initBuffer(win); + } + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); + return win; +} + +RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { + if (win == NULL || ((win->_flags & RGFW_windowFreeOnClose) && (win->_flags & RGFW_EVENT_QUIT))) return NULL; + RGFW_event* ev = RGFW_window_checkEventCore(win); + if (ev) return ev; + + emscripten_sample_gamepad_data(); + /* check gamepads */ + int i; + for (i = 0; (i < emscripten_get_num_gamepads()) && (i < 4); i++) { + if (RGFW_gamepads[i] == 0) + continue; + EmscriptenGamepadEvent gamepadState; + + if (emscripten_get_gamepad_status(i, &gamepadState) != EMSCRIPTEN_RESULT_SUCCESS) + break; + + /* Register buttons data for every connected gamepad */ + int j; + for (j = 0; (j < gamepadState.numButtons) && (j < 16); j++) { + u32 map[] = { + RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadX, RGFW_gamepadY, + RGFW_gamepadL1, RGFW_gamepadR1, RGFW_gamepadL2, RGFW_gamepadR2, + RGFW_gamepadSelect, RGFW_gamepadStart, + RGFW_gamepadL3, RGFW_gamepadR3, + RGFW_gamepadUp, RGFW_gamepadDown, RGFW_gamepadLeft, RGFW_gamepadRight, RGFW_gamepadHome + }; + + + u32 button = map[j]; + if (button == 404) + continue; + + if (RGFW_gamepadPressed[i][button].current != gamepadState.digitalButton[j]) { + if (gamepadState.digitalButton[j]) + win->event.type = RGFW_gamepadButtonPressed; + else + win->event.type = RGFW_gamepadButtonReleased; + + win->event.gamepad = i; + win->event.button = map[j]; + + RGFW_gamepadPressed[i][button].prev = RGFW_gamepadPressed[i][button].current; + RGFW_gamepadPressed[i][button].current = gamepadState.digitalButton[j]; + + RGFW_gamepadButtonCallback(win, win->event.gamepad, win->event.button, gamepadState.digitalButton[j]); + return &win->event; + } + } + + for (j = 0; (j < gamepadState.numAxes) && (j < 4); j += 2) { + win->event.axisesCount = gamepadState.numAxes / 2; + if (RGFW_gamepadAxes[i][(size_t)(j / 2)].x != (i8)(gamepadState.axis[j] * 100.0f) || + RGFW_gamepadAxes[i][(size_t)(j / 2)].y != (i8)(gamepadState.axis[j + 1] * 100.0f) + ) { + + RGFW_gamepadAxes[i][(size_t)(j / 2)].x = (i8)(gamepadState.axis[j] * 100.0f); + RGFW_gamepadAxes[i][(size_t)(j / 2)].y = (i8)(gamepadState.axis[j + 1] * 100.0f); + win->event.axis[(size_t)(j / 2)] = RGFW_gamepadAxes[i][(size_t)(j / 2)]; + + win->event.type = RGFW_gamepadAxisMove; + win->event.gamepad = i; + win->event.whichAxis = j / 2; + + RGFW_gamepadAxisCallback(win, win->event.gamepad, win->event.axis, win->event.axisesCount, win->event.whichAxis); + return &win->event; + } + } + } + + return NULL; +} + +void RGFW_window_resize(RGFW_window* win, RGFW_area a) { + RGFW_UNUSED(win); + emscripten_set_canvas_element_size("#canvas", a.w, a.h); +} + +/* NOTE: I don't know if this is possible */ +void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v) { RGFW_UNUSED(win); RGFW_UNUSED(v); } +/* this one might be possible but it looks iffy */ +RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { RGFW_UNUSED(channels); RGFW_UNUSED(a); RGFW_UNUSED(icon); return NULL; } + +void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { RGFW_UNUSED(win); RGFW_UNUSED(mouse); } +void RGFW_freeMouse(RGFW_mouse* mouse) { RGFW_UNUSED(mouse); } + +RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { + static const char cursors[16][16] = { + "default", "default", "text", "crosshair", + "pointer", "ew-resize", "ns-resize", "nwse-resize", "nesw-resize", + "move", "not-allowed" + }; + + RGFW_UNUSED(win); + EM_ASM( { document.getElementById("canvas").style.cursor = UTF8ToString($0); }, cursors[mouse]); + return RGFW_TRUE; +} + +RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { + return RGFW_window_setMouseStandard(win, RGFW_mouseNormal); +} + +void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show) { + RGFW_window_showMouseFlags(win, show); + if (show) + RGFW_window_setMouseDefault(win); + else + EM_ASM(document.getElementById('canvas').style.cursor = 'none';); +} + +RGFW_point RGFW_getGlobalMousePoint(void) { + RGFW_point point; + point.x = EM_ASM_INT({ + return window.mouseX || 0; + }); + point.y = EM_ASM_INT({ + return window.mouseY || 0; + }); + return point; +} + +void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { + RGFW_UNUSED(win); + + EM_ASM_({ + var canvas = document.getElementById('canvas'); + if ($0) { + canvas.style.pointerEvents = 'none'; + } else { + canvas.style.pointerEvents = 'auto'; + } + }, passthrough); +} + +void RGFW_writeClipboard(const char* text, u32 textLen) { + RGFW_UNUSED(textLen); + EM_ASM({ navigator.clipboard.writeText(UTF8ToString($0)); }, text); +} + + +RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { + RGFW_UNUSED(str); RGFW_UNUSED(strCapacity); + /* + placeholder code for later + I'm not sure if this is possible do the the async stuff + */ + return 0; +} + +void RGFW_window_swapBuffers_software(RGFW_window* win) { +#if defined(RGFW_OSMESA) + EM_ASM_({ + var data = Module.HEAPU8.slice($0, $0 + $1 * $2 * 4); + let context = document.getElementById("canvas").getContext("2d"); + let image = context.getImageData(0, 0, $1, $2); + image.data.set(data); + context.putImageData(image, 0, $4 - $2); + }, win->buffer, win->bufferSize.w, win->bufferSize.h, win->r.w, win->r.h); +#elif defined(RGFW_BUFFER) + EM_ASM_({ + var data = Module.HEAPU8.slice($0, $0 + $1 * $2 * 4); + let context = document.getElementById("canvas").getContext("2d"); + let image = context.getImageData(0, 0, $1, $2); + image.data.set(data); + context.putImageData(image, 0, 0); + }, win->buffer, win->bufferSize.w, win->bufferSize.h, win->r.w, win->r.h); + emscripten_sleep(0); +#else + RGFW_UNUSED(win); +#endif +} + +void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { +#if !defined(RGFW_WEBGPU) && !(defined(RGFW_OSMESA) || defined(RGFW_BUFFER)) + if (win == NULL) + emscripten_webgl_make_context_current(0); + else + emscripten_webgl_make_context_current(win->src.ctx); +#endif +} + + +void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { +#ifndef RGFW_WEBGPU + emscripten_webgl_commit_frame(); + +#endif + emscripten_sleep(0); +} + +#ifndef RGFW_WEBGPU +void* RGFW_getCurrent_OpenGL(void) { return (void*)emscripten_webgl_get_current_context(); } +#endif + +#ifndef RGFW_EGL +void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { RGFW_UNUSED(win); RGFW_UNUSED(swapInterval); } +#endif + +void RGFW_deinit(void) { _RGFW.windowCount = -1; RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context deinitialized"); } + +void RGFW_window_close(RGFW_window* win) { + if ((win->_flags & RGFW_windowNoInitAPI) == 0) RGFW_window_freeOpenGL(win); + + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + if ((win->_flags & RGFW_BUFFER_ALLOC)) + RGFW_FREE(win->buffer); + #endif + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a window was freed"); + _RGFW.windowCount--; + if (_RGFW.windowCount == 0) RGFW_deinit(); + + RGFW_clipboard_switch(NULL); + RGFW_FREE(win->event.droppedFiles); + if ((win->_flags & RGFW_WINDOW_ALLOC)) { + RGFW_FREE(win); + win = NULL; + } +} + +int RGFW_innerWidth(void) { return EM_ASM_INT({ return window.innerWidth; }); } +int RGFW_innerHeight(void) { return EM_ASM_INT({ return window.innerHeight; }); } + +RGFW_area RGFW_getScreenSize(void) { + return RGFW_AREA(RGFW_innerWidth(), RGFW_innerHeight()); +} + +RGFW_proc RGFW_getProcAddress(const char* procname) { + return (RGFW_proc)emscripten_webgl_get_proc_address(procname); +} + +void RGFW_sleep(u64 milisecond) { + emscripten_sleep(milisecond); +} + +u64 RGFW_getTimerFreq(void) { return (u64)1000; } +u64 RGFW_getTimerValue(void) { return emscripten_get_now() * 1e+6; } + +void RGFW_releaseCursor(RGFW_window* win) { + RGFW_UNUSED(win); + emscripten_exit_pointerlock(); +} + +void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { + RGFW_UNUSED(win); RGFW_UNUSED(r); + + emscripten_request_pointerlock("#canvas", 1); +} + + +void RGFW_window_setName(RGFW_window* win, const char* name) { + RGFW_UNUSED(win); + emscripten_set_window_title(name); +} + +void RGFW_window_maximize(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + RGFW_area screen = RGFW_getScreenSize(); + RGFW_window_move(win, RGFW_POINT(0, 0)); + RGFW_window_resize(win, screen); +} + +void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { + RGFW_ASSERT(win != NULL); + if (fullscreen) { + win->_flags |= RGFW_windowFullscreen; + EM_ASM( Module.requestFullscreen(false, true); ); + return; + } + win->_flags &= ~(u32)RGFW_windowFullscreen; + EM_ASM( Module.exitFullscreen(false, true); ); +} + +void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { + RGFW_UNUSED(win); + EM_ASM({ + var element = document.getElementById("canvas"); + if (element) + element.style.opacity = $1; + }, "elementId", opacity); +} + +/* unsupported functions */ +void RGFW_window_focus(RGFW_window* win) { RGFW_UNUSED(win); } +void RGFW_window_raise(RGFW_window* win) { RGFW_UNUSED(win); } +RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { RGFW_UNUSED(mon); RGFW_UNUSED(mode); RGFW_UNUSED(request); return RGFW_FALSE; } +RGFW_monitor* RGFW_getMonitors(size_t* len) { RGFW_UNUSED(len); return NULL; } +RGFW_monitor RGFW_getPrimaryMonitor(void) { return (RGFW_monitor){}; } +void RGFW_window_move(RGFW_window* win, RGFW_point v) { RGFW_UNUSED(win); RGFW_UNUSED(v); } +void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { RGFW_UNUSED(win); RGFW_UNUSED(a); } +void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { RGFW_UNUSED(win); RGFW_UNUSED(a); } +void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { RGFW_UNUSED(win); RGFW_UNUSED(a); } +void RGFW_window_minimize(RGFW_window* win) { RGFW_UNUSED(win); } +void RGFW_window_restore(RGFW_window* win) { RGFW_UNUSED(win); } +void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { RGFW_UNUSED(win); RGFW_UNUSED(floating); } +void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { RGFW_UNUSED(win); RGFW_UNUSED(border); } +RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* icon, RGFW_area a, i32 channels, u8 type) { RGFW_UNUSED(win); RGFW_UNUSED(icon); RGFW_UNUSED(a); RGFW_UNUSED(channels); RGFW_UNUSED(type); return RGFW_FALSE; } +void RGFW_window_hide(RGFW_window* win) { RGFW_UNUSED(win); } +void RGFW_window_show(RGFW_window* win) {RGFW_UNUSED(win); } +RGFW_bool RGFW_window_isHidden(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } +RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } +RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } +RGFW_bool RGFW_window_isFloating(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } +RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { RGFW_UNUSED(win); return (RGFW_monitor){}; } +#endif + +/* end of web asm defines */ + +/* unix (macOS, linux, web asm) only stuff */ +#if defined(RGFW_X11) || defined(RGFW_MACOS) || defined(RGFW_WASM) || defined(RGFW_WAYLAND) +#ifndef RGFW_NO_THREADS +#include + +RGFW_thread RGFW_createThread(RGFW_threadFunc_ptr ptr, void* args) { + RGFW_thread t; + pthread_create((pthread_t*) &t, NULL, *ptr, args); + return t; +} +void RGFW_cancelThread(RGFW_thread thread) { pthread_cancel((pthread_t) thread); } +void RGFW_joinThread(RGFW_thread thread) { pthread_join((pthread_t) thread, NULL); } + +#if defined(__linux__) +void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { pthread_setschedprio((pthread_t)thread, priority); } +#else +void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { RGFW_UNUSED(thread); RGFW_UNUSED(priority); } +#endif +#endif + +#ifndef RGFW_WASM +void RGFW_sleep(u64 ms) { + struct timespec time; + time.tv_sec = 0; + time.tv_nsec = (long int)((double)ms * 1e+6); + + #ifndef RGFW_NO_UNIX_CLOCK + nanosleep(&time, NULL); + #endif +} +#endif + +#endif /* end of unix / mac stuff */ +#endif /* RGFW_IMPLEMENTATION */ + +#if defined(__cplusplus) && !defined(__EMSCRIPTEN__) +} +#endif + +#if _MSC_VER + #pragma warning( pop ) +#endif diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 1c4f606a7..144d9f49f 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -584,7 +584,7 @@ void SetWindowPosition(int x, int y) // Set monitor for the current window void SetWindowMonitor(int monitor) { - RGFW_window_moveToMonitor(platform.window, RGFW_getMonitors()[monitor]); + RGFW_window_moveToMonitor(platform.window, RGFW_getMonitors(NULL)[monitor]); } // Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) @@ -641,7 +641,7 @@ int GetMonitorCount(void) #define MAX_MONITORS_SUPPORTED 6 int count = MAX_MONITORS_SUPPORTED; - RGFW_monitor *mons = RGFW_getMonitors(); + RGFW_monitor *mons = RGFW_getMonitors(NULL); for (int i = 0; i < 6; i++) { @@ -658,7 +658,7 @@ int GetMonitorCount(void) // Get current monitor where window is placed int GetCurrentMonitor(void) { - RGFW_monitor *mons = RGFW_getMonitors(); + RGFW_monitor *mons = RGFW_getMonitors(NULL); RGFW_monitor mon = { 0 }; if (platform.window) mon = RGFW_window_getMonitor(platform.window); @@ -675,7 +675,7 @@ int GetCurrentMonitor(void) // Get selected monitor position Vector2 GetMonitorPosition(int monitor) { - RGFW_monitor *mons = RGFW_getMonitors(); + RGFW_monitor *mons = RGFW_getMonitors(NULL); return (Vector2){ (float)mons[monitor].x, (float)mons[monitor].y }; } @@ -683,7 +683,7 @@ Vector2 GetMonitorPosition(int monitor) // Get selected monitor width (currently used by monitor) int GetMonitorWidth(int monitor) { - RGFW_monitor *mons = RGFW_getMonitors(); + RGFW_monitor *mons = RGFW_getMonitors(NULL); return mons[monitor].mode.area.w; } @@ -691,7 +691,7 @@ int GetMonitorWidth(int monitor) // Get selected monitor height (currently used by monitor) int GetMonitorHeight(int monitor) { - RGFW_monitor *mons = RGFW_getMonitors(); + RGFW_monitor *mons = RGFW_getMonitors(NULL); return mons[monitor].mode.area.h; } @@ -699,7 +699,7 @@ int GetMonitorHeight(int monitor) // Get selected monitor physical width in millimetres int GetMonitorPhysicalWidth(int monitor) { - RGFW_monitor *mons = RGFW_getMonitors(); + RGFW_monitor *mons = RGFW_getMonitors(NULL); return mons[monitor].physW; } @@ -707,7 +707,7 @@ int GetMonitorPhysicalWidth(int monitor) // Get selected monitor physical height in millimetres int GetMonitorPhysicalHeight(int monitor) { - RGFW_monitor *mons = RGFW_getMonitors(); + RGFW_monitor *mons = RGFW_getMonitors(NULL); return (int)mons[monitor].physH; } @@ -715,7 +715,7 @@ int GetMonitorPhysicalHeight(int monitor) // Get selected monitor refresh rate int GetMonitorRefreshRate(int monitor) { - RGFW_monitor *mons = RGFW_getMonitors(); + RGFW_monitor *mons = RGFW_getMonitors(NULL); return (int)mons[monitor].mode.refreshRate; } @@ -723,7 +723,7 @@ int GetMonitorRefreshRate(int monitor) // Get the human-readable, UTF-8 encoded name of the selected monitor const char *GetMonitorName(int monitor) { - RGFW_monitor *mons = RGFW_getMonitors(); + RGFW_monitor *mons = RGFW_getMonitors(NULL); return mons[monitor].name; } From 34181726171adeab30d23469d7fe40ae2d761aee Mon Sep 17 00:00:00 2001 From: M374LX Date: Thu, 29 May 2025 23:01:43 -0300 Subject: [PATCH 019/242] Update comments --- src/platforms/rcore_desktop_glfw.c | 6 +++--- src/platforms/rcore_desktop_sdl.c | 2 +- src/platforms/rcore_drm.c | 6 +++--- src/raylib.h | 6 +++--- src/rcore.c | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 675d09f22..83c13d34e 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1,6 +1,6 @@ /********************************************************************************************** * -* rcore_desktop - Functions to manage window, graphics device and inputs +* rcore_desktop_glfw - Functions to manage window, graphics device and inputs * * PLATFORM: DESKTOP: GLFW * - Windows (Win32, Win64) @@ -1238,7 +1238,7 @@ void PollInputEvents(void) } } - // Get current axis state + // Get current state of axes const float *axes = state.axes; for (int k = 0; (axes != NULL) && (k < GLFW_GAMEPAD_AXIS_LAST + 1); k++) @@ -1246,7 +1246,7 @@ void PollInputEvents(void) CORE.Input.Gamepad.axisState[i][k] = axes[k]; } - // Register buttons for 2nd triggers (because GLFW doesn't count these as buttons but rather axis) + // Register buttons for 2nd triggers (because GLFW doesn't count these as buttons but rather as axes) if (CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_LEFT_TRIGGER] > 0.1f) { CORE.Input.Gamepad.currentButtonState[i][GAMEPAD_BUTTON_LEFT_TRIGGER_2] = 1; diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 1621dd2a5..aacb2f800 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -1825,7 +1825,7 @@ void PollInputEvents(void) { if (platform.gamepadId[i] == event.jaxis.which) { - // SDL axis value range is -32768 to 32767, we normalize it to RayLib's -1.0 to 1.0f range + // SDL axis value range is -32768 to 32767, we normalize it to raylib's -1.0 to 1.0f range float value = event.jaxis.value/(float)32767; CORE.Input.Gamepad.axisState[i][axis] = value; diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index ac56e0f9e..3ec3135e1 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -124,7 +124,7 @@ typedef struct { // Gamepad data int gamepadStreamFd[MAX_GAMEPADS]; // Gamepad device file descriptor - int gamepadAbsAxisRange[MAX_GAMEPADS][MAX_GAMEPAD_AXES][2]; // [0] = min, [1] = range value of the axis + int gamepadAbsAxisRange[MAX_GAMEPADS][MAX_GAMEPAD_AXES][2]; // [0] = min, [1] = range value of the axes int gamepadAbsAxisMap[MAX_GAMEPADS][ABS_CNT]; // Maps the axes gamepads from the evdev api to a sequential one int gamepadCount; // The number of gamepads registered } PlatformData; @@ -1460,7 +1460,7 @@ static void ConfigureEvdevDevice(char *device) // matter if we support them else if (hasAbsXY && TEST_BIT(keyBits, BTN_MOUSE)) isMouse = true; - // If any of the common joystick axis is present, we assume it's a gamepad + // If any of the common joystick axes are present, we assume it's a gamepad else { for (int axis = (hasAbsXY? ABS_Z : ABS_X); axis < ABS_PRESSURE; axis++) @@ -1546,7 +1546,7 @@ static void ConfigureEvdevDevice(char *device) if (absAxisCount > 0) { // TODO / NOTE - // So gamepad axis (as in the actual linux joydev.c) are just simply enumerated + // So gamepad axes (as in the actual linux joydev.c) are just simply enumerated // and (at least for some input drivers like xpat) it's convention to use // ABS_X, ABX_Y for one joystick ABS_RX, ABS_RY for the other and the Z axes for the // shoulder buttons diff --git a/src/raylib.h b/src/raylib.h index 563156525..8e5de3aa6 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -743,7 +743,7 @@ typedef enum { GAMEPAD_BUTTON_RIGHT_THUMB // Gamepad joystick pressed button right } GamepadButton; -// Gamepad axis +// Gamepad axes typedef enum { GAMEPAD_AXIS_LEFT_X = 0, // Gamepad left stick X axis GAMEPAD_AXIS_LEFT_Y = 1, // Gamepad left stick Y axis @@ -1192,8 +1192,8 @@ RLAPI bool IsGamepadButtonDown(int gamepad, int button); // Check if a game RLAPI bool IsGamepadButtonReleased(int gamepad, int button); // Check if a gamepad button has been released once RLAPI bool IsGamepadButtonUp(int gamepad, int button); // Check if a gamepad button is NOT being pressed RLAPI int GetGamepadButtonPressed(void); // Get the last gamepad button pressed -RLAPI int GetGamepadAxisCount(int gamepad); // Get gamepad axis count for a gamepad -RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Get axis movement value for a gamepad axis +RLAPI int GetGamepadAxisCount(int gamepad); // Get axis count for a gamepad +RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Get movement value for a gamepad axis RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB) RLAPI void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration); // Set gamepad vibration for both motors (duration in seconds) diff --git a/src/rcore.c b/src/rcore.c index bfa0cc036..f216a197a 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -235,10 +235,10 @@ __declspec(dllimport) unsigned int __stdcall timeEndPeriod(unsigned int uPeriod) #define MAX_GAMEPADS 4 // Maximum number of gamepads supported #endif #ifndef MAX_GAMEPAD_NAME_LENGTH - #define MAX_GAMEPAD_NAME_LENGTH 128 // Maximum number of characters of gamepad name (byte size) + #define MAX_GAMEPAD_NAME_LENGTH 128 // Maximum number of characters in a gamepad name (byte size) #endif #ifndef MAX_GAMEPAD_AXES - #define MAX_GAMEPAD_AXES 8 // Maximum number of axis supported (per gamepad) + #define MAX_GAMEPAD_AXES 8 // Maximum number of axes supported (per gamepad) #endif #ifndef MAX_GAMEPAD_BUTTONS #define MAX_GAMEPAD_BUTTONS 32 // Maximum number of buttons supported (per gamepad) @@ -354,7 +354,7 @@ typedef struct CoreData { } Touch; struct { int lastButtonPressed; // Register last gamepad button pressed - int axisCount[MAX_GAMEPADS]; // Register number of available gamepad axis + int axisCount[MAX_GAMEPADS]; // Register number of available gamepad axes bool ready[MAX_GAMEPADS]; // Flag to know if gamepad is ready char name[MAX_GAMEPADS][MAX_GAMEPAD_NAME_LENGTH]; // Gamepad name holder char currentButtonState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Current gamepad buttons state From 8f2ecfba4db8a9a7006b6814dc2412bd8f572386 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 30 May 2025 02:04:09 +0000 Subject: [PATCH 020/242] Update raylib_api.* by CI --- parser/output/raylib_api.json | 6 +++--- parser/output/raylib_api.lua | 6 +++--- parser/output/raylib_api.txt | 6 +++--- parser/output/raylib_api.xml | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index 72a3477a4..6ef2a9157 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -2300,7 +2300,7 @@ }, { "name": "GamepadAxis", - "description": "Gamepad axis", + "description": "Gamepad axes", "values": [ { "name": "GAMEPAD_AXIS_LEFT_X", @@ -5017,7 +5017,7 @@ }, { "name": "GetGamepadAxisCount", - "description": "Get gamepad axis count for a gamepad", + "description": "Get axis count for a gamepad", "returnType": "int", "params": [ { @@ -5028,7 +5028,7 @@ }, { "name": "GetGamepadAxisMovement", - "description": "Get axis movement value for a gamepad axis", + "description": "Get movement value for a gamepad axis", "returnType": "float", "params": [ { diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 662d45a78..e0ef9277e 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -2300,7 +2300,7 @@ return { }, { name = "GamepadAxis", - description = "Gamepad axis", + description = "Gamepad axes", values = { { name = "GAMEPAD_AXIS_LEFT_X", @@ -4426,7 +4426,7 @@ return { }, { name = "GetGamepadAxisCount", - description = "Get gamepad axis count for a gamepad", + description = "Get axis count for a gamepad", returnType = "int", params = { {type = "int", name = "gamepad"} @@ -4434,7 +4434,7 @@ return { }, { name = "GetGamepadAxisMovement", - description = "Get axis movement value for a gamepad axis", + description = "Get movement value for a gamepad axis", returnType = "float", params = { {type = "int", name = "gamepad"}, diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index c399c5291..4d83e3ca0 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -774,7 +774,7 @@ Enum 06: GamepadButton (18 values) Value[GAMEPAD_BUTTON_RIGHT_THUMB]: 17 Enum 07: GamepadAxis (6 values) Name: GamepadAxis - Description: Gamepad axis + Description: Gamepad axes Value[GAMEPAD_AXIS_LEFT_X]: 0 Value[GAMEPAD_AXIS_LEFT_Y]: 1 Value[GAMEPAD_AXIS_RIGHT_X]: 2 @@ -1943,12 +1943,12 @@ Function 176: GetGamepadButtonPressed() (0 input parameters) Function 177: GetGamepadAxisCount() (1 input parameters) Name: GetGamepadAxisCount Return type: int - Description: Get gamepad axis count for a gamepad + Description: Get axis count for a gamepad Param[1]: gamepad (type: int) Function 178: GetGamepadAxisMovement() (2 input parameters) Name: GetGamepadAxisMovement Return type: float - Description: Get axis movement value for a gamepad axis + Description: Get movement value for a gamepad axis Param[1]: gamepad (type: int) Param[2]: axis (type: int) Function 179: SetGamepadMappings() (1 input parameters) diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 511d5d121..b761f8b56 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -486,7 +486,7 @@ - + @@ -1216,10 +1216,10 @@ - + - + From b26f6d34baccd479a4a41a8348debd75d434b646 Mon Sep 17 00:00:00 2001 From: Nikolas Date: Fri, 30 May 2025 14:30:24 +0200 Subject: [PATCH 021/242] Allow passing options to raygui in build.zig --- build.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.zig b/build.zig index 0b086dc4d..e445bf576 100644 --- a/build.zig +++ b/build.zig @@ -366,8 +366,8 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. return raylib; } -pub fn addRaygui(b: *std.Build, raylib: *std.Build.Step.Compile, raygui_dep: *std.Build.Dependency) void { - const raylib_dep = b.dependencyFromBuildZig(@This(), .{}); +pub fn addRaygui(b: *std.Build, raylib: *std.Build.Step.Compile, raygui_dep: *std.Build.Dependency, options: Options) void { + const raylib_dep = b.dependencyFromBuildZig(@This(), options); var gen_step = b.addWriteFiles(); raylib.step.dependOn(&gen_step.step); From bc2b2864e08407ca0eefab7ab6d05d000c0c0186 Mon Sep 17 00:00:00 2001 From: M374LX Date: Sat, 31 May 2025 14:24:38 -0300 Subject: [PATCH 022/242] RGFW: fix Escape always closing the window --- src/platforms/rcore_desktop_rgfw.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 1c4f606a7..8dc1cf54c 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -1320,6 +1320,13 @@ int InitPlatform(void) platform.window = RGFW_createWindow(CORE.Window.title, RGFW_RECT(0, 0, CORE.Window.screen.width, CORE.Window.screen.height), flags); platform.mon.mode.area.w = 0; + if (platform.window != NULL) + { + // NOTE: RGFW's exit key is distinct from raylib's exit key (which can + // be set with SetExitKey()) and defaults to Escape + platform.window->exitKey = RGFW_keyNULL; + } + #ifndef PLATFORM_WEB_RGFW RGFW_area screenSize = RGFW_getScreenSize(); CORE.Window.display.width = screenSize.w; From 3414d96eafb639946eb352b2eb6a6d04ae3557e5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 31 May 2025 18:41:49 +0000 Subject: [PATCH 023/242] Update raylib_api.* by CI --- parser/output/raylib_api.json | 46 +++ parser/output/raylib_api.lua | 22 + parser/output/raylib_api.txt | 728 +++++++++++++++++----------------- parser/output/raylib_api.xml | 14 +- 4 files changed, 453 insertions(+), 357 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index 6ef2a9157..efbac71b0 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -5702,6 +5702,29 @@ } ] }, + { + "name": "DrawEllipseV", + "description": "Draw ellipse (Vector version)", + "returnType": "void", + "params": [ + { + "type": "Vector2", + "name": "center" + }, + { + "type": "float", + "name": "radiusH" + }, + { + "type": "float", + "name": "radiusV" + }, + { + "type": "Color", + "name": "color" + } + ] + }, { "name": "DrawEllipseLines", "description": "Draw ellipse outline", @@ -5729,6 +5752,29 @@ } ] }, + { + "name": "DrawEllipseLinesV", + "description": "Draw ellipse outline (Vector version)", + "returnType": "void", + "params": [ + { + "type": "Vector2", + "name": "center" + }, + { + "type": "float", + "name": "radiusH" + }, + { + "type": "float", + "name": "radiusV" + }, + { + "type": "Color", + "name": "color" + } + ] + }, { "name": "DrawRing", "description": "Draw ring", diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index e0ef9277e..08615aa22 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -4838,6 +4838,17 @@ return { {type = "Color", name = "color"} } }, + { + name = "DrawEllipseV", + description = "Draw ellipse (Vector version)", + returnType = "void", + params = { + {type = "Vector2", name = "center"}, + {type = "float", name = "radiusH"}, + {type = "float", name = "radiusV"}, + {type = "Color", name = "color"} + } + }, { name = "DrawEllipseLines", description = "Draw ellipse outline", @@ -4850,6 +4861,17 @@ return { {type = "Color", name = "color"} } }, + { + name = "DrawEllipseLinesV", + description = "Draw ellipse outline (Vector version)", + returnType = "void", + params = { + {type = "Vector2", name = "center"}, + {type = "float", name = "radiusH"}, + {type = "float", name = "radiusV"}, + {type = "Color", name = "color"} + } + }, { name = "DrawRing", description = "Draw ring", diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 4d83e3ca0..2f44d94cc 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -993,7 +993,7 @@ Callback 006: AudioCallback() (2 input parameters) Param[1]: bufferData (type: void *) Param[2]: frames (type: unsigned int) -Functions found: 582 +Functions found: 584 Function 001: InitWindow() (3 input parameters) Name: InitWindow @@ -2252,7 +2252,15 @@ Function 227: DrawEllipse() (5 input parameters) Param[3]: radiusH (type: float) Param[4]: radiusV (type: float) Param[5]: color (type: Color) -Function 228: DrawEllipseLines() (5 input parameters) +Function 228: DrawEllipseV() (4 input parameters) + Name: DrawEllipseV + Return type: void + Description: Draw ellipse (Vector version) + Param[1]: center (type: Vector2) + Param[2]: radiusH (type: float) + Param[3]: radiusV (type: float) + Param[4]: color (type: Color) +Function 229: DrawEllipseLines() (5 input parameters) Name: DrawEllipseLines Return type: void Description: Draw ellipse outline @@ -2261,7 +2269,15 @@ Function 228: DrawEllipseLines() (5 input parameters) Param[3]: radiusH (type: float) Param[4]: radiusV (type: float) Param[5]: color (type: Color) -Function 229: DrawRing() (7 input parameters) +Function 230: DrawEllipseLinesV() (4 input parameters) + Name: DrawEllipseLinesV + Return type: void + Description: Draw ellipse outline (Vector version) + Param[1]: center (type: Vector2) + Param[2]: radiusH (type: float) + Param[3]: radiusV (type: float) + Param[4]: color (type: Color) +Function 231: DrawRing() (7 input parameters) Name: DrawRing Return type: void Description: Draw ring @@ -2272,7 +2288,7 @@ Function 229: DrawRing() (7 input parameters) Param[5]: endAngle (type: float) Param[6]: segments (type: int) Param[7]: color (type: Color) -Function 230: DrawRingLines() (7 input parameters) +Function 232: DrawRingLines() (7 input parameters) Name: DrawRingLines Return type: void Description: Draw ring outline @@ -2283,7 +2299,7 @@ Function 230: DrawRingLines() (7 input parameters) Param[5]: endAngle (type: float) Param[6]: segments (type: int) Param[7]: color (type: Color) -Function 231: DrawRectangle() (5 input parameters) +Function 233: DrawRectangle() (5 input parameters) Name: DrawRectangle Return type: void Description: Draw a color-filled rectangle @@ -2292,20 +2308,20 @@ Function 231: DrawRectangle() (5 input parameters) Param[3]: width (type: int) Param[4]: height (type: int) Param[5]: color (type: Color) -Function 232: DrawRectangleV() (3 input parameters) +Function 234: DrawRectangleV() (3 input parameters) Name: DrawRectangleV Return type: void Description: Draw a color-filled rectangle (Vector version) Param[1]: position (type: Vector2) Param[2]: size (type: Vector2) Param[3]: color (type: Color) -Function 233: DrawRectangleRec() (2 input parameters) +Function 235: DrawRectangleRec() (2 input parameters) Name: DrawRectangleRec Return type: void Description: Draw a color-filled rectangle Param[1]: rec (type: Rectangle) Param[2]: color (type: Color) -Function 234: DrawRectanglePro() (4 input parameters) +Function 236: DrawRectanglePro() (4 input parameters) Name: DrawRectanglePro Return type: void Description: Draw a color-filled rectangle with pro parameters @@ -2313,7 +2329,7 @@ Function 234: DrawRectanglePro() (4 input parameters) Param[2]: origin (type: Vector2) Param[3]: rotation (type: float) Param[4]: color (type: Color) -Function 235: DrawRectangleGradientV() (6 input parameters) +Function 237: DrawRectangleGradientV() (6 input parameters) Name: DrawRectangleGradientV Return type: void Description: Draw a vertical-gradient-filled rectangle @@ -2323,7 +2339,7 @@ Function 235: DrawRectangleGradientV() (6 input parameters) Param[4]: height (type: int) Param[5]: top (type: Color) Param[6]: bottom (type: Color) -Function 236: DrawRectangleGradientH() (6 input parameters) +Function 238: DrawRectangleGradientH() (6 input parameters) Name: DrawRectangleGradientH Return type: void Description: Draw a horizontal-gradient-filled rectangle @@ -2333,7 +2349,7 @@ Function 236: DrawRectangleGradientH() (6 input parameters) Param[4]: height (type: int) Param[5]: left (type: Color) Param[6]: right (type: Color) -Function 237: DrawRectangleGradientEx() (5 input parameters) +Function 239: DrawRectangleGradientEx() (5 input parameters) Name: DrawRectangleGradientEx Return type: void Description: Draw a gradient-filled rectangle with custom vertex colors @@ -2342,7 +2358,7 @@ Function 237: DrawRectangleGradientEx() (5 input parameters) Param[3]: bottomLeft (type: Color) Param[4]: topRight (type: Color) Param[5]: bottomRight (type: Color) -Function 238: DrawRectangleLines() (5 input parameters) +Function 240: DrawRectangleLines() (5 input parameters) Name: DrawRectangleLines Return type: void Description: Draw rectangle outline @@ -2351,14 +2367,14 @@ Function 238: DrawRectangleLines() (5 input parameters) Param[3]: width (type: int) Param[4]: height (type: int) Param[5]: color (type: Color) -Function 239: DrawRectangleLinesEx() (3 input parameters) +Function 241: DrawRectangleLinesEx() (3 input parameters) Name: DrawRectangleLinesEx Return type: void Description: Draw rectangle outline with extended parameters Param[1]: rec (type: Rectangle) Param[2]: lineThick (type: float) Param[3]: color (type: Color) -Function 240: DrawRectangleRounded() (4 input parameters) +Function 242: DrawRectangleRounded() (4 input parameters) Name: DrawRectangleRounded Return type: void Description: Draw rectangle with rounded edges @@ -2366,7 +2382,7 @@ Function 240: DrawRectangleRounded() (4 input parameters) Param[2]: roundness (type: float) Param[3]: segments (type: int) Param[4]: color (type: Color) -Function 241: DrawRectangleRoundedLines() (4 input parameters) +Function 243: DrawRectangleRoundedLines() (4 input parameters) Name: DrawRectangleRoundedLines Return type: void Description: Draw rectangle lines with rounded edges @@ -2374,7 +2390,7 @@ Function 241: DrawRectangleRoundedLines() (4 input parameters) Param[2]: roundness (type: float) Param[3]: segments (type: int) Param[4]: color (type: Color) -Function 242: DrawRectangleRoundedLinesEx() (5 input parameters) +Function 244: DrawRectangleRoundedLinesEx() (5 input parameters) Name: DrawRectangleRoundedLinesEx Return type: void Description: Draw rectangle with rounded edges outline @@ -2383,7 +2399,7 @@ Function 242: DrawRectangleRoundedLinesEx() (5 input parameters) Param[3]: segments (type: int) Param[4]: lineThick (type: float) Param[5]: color (type: Color) -Function 243: DrawTriangle() (4 input parameters) +Function 245: DrawTriangle() (4 input parameters) Name: DrawTriangle Return type: void Description: Draw a color-filled triangle (vertex in counter-clockwise order!) @@ -2391,7 +2407,7 @@ Function 243: DrawTriangle() (4 input parameters) Param[2]: v2 (type: Vector2) Param[3]: v3 (type: Vector2) Param[4]: color (type: Color) -Function 244: DrawTriangleLines() (4 input parameters) +Function 246: DrawTriangleLines() (4 input parameters) Name: DrawTriangleLines Return type: void Description: Draw triangle outline (vertex in counter-clockwise order!) @@ -2399,21 +2415,21 @@ Function 244: DrawTriangleLines() (4 input parameters) Param[2]: v2 (type: Vector2) Param[3]: v3 (type: Vector2) Param[4]: color (type: Color) -Function 245: DrawTriangleFan() (3 input parameters) +Function 247: DrawTriangleFan() (3 input parameters) Name: DrawTriangleFan Return type: void Description: Draw a triangle fan defined by points (first vertex is the center) Param[1]: points (type: const Vector2 *) Param[2]: pointCount (type: int) Param[3]: color (type: Color) -Function 246: DrawTriangleStrip() (3 input parameters) +Function 248: DrawTriangleStrip() (3 input parameters) Name: DrawTriangleStrip Return type: void Description: Draw a triangle strip defined by points Param[1]: points (type: const Vector2 *) Param[2]: pointCount (type: int) Param[3]: color (type: Color) -Function 247: DrawPoly() (5 input parameters) +Function 249: DrawPoly() (5 input parameters) Name: DrawPoly Return type: void Description: Draw a regular polygon (Vector version) @@ -2422,7 +2438,7 @@ Function 247: DrawPoly() (5 input parameters) Param[3]: radius (type: float) Param[4]: rotation (type: float) Param[5]: color (type: Color) -Function 248: DrawPolyLines() (5 input parameters) +Function 250: DrawPolyLines() (5 input parameters) Name: DrawPolyLines Return type: void Description: Draw a polygon outline of n sides @@ -2431,7 +2447,7 @@ Function 248: DrawPolyLines() (5 input parameters) Param[3]: radius (type: float) Param[4]: rotation (type: float) Param[5]: color (type: Color) -Function 249: DrawPolyLinesEx() (6 input parameters) +Function 251: DrawPolyLinesEx() (6 input parameters) Name: DrawPolyLinesEx Return type: void Description: Draw a polygon outline of n sides with extended parameters @@ -2441,7 +2457,7 @@ Function 249: DrawPolyLinesEx() (6 input parameters) Param[4]: rotation (type: float) Param[5]: lineThick (type: float) Param[6]: color (type: Color) -Function 250: DrawSplineLinear() (4 input parameters) +Function 252: DrawSplineLinear() (4 input parameters) Name: DrawSplineLinear Return type: void Description: Draw spline: Linear, minimum 2 points @@ -2449,7 +2465,7 @@ Function 250: DrawSplineLinear() (4 input parameters) Param[2]: pointCount (type: int) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 251: DrawSplineBasis() (4 input parameters) +Function 253: DrawSplineBasis() (4 input parameters) Name: DrawSplineBasis Return type: void Description: Draw spline: B-Spline, minimum 4 points @@ -2457,7 +2473,7 @@ Function 251: DrawSplineBasis() (4 input parameters) Param[2]: pointCount (type: int) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 252: DrawSplineCatmullRom() (4 input parameters) +Function 254: DrawSplineCatmullRom() (4 input parameters) Name: DrawSplineCatmullRom Return type: void Description: Draw spline: Catmull-Rom, minimum 4 points @@ -2465,7 +2481,7 @@ Function 252: DrawSplineCatmullRom() (4 input parameters) Param[2]: pointCount (type: int) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 253: DrawSplineBezierQuadratic() (4 input parameters) +Function 255: DrawSplineBezierQuadratic() (4 input parameters) Name: DrawSplineBezierQuadratic Return type: void Description: Draw spline: Quadratic Bezier, minimum 3 points (1 control point): [p1, c2, p3, c4...] @@ -2473,7 +2489,7 @@ Function 253: DrawSplineBezierQuadratic() (4 input parameters) Param[2]: pointCount (type: int) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 254: DrawSplineBezierCubic() (4 input parameters) +Function 256: DrawSplineBezierCubic() (4 input parameters) Name: DrawSplineBezierCubic Return type: void Description: Draw spline: Cubic Bezier, minimum 4 points (2 control points): [p1, c2, c3, p4, c5, c6...] @@ -2481,7 +2497,7 @@ Function 254: DrawSplineBezierCubic() (4 input parameters) Param[2]: pointCount (type: int) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 255: DrawSplineSegmentLinear() (4 input parameters) +Function 257: DrawSplineSegmentLinear() (4 input parameters) Name: DrawSplineSegmentLinear Return type: void Description: Draw spline segment: Linear, 2 points @@ -2489,7 +2505,7 @@ Function 255: DrawSplineSegmentLinear() (4 input parameters) Param[2]: p2 (type: Vector2) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 256: DrawSplineSegmentBasis() (6 input parameters) +Function 258: DrawSplineSegmentBasis() (6 input parameters) Name: DrawSplineSegmentBasis Return type: void Description: Draw spline segment: B-Spline, 4 points @@ -2499,7 +2515,7 @@ Function 256: DrawSplineSegmentBasis() (6 input parameters) Param[4]: p4 (type: Vector2) Param[5]: thick (type: float) Param[6]: color (type: Color) -Function 257: DrawSplineSegmentCatmullRom() (6 input parameters) +Function 259: DrawSplineSegmentCatmullRom() (6 input parameters) Name: DrawSplineSegmentCatmullRom Return type: void Description: Draw spline segment: Catmull-Rom, 4 points @@ -2509,7 +2525,7 @@ Function 257: DrawSplineSegmentCatmullRom() (6 input parameters) Param[4]: p4 (type: Vector2) Param[5]: thick (type: float) Param[6]: color (type: Color) -Function 258: DrawSplineSegmentBezierQuadratic() (5 input parameters) +Function 260: DrawSplineSegmentBezierQuadratic() (5 input parameters) Name: DrawSplineSegmentBezierQuadratic Return type: void Description: Draw spline segment: Quadratic Bezier, 2 points, 1 control point @@ -2518,7 +2534,7 @@ Function 258: DrawSplineSegmentBezierQuadratic() (5 input parameters) Param[3]: p3 (type: Vector2) Param[4]: thick (type: float) Param[5]: color (type: Color) -Function 259: DrawSplineSegmentBezierCubic() (6 input parameters) +Function 261: DrawSplineSegmentBezierCubic() (6 input parameters) Name: DrawSplineSegmentBezierCubic Return type: void Description: Draw spline segment: Cubic Bezier, 2 points, 2 control points @@ -2528,14 +2544,14 @@ Function 259: DrawSplineSegmentBezierCubic() (6 input parameters) Param[4]: p4 (type: Vector2) Param[5]: thick (type: float) Param[6]: color (type: Color) -Function 260: GetSplinePointLinear() (3 input parameters) +Function 262: GetSplinePointLinear() (3 input parameters) Name: GetSplinePointLinear Return type: Vector2 Description: Get (evaluate) spline point: Linear Param[1]: startPos (type: Vector2) Param[2]: endPos (type: Vector2) Param[3]: t (type: float) -Function 261: GetSplinePointBasis() (5 input parameters) +Function 263: GetSplinePointBasis() (5 input parameters) Name: GetSplinePointBasis Return type: Vector2 Description: Get (evaluate) spline point: B-Spline @@ -2544,7 +2560,7 @@ Function 261: GetSplinePointBasis() (5 input parameters) Param[3]: p3 (type: Vector2) Param[4]: p4 (type: Vector2) Param[5]: t (type: float) -Function 262: GetSplinePointCatmullRom() (5 input parameters) +Function 264: GetSplinePointCatmullRom() (5 input parameters) Name: GetSplinePointCatmullRom Return type: Vector2 Description: Get (evaluate) spline point: Catmull-Rom @@ -2553,7 +2569,7 @@ Function 262: GetSplinePointCatmullRom() (5 input parameters) Param[3]: p3 (type: Vector2) Param[4]: p4 (type: Vector2) Param[5]: t (type: float) -Function 263: GetSplinePointBezierQuad() (4 input parameters) +Function 265: GetSplinePointBezierQuad() (4 input parameters) Name: GetSplinePointBezierQuad Return type: Vector2 Description: Get (evaluate) spline point: Quadratic Bezier @@ -2561,7 +2577,7 @@ Function 263: GetSplinePointBezierQuad() (4 input parameters) Param[2]: c2 (type: Vector2) Param[3]: p3 (type: Vector2) Param[4]: t (type: float) -Function 264: GetSplinePointBezierCubic() (5 input parameters) +Function 266: GetSplinePointBezierCubic() (5 input parameters) Name: GetSplinePointBezierCubic Return type: Vector2 Description: Get (evaluate) spline point: Cubic Bezier @@ -2570,13 +2586,13 @@ Function 264: GetSplinePointBezierCubic() (5 input parameters) Param[3]: c3 (type: Vector2) Param[4]: p4 (type: Vector2) Param[5]: t (type: float) -Function 265: CheckCollisionRecs() (2 input parameters) +Function 267: CheckCollisionRecs() (2 input parameters) Name: CheckCollisionRecs Return type: bool Description: Check collision between two rectangles Param[1]: rec1 (type: Rectangle) Param[2]: rec2 (type: Rectangle) -Function 266: CheckCollisionCircles() (4 input parameters) +Function 268: CheckCollisionCircles() (4 input parameters) Name: CheckCollisionCircles Return type: bool Description: Check collision between two circles @@ -2584,14 +2600,14 @@ Function 266: CheckCollisionCircles() (4 input parameters) Param[2]: radius1 (type: float) Param[3]: center2 (type: Vector2) Param[4]: radius2 (type: float) -Function 267: CheckCollisionCircleRec() (3 input parameters) +Function 269: CheckCollisionCircleRec() (3 input parameters) Name: CheckCollisionCircleRec Return type: bool Description: Check collision between circle and rectangle Param[1]: center (type: Vector2) Param[2]: radius (type: float) Param[3]: rec (type: Rectangle) -Function 268: CheckCollisionCircleLine() (4 input parameters) +Function 270: CheckCollisionCircleLine() (4 input parameters) Name: CheckCollisionCircleLine Return type: bool Description: Check if circle collides with a line created betweeen two points [p1] and [p2] @@ -2599,20 +2615,20 @@ Function 268: CheckCollisionCircleLine() (4 input parameters) Param[2]: radius (type: float) Param[3]: p1 (type: Vector2) Param[4]: p2 (type: Vector2) -Function 269: CheckCollisionPointRec() (2 input parameters) +Function 271: CheckCollisionPointRec() (2 input parameters) Name: CheckCollisionPointRec Return type: bool Description: Check if point is inside rectangle Param[1]: point (type: Vector2) Param[2]: rec (type: Rectangle) -Function 270: CheckCollisionPointCircle() (3 input parameters) +Function 272: CheckCollisionPointCircle() (3 input parameters) Name: CheckCollisionPointCircle Return type: bool Description: Check if point is inside circle Param[1]: point (type: Vector2) Param[2]: center (type: Vector2) Param[3]: radius (type: float) -Function 271: CheckCollisionPointTriangle() (4 input parameters) +Function 273: CheckCollisionPointTriangle() (4 input parameters) Name: CheckCollisionPointTriangle Return type: bool Description: Check if point is inside a triangle @@ -2620,7 +2636,7 @@ Function 271: CheckCollisionPointTriangle() (4 input parameters) Param[2]: p1 (type: Vector2) Param[3]: p2 (type: Vector2) Param[4]: p3 (type: Vector2) -Function 272: CheckCollisionPointLine() (4 input parameters) +Function 274: CheckCollisionPointLine() (4 input parameters) Name: CheckCollisionPointLine Return type: bool Description: Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] @@ -2628,14 +2644,14 @@ Function 272: CheckCollisionPointLine() (4 input parameters) Param[2]: p1 (type: Vector2) Param[3]: p2 (type: Vector2) Param[4]: threshold (type: int) -Function 273: CheckCollisionPointPoly() (3 input parameters) +Function 275: CheckCollisionPointPoly() (3 input parameters) Name: CheckCollisionPointPoly Return type: bool Description: Check if point is within a polygon described by array of vertices Param[1]: point (type: Vector2) Param[2]: points (type: const Vector2 *) Param[3]: pointCount (type: int) -Function 274: CheckCollisionLines() (5 input parameters) +Function 276: CheckCollisionLines() (5 input parameters) Name: CheckCollisionLines Return type: bool Description: Check the collision between two lines defined by two points each, returns collision point by reference @@ -2644,18 +2660,18 @@ Function 274: CheckCollisionLines() (5 input parameters) Param[3]: startPos2 (type: Vector2) Param[4]: endPos2 (type: Vector2) Param[5]: collisionPoint (type: Vector2 *) -Function 275: GetCollisionRec() (2 input parameters) +Function 277: GetCollisionRec() (2 input parameters) Name: GetCollisionRec Return type: Rectangle Description: Get collision rectangle for two rectangles collision Param[1]: rec1 (type: Rectangle) Param[2]: rec2 (type: Rectangle) -Function 276: LoadImage() (1 input parameters) +Function 278: LoadImage() (1 input parameters) Name: LoadImage Return type: Image Description: Load image from file into CPU memory (RAM) Param[1]: fileName (type: const char *) -Function 277: LoadImageRaw() (5 input parameters) +Function 279: LoadImageRaw() (5 input parameters) Name: LoadImageRaw Return type: Image Description: Load image from RAW file data @@ -2664,13 +2680,13 @@ Function 277: LoadImageRaw() (5 input parameters) Param[3]: height (type: int) Param[4]: format (type: int) Param[5]: headerSize (type: int) -Function 278: LoadImageAnim() (2 input parameters) +Function 280: LoadImageAnim() (2 input parameters) Name: LoadImageAnim Return type: Image Description: Load image sequence from file (frames appended to image.data) Param[1]: fileName (type: const char *) Param[2]: frames (type: int *) -Function 279: LoadImageAnimFromMemory() (4 input parameters) +Function 281: LoadImageAnimFromMemory() (4 input parameters) Name: LoadImageAnimFromMemory Return type: Image Description: Load image sequence from memory buffer @@ -2678,60 +2694,60 @@ Function 279: LoadImageAnimFromMemory() (4 input parameters) Param[2]: fileData (type: const unsigned char *) Param[3]: dataSize (type: int) Param[4]: frames (type: int *) -Function 280: LoadImageFromMemory() (3 input parameters) +Function 282: LoadImageFromMemory() (3 input parameters) Name: LoadImageFromMemory Return type: Image Description: Load image from memory buffer, fileType refers to extension: i.e. '.png' Param[1]: fileType (type: const char *) Param[2]: fileData (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 281: LoadImageFromTexture() (1 input parameters) +Function 283: LoadImageFromTexture() (1 input parameters) Name: LoadImageFromTexture Return type: Image Description: Load image from GPU texture data Param[1]: texture (type: Texture2D) -Function 282: LoadImageFromScreen() (0 input parameters) +Function 284: LoadImageFromScreen() (0 input parameters) Name: LoadImageFromScreen Return type: Image Description: Load image from screen buffer and (screenshot) No input parameters -Function 283: IsImageValid() (1 input parameters) +Function 285: IsImageValid() (1 input parameters) Name: IsImageValid Return type: bool Description: Check if an image is valid (data and parameters) Param[1]: image (type: Image) -Function 284: UnloadImage() (1 input parameters) +Function 286: UnloadImage() (1 input parameters) Name: UnloadImage Return type: void Description: Unload image from CPU memory (RAM) Param[1]: image (type: Image) -Function 285: ExportImage() (2 input parameters) +Function 287: ExportImage() (2 input parameters) Name: ExportImage Return type: bool Description: Export image data to file, returns true on success Param[1]: image (type: Image) Param[2]: fileName (type: const char *) -Function 286: ExportImageToMemory() (3 input parameters) +Function 288: ExportImageToMemory() (3 input parameters) Name: ExportImageToMemory Return type: unsigned char * Description: Export image to memory buffer Param[1]: image (type: Image) Param[2]: fileType (type: const char *) Param[3]: fileSize (type: int *) -Function 287: ExportImageAsCode() (2 input parameters) +Function 289: ExportImageAsCode() (2 input parameters) Name: ExportImageAsCode Return type: bool Description: Export image as code file defining an array of bytes, returns true on success Param[1]: image (type: Image) Param[2]: fileName (type: const char *) -Function 288: GenImageColor() (3 input parameters) +Function 290: GenImageColor() (3 input parameters) Name: GenImageColor Return type: Image Description: Generate image: plain color Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: color (type: Color) -Function 289: GenImageGradientLinear() (5 input parameters) +Function 291: GenImageGradientLinear() (5 input parameters) Name: GenImageGradientLinear Return type: Image Description: Generate image: linear gradient, direction in degrees [0..360], 0=Vertical gradient @@ -2740,7 +2756,7 @@ Function 289: GenImageGradientLinear() (5 input parameters) Param[3]: direction (type: int) Param[4]: start (type: Color) Param[5]: end (type: Color) -Function 290: GenImageGradientRadial() (5 input parameters) +Function 292: GenImageGradientRadial() (5 input parameters) Name: GenImageGradientRadial Return type: Image Description: Generate image: radial gradient @@ -2749,7 +2765,7 @@ Function 290: GenImageGradientRadial() (5 input parameters) Param[3]: density (type: float) Param[4]: inner (type: Color) Param[5]: outer (type: Color) -Function 291: GenImageGradientSquare() (5 input parameters) +Function 293: GenImageGradientSquare() (5 input parameters) Name: GenImageGradientSquare Return type: Image Description: Generate image: square gradient @@ -2758,7 +2774,7 @@ Function 291: GenImageGradientSquare() (5 input parameters) Param[3]: density (type: float) Param[4]: inner (type: Color) Param[5]: outer (type: Color) -Function 292: GenImageChecked() (6 input parameters) +Function 294: GenImageChecked() (6 input parameters) Name: GenImageChecked Return type: Image Description: Generate image: checked @@ -2768,14 +2784,14 @@ Function 292: GenImageChecked() (6 input parameters) Param[4]: checksY (type: int) Param[5]: col1 (type: Color) Param[6]: col2 (type: Color) -Function 293: GenImageWhiteNoise() (3 input parameters) +Function 295: GenImageWhiteNoise() (3 input parameters) Name: GenImageWhiteNoise Return type: Image Description: Generate image: white noise Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: factor (type: float) -Function 294: GenImagePerlinNoise() (5 input parameters) +Function 296: GenImagePerlinNoise() (5 input parameters) Name: GenImagePerlinNoise Return type: Image Description: Generate image: perlin noise @@ -2784,45 +2800,45 @@ Function 294: GenImagePerlinNoise() (5 input parameters) Param[3]: offsetX (type: int) Param[4]: offsetY (type: int) Param[5]: scale (type: float) -Function 295: GenImageCellular() (3 input parameters) +Function 297: GenImageCellular() (3 input parameters) Name: GenImageCellular Return type: Image Description: Generate image: cellular algorithm, bigger tileSize means bigger cells Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: tileSize (type: int) -Function 296: GenImageText() (3 input parameters) +Function 298: GenImageText() (3 input parameters) Name: GenImageText Return type: Image Description: Generate image: grayscale image from text data Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: text (type: const char *) -Function 297: ImageCopy() (1 input parameters) +Function 299: ImageCopy() (1 input parameters) Name: ImageCopy Return type: Image Description: Create an image duplicate (useful for transformations) Param[1]: image (type: Image) -Function 298: ImageFromImage() (2 input parameters) +Function 300: ImageFromImage() (2 input parameters) Name: ImageFromImage Return type: Image Description: Create an image from another image piece Param[1]: image (type: Image) Param[2]: rec (type: Rectangle) -Function 299: ImageFromChannel() (2 input parameters) +Function 301: ImageFromChannel() (2 input parameters) Name: ImageFromChannel Return type: Image Description: Create an image from a selected channel of another image (GRAYSCALE) Param[1]: image (type: Image) Param[2]: selectedChannel (type: int) -Function 300: ImageText() (3 input parameters) +Function 302: ImageText() (3 input parameters) Name: ImageText Return type: Image Description: Create an image from text (default font) Param[1]: text (type: const char *) Param[2]: fontSize (type: int) Param[3]: color (type: Color) -Function 301: ImageTextEx() (5 input parameters) +Function 303: ImageTextEx() (5 input parameters) Name: ImageTextEx Return type: Image Description: Create an image from text (custom sprite font) @@ -2831,76 +2847,76 @@ Function 301: ImageTextEx() (5 input parameters) Param[3]: fontSize (type: float) Param[4]: spacing (type: float) Param[5]: tint (type: Color) -Function 302: ImageFormat() (2 input parameters) +Function 304: ImageFormat() (2 input parameters) Name: ImageFormat Return type: void Description: Convert image data to desired format Param[1]: image (type: Image *) Param[2]: newFormat (type: int) -Function 303: ImageToPOT() (2 input parameters) +Function 305: ImageToPOT() (2 input parameters) Name: ImageToPOT Return type: void Description: Convert image to POT (power-of-two) Param[1]: image (type: Image *) Param[2]: fill (type: Color) -Function 304: ImageCrop() (2 input parameters) +Function 306: ImageCrop() (2 input parameters) Name: ImageCrop Return type: void Description: Crop an image to a defined rectangle Param[1]: image (type: Image *) Param[2]: crop (type: Rectangle) -Function 305: ImageAlphaCrop() (2 input parameters) +Function 307: ImageAlphaCrop() (2 input parameters) Name: ImageAlphaCrop Return type: void Description: Crop image depending on alpha value Param[1]: image (type: Image *) Param[2]: threshold (type: float) -Function 306: ImageAlphaClear() (3 input parameters) +Function 308: ImageAlphaClear() (3 input parameters) Name: ImageAlphaClear Return type: void Description: Clear alpha channel to desired color Param[1]: image (type: Image *) Param[2]: color (type: Color) Param[3]: threshold (type: float) -Function 307: ImageAlphaMask() (2 input parameters) +Function 309: ImageAlphaMask() (2 input parameters) Name: ImageAlphaMask Return type: void Description: Apply alpha mask to image Param[1]: image (type: Image *) Param[2]: alphaMask (type: Image) -Function 308: ImageAlphaPremultiply() (1 input parameters) +Function 310: ImageAlphaPremultiply() (1 input parameters) Name: ImageAlphaPremultiply Return type: void Description: Premultiply alpha channel Param[1]: image (type: Image *) -Function 309: ImageBlurGaussian() (2 input parameters) +Function 311: ImageBlurGaussian() (2 input parameters) Name: ImageBlurGaussian Return type: void Description: Apply Gaussian blur using a box blur approximation Param[1]: image (type: Image *) Param[2]: blurSize (type: int) -Function 310: ImageKernelConvolution() (3 input parameters) +Function 312: ImageKernelConvolution() (3 input parameters) Name: ImageKernelConvolution Return type: void Description: Apply custom square convolution kernel to image Param[1]: image (type: Image *) Param[2]: kernel (type: const float *) Param[3]: kernelSize (type: int) -Function 311: ImageResize() (3 input parameters) +Function 313: ImageResize() (3 input parameters) Name: ImageResize Return type: void Description: Resize image (Bicubic scaling algorithm) Param[1]: image (type: Image *) Param[2]: newWidth (type: int) Param[3]: newHeight (type: int) -Function 312: ImageResizeNN() (3 input parameters) +Function 314: ImageResizeNN() (3 input parameters) Name: ImageResizeNN Return type: void Description: Resize image (Nearest-Neighbor scaling algorithm) Param[1]: image (type: Image *) Param[2]: newWidth (type: int) Param[3]: newHeight (type: int) -Function 313: ImageResizeCanvas() (6 input parameters) +Function 315: ImageResizeCanvas() (6 input parameters) Name: ImageResizeCanvas Return type: void Description: Resize canvas and fill with color @@ -2910,12 +2926,12 @@ Function 313: ImageResizeCanvas() (6 input parameters) Param[4]: offsetX (type: int) Param[5]: offsetY (type: int) Param[6]: fill (type: Color) -Function 314: ImageMipmaps() (1 input parameters) +Function 316: ImageMipmaps() (1 input parameters) Name: ImageMipmaps Return type: void Description: Compute all mipmap levels for a provided image Param[1]: image (type: Image *) -Function 315: ImageDither() (5 input parameters) +Function 317: ImageDither() (5 input parameters) Name: ImageDither Return type: void Description: Dither image data to 16bpp or lower (Floyd-Steinberg dithering) @@ -2924,109 +2940,109 @@ Function 315: ImageDither() (5 input parameters) Param[3]: gBpp (type: int) Param[4]: bBpp (type: int) Param[5]: aBpp (type: int) -Function 316: ImageFlipVertical() (1 input parameters) +Function 318: ImageFlipVertical() (1 input parameters) Name: ImageFlipVertical Return type: void Description: Flip image vertically Param[1]: image (type: Image *) -Function 317: ImageFlipHorizontal() (1 input parameters) +Function 319: ImageFlipHorizontal() (1 input parameters) Name: ImageFlipHorizontal Return type: void Description: Flip image horizontally Param[1]: image (type: Image *) -Function 318: ImageRotate() (2 input parameters) +Function 320: ImageRotate() (2 input parameters) Name: ImageRotate Return type: void Description: Rotate image by input angle in degrees (-359 to 359) Param[1]: image (type: Image *) Param[2]: degrees (type: int) -Function 319: ImageRotateCW() (1 input parameters) +Function 321: ImageRotateCW() (1 input parameters) Name: ImageRotateCW Return type: void Description: Rotate image clockwise 90deg Param[1]: image (type: Image *) -Function 320: ImageRotateCCW() (1 input parameters) +Function 322: ImageRotateCCW() (1 input parameters) Name: ImageRotateCCW Return type: void Description: Rotate image counter-clockwise 90deg Param[1]: image (type: Image *) -Function 321: ImageColorTint() (2 input parameters) +Function 323: ImageColorTint() (2 input parameters) Name: ImageColorTint Return type: void Description: Modify image color: tint Param[1]: image (type: Image *) Param[2]: color (type: Color) -Function 322: ImageColorInvert() (1 input parameters) +Function 324: ImageColorInvert() (1 input parameters) Name: ImageColorInvert Return type: void Description: Modify image color: invert Param[1]: image (type: Image *) -Function 323: ImageColorGrayscale() (1 input parameters) +Function 325: ImageColorGrayscale() (1 input parameters) Name: ImageColorGrayscale Return type: void Description: Modify image color: grayscale Param[1]: image (type: Image *) -Function 324: ImageColorContrast() (2 input parameters) +Function 326: ImageColorContrast() (2 input parameters) Name: ImageColorContrast Return type: void Description: Modify image color: contrast (-100 to 100) Param[1]: image (type: Image *) Param[2]: contrast (type: float) -Function 325: ImageColorBrightness() (2 input parameters) +Function 327: ImageColorBrightness() (2 input parameters) Name: ImageColorBrightness Return type: void Description: Modify image color: brightness (-255 to 255) Param[1]: image (type: Image *) Param[2]: brightness (type: int) -Function 326: ImageColorReplace() (3 input parameters) +Function 328: ImageColorReplace() (3 input parameters) Name: ImageColorReplace Return type: void Description: Modify image color: replace color Param[1]: image (type: Image *) Param[2]: color (type: Color) Param[3]: replace (type: Color) -Function 327: LoadImageColors() (1 input parameters) +Function 329: LoadImageColors() (1 input parameters) Name: LoadImageColors Return type: Color * Description: Load color data from image as a Color array (RGBA - 32bit) Param[1]: image (type: Image) -Function 328: LoadImagePalette() (3 input parameters) +Function 330: LoadImagePalette() (3 input parameters) Name: LoadImagePalette Return type: Color * Description: Load colors palette from image as a Color array (RGBA - 32bit) Param[1]: image (type: Image) Param[2]: maxPaletteSize (type: int) Param[3]: colorCount (type: int *) -Function 329: UnloadImageColors() (1 input parameters) +Function 331: UnloadImageColors() (1 input parameters) Name: UnloadImageColors Return type: void Description: Unload color data loaded with LoadImageColors() Param[1]: colors (type: Color *) -Function 330: UnloadImagePalette() (1 input parameters) +Function 332: UnloadImagePalette() (1 input parameters) Name: UnloadImagePalette Return type: void Description: Unload colors palette loaded with LoadImagePalette() Param[1]: colors (type: Color *) -Function 331: GetImageAlphaBorder() (2 input parameters) +Function 333: GetImageAlphaBorder() (2 input parameters) Name: GetImageAlphaBorder Return type: Rectangle Description: Get image alpha border rectangle Param[1]: image (type: Image) Param[2]: threshold (type: float) -Function 332: GetImageColor() (3 input parameters) +Function 334: GetImageColor() (3 input parameters) Name: GetImageColor Return type: Color Description: Get image pixel color at (x, y) position Param[1]: image (type: Image) Param[2]: x (type: int) Param[3]: y (type: int) -Function 333: ImageClearBackground() (2 input parameters) +Function 335: ImageClearBackground() (2 input parameters) Name: ImageClearBackground Return type: void Description: Clear image background with given color Param[1]: dst (type: Image *) Param[2]: color (type: Color) -Function 334: ImageDrawPixel() (4 input parameters) +Function 336: ImageDrawPixel() (4 input parameters) Name: ImageDrawPixel Return type: void Description: Draw pixel within an image @@ -3034,14 +3050,14 @@ Function 334: ImageDrawPixel() (4 input parameters) Param[2]: posX (type: int) Param[3]: posY (type: int) Param[4]: color (type: Color) -Function 335: ImageDrawPixelV() (3 input parameters) +Function 337: ImageDrawPixelV() (3 input parameters) Name: ImageDrawPixelV Return type: void Description: Draw pixel within an image (Vector version) Param[1]: dst (type: Image *) Param[2]: position (type: Vector2) Param[3]: color (type: Color) -Function 336: ImageDrawLine() (6 input parameters) +Function 338: ImageDrawLine() (6 input parameters) Name: ImageDrawLine Return type: void Description: Draw line within an image @@ -3051,7 +3067,7 @@ Function 336: ImageDrawLine() (6 input parameters) Param[4]: endPosX (type: int) Param[5]: endPosY (type: int) Param[6]: color (type: Color) -Function 337: ImageDrawLineV() (4 input parameters) +Function 339: ImageDrawLineV() (4 input parameters) Name: ImageDrawLineV Return type: void Description: Draw line within an image (Vector version) @@ -3059,7 +3075,7 @@ Function 337: ImageDrawLineV() (4 input parameters) Param[2]: start (type: Vector2) Param[3]: end (type: Vector2) Param[4]: color (type: Color) -Function 338: ImageDrawLineEx() (5 input parameters) +Function 340: ImageDrawLineEx() (5 input parameters) Name: ImageDrawLineEx Return type: void Description: Draw a line defining thickness within an image @@ -3068,7 +3084,7 @@ Function 338: ImageDrawLineEx() (5 input parameters) Param[3]: end (type: Vector2) Param[4]: thick (type: int) Param[5]: color (type: Color) -Function 339: ImageDrawCircle() (5 input parameters) +Function 341: ImageDrawCircle() (5 input parameters) Name: ImageDrawCircle Return type: void Description: Draw a filled circle within an image @@ -3077,7 +3093,7 @@ Function 339: ImageDrawCircle() (5 input parameters) Param[3]: centerY (type: int) Param[4]: radius (type: int) Param[5]: color (type: Color) -Function 340: ImageDrawCircleV() (4 input parameters) +Function 342: ImageDrawCircleV() (4 input parameters) Name: ImageDrawCircleV Return type: void Description: Draw a filled circle within an image (Vector version) @@ -3085,7 +3101,7 @@ Function 340: ImageDrawCircleV() (4 input parameters) Param[2]: center (type: Vector2) Param[3]: radius (type: int) Param[4]: color (type: Color) -Function 341: ImageDrawCircleLines() (5 input parameters) +Function 343: ImageDrawCircleLines() (5 input parameters) Name: ImageDrawCircleLines Return type: void Description: Draw circle outline within an image @@ -3094,7 +3110,7 @@ Function 341: ImageDrawCircleLines() (5 input parameters) Param[3]: centerY (type: int) Param[4]: radius (type: int) Param[5]: color (type: Color) -Function 342: ImageDrawCircleLinesV() (4 input parameters) +Function 344: ImageDrawCircleLinesV() (4 input parameters) Name: ImageDrawCircleLinesV Return type: void Description: Draw circle outline within an image (Vector version) @@ -3102,7 +3118,7 @@ Function 342: ImageDrawCircleLinesV() (4 input parameters) Param[2]: center (type: Vector2) Param[3]: radius (type: int) Param[4]: color (type: Color) -Function 343: ImageDrawRectangle() (6 input parameters) +Function 345: ImageDrawRectangle() (6 input parameters) Name: ImageDrawRectangle Return type: void Description: Draw rectangle within an image @@ -3112,7 +3128,7 @@ Function 343: ImageDrawRectangle() (6 input parameters) Param[4]: width (type: int) Param[5]: height (type: int) Param[6]: color (type: Color) -Function 344: ImageDrawRectangleV() (4 input parameters) +Function 346: ImageDrawRectangleV() (4 input parameters) Name: ImageDrawRectangleV Return type: void Description: Draw rectangle within an image (Vector version) @@ -3120,14 +3136,14 @@ Function 344: ImageDrawRectangleV() (4 input parameters) Param[2]: position (type: Vector2) Param[3]: size (type: Vector2) Param[4]: color (type: Color) -Function 345: ImageDrawRectangleRec() (3 input parameters) +Function 347: ImageDrawRectangleRec() (3 input parameters) Name: ImageDrawRectangleRec Return type: void Description: Draw rectangle within an image Param[1]: dst (type: Image *) Param[2]: rec (type: Rectangle) Param[3]: color (type: Color) -Function 346: ImageDrawRectangleLines() (4 input parameters) +Function 348: ImageDrawRectangleLines() (4 input parameters) Name: ImageDrawRectangleLines Return type: void Description: Draw rectangle lines within an image @@ -3135,7 +3151,7 @@ Function 346: ImageDrawRectangleLines() (4 input parameters) Param[2]: rec (type: Rectangle) Param[3]: thick (type: int) Param[4]: color (type: Color) -Function 347: ImageDrawTriangle() (5 input parameters) +Function 349: ImageDrawTriangle() (5 input parameters) Name: ImageDrawTriangle Return type: void Description: Draw triangle within an image @@ -3144,7 +3160,7 @@ Function 347: ImageDrawTriangle() (5 input parameters) Param[3]: v2 (type: Vector2) Param[4]: v3 (type: Vector2) Param[5]: color (type: Color) -Function 348: ImageDrawTriangleEx() (7 input parameters) +Function 350: ImageDrawTriangleEx() (7 input parameters) Name: ImageDrawTriangleEx Return type: void Description: Draw triangle with interpolated colors within an image @@ -3155,7 +3171,7 @@ Function 348: ImageDrawTriangleEx() (7 input parameters) Param[5]: c1 (type: Color) Param[6]: c2 (type: Color) Param[7]: c3 (type: Color) -Function 349: ImageDrawTriangleLines() (5 input parameters) +Function 351: ImageDrawTriangleLines() (5 input parameters) Name: ImageDrawTriangleLines Return type: void Description: Draw triangle outline within an image @@ -3164,7 +3180,7 @@ Function 349: ImageDrawTriangleLines() (5 input parameters) Param[3]: v2 (type: Vector2) Param[4]: v3 (type: Vector2) Param[5]: color (type: Color) -Function 350: ImageDrawTriangleFan() (4 input parameters) +Function 352: ImageDrawTriangleFan() (4 input parameters) Name: ImageDrawTriangleFan Return type: void Description: Draw a triangle fan defined by points within an image (first vertex is the center) @@ -3172,7 +3188,7 @@ Function 350: ImageDrawTriangleFan() (4 input parameters) Param[2]: points (type: Vector2 *) Param[3]: pointCount (type: int) Param[4]: color (type: Color) -Function 351: ImageDrawTriangleStrip() (4 input parameters) +Function 353: ImageDrawTriangleStrip() (4 input parameters) Name: ImageDrawTriangleStrip Return type: void Description: Draw a triangle strip defined by points within an image @@ -3180,7 +3196,7 @@ Function 351: ImageDrawTriangleStrip() (4 input parameters) Param[2]: points (type: Vector2 *) Param[3]: pointCount (type: int) Param[4]: color (type: Color) -Function 352: ImageDraw() (5 input parameters) +Function 354: ImageDraw() (5 input parameters) Name: ImageDraw Return type: void Description: Draw a source image within a destination image (tint applied to source) @@ -3189,7 +3205,7 @@ Function 352: ImageDraw() (5 input parameters) Param[3]: srcRec (type: Rectangle) Param[4]: dstRec (type: Rectangle) Param[5]: tint (type: Color) -Function 353: ImageDrawText() (6 input parameters) +Function 355: ImageDrawText() (6 input parameters) Name: ImageDrawText Return type: void Description: Draw text (using default font) within an image (destination) @@ -3199,7 +3215,7 @@ Function 353: ImageDrawText() (6 input parameters) Param[4]: posY (type: int) Param[5]: fontSize (type: int) Param[6]: color (type: Color) -Function 354: ImageDrawTextEx() (7 input parameters) +Function 356: ImageDrawTextEx() (7 input parameters) Name: ImageDrawTextEx Return type: void Description: Draw text (custom sprite font) within an image (destination) @@ -3210,79 +3226,79 @@ Function 354: ImageDrawTextEx() (7 input parameters) Param[5]: fontSize (type: float) Param[6]: spacing (type: float) Param[7]: tint (type: Color) -Function 355: LoadTexture() (1 input parameters) +Function 357: LoadTexture() (1 input parameters) Name: LoadTexture Return type: Texture2D Description: Load texture from file into GPU memory (VRAM) Param[1]: fileName (type: const char *) -Function 356: LoadTextureFromImage() (1 input parameters) +Function 358: LoadTextureFromImage() (1 input parameters) Name: LoadTextureFromImage Return type: Texture2D Description: Load texture from image data Param[1]: image (type: Image) -Function 357: LoadTextureCubemap() (2 input parameters) +Function 359: LoadTextureCubemap() (2 input parameters) Name: LoadTextureCubemap Return type: TextureCubemap Description: Load cubemap from image, multiple image cubemap layouts supported Param[1]: image (type: Image) Param[2]: layout (type: int) -Function 358: LoadRenderTexture() (2 input parameters) +Function 360: LoadRenderTexture() (2 input parameters) Name: LoadRenderTexture Return type: RenderTexture2D Description: Load texture for rendering (framebuffer) Param[1]: width (type: int) Param[2]: height (type: int) -Function 359: IsTextureValid() (1 input parameters) +Function 361: IsTextureValid() (1 input parameters) Name: IsTextureValid Return type: bool Description: Check if a texture is valid (loaded in GPU) Param[1]: texture (type: Texture2D) -Function 360: UnloadTexture() (1 input parameters) +Function 362: UnloadTexture() (1 input parameters) Name: UnloadTexture Return type: void Description: Unload texture from GPU memory (VRAM) Param[1]: texture (type: Texture2D) -Function 361: IsRenderTextureValid() (1 input parameters) +Function 363: IsRenderTextureValid() (1 input parameters) Name: IsRenderTextureValid Return type: bool Description: Check if a render texture is valid (loaded in GPU) Param[1]: target (type: RenderTexture2D) -Function 362: UnloadRenderTexture() (1 input parameters) +Function 364: UnloadRenderTexture() (1 input parameters) Name: UnloadRenderTexture Return type: void Description: Unload render texture from GPU memory (VRAM) Param[1]: target (type: RenderTexture2D) -Function 363: UpdateTexture() (2 input parameters) +Function 365: UpdateTexture() (2 input parameters) Name: UpdateTexture Return type: void Description: Update GPU texture with new data Param[1]: texture (type: Texture2D) Param[2]: pixels (type: const void *) -Function 364: UpdateTextureRec() (3 input parameters) +Function 366: UpdateTextureRec() (3 input parameters) Name: UpdateTextureRec Return type: void Description: Update GPU texture rectangle with new data Param[1]: texture (type: Texture2D) Param[2]: rec (type: Rectangle) Param[3]: pixels (type: const void *) -Function 365: GenTextureMipmaps() (1 input parameters) +Function 367: GenTextureMipmaps() (1 input parameters) Name: GenTextureMipmaps Return type: void Description: Generate GPU mipmaps for a texture Param[1]: texture (type: Texture2D *) -Function 366: SetTextureFilter() (2 input parameters) +Function 368: SetTextureFilter() (2 input parameters) Name: SetTextureFilter Return type: void Description: Set texture scaling filter mode Param[1]: texture (type: Texture2D) Param[2]: filter (type: int) -Function 367: SetTextureWrap() (2 input parameters) +Function 369: SetTextureWrap() (2 input parameters) Name: SetTextureWrap Return type: void Description: Set texture wrapping mode Param[1]: texture (type: Texture2D) Param[2]: wrap (type: int) -Function 368: DrawTexture() (4 input parameters) +Function 370: DrawTexture() (4 input parameters) Name: DrawTexture Return type: void Description: Draw a Texture2D @@ -3290,14 +3306,14 @@ Function 368: DrawTexture() (4 input parameters) Param[2]: posX (type: int) Param[3]: posY (type: int) Param[4]: tint (type: Color) -Function 369: DrawTextureV() (3 input parameters) +Function 371: DrawTextureV() (3 input parameters) Name: DrawTextureV Return type: void Description: Draw a Texture2D with position defined as Vector2 Param[1]: texture (type: Texture2D) Param[2]: position (type: Vector2) Param[3]: tint (type: Color) -Function 370: DrawTextureEx() (5 input parameters) +Function 372: DrawTextureEx() (5 input parameters) Name: DrawTextureEx Return type: void Description: Draw a Texture2D with extended parameters @@ -3306,7 +3322,7 @@ Function 370: DrawTextureEx() (5 input parameters) Param[3]: rotation (type: float) Param[4]: scale (type: float) Param[5]: tint (type: Color) -Function 371: DrawTextureRec() (4 input parameters) +Function 373: DrawTextureRec() (4 input parameters) Name: DrawTextureRec Return type: void Description: Draw a part of a texture defined by a rectangle @@ -3314,7 +3330,7 @@ Function 371: DrawTextureRec() (4 input parameters) Param[2]: source (type: Rectangle) Param[3]: position (type: Vector2) Param[4]: tint (type: Color) -Function 372: DrawTexturePro() (6 input parameters) +Function 374: DrawTexturePro() (6 input parameters) Name: DrawTexturePro Return type: void Description: Draw a part of a texture defined by a rectangle with 'pro' parameters @@ -3324,7 +3340,7 @@ Function 372: DrawTexturePro() (6 input parameters) Param[4]: origin (type: Vector2) Param[5]: rotation (type: float) Param[6]: tint (type: Color) -Function 373: DrawTextureNPatch() (6 input parameters) +Function 375: DrawTextureNPatch() (6 input parameters) Name: DrawTextureNPatch Return type: void Description: Draws a texture (or part of it) that stretches or shrinks nicely @@ -3334,119 +3350,119 @@ Function 373: DrawTextureNPatch() (6 input parameters) Param[4]: origin (type: Vector2) Param[5]: rotation (type: float) Param[6]: tint (type: Color) -Function 374: ColorIsEqual() (2 input parameters) +Function 376: ColorIsEqual() (2 input parameters) Name: ColorIsEqual Return type: bool Description: Check if two colors are equal Param[1]: col1 (type: Color) Param[2]: col2 (type: Color) -Function 375: Fade() (2 input parameters) +Function 377: Fade() (2 input parameters) Name: Fade Return type: Color Description: Get color with alpha applied, alpha goes from 0.0f to 1.0f Param[1]: color (type: Color) Param[2]: alpha (type: float) -Function 376: ColorToInt() (1 input parameters) +Function 378: ColorToInt() (1 input parameters) Name: ColorToInt Return type: int Description: Get hexadecimal value for a Color (0xRRGGBBAA) Param[1]: color (type: Color) -Function 377: ColorNormalize() (1 input parameters) +Function 379: ColorNormalize() (1 input parameters) Name: ColorNormalize Return type: Vector4 Description: Get Color normalized as float [0..1] Param[1]: color (type: Color) -Function 378: ColorFromNormalized() (1 input parameters) +Function 380: ColorFromNormalized() (1 input parameters) Name: ColorFromNormalized Return type: Color Description: Get Color from normalized values [0..1] Param[1]: normalized (type: Vector4) -Function 379: ColorToHSV() (1 input parameters) +Function 381: ColorToHSV() (1 input parameters) Name: ColorToHSV Return type: Vector3 Description: Get HSV values for a Color, hue [0..360], saturation/value [0..1] Param[1]: color (type: Color) -Function 380: ColorFromHSV() (3 input parameters) +Function 382: ColorFromHSV() (3 input parameters) Name: ColorFromHSV Return type: Color Description: Get a Color from HSV values, hue [0..360], saturation/value [0..1] Param[1]: hue (type: float) Param[2]: saturation (type: float) Param[3]: value (type: float) -Function 381: ColorTint() (2 input parameters) +Function 383: ColorTint() (2 input parameters) Name: ColorTint Return type: Color Description: Get color multiplied with another color Param[1]: color (type: Color) Param[2]: tint (type: Color) -Function 382: ColorBrightness() (2 input parameters) +Function 384: ColorBrightness() (2 input parameters) Name: ColorBrightness Return type: Color Description: Get color with brightness correction, brightness factor goes from -1.0f to 1.0f Param[1]: color (type: Color) Param[2]: factor (type: float) -Function 383: ColorContrast() (2 input parameters) +Function 385: ColorContrast() (2 input parameters) Name: ColorContrast Return type: Color Description: Get color with contrast correction, contrast values between -1.0f and 1.0f Param[1]: color (type: Color) Param[2]: contrast (type: float) -Function 384: ColorAlpha() (2 input parameters) +Function 386: ColorAlpha() (2 input parameters) Name: ColorAlpha Return type: Color Description: Get color with alpha applied, alpha goes from 0.0f to 1.0f Param[1]: color (type: Color) Param[2]: alpha (type: float) -Function 385: ColorAlphaBlend() (3 input parameters) +Function 387: ColorAlphaBlend() (3 input parameters) Name: ColorAlphaBlend Return type: Color Description: Get src alpha-blended into dst color with tint Param[1]: dst (type: Color) Param[2]: src (type: Color) Param[3]: tint (type: Color) -Function 386: ColorLerp() (3 input parameters) +Function 388: ColorLerp() (3 input parameters) Name: ColorLerp Return type: Color Description: Get color lerp interpolation between two colors, factor [0.0f..1.0f] Param[1]: color1 (type: Color) Param[2]: color2 (type: Color) Param[3]: factor (type: float) -Function 387: GetColor() (1 input parameters) +Function 389: GetColor() (1 input parameters) Name: GetColor Return type: Color Description: Get Color structure from hexadecimal value Param[1]: hexValue (type: unsigned int) -Function 388: GetPixelColor() (2 input parameters) +Function 390: GetPixelColor() (2 input parameters) Name: GetPixelColor Return type: Color Description: Get Color from a source pixel pointer of certain format Param[1]: srcPtr (type: void *) Param[2]: format (type: int) -Function 389: SetPixelColor() (3 input parameters) +Function 391: SetPixelColor() (3 input parameters) Name: SetPixelColor Return type: void Description: Set color formatted into destination pixel pointer Param[1]: dstPtr (type: void *) Param[2]: color (type: Color) Param[3]: format (type: int) -Function 390: GetPixelDataSize() (3 input parameters) +Function 392: GetPixelDataSize() (3 input parameters) Name: GetPixelDataSize Return type: int Description: Get pixel data size in bytes for certain format Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: format (type: int) -Function 391: GetFontDefault() (0 input parameters) +Function 393: GetFontDefault() (0 input parameters) Name: GetFontDefault Return type: Font Description: Get the default Font No input parameters -Function 392: LoadFont() (1 input parameters) +Function 394: LoadFont() (1 input parameters) Name: LoadFont Return type: Font Description: Load font from file into GPU memory (VRAM) Param[1]: fileName (type: const char *) -Function 393: LoadFontEx() (4 input parameters) +Function 395: LoadFontEx() (4 input parameters) Name: LoadFontEx Return type: Font Description: Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height @@ -3454,14 +3470,14 @@ Function 393: LoadFontEx() (4 input parameters) Param[2]: fontSize (type: int) Param[3]: codepoints (type: int *) Param[4]: codepointCount (type: int) -Function 394: LoadFontFromImage() (3 input parameters) +Function 396: LoadFontFromImage() (3 input parameters) Name: LoadFontFromImage Return type: Font Description: Load font from Image (XNA style) Param[1]: image (type: Image) Param[2]: key (type: Color) Param[3]: firstChar (type: int) -Function 395: LoadFontFromMemory() (6 input parameters) +Function 397: LoadFontFromMemory() (6 input parameters) Name: LoadFontFromMemory Return type: Font Description: Load font from memory buffer, fileType refers to extension: i.e. '.ttf' @@ -3471,12 +3487,12 @@ Function 395: LoadFontFromMemory() (6 input parameters) Param[4]: fontSize (type: int) Param[5]: codepoints (type: int *) Param[6]: codepointCount (type: int) -Function 396: IsFontValid() (1 input parameters) +Function 398: IsFontValid() (1 input parameters) Name: IsFontValid Return type: bool Description: Check if a font is valid (font data loaded, WARNING: GPU texture not checked) Param[1]: font (type: Font) -Function 397: LoadFontData() (6 input parameters) +Function 399: LoadFontData() (6 input parameters) Name: LoadFontData Return type: GlyphInfo * Description: Load font data for further use @@ -3486,7 +3502,7 @@ Function 397: LoadFontData() (6 input parameters) Param[4]: codepoints (type: int *) Param[5]: codepointCount (type: int) Param[6]: type (type: int) -Function 398: GenImageFontAtlas() (6 input parameters) +Function 400: GenImageFontAtlas() (6 input parameters) Name: GenImageFontAtlas Return type: Image Description: Generate image font atlas using chars info @@ -3496,30 +3512,30 @@ Function 398: GenImageFontAtlas() (6 input parameters) Param[4]: fontSize (type: int) Param[5]: padding (type: int) Param[6]: packMethod (type: int) -Function 399: UnloadFontData() (2 input parameters) +Function 401: UnloadFontData() (2 input parameters) Name: UnloadFontData Return type: void Description: Unload font chars info data (RAM) Param[1]: glyphs (type: GlyphInfo *) Param[2]: glyphCount (type: int) -Function 400: UnloadFont() (1 input parameters) +Function 402: UnloadFont() (1 input parameters) Name: UnloadFont Return type: void Description: Unload font from GPU memory (VRAM) Param[1]: font (type: Font) -Function 401: ExportFontAsCode() (2 input parameters) +Function 403: ExportFontAsCode() (2 input parameters) Name: ExportFontAsCode Return type: bool Description: Export font as code file, returns true on success Param[1]: font (type: Font) Param[2]: fileName (type: const char *) -Function 402: DrawFPS() (2 input parameters) +Function 404: DrawFPS() (2 input parameters) Name: DrawFPS Return type: void Description: Draw current FPS Param[1]: posX (type: int) Param[2]: posY (type: int) -Function 403: DrawText() (5 input parameters) +Function 405: DrawText() (5 input parameters) Name: DrawText Return type: void Description: Draw text (using default font) @@ -3528,7 +3544,7 @@ Function 403: DrawText() (5 input parameters) Param[3]: posY (type: int) Param[4]: fontSize (type: int) Param[5]: color (type: Color) -Function 404: DrawTextEx() (6 input parameters) +Function 406: DrawTextEx() (6 input parameters) Name: DrawTextEx Return type: void Description: Draw text using font and additional parameters @@ -3538,7 +3554,7 @@ Function 404: DrawTextEx() (6 input parameters) Param[4]: fontSize (type: float) Param[5]: spacing (type: float) Param[6]: tint (type: Color) -Function 405: DrawTextPro() (8 input parameters) +Function 407: DrawTextPro() (8 input parameters) Name: DrawTextPro Return type: void Description: Draw text using Font and pro parameters (rotation) @@ -3550,7 +3566,7 @@ Function 405: DrawTextPro() (8 input parameters) Param[6]: fontSize (type: float) Param[7]: spacing (type: float) Param[8]: tint (type: Color) -Function 406: DrawTextCodepoint() (5 input parameters) +Function 408: DrawTextCodepoint() (5 input parameters) Name: DrawTextCodepoint Return type: void Description: Draw one character (codepoint) @@ -3559,7 +3575,7 @@ Function 406: DrawTextCodepoint() (5 input parameters) Param[3]: position (type: Vector2) Param[4]: fontSize (type: float) Param[5]: tint (type: Color) -Function 407: DrawTextCodepoints() (7 input parameters) +Function 409: DrawTextCodepoints() (7 input parameters) Name: DrawTextCodepoints Return type: void Description: Draw multiple character (codepoint) @@ -3570,18 +3586,18 @@ Function 407: DrawTextCodepoints() (7 input parameters) Param[5]: fontSize (type: float) Param[6]: spacing (type: float) Param[7]: tint (type: Color) -Function 408: SetTextLineSpacing() (1 input parameters) +Function 410: SetTextLineSpacing() (1 input parameters) Name: SetTextLineSpacing Return type: void Description: Set vertical line spacing when drawing with line-breaks Param[1]: spacing (type: int) -Function 409: MeasureText() (2 input parameters) +Function 411: MeasureText() (2 input parameters) Name: MeasureText Return type: int Description: Measure string width for default font Param[1]: text (type: const char *) Param[2]: fontSize (type: int) -Function 410: MeasureTextEx() (4 input parameters) +Function 412: MeasureTextEx() (4 input parameters) Name: MeasureTextEx Return type: Vector2 Description: Measure string size for Font @@ -3589,195 +3605,195 @@ Function 410: MeasureTextEx() (4 input parameters) Param[2]: text (type: const char *) Param[3]: fontSize (type: float) Param[4]: spacing (type: float) -Function 411: GetGlyphIndex() (2 input parameters) +Function 413: GetGlyphIndex() (2 input parameters) Name: GetGlyphIndex Return type: int Description: Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found Param[1]: font (type: Font) Param[2]: codepoint (type: int) -Function 412: GetGlyphInfo() (2 input parameters) +Function 414: GetGlyphInfo() (2 input parameters) Name: GetGlyphInfo Return type: GlyphInfo Description: Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found Param[1]: font (type: Font) Param[2]: codepoint (type: int) -Function 413: GetGlyphAtlasRec() (2 input parameters) +Function 415: GetGlyphAtlasRec() (2 input parameters) Name: GetGlyphAtlasRec Return type: Rectangle Description: Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found Param[1]: font (type: Font) Param[2]: codepoint (type: int) -Function 414: LoadUTF8() (2 input parameters) +Function 416: LoadUTF8() (2 input parameters) Name: LoadUTF8 Return type: char * Description: Load UTF-8 text encoded from codepoints array Param[1]: codepoints (type: const int *) Param[2]: length (type: int) -Function 415: UnloadUTF8() (1 input parameters) +Function 417: UnloadUTF8() (1 input parameters) Name: UnloadUTF8 Return type: void Description: Unload UTF-8 text encoded from codepoints array Param[1]: text (type: char *) -Function 416: LoadCodepoints() (2 input parameters) +Function 418: LoadCodepoints() (2 input parameters) Name: LoadCodepoints Return type: int * Description: Load all codepoints from a UTF-8 text string, codepoints count returned by parameter Param[1]: text (type: const char *) Param[2]: count (type: int *) -Function 417: UnloadCodepoints() (1 input parameters) +Function 419: UnloadCodepoints() (1 input parameters) Name: UnloadCodepoints Return type: void Description: Unload codepoints data from memory Param[1]: codepoints (type: int *) -Function 418: GetCodepointCount() (1 input parameters) +Function 420: GetCodepointCount() (1 input parameters) Name: GetCodepointCount Return type: int Description: Get total number of codepoints in a UTF-8 encoded string Param[1]: text (type: const char *) -Function 419: GetCodepoint() (2 input parameters) +Function 421: GetCodepoint() (2 input parameters) Name: GetCodepoint Return type: int Description: Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure Param[1]: text (type: const char *) Param[2]: codepointSize (type: int *) -Function 420: GetCodepointNext() (2 input parameters) +Function 422: GetCodepointNext() (2 input parameters) Name: GetCodepointNext Return type: int Description: Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure Param[1]: text (type: const char *) Param[2]: codepointSize (type: int *) -Function 421: GetCodepointPrevious() (2 input parameters) +Function 423: GetCodepointPrevious() (2 input parameters) Name: GetCodepointPrevious Return type: int Description: Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure Param[1]: text (type: const char *) Param[2]: codepointSize (type: int *) -Function 422: CodepointToUTF8() (2 input parameters) +Function 424: CodepointToUTF8() (2 input parameters) Name: CodepointToUTF8 Return type: const char * Description: Encode one codepoint into UTF-8 byte array (array length returned as parameter) Param[1]: codepoint (type: int) Param[2]: utf8Size (type: int *) -Function 423: TextCopy() (2 input parameters) +Function 425: TextCopy() (2 input parameters) Name: TextCopy Return type: int Description: Copy one string to another, returns bytes copied Param[1]: dst (type: char *) Param[2]: src (type: const char *) -Function 424: TextIsEqual() (2 input parameters) +Function 426: TextIsEqual() (2 input parameters) Name: TextIsEqual Return type: bool Description: Check if two text string are equal Param[1]: text1 (type: const char *) Param[2]: text2 (type: const char *) -Function 425: TextLength() (1 input parameters) +Function 427: TextLength() (1 input parameters) Name: TextLength Return type: unsigned int Description: Get text length, checks for '\0' ending Param[1]: text (type: const char *) -Function 426: TextFormat() (2 input parameters) +Function 428: TextFormat() (2 input parameters) Name: TextFormat Return type: const char * Description: Text formatting with variables (sprintf() style) Param[1]: text (type: const char *) Param[2]: args (type: ...) -Function 427: TextSubtext() (3 input parameters) +Function 429: TextSubtext() (3 input parameters) Name: TextSubtext Return type: const char * Description: Get a piece of a text string Param[1]: text (type: const char *) Param[2]: position (type: int) Param[3]: length (type: int) -Function 428: TextReplace() (3 input parameters) +Function 430: TextReplace() (3 input parameters) Name: TextReplace Return type: char * Description: Replace text string (WARNING: memory must be freed!) Param[1]: text (type: const char *) Param[2]: replace (type: const char *) Param[3]: by (type: const char *) -Function 429: TextInsert() (3 input parameters) +Function 431: TextInsert() (3 input parameters) Name: TextInsert Return type: char * Description: Insert text in a position (WARNING: memory must be freed!) Param[1]: text (type: const char *) Param[2]: insert (type: const char *) Param[3]: position (type: int) -Function 430: TextJoin() (3 input parameters) +Function 432: TextJoin() (3 input parameters) Name: TextJoin Return type: char * Description: Join text strings with delimiter Param[1]: textList (type: char **) Param[2]: count (type: int) Param[3]: delimiter (type: const char *) -Function 431: TextSplit() (3 input parameters) +Function 433: TextSplit() (3 input parameters) Name: TextSplit Return type: char ** Description: Split text into multiple strings Param[1]: text (type: const char *) Param[2]: delimiter (type: char) Param[3]: count (type: int *) -Function 432: TextAppend() (3 input parameters) +Function 434: TextAppend() (3 input parameters) Name: TextAppend Return type: void Description: Append text at specific position and move cursor! Param[1]: text (type: char *) Param[2]: append (type: const char *) Param[3]: position (type: int *) -Function 433: TextFindIndex() (2 input parameters) +Function 435: TextFindIndex() (2 input parameters) Name: TextFindIndex Return type: int Description: Find first text occurrence within a string Param[1]: text (type: const char *) Param[2]: find (type: const char *) -Function 434: TextToUpper() (1 input parameters) +Function 436: TextToUpper() (1 input parameters) Name: TextToUpper Return type: char * Description: Get upper case version of provided string Param[1]: text (type: const char *) -Function 435: TextToLower() (1 input parameters) +Function 437: TextToLower() (1 input parameters) Name: TextToLower Return type: char * Description: Get lower case version of provided string Param[1]: text (type: const char *) -Function 436: TextToPascal() (1 input parameters) +Function 438: TextToPascal() (1 input parameters) Name: TextToPascal Return type: char * Description: Get Pascal case notation version of provided string Param[1]: text (type: const char *) -Function 437: TextToSnake() (1 input parameters) +Function 439: TextToSnake() (1 input parameters) Name: TextToSnake Return type: char * Description: Get Snake case notation version of provided string Param[1]: text (type: const char *) -Function 438: TextToCamel() (1 input parameters) +Function 440: TextToCamel() (1 input parameters) Name: TextToCamel Return type: char * Description: Get Camel case notation version of provided string Param[1]: text (type: const char *) -Function 439: TextToInteger() (1 input parameters) +Function 441: TextToInteger() (1 input parameters) Name: TextToInteger Return type: int Description: Get integer value from text Param[1]: text (type: const char *) -Function 440: TextToFloat() (1 input parameters) +Function 442: TextToFloat() (1 input parameters) Name: TextToFloat Return type: float Description: Get float value from text Param[1]: text (type: const char *) -Function 441: DrawLine3D() (3 input parameters) +Function 443: DrawLine3D() (3 input parameters) Name: DrawLine3D Return type: void Description: Draw a line in 3D world space Param[1]: startPos (type: Vector3) Param[2]: endPos (type: Vector3) Param[3]: color (type: Color) -Function 442: DrawPoint3D() (2 input parameters) +Function 444: DrawPoint3D() (2 input parameters) Name: DrawPoint3D Return type: void Description: Draw a point in 3D space, actually a small line Param[1]: position (type: Vector3) Param[2]: color (type: Color) -Function 443: DrawCircle3D() (5 input parameters) +Function 445: DrawCircle3D() (5 input parameters) Name: DrawCircle3D Return type: void Description: Draw a circle in 3D world space @@ -3786,7 +3802,7 @@ Function 443: DrawCircle3D() (5 input parameters) Param[3]: rotationAxis (type: Vector3) Param[4]: rotationAngle (type: float) Param[5]: color (type: Color) -Function 444: DrawTriangle3D() (4 input parameters) +Function 446: DrawTriangle3D() (4 input parameters) Name: DrawTriangle3D Return type: void Description: Draw a color-filled triangle (vertex in counter-clockwise order!) @@ -3794,14 +3810,14 @@ Function 444: DrawTriangle3D() (4 input parameters) Param[2]: v2 (type: Vector3) Param[3]: v3 (type: Vector3) Param[4]: color (type: Color) -Function 445: DrawTriangleStrip3D() (3 input parameters) +Function 447: DrawTriangleStrip3D() (3 input parameters) Name: DrawTriangleStrip3D Return type: void Description: Draw a triangle strip defined by points Param[1]: points (type: const Vector3 *) Param[2]: pointCount (type: int) Param[3]: color (type: Color) -Function 446: DrawCube() (5 input parameters) +Function 448: DrawCube() (5 input parameters) Name: DrawCube Return type: void Description: Draw cube @@ -3810,14 +3826,14 @@ Function 446: DrawCube() (5 input parameters) Param[3]: height (type: float) Param[4]: length (type: float) Param[5]: color (type: Color) -Function 447: DrawCubeV() (3 input parameters) +Function 449: DrawCubeV() (3 input parameters) Name: DrawCubeV Return type: void Description: Draw cube (Vector version) Param[1]: position (type: Vector3) Param[2]: size (type: Vector3) Param[3]: color (type: Color) -Function 448: DrawCubeWires() (5 input parameters) +Function 450: DrawCubeWires() (5 input parameters) Name: DrawCubeWires Return type: void Description: Draw cube wires @@ -3826,21 +3842,21 @@ Function 448: DrawCubeWires() (5 input parameters) Param[3]: height (type: float) Param[4]: length (type: float) Param[5]: color (type: Color) -Function 449: DrawCubeWiresV() (3 input parameters) +Function 451: DrawCubeWiresV() (3 input parameters) Name: DrawCubeWiresV Return type: void Description: Draw cube wires (Vector version) Param[1]: position (type: Vector3) Param[2]: size (type: Vector3) Param[3]: color (type: Color) -Function 450: DrawSphere() (3 input parameters) +Function 452: DrawSphere() (3 input parameters) Name: DrawSphere Return type: void Description: Draw sphere Param[1]: centerPos (type: Vector3) Param[2]: radius (type: float) Param[3]: color (type: Color) -Function 451: DrawSphereEx() (5 input parameters) +Function 453: DrawSphereEx() (5 input parameters) Name: DrawSphereEx Return type: void Description: Draw sphere with extended parameters @@ -3849,7 +3865,7 @@ Function 451: DrawSphereEx() (5 input parameters) Param[3]: rings (type: int) Param[4]: slices (type: int) Param[5]: color (type: Color) -Function 452: DrawSphereWires() (5 input parameters) +Function 454: DrawSphereWires() (5 input parameters) Name: DrawSphereWires Return type: void Description: Draw sphere wires @@ -3858,7 +3874,7 @@ Function 452: DrawSphereWires() (5 input parameters) Param[3]: rings (type: int) Param[4]: slices (type: int) Param[5]: color (type: Color) -Function 453: DrawCylinder() (6 input parameters) +Function 455: DrawCylinder() (6 input parameters) Name: DrawCylinder Return type: void Description: Draw a cylinder/cone @@ -3868,7 +3884,7 @@ Function 453: DrawCylinder() (6 input parameters) Param[4]: height (type: float) Param[5]: slices (type: int) Param[6]: color (type: Color) -Function 454: DrawCylinderEx() (6 input parameters) +Function 456: DrawCylinderEx() (6 input parameters) Name: DrawCylinderEx Return type: void Description: Draw a cylinder with base at startPos and top at endPos @@ -3878,7 +3894,7 @@ Function 454: DrawCylinderEx() (6 input parameters) Param[4]: endRadius (type: float) Param[5]: sides (type: int) Param[6]: color (type: Color) -Function 455: DrawCylinderWires() (6 input parameters) +Function 457: DrawCylinderWires() (6 input parameters) Name: DrawCylinderWires Return type: void Description: Draw a cylinder/cone wires @@ -3888,7 +3904,7 @@ Function 455: DrawCylinderWires() (6 input parameters) Param[4]: height (type: float) Param[5]: slices (type: int) Param[6]: color (type: Color) -Function 456: DrawCylinderWiresEx() (6 input parameters) +Function 458: DrawCylinderWiresEx() (6 input parameters) Name: DrawCylinderWiresEx Return type: void Description: Draw a cylinder wires with base at startPos and top at endPos @@ -3898,7 +3914,7 @@ Function 456: DrawCylinderWiresEx() (6 input parameters) Param[4]: endRadius (type: float) Param[5]: sides (type: int) Param[6]: color (type: Color) -Function 457: DrawCapsule() (6 input parameters) +Function 459: DrawCapsule() (6 input parameters) Name: DrawCapsule Return type: void Description: Draw a capsule with the center of its sphere caps at startPos and endPos @@ -3908,7 +3924,7 @@ Function 457: DrawCapsule() (6 input parameters) Param[4]: slices (type: int) Param[5]: rings (type: int) Param[6]: color (type: Color) -Function 458: DrawCapsuleWires() (6 input parameters) +Function 460: DrawCapsuleWires() (6 input parameters) Name: DrawCapsuleWires Return type: void Description: Draw capsule wireframe with the center of its sphere caps at startPos and endPos @@ -3918,51 +3934,51 @@ Function 458: DrawCapsuleWires() (6 input parameters) Param[4]: slices (type: int) Param[5]: rings (type: int) Param[6]: color (type: Color) -Function 459: DrawPlane() (3 input parameters) +Function 461: DrawPlane() (3 input parameters) Name: DrawPlane Return type: void Description: Draw a plane XZ Param[1]: centerPos (type: Vector3) Param[2]: size (type: Vector2) Param[3]: color (type: Color) -Function 460: DrawRay() (2 input parameters) +Function 462: DrawRay() (2 input parameters) Name: DrawRay Return type: void Description: Draw a ray line Param[1]: ray (type: Ray) Param[2]: color (type: Color) -Function 461: DrawGrid() (2 input parameters) +Function 463: DrawGrid() (2 input parameters) Name: DrawGrid Return type: void Description: Draw a grid (centered at (0, 0, 0)) Param[1]: slices (type: int) Param[2]: spacing (type: float) -Function 462: LoadModel() (1 input parameters) +Function 464: LoadModel() (1 input parameters) Name: LoadModel Return type: Model Description: Load model from files (meshes and materials) Param[1]: fileName (type: const char *) -Function 463: LoadModelFromMesh() (1 input parameters) +Function 465: LoadModelFromMesh() (1 input parameters) Name: LoadModelFromMesh Return type: Model Description: Load model from generated mesh (default material) Param[1]: mesh (type: Mesh) -Function 464: IsModelValid() (1 input parameters) +Function 466: IsModelValid() (1 input parameters) Name: IsModelValid Return type: bool Description: Check if a model is valid (loaded in GPU, VAO/VBOs) Param[1]: model (type: Model) -Function 465: UnloadModel() (1 input parameters) +Function 467: UnloadModel() (1 input parameters) Name: UnloadModel Return type: void Description: Unload model (including meshes) from memory (RAM and/or VRAM) Param[1]: model (type: Model) -Function 466: GetModelBoundingBox() (1 input parameters) +Function 468: GetModelBoundingBox() (1 input parameters) Name: GetModelBoundingBox Return type: BoundingBox Description: Compute model bounding box limits (considers all meshes) Param[1]: model (type: Model) -Function 467: DrawModel() (4 input parameters) +Function 469: DrawModel() (4 input parameters) Name: DrawModel Return type: void Description: Draw a model (with texture if set) @@ -3970,7 +3986,7 @@ Function 467: DrawModel() (4 input parameters) Param[2]: position (type: Vector3) Param[3]: scale (type: float) Param[4]: tint (type: Color) -Function 468: DrawModelEx() (6 input parameters) +Function 470: DrawModelEx() (6 input parameters) Name: DrawModelEx Return type: void Description: Draw a model with extended parameters @@ -3980,7 +3996,7 @@ Function 468: DrawModelEx() (6 input parameters) Param[4]: rotationAngle (type: float) Param[5]: scale (type: Vector3) Param[6]: tint (type: Color) -Function 469: DrawModelWires() (4 input parameters) +Function 471: DrawModelWires() (4 input parameters) Name: DrawModelWires Return type: void Description: Draw a model wires (with texture if set) @@ -3988,7 +4004,7 @@ Function 469: DrawModelWires() (4 input parameters) Param[2]: position (type: Vector3) Param[3]: scale (type: float) Param[4]: tint (type: Color) -Function 470: DrawModelWiresEx() (6 input parameters) +Function 472: DrawModelWiresEx() (6 input parameters) Name: DrawModelWiresEx Return type: void Description: Draw a model wires (with texture if set) with extended parameters @@ -3998,7 +4014,7 @@ Function 470: DrawModelWiresEx() (6 input parameters) Param[4]: rotationAngle (type: float) Param[5]: scale (type: Vector3) Param[6]: tint (type: Color) -Function 471: DrawModelPoints() (4 input parameters) +Function 473: DrawModelPoints() (4 input parameters) Name: DrawModelPoints Return type: void Description: Draw a model as points @@ -4006,7 +4022,7 @@ Function 471: DrawModelPoints() (4 input parameters) Param[2]: position (type: Vector3) Param[3]: scale (type: float) Param[4]: tint (type: Color) -Function 472: DrawModelPointsEx() (6 input parameters) +Function 474: DrawModelPointsEx() (6 input parameters) Name: DrawModelPointsEx Return type: void Description: Draw a model as points with extended parameters @@ -4016,13 +4032,13 @@ Function 472: DrawModelPointsEx() (6 input parameters) Param[4]: rotationAngle (type: float) Param[5]: scale (type: Vector3) Param[6]: tint (type: Color) -Function 473: DrawBoundingBox() (2 input parameters) +Function 475: DrawBoundingBox() (2 input parameters) Name: DrawBoundingBox Return type: void Description: Draw bounding box (wires) Param[1]: box (type: BoundingBox) Param[2]: color (type: Color) -Function 474: DrawBillboard() (5 input parameters) +Function 476: DrawBillboard() (5 input parameters) Name: DrawBillboard Return type: void Description: Draw a billboard texture @@ -4031,7 +4047,7 @@ Function 474: DrawBillboard() (5 input parameters) Param[3]: position (type: Vector3) Param[4]: scale (type: float) Param[5]: tint (type: Color) -Function 475: DrawBillboardRec() (6 input parameters) +Function 477: DrawBillboardRec() (6 input parameters) Name: DrawBillboardRec Return type: void Description: Draw a billboard texture defined by source @@ -4041,7 +4057,7 @@ Function 475: DrawBillboardRec() (6 input parameters) Param[4]: position (type: Vector3) Param[5]: size (type: Vector2) Param[6]: tint (type: Color) -Function 476: DrawBillboardPro() (9 input parameters) +Function 478: DrawBillboardPro() (9 input parameters) Name: DrawBillboardPro Return type: void Description: Draw a billboard texture defined by source and rotation @@ -4054,13 +4070,13 @@ Function 476: DrawBillboardPro() (9 input parameters) Param[7]: origin (type: Vector2) Param[8]: rotation (type: float) Param[9]: tint (type: Color) -Function 477: UploadMesh() (2 input parameters) +Function 479: UploadMesh() (2 input parameters) Name: UploadMesh Return type: void Description: Upload mesh vertex data in GPU and provide VAO/VBO ids Param[1]: mesh (type: Mesh *) Param[2]: dynamic (type: bool) -Function 478: UpdateMeshBuffer() (5 input parameters) +Function 480: UpdateMeshBuffer() (5 input parameters) Name: UpdateMeshBuffer Return type: void Description: Update mesh vertex data in GPU for a specific buffer index @@ -4069,19 +4085,19 @@ Function 478: UpdateMeshBuffer() (5 input parameters) Param[3]: data (type: const void *) Param[4]: dataSize (type: int) Param[5]: offset (type: int) -Function 479: UnloadMesh() (1 input parameters) +Function 481: UnloadMesh() (1 input parameters) Name: UnloadMesh Return type: void Description: Unload mesh data from CPU and GPU Param[1]: mesh (type: Mesh) -Function 480: DrawMesh() (3 input parameters) +Function 482: DrawMesh() (3 input parameters) Name: DrawMesh Return type: void Description: Draw a 3d mesh with material and transform Param[1]: mesh (type: Mesh) Param[2]: material (type: Material) Param[3]: transform (type: Matrix) -Function 481: DrawMeshInstanced() (4 input parameters) +Function 483: DrawMeshInstanced() (4 input parameters) Name: DrawMeshInstanced Return type: void Description: Draw multiple mesh instances with material and different transforms @@ -4089,35 +4105,35 @@ Function 481: DrawMeshInstanced() (4 input parameters) Param[2]: material (type: Material) Param[3]: transforms (type: const Matrix *) Param[4]: instances (type: int) -Function 482: GetMeshBoundingBox() (1 input parameters) +Function 484: GetMeshBoundingBox() (1 input parameters) Name: GetMeshBoundingBox Return type: BoundingBox Description: Compute mesh bounding box limits Param[1]: mesh (type: Mesh) -Function 483: GenMeshTangents() (1 input parameters) +Function 485: GenMeshTangents() (1 input parameters) Name: GenMeshTangents Return type: void Description: Compute mesh tangents Param[1]: mesh (type: Mesh *) -Function 484: ExportMesh() (2 input parameters) +Function 486: ExportMesh() (2 input parameters) Name: ExportMesh Return type: bool Description: Export mesh data to file, returns true on success Param[1]: mesh (type: Mesh) Param[2]: fileName (type: const char *) -Function 485: ExportMeshAsCode() (2 input parameters) +Function 487: ExportMeshAsCode() (2 input parameters) Name: ExportMeshAsCode Return type: bool Description: Export mesh as code file (.h) defining multiple arrays of vertex attributes Param[1]: mesh (type: Mesh) Param[2]: fileName (type: const char *) -Function 486: GenMeshPoly() (2 input parameters) +Function 488: GenMeshPoly() (2 input parameters) Name: GenMeshPoly Return type: Mesh Description: Generate polygonal mesh Param[1]: sides (type: int) Param[2]: radius (type: float) -Function 487: GenMeshPlane() (4 input parameters) +Function 489: GenMeshPlane() (4 input parameters) Name: GenMeshPlane Return type: Mesh Description: Generate plane mesh (with subdivisions) @@ -4125,42 +4141,42 @@ Function 487: GenMeshPlane() (4 input parameters) Param[2]: length (type: float) Param[3]: resX (type: int) Param[4]: resZ (type: int) -Function 488: GenMeshCube() (3 input parameters) +Function 490: GenMeshCube() (3 input parameters) Name: GenMeshCube Return type: Mesh Description: Generate cuboid mesh Param[1]: width (type: float) Param[2]: height (type: float) Param[3]: length (type: float) -Function 489: GenMeshSphere() (3 input parameters) +Function 491: GenMeshSphere() (3 input parameters) Name: GenMeshSphere Return type: Mesh Description: Generate sphere mesh (standard sphere) Param[1]: radius (type: float) Param[2]: rings (type: int) Param[3]: slices (type: int) -Function 490: GenMeshHemiSphere() (3 input parameters) +Function 492: GenMeshHemiSphere() (3 input parameters) Name: GenMeshHemiSphere Return type: Mesh Description: Generate half-sphere mesh (no bottom cap) Param[1]: radius (type: float) Param[2]: rings (type: int) Param[3]: slices (type: int) -Function 491: GenMeshCylinder() (3 input parameters) +Function 493: GenMeshCylinder() (3 input parameters) Name: GenMeshCylinder Return type: Mesh Description: Generate cylinder mesh Param[1]: radius (type: float) Param[2]: height (type: float) Param[3]: slices (type: int) -Function 492: GenMeshCone() (3 input parameters) +Function 494: GenMeshCone() (3 input parameters) Name: GenMeshCone Return type: Mesh Description: Generate cone/pyramid mesh Param[1]: radius (type: float) Param[2]: height (type: float) Param[3]: slices (type: int) -Function 493: GenMeshTorus() (4 input parameters) +Function 495: GenMeshTorus() (4 input parameters) Name: GenMeshTorus Return type: Mesh Description: Generate torus mesh @@ -4168,7 +4184,7 @@ Function 493: GenMeshTorus() (4 input parameters) Param[2]: size (type: float) Param[3]: radSeg (type: int) Param[4]: sides (type: int) -Function 494: GenMeshKnot() (4 input parameters) +Function 496: GenMeshKnot() (4 input parameters) Name: GenMeshKnot Return type: Mesh Description: Generate trefoil knot mesh @@ -4176,91 +4192,91 @@ Function 494: GenMeshKnot() (4 input parameters) Param[2]: size (type: float) Param[3]: radSeg (type: int) Param[4]: sides (type: int) -Function 495: GenMeshHeightmap() (2 input parameters) +Function 497: GenMeshHeightmap() (2 input parameters) Name: GenMeshHeightmap Return type: Mesh Description: Generate heightmap mesh from image data Param[1]: heightmap (type: Image) Param[2]: size (type: Vector3) -Function 496: GenMeshCubicmap() (2 input parameters) +Function 498: GenMeshCubicmap() (2 input parameters) Name: GenMeshCubicmap Return type: Mesh Description: Generate cubes-based map mesh from image data Param[1]: cubicmap (type: Image) Param[2]: cubeSize (type: Vector3) -Function 497: LoadMaterials() (2 input parameters) +Function 499: LoadMaterials() (2 input parameters) Name: LoadMaterials Return type: Material * Description: Load materials from model file Param[1]: fileName (type: const char *) Param[2]: materialCount (type: int *) -Function 498: LoadMaterialDefault() (0 input parameters) +Function 500: LoadMaterialDefault() (0 input parameters) Name: LoadMaterialDefault Return type: Material Description: Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) No input parameters -Function 499: IsMaterialValid() (1 input parameters) +Function 501: IsMaterialValid() (1 input parameters) Name: IsMaterialValid Return type: bool Description: Check if a material is valid (shader assigned, map textures loaded in GPU) Param[1]: material (type: Material) -Function 500: UnloadMaterial() (1 input parameters) +Function 502: UnloadMaterial() (1 input parameters) Name: UnloadMaterial Return type: void Description: Unload material from GPU memory (VRAM) Param[1]: material (type: Material) -Function 501: SetMaterialTexture() (3 input parameters) +Function 503: SetMaterialTexture() (3 input parameters) Name: SetMaterialTexture Return type: void Description: Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) Param[1]: material (type: Material *) Param[2]: mapType (type: int) Param[3]: texture (type: Texture2D) -Function 502: SetModelMeshMaterial() (3 input parameters) +Function 504: SetModelMeshMaterial() (3 input parameters) Name: SetModelMeshMaterial Return type: void Description: Set material for a mesh Param[1]: model (type: Model *) Param[2]: meshId (type: int) Param[3]: materialId (type: int) -Function 503: LoadModelAnimations() (2 input parameters) +Function 505: LoadModelAnimations() (2 input parameters) Name: LoadModelAnimations Return type: ModelAnimation * Description: Load model animations from file Param[1]: fileName (type: const char *) Param[2]: animCount (type: int *) -Function 504: UpdateModelAnimation() (3 input parameters) +Function 506: UpdateModelAnimation() (3 input parameters) Name: UpdateModelAnimation Return type: void Description: Update model animation pose (CPU) Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) Param[3]: frame (type: int) -Function 505: UpdateModelAnimationBones() (3 input parameters) +Function 507: UpdateModelAnimationBones() (3 input parameters) Name: UpdateModelAnimationBones Return type: void Description: Update model animation mesh bone matrices (GPU skinning) Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) Param[3]: frame (type: int) -Function 506: UnloadModelAnimation() (1 input parameters) +Function 508: UnloadModelAnimation() (1 input parameters) Name: UnloadModelAnimation Return type: void Description: Unload animation data Param[1]: anim (type: ModelAnimation) -Function 507: UnloadModelAnimations() (2 input parameters) +Function 509: UnloadModelAnimations() (2 input parameters) Name: UnloadModelAnimations Return type: void Description: Unload animation array data Param[1]: animations (type: ModelAnimation *) Param[2]: animCount (type: int) -Function 508: IsModelAnimationValid() (2 input parameters) +Function 510: IsModelAnimationValid() (2 input parameters) Name: IsModelAnimationValid Return type: bool Description: Check model animation skeleton match Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) -Function 509: CheckCollisionSpheres() (4 input parameters) +Function 511: CheckCollisionSpheres() (4 input parameters) Name: CheckCollisionSpheres Return type: bool Description: Check collision between two spheres @@ -4268,40 +4284,40 @@ Function 509: CheckCollisionSpheres() (4 input parameters) Param[2]: radius1 (type: float) Param[3]: center2 (type: Vector3) Param[4]: radius2 (type: float) -Function 510: CheckCollisionBoxes() (2 input parameters) +Function 512: CheckCollisionBoxes() (2 input parameters) Name: CheckCollisionBoxes Return type: bool Description: Check collision between two bounding boxes Param[1]: box1 (type: BoundingBox) Param[2]: box2 (type: BoundingBox) -Function 511: CheckCollisionBoxSphere() (3 input parameters) +Function 513: CheckCollisionBoxSphere() (3 input parameters) Name: CheckCollisionBoxSphere Return type: bool Description: Check collision between box and sphere Param[1]: box (type: BoundingBox) Param[2]: center (type: Vector3) Param[3]: radius (type: float) -Function 512: GetRayCollisionSphere() (3 input parameters) +Function 514: GetRayCollisionSphere() (3 input parameters) Name: GetRayCollisionSphere Return type: RayCollision Description: Get collision info between ray and sphere Param[1]: ray (type: Ray) Param[2]: center (type: Vector3) Param[3]: radius (type: float) -Function 513: GetRayCollisionBox() (2 input parameters) +Function 515: GetRayCollisionBox() (2 input parameters) Name: GetRayCollisionBox Return type: RayCollision Description: Get collision info between ray and box Param[1]: ray (type: Ray) Param[2]: box (type: BoundingBox) -Function 514: GetRayCollisionMesh() (3 input parameters) +Function 516: GetRayCollisionMesh() (3 input parameters) Name: GetRayCollisionMesh Return type: RayCollision Description: Get collision info between ray and mesh Param[1]: ray (type: Ray) Param[2]: mesh (type: Mesh) Param[3]: transform (type: Matrix) -Function 515: GetRayCollisionTriangle() (4 input parameters) +Function 517: GetRayCollisionTriangle() (4 input parameters) Name: GetRayCollisionTriangle Return type: RayCollision Description: Get collision info between ray and triangle @@ -4309,7 +4325,7 @@ Function 515: GetRayCollisionTriangle() (4 input parameters) Param[2]: p1 (type: Vector3) Param[3]: p2 (type: Vector3) Param[4]: p3 (type: Vector3) -Function 516: GetRayCollisionQuad() (5 input parameters) +Function 518: GetRayCollisionQuad() (5 input parameters) Name: GetRayCollisionQuad Return type: RayCollision Description: Get collision info between ray and quad @@ -4318,158 +4334,158 @@ Function 516: GetRayCollisionQuad() (5 input parameters) Param[3]: p2 (type: Vector3) Param[4]: p3 (type: Vector3) Param[5]: p4 (type: Vector3) -Function 517: InitAudioDevice() (0 input parameters) +Function 519: InitAudioDevice() (0 input parameters) Name: InitAudioDevice Return type: void Description: Initialize audio device and context No input parameters -Function 518: CloseAudioDevice() (0 input parameters) +Function 520: CloseAudioDevice() (0 input parameters) Name: CloseAudioDevice Return type: void Description: Close the audio device and context No input parameters -Function 519: IsAudioDeviceReady() (0 input parameters) +Function 521: IsAudioDeviceReady() (0 input parameters) Name: IsAudioDeviceReady Return type: bool Description: Check if audio device has been initialized successfully No input parameters -Function 520: SetMasterVolume() (1 input parameters) +Function 522: SetMasterVolume() (1 input parameters) Name: SetMasterVolume Return type: void Description: Set master volume (listener) Param[1]: volume (type: float) -Function 521: GetMasterVolume() (0 input parameters) +Function 523: GetMasterVolume() (0 input parameters) Name: GetMasterVolume Return type: float Description: Get master volume (listener) No input parameters -Function 522: LoadWave() (1 input parameters) +Function 524: LoadWave() (1 input parameters) Name: LoadWave Return type: Wave Description: Load wave data from file Param[1]: fileName (type: const char *) -Function 523: LoadWaveFromMemory() (3 input parameters) +Function 525: LoadWaveFromMemory() (3 input parameters) Name: LoadWaveFromMemory Return type: Wave Description: Load wave from memory buffer, fileType refers to extension: i.e. '.wav' Param[1]: fileType (type: const char *) Param[2]: fileData (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 524: IsWaveValid() (1 input parameters) +Function 526: IsWaveValid() (1 input parameters) Name: IsWaveValid Return type: bool Description: Checks if wave data is valid (data loaded and parameters) Param[1]: wave (type: Wave) -Function 525: LoadSound() (1 input parameters) +Function 527: LoadSound() (1 input parameters) Name: LoadSound Return type: Sound Description: Load sound from file Param[1]: fileName (type: const char *) -Function 526: LoadSoundFromWave() (1 input parameters) +Function 528: LoadSoundFromWave() (1 input parameters) Name: LoadSoundFromWave Return type: Sound Description: Load sound from wave data Param[1]: wave (type: Wave) -Function 527: LoadSoundAlias() (1 input parameters) +Function 529: LoadSoundAlias() (1 input parameters) Name: LoadSoundAlias Return type: Sound Description: Create a new sound that shares the same sample data as the source sound, does not own the sound data Param[1]: source (type: Sound) -Function 528: IsSoundValid() (1 input parameters) +Function 530: IsSoundValid() (1 input parameters) Name: IsSoundValid Return type: bool Description: Checks if a sound is valid (data loaded and buffers initialized) Param[1]: sound (type: Sound) -Function 529: UpdateSound() (3 input parameters) +Function 531: UpdateSound() (3 input parameters) Name: UpdateSound Return type: void Description: Update sound buffer with new data Param[1]: sound (type: Sound) Param[2]: data (type: const void *) Param[3]: sampleCount (type: int) -Function 530: UnloadWave() (1 input parameters) +Function 532: UnloadWave() (1 input parameters) Name: UnloadWave Return type: void Description: Unload wave data Param[1]: wave (type: Wave) -Function 531: UnloadSound() (1 input parameters) +Function 533: UnloadSound() (1 input parameters) Name: UnloadSound Return type: void Description: Unload sound Param[1]: sound (type: Sound) -Function 532: UnloadSoundAlias() (1 input parameters) +Function 534: UnloadSoundAlias() (1 input parameters) Name: UnloadSoundAlias Return type: void Description: Unload a sound alias (does not deallocate sample data) Param[1]: alias (type: Sound) -Function 533: ExportWave() (2 input parameters) +Function 535: ExportWave() (2 input parameters) Name: ExportWave Return type: bool Description: Export wave data to file, returns true on success Param[1]: wave (type: Wave) Param[2]: fileName (type: const char *) -Function 534: ExportWaveAsCode() (2 input parameters) +Function 536: ExportWaveAsCode() (2 input parameters) Name: ExportWaveAsCode Return type: bool Description: Export wave sample data to code (.h), returns true on success Param[1]: wave (type: Wave) Param[2]: fileName (type: const char *) -Function 535: PlaySound() (1 input parameters) +Function 537: PlaySound() (1 input parameters) Name: PlaySound Return type: void Description: Play a sound Param[1]: sound (type: Sound) -Function 536: StopSound() (1 input parameters) +Function 538: StopSound() (1 input parameters) Name: StopSound Return type: void Description: Stop playing a sound Param[1]: sound (type: Sound) -Function 537: PauseSound() (1 input parameters) +Function 539: PauseSound() (1 input parameters) Name: PauseSound Return type: void Description: Pause a sound Param[1]: sound (type: Sound) -Function 538: ResumeSound() (1 input parameters) +Function 540: ResumeSound() (1 input parameters) Name: ResumeSound Return type: void Description: Resume a paused sound Param[1]: sound (type: Sound) -Function 539: IsSoundPlaying() (1 input parameters) +Function 541: IsSoundPlaying() (1 input parameters) Name: IsSoundPlaying Return type: bool Description: Check if a sound is currently playing Param[1]: sound (type: Sound) -Function 540: SetSoundVolume() (2 input parameters) +Function 542: SetSoundVolume() (2 input parameters) Name: SetSoundVolume Return type: void Description: Set volume for a sound (1.0 is max level) Param[1]: sound (type: Sound) Param[2]: volume (type: float) -Function 541: SetSoundPitch() (2 input parameters) +Function 543: SetSoundPitch() (2 input parameters) Name: SetSoundPitch Return type: void Description: Set pitch for a sound (1.0 is base level) Param[1]: sound (type: Sound) Param[2]: pitch (type: float) -Function 542: SetSoundPan() (2 input parameters) +Function 544: SetSoundPan() (2 input parameters) Name: SetSoundPan Return type: void Description: Set pan for a sound (0.5 is center) Param[1]: sound (type: Sound) Param[2]: pan (type: float) -Function 543: WaveCopy() (1 input parameters) +Function 545: WaveCopy() (1 input parameters) Name: WaveCopy Return type: Wave Description: Copy a wave to a new wave Param[1]: wave (type: Wave) -Function 544: WaveCrop() (3 input parameters) +Function 546: WaveCrop() (3 input parameters) Name: WaveCrop Return type: void Description: Crop a wave to defined frames range Param[1]: wave (type: Wave *) Param[2]: initFrame (type: int) Param[3]: finalFrame (type: int) -Function 545: WaveFormat() (4 input parameters) +Function 547: WaveFormat() (4 input parameters) Name: WaveFormat Return type: void Description: Convert wave data to desired format @@ -4477,203 +4493,203 @@ Function 545: WaveFormat() (4 input parameters) Param[2]: sampleRate (type: int) Param[3]: sampleSize (type: int) Param[4]: channels (type: int) -Function 546: LoadWaveSamples() (1 input parameters) +Function 548: LoadWaveSamples() (1 input parameters) Name: LoadWaveSamples Return type: float * Description: Load samples data from wave as a 32bit float data array Param[1]: wave (type: Wave) -Function 547: UnloadWaveSamples() (1 input parameters) +Function 549: UnloadWaveSamples() (1 input parameters) Name: UnloadWaveSamples Return type: void Description: Unload samples data loaded with LoadWaveSamples() Param[1]: samples (type: float *) -Function 548: LoadMusicStream() (1 input parameters) +Function 550: LoadMusicStream() (1 input parameters) Name: LoadMusicStream Return type: Music Description: Load music stream from file Param[1]: fileName (type: const char *) -Function 549: LoadMusicStreamFromMemory() (3 input parameters) +Function 551: LoadMusicStreamFromMemory() (3 input parameters) Name: LoadMusicStreamFromMemory Return type: Music Description: Load music stream from data Param[1]: fileType (type: const char *) Param[2]: data (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 550: IsMusicValid() (1 input parameters) +Function 552: IsMusicValid() (1 input parameters) Name: IsMusicValid Return type: bool Description: Checks if a music stream is valid (context and buffers initialized) Param[1]: music (type: Music) -Function 551: UnloadMusicStream() (1 input parameters) +Function 553: UnloadMusicStream() (1 input parameters) Name: UnloadMusicStream Return type: void Description: Unload music stream Param[1]: music (type: Music) -Function 552: PlayMusicStream() (1 input parameters) +Function 554: PlayMusicStream() (1 input parameters) Name: PlayMusicStream Return type: void Description: Start music playing Param[1]: music (type: Music) -Function 553: IsMusicStreamPlaying() (1 input parameters) +Function 555: IsMusicStreamPlaying() (1 input parameters) Name: IsMusicStreamPlaying Return type: bool Description: Check if music is playing Param[1]: music (type: Music) -Function 554: UpdateMusicStream() (1 input parameters) +Function 556: UpdateMusicStream() (1 input parameters) Name: UpdateMusicStream Return type: void Description: Updates buffers for music streaming Param[1]: music (type: Music) -Function 555: StopMusicStream() (1 input parameters) +Function 557: StopMusicStream() (1 input parameters) Name: StopMusicStream Return type: void Description: Stop music playing Param[1]: music (type: Music) -Function 556: PauseMusicStream() (1 input parameters) +Function 558: PauseMusicStream() (1 input parameters) Name: PauseMusicStream Return type: void Description: Pause music playing Param[1]: music (type: Music) -Function 557: ResumeMusicStream() (1 input parameters) +Function 559: ResumeMusicStream() (1 input parameters) Name: ResumeMusicStream Return type: void Description: Resume playing paused music Param[1]: music (type: Music) -Function 558: SeekMusicStream() (2 input parameters) +Function 560: SeekMusicStream() (2 input parameters) Name: SeekMusicStream Return type: void Description: Seek music to a position (in seconds) Param[1]: music (type: Music) Param[2]: position (type: float) -Function 559: SetMusicVolume() (2 input parameters) +Function 561: SetMusicVolume() (2 input parameters) Name: SetMusicVolume Return type: void Description: Set volume for music (1.0 is max level) Param[1]: music (type: Music) Param[2]: volume (type: float) -Function 560: SetMusicPitch() (2 input parameters) +Function 562: SetMusicPitch() (2 input parameters) Name: SetMusicPitch Return type: void Description: Set pitch for a music (1.0 is base level) Param[1]: music (type: Music) Param[2]: pitch (type: float) -Function 561: SetMusicPan() (2 input parameters) +Function 563: SetMusicPan() (2 input parameters) Name: SetMusicPan Return type: void Description: Set pan for a music (0.5 is center) Param[1]: music (type: Music) Param[2]: pan (type: float) -Function 562: GetMusicTimeLength() (1 input parameters) +Function 564: GetMusicTimeLength() (1 input parameters) Name: GetMusicTimeLength Return type: float Description: Get music time length (in seconds) Param[1]: music (type: Music) -Function 563: GetMusicTimePlayed() (1 input parameters) +Function 565: GetMusicTimePlayed() (1 input parameters) Name: GetMusicTimePlayed Return type: float Description: Get current music time played (in seconds) Param[1]: music (type: Music) -Function 564: LoadAudioStream() (3 input parameters) +Function 566: LoadAudioStream() (3 input parameters) Name: LoadAudioStream Return type: AudioStream Description: Load audio stream (to stream raw audio pcm data) Param[1]: sampleRate (type: unsigned int) Param[2]: sampleSize (type: unsigned int) Param[3]: channels (type: unsigned int) -Function 565: IsAudioStreamValid() (1 input parameters) +Function 567: IsAudioStreamValid() (1 input parameters) Name: IsAudioStreamValid Return type: bool Description: Checks if an audio stream is valid (buffers initialized) Param[1]: stream (type: AudioStream) -Function 566: UnloadAudioStream() (1 input parameters) +Function 568: UnloadAudioStream() (1 input parameters) Name: UnloadAudioStream Return type: void Description: Unload audio stream and free memory Param[1]: stream (type: AudioStream) -Function 567: UpdateAudioStream() (3 input parameters) +Function 569: UpdateAudioStream() (3 input parameters) Name: UpdateAudioStream Return type: void Description: Update audio stream buffers with data Param[1]: stream (type: AudioStream) Param[2]: data (type: const void *) Param[3]: frameCount (type: int) -Function 568: IsAudioStreamProcessed() (1 input parameters) +Function 570: IsAudioStreamProcessed() (1 input parameters) Name: IsAudioStreamProcessed Return type: bool Description: Check if any audio stream buffers requires refill Param[1]: stream (type: AudioStream) -Function 569: PlayAudioStream() (1 input parameters) +Function 571: PlayAudioStream() (1 input parameters) Name: PlayAudioStream Return type: void Description: Play audio stream Param[1]: stream (type: AudioStream) -Function 570: PauseAudioStream() (1 input parameters) +Function 572: PauseAudioStream() (1 input parameters) Name: PauseAudioStream Return type: void Description: Pause audio stream Param[1]: stream (type: AudioStream) -Function 571: ResumeAudioStream() (1 input parameters) +Function 573: ResumeAudioStream() (1 input parameters) Name: ResumeAudioStream Return type: void Description: Resume audio stream Param[1]: stream (type: AudioStream) -Function 572: IsAudioStreamPlaying() (1 input parameters) +Function 574: IsAudioStreamPlaying() (1 input parameters) Name: IsAudioStreamPlaying Return type: bool Description: Check if audio stream is playing Param[1]: stream (type: AudioStream) -Function 573: StopAudioStream() (1 input parameters) +Function 575: StopAudioStream() (1 input parameters) Name: StopAudioStream Return type: void Description: Stop audio stream Param[1]: stream (type: AudioStream) -Function 574: SetAudioStreamVolume() (2 input parameters) +Function 576: SetAudioStreamVolume() (2 input parameters) Name: SetAudioStreamVolume Return type: void Description: Set volume for audio stream (1.0 is max level) Param[1]: stream (type: AudioStream) Param[2]: volume (type: float) -Function 575: SetAudioStreamPitch() (2 input parameters) +Function 577: SetAudioStreamPitch() (2 input parameters) Name: SetAudioStreamPitch Return type: void Description: Set pitch for audio stream (1.0 is base level) Param[1]: stream (type: AudioStream) Param[2]: pitch (type: float) -Function 576: SetAudioStreamPan() (2 input parameters) +Function 578: SetAudioStreamPan() (2 input parameters) Name: SetAudioStreamPan Return type: void Description: Set pan for audio stream (0.5 is centered) Param[1]: stream (type: AudioStream) Param[2]: pan (type: float) -Function 577: SetAudioStreamBufferSizeDefault() (1 input parameters) +Function 579: SetAudioStreamBufferSizeDefault() (1 input parameters) Name: SetAudioStreamBufferSizeDefault Return type: void Description: Default size for new audio streams Param[1]: size (type: int) -Function 578: SetAudioStreamCallback() (2 input parameters) +Function 580: SetAudioStreamCallback() (2 input parameters) Name: SetAudioStreamCallback Return type: void Description: Audio thread callback to request new data Param[1]: stream (type: AudioStream) Param[2]: callback (type: AudioCallback) -Function 579: AttachAudioStreamProcessor() (2 input parameters) +Function 581: AttachAudioStreamProcessor() (2 input parameters) Name: AttachAudioStreamProcessor Return type: void Description: Attach audio stream processor to stream, receives frames x 2 samples as 'float' (stereo) Param[1]: stream (type: AudioStream) Param[2]: processor (type: AudioCallback) -Function 580: DetachAudioStreamProcessor() (2 input parameters) +Function 582: DetachAudioStreamProcessor() (2 input parameters) Name: DetachAudioStreamProcessor Return type: void Description: Detach audio stream processor from stream Param[1]: stream (type: AudioStream) Param[2]: processor (type: AudioCallback) -Function 581: AttachAudioMixedProcessor() (1 input parameters) +Function 583: AttachAudioMixedProcessor() (1 input parameters) Name: AttachAudioMixedProcessor Return type: void Description: Attach audio stream processor to the entire audio pipeline, receives frames x 2 samples as 'float' (stereo) Param[1]: processor (type: AudioCallback) -Function 582: DetachAudioMixedProcessor() (1 input parameters) +Function 584: DetachAudioMixedProcessor() (1 input parameters) Name: DetachAudioMixedProcessor Return type: void Description: Detach audio stream processor from the entire audio pipeline diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index b761f8b56..52f7f5b1d 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -679,7 +679,7 @@ - + @@ -1409,6 +1409,12 @@ + + + + + + @@ -1416,6 +1422,12 @@ + + + + + + From 6eeaf1dd5b22778a704d41c402cc8c0fc22731c3 Mon Sep 17 00:00:00 2001 From: M374LX Date: Sat, 31 May 2025 16:43:25 -0300 Subject: [PATCH 024/242] Update RGFW to 1.7.5-dev --- src/external/RGFW.h | 902 ++++++++++++++++++----------- src/platforms/rcore_desktop_rgfw.c | 1 + 2 files changed, 562 insertions(+), 341 deletions(-) diff --git a/src/external/RGFW.h b/src/external/RGFW.h index f121ef630..1582fbfad 100644 --- a/src/external/RGFW.h +++ b/src/external/RGFW.h @@ -1,7 +1,7 @@ /* * -* RGFW 1.7 -* +* RGFW 1.7.5-dev + * Copyright (C) 2022-25 ColleagueRiley * * libpng license @@ -160,7 +160,7 @@ int main() { @Easymode -> support, testing/debugging, bug fixes and reviews Joshua Rowe (omnisci3nce) - bug fix, review (macOS) @lesleyrs -> bug fix, review (OpenGL) - Nick Porcino (meshula) - testing, organization, review (MacOS, examples) + Nick Porcino (meshula) - testing, organization, review (MacOS, examples) @DarekParodia -> code review (X11) (C++) */ @@ -202,8 +202,12 @@ int main() { #define RGFW_ASSERT assert #endif -#if !defined(RGFW_MEMCPY) || !defined(RGFW_STRNCMP) || !defined(RGFW_STRNCPY) - #include +#if !defined(RGFW_MEMCPY) || !defined(RGFW_STRNCMP) || !defined(RGFW_STRNCPY) || !defined(RGFW_MEMSET) + #include +#endif + +#ifndef RGFW_MEMSET + #define RGFW_MEMSET(ptr, value, num) memset(ptr, value, num) #endif #ifndef RGFW_MEMCPY @@ -225,18 +229,10 @@ int main() { #ifndef RGFW_STRTOL /* required for X11 XDnD and X11 Monitor DPI */ #include - #define RGFW_STRTOL(str, endptr, base) strtol(str, endptr, base) + #define RGFW_STRTOL(str, endptr, base) strtol(str, endptr, base) #define RGFW_ATOF(num) atof(num) #endif -#if !_MSC_VER - #ifndef inline - #ifndef __APPLE__ - #define inline __inline - #endif - #endif -#endif - #ifdef RGFW_WIN95 /* for windows 95 testing (not that it really works) */ #define RGFW_NO_MONITOR #define RGFW_NO_PASSTHROUGH @@ -258,14 +254,17 @@ int main() { #define RGFWDEF __attribute__((visibility("default"))) #endif #endif + #ifndef RGFWDEF + #define RGFWDEF + #endif #endif #ifndef RGFWDEF - #ifdef RGFW_C89 - #define RGFWDEF __inline - #else - #define RGFWDEF inline - #endif + #ifdef RGFW_C89 + #define RGFWDEF __inline + #else + #define RGFWDEF inline + #endif #endif #ifndef RGFW_ENUM @@ -285,15 +284,15 @@ int main() { #include #ifndef RGFW_INT_DEFINED #ifdef RGFW_USE_INT /* optional for any system that might not have stdint.h */ - typedef unsigned char u8; - typedef signed char i8; - typedef unsigned short u16; - typedef signed short i16; - typedef unsigned long int u32; - typedef signed long int i32; - typedef unsigned long long u64; - typedef signed long long i64; - #else /* use stdint standard types instead of c ""standard"" types */ + typedef unsigned char u8; + typedef signed char i8; + typedef unsigned short u16; + typedef signed short i16; + typedef unsigned long int u32; + typedef signed long int i32; + typedef unsigned long long u64; + typedef signed long long i64; + #else /* use stdint standard types instead of c "standard" types */ #include typedef uint8_t u8; @@ -305,7 +304,7 @@ int main() { typedef uint64_t u64; typedef int64_t i64; #endif - #define RGFW_INT_DEFINED + #define RGFW_INT_DEFINED #endif #ifndef RGFW_BOOL_DEFINED @@ -566,17 +565,17 @@ typedef RGFW_ENUM(u8, RGFW_gamepadCodes) { /*! basic vector type, if there's not already a point/vector type of choice */ #ifndef RGFW_point - typedef struct { i32 x, y; } RGFW_point; + typedef struct RGFW_point { i32 x, y; } RGFW_point; #endif /*! basic rect type, if there's not already a rect type of choice */ #ifndef RGFW_rect - typedef struct { i32 x, y, w, h; } RGFW_rect; + typedef struct RGFW_rect { i32 x, y, w, h; } RGFW_rect; #endif /*! basic area type, if there's not already a area type of choice */ #ifndef RGFW_area - typedef struct { u32 w, h; } RGFW_area; + typedef struct RGFW_area { u32 w, h; } RGFW_area; #endif #if defined(__cplusplus) && !defined(__APPLE__) @@ -714,6 +713,7 @@ typedef struct RGFW_window_src { i64 counter_value; XID counter; #endif + RGFW_rect r; #endif /* RGFW_X11 */ #if defined(RGFW_WAYLAND) struct wl_display* wl_display; @@ -811,9 +811,11 @@ typedef struct RGFW_window { RGFW_event event; /*!< current event */ RGFW_rect r; /*!< the x, y, w and h of the struct */ - + + /*! which key RGFW_window_shouldClose checks. Settting this to RGFW_keyNULL disables the feature. */ + RGFW_key exitKey; RGFW_point _lastMousePoint; /*!< last cusor point (for raw mouse data) */ - + u32 _flags; /*!< windows flags (for RGFW to check) */ RGFW_rect _oldRect; /*!< rect before fullscreen */ } RGFW_window; /*!< window structure for managing the window */ @@ -990,6 +992,8 @@ RGFWDEF RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win); /*!< sets the m this is useful for a 3D camera */ RGFWDEF void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area); +/*! if the mouse is held by RGFW */ +RGFWDEF RGFW_bool RGFW_window_mouseHeld(RGFW_window* win); /*! stop holding the mouse and let it move freely */ RGFWDEF void RGFW_window_mouseUnhold(RGFW_window* win); @@ -1101,14 +1105,14 @@ typedef RGFW_ENUM(u8, RGFW_errorCode) { RGFW_warningWayland, RGFW_warningOpenGL }; -typedef struct RGFW_debugContext { RGFW_window* win; RGFW_monitor monitor; u32 srcError; } RGFW_debugContext; +typedef struct RGFW_debugContext { RGFW_window* win; RGFW_monitor* monitor; u32 srcError; } RGFW_debugContext; #if defined(__cplusplus) && !defined(__APPLE__) -#define RGFW_DEBUG_CTX(win, err) {win, { 0 }, err} -#define RGFW_DEBUG_CTX_MON(monitor) {_RGFW.root, monitor, 0} +#define RGFW_DEBUG_CTX(win, err) {win, NULL, err} +#define RGFW_DEBUG_CTX_MON(monitor) {_RGFW.root, &monitor, 0} #else -#define RGFW_DEBUG_CTX(win, err) (RGFW_debugContext){win, (RGFW_monitor){ 0 }, err} -#define RGFW_DEBUG_CTX_MON(monitor) (RGFW_debugContext){_RGFW.root, monitor, 0} +#define RGFW_DEBUG_CTX(win, err) (RGFW_debugContext){win, NULL, err} +#define RGFW_DEBUG_CTX_MON(monitor) (RGFW_debugContext){_RGFW.root, &monitor, 0} #endif typedef void (* RGFW_debugfunc)(RGFW_debugType type, RGFW_errorCode err, RGFW_debugContext ctx, const char* msg); @@ -1300,10 +1304,13 @@ typedef RGFW_ENUM(u8, RGFW_glHints) { RGFW_glCore = 0, RGFW_glCompatibility /*!< RGFW_glProfile options */ }; RGFWDEF void RGFW_setGLHint(RGFW_glHints hint, i32 value); +RGFWDEF RGFW_bool RGFW_extensionSupported(const char* extension, size_t len); /*!< check if whether the specified API extension is supported by the current OpenGL or OpenGL ES context */ RGFWDEF RGFW_proc RGFW_getProcAddress(const char* procname); /*!< get native opengl proc address */ RGFWDEF void RGFW_window_makeCurrent_OpenGL(RGFW_window* win); /*!< to be called by RGFW_window_makeCurrent */ RGFWDEF void RGFW_window_swapBuffers_OpenGL(RGFW_window* win); /*!< swap opengl buffer (only) called by RGFW_window_swapInterval */ void* RGFW_getCurrent_OpenGL(void); /*!< get the current context (OpenGL backend (GLX) (WGL) (EGL) (cocoa) (webgl))*/ + +RGFWDEF RGFW_bool RGFW_extensionSupportedPlatform(const char* extension, size_t len); /*!< check if whether the specified platform-specific API extension is supported by the current OpenGL or OpenGL ES context */ #endif #ifdef RGFW_VULKAN #if defined(RGFW_WAYLAND) && defined(RGFW_X11) @@ -1378,6 +1385,9 @@ RGFWDEF RGFW_window* RGFW_getRootWindow(void); void RGFW_eventQueuePush(RGFW_event event); RGFW_event* RGFW_eventQueuePop(RGFW_window* win); +/* for C++ / C89 */ +#define RGFW_eventQueuePushEx(eventInit) { RGFW_event e; eventInit; RGFW_eventQueuePush(e); } + /*! key codes and mouse icon enums */ @@ -1468,7 +1478,6 @@ typedef RGFW_ENUM(u8, RGFW_key) { RGFW_down, RGFW_left, RGFW_right, - RGFW_insert, RGFW_end, RGFW_home, @@ -1511,7 +1520,6 @@ typedef RGFW_ENUM(u8, RGFW_mouseIcons) { RGFW_mouseNotAllowed, RGFW_mouseIconFinal = 16 /* padding for alignment */ }; - /** @} */ #endif /* RGFW_HEADER */ @@ -1593,7 +1601,7 @@ void RGFW_sendDebugInfo(RGFW_debugType type, RGFW_errorCode err, RGFW_debugConte #ifdef RGFW_BUFFER case RGFW_errBuffer: case RGFW_infoBuffer: printf(" buffer size: %i %i\n", ctx.win->bufferSize.w, ctx.win->bufferSize.h); break; #endif - case RGFW_infoMonitor: printf(": scale (%s):\n rect: {%i, %i, %i, %i}\n physical size:%f %f\n scale: %f %f\n pixelRatio: %f\n refreshRate: %i\n depth: %i\n", ctx.monitor.name, ctx.monitor.x, ctx.monitor.y, ctx.monitor.mode.area.w, ctx.monitor.mode.area.h, ctx.monitor.physW, ctx.monitor.physH, ctx.monitor.scaleX, ctx.monitor.scaleY, ctx.monitor.pixelRatio, ctx.monitor.mode.refreshRate, ctx.monitor.mode.red + ctx.monitor.mode.green + ctx.monitor.mode.blue); break; + case RGFW_infoMonitor: printf(": scale (%s):\n rect: {%i, %i, %i, %i}\n physical size:%f %f\n scale: %f %f\n pixelRatio: %f\n refreshRate: %i\n depth: %i\n", ctx.monitor->name, ctx.monitor->x, ctx.monitor->y, ctx.monitor->mode.area.w, ctx.monitor->mode.area.h, ctx.monitor->physW, ctx.monitor->physH, ctx.monitor->scaleX, ctx.monitor->scaleY, ctx.monitor->pixelRatio, ctx.monitor->mode.refreshRate, ctx.monitor->mode.red + ctx.monitor->mode.green + ctx.monitor->mode.blue); break; case RGFW_infoWindow: printf(" with rect of {%i, %i, %i, %i} \n", ctx.win->r.x, ctx.win->r.y,ctx. win->r.w, ctx.win->r.h); break; case RGFW_errDirectXContext: printf(" srcError %i\n", ctx.srcError); break; default: printf("\n"); @@ -1725,7 +1733,7 @@ void RGFW_init_keys(void) { RGFW_MAP [RGFW_OS_BASED_VALUE(133, 0x15B, 55, DOM_VK_WIN)] = RGFW_superL, #if !defined(RGFW_MACOS) && !defined(RGFW_WASM) RGFW_MAP [RGFW_OS_BASED_VALUE(105, 0x11D, 59, 0)] = RGFW_controlR RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(135, 0x15C, 55, 0)] = RGFW_superR, + RGFW_MAP [RGFW_OS_BASED_VALUE(134, 0x15C, 55, 0)] = RGFW_superR, RGFW_MAP [RGFW_OS_BASED_VALUE(62, 0x036, 56, 0)] = RGFW_shiftR RGFW_NEXT RGFW_MAP [RGFW_OS_BASED_VALUE(108, 0x138, 58, 0)] = RGFW_altR, #endif @@ -1783,15 +1791,13 @@ typedef struct { RGFW_keyState RGFW_keyboard[RGFW_keyLast] = { {0, 0} }; -RGFWDEF void RGFW_resetKey(void); -void RGFW_resetKey(void) { - size_t len = RGFW_keyLast; /*!< last_key == length */ - +RGFWDEF void RGFW_resetKeyPrev(void); +void RGFW_resetKeyPrev(void) { size_t i; /*!< reset each previous state */ - for (i = 0; i < len; i++) - RGFW_keyboard[i].prev = 0; + for (i = 0; i < RGFW_keyLast; i++) RGFW_keyboard[i].prev = 0; } - +RGFWDEF void RGFW_resetKey(void); +void RGFW_resetKey(void) { RGFW_MEMSET(RGFW_keyboard, 0, sizeof(RGFW_keyboard)); } /* this is the end of keycode data */ @@ -1879,17 +1885,16 @@ void RGFW_window_checkMode(RGFW_window* win); void RGFW_window_checkMode(RGFW_window* win) { if (RGFW_window_isMinimized(win)) { win->_flags |= RGFW_windowMinimize; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMinimized, ._win = win}); RGFW_windowMinimizedCallback(win, win->r); } else if (RGFW_window_isMaximized(win)) { win->_flags |= RGFW_windowMaximize; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMaximized, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_windowMaximized; e._win = win); RGFW_windowMaximizedCallback(win, win->r); } else if (((win->_flags & RGFW_windowMinimize) && !RGFW_window_isMaximized(win)) || (win->_flags & RGFW_windowMaximize && !RGFW_window_isMaximized(win))) { win->_flags &= ~(u32)RGFW_windowMinimize; if (RGFW_window_isMaximized(win) == RGFW_FALSE) win->_flags &= ~(u32)RGFW_windowMaximize; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowRestored, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_windowRestored; e._win = win); RGFW_windowRestoredCallback(win, win->r); } } @@ -1948,10 +1953,12 @@ typedef struct RGFW_globalStruct { RGFW_event events[RGFW_MAX_EVENTS]; } RGFW_globalStruct; -#ifndef RGFW_C89 +#if !defined(RGFW_C89) && !defined(__cplusplus) RGFW_globalStruct _RGFW = {.root = NULL, .current = NULL, .windowCount = -1, .eventLen = 0, .eventIndex = 0}; +#define _RGFW_init RGFW_TRUE #else -RGFW_globalStruct _RGFW = {NULL, NULL, -1, 0, 0}; +RGFW_bool _RGFW_init = RGFW_FALSE; +RGFW_globalStruct _RGFW; #endif void RGFW_eventQueuePush(RGFW_event event) { @@ -1961,9 +1968,10 @@ void RGFW_eventQueuePush(RGFW_event event) { } RGFW_event* RGFW_eventQueuePop(RGFW_window* win) { - if (_RGFW.eventLen == 0) return NULL; + RGFW_event* ev; + if (_RGFW.eventLen == 0) return NULL; - RGFW_event* ev = (RGFW_event*)&_RGFW.events[_RGFW.eventIndex]; + ev = (RGFW_event*)&_RGFW.events[_RGFW.eventIndex]; _RGFW.eventLen--; if (_RGFW.eventLen && _RGFW.eventIndex < (_RGFW.eventLen - 1)) @@ -1976,27 +1984,29 @@ RGFW_event* RGFW_eventQueuePop(RGFW_window* win) { return NULL; } + ev->droppedFilesCount = win->event.droppedFilesCount; ev->droppedFiles = win->event.droppedFiles; return ev; } RGFW_event* RGFW_window_checkEventCore(RGFW_window* win); RGFW_event* RGFW_window_checkEventCore(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - if (win->event.type == 0 && _RGFW.eventLen == 0) - RGFW_resetKey(); + RGFW_event* ev; + RGFW_ASSERT(win != NULL); + if (win->event.type == 0 && _RGFW.eventLen == 0) + RGFW_resetKeyPrev(); if (win->event.type == RGFW_quit && win->_flags & RGFW_windowFreeOnClose) { - static RGFW_event ev; - ev = win->event; + static RGFW_event event; + event = win->event; RGFW_window_close(win); - return &ev; + return &event; } if (win->event.type != RGFW_DNDInit) win->event.type = 0; /* check queued events */ - RGFW_event* ev = RGFW_eventQueuePop(win); + ev = RGFW_eventQueuePop(win); if (ev != NULL) { if (ev->type == RGFW_quit) RGFW_window_setShouldClose(win, RGFW_TRUE); win->event = *ev; @@ -2014,7 +2024,7 @@ RGFW_window* RGFW_getRootWindow(void) { return _RGFW.root; } /* do a basic initialization for RGFW_window, this is to standard it for each OS */ void RGFW_window_basic_init(RGFW_window* win, RGFW_rect rect, RGFW_windowFlags flags) { RGFW_UNUSED(flags); - if (_RGFW.windowCount == -1) RGFW_init(); + if (_RGFW.windowCount == -1 || _RGFW_init == RGFW_FALSE) RGFW_init(); _RGFW.windowCount++; /* rect based the requested flags */ @@ -2027,19 +2037,23 @@ void RGFW_window_basic_init(RGFW_window* win, RGFW_rect rect, RGFW_windowFlags f /* set and init the new window's data */ win->r = rect; + win->exitKey = RGFW_escape; win->event.droppedFilesCount = 0; win->_flags = 0 | (win->_flags & RGFW_WINDOW_ALLOC); win->_flags |= flags; win->event.keyMod = 0; - win->_lastMousePoint = RGFW_POINT(0, 0); + win->_lastMousePoint.x = 0; + win->_lastMousePoint.y = 0; win->event.droppedFiles = (char**)RGFW_ALLOC(RGFW_MAX_PATH * RGFW_MAX_DROPS); - RGFW_ASSERT(win->event.droppedFiles != NULL); - - u32 i; - for (i = 0; i < RGFW_MAX_DROPS; i++) - win->event.droppedFiles[i] = (char*)(win->event.droppedFiles + RGFW_MAX_DROPS + (i * RGFW_MAX_PATH)); + RGFW_ASSERT(win->event.droppedFiles != NULL); + + { + u32 i; + for (i = 0; i < RGFW_MAX_DROPS; i++) + win->event.droppedFiles[i] = (char*)(win->event.droppedFiles + RGFW_MAX_DROPS + (i * RGFW_MAX_PATH)); + } } void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags flags) { @@ -2199,10 +2213,11 @@ void RGFW_window_center(RGFW_window* win) { } RGFW_bool RGFW_monitor_scaleToWindow(RGFW_monitor mon, RGFW_window* win) { - RGFW_ASSERT(win != NULL); - RGFW_monitorMode mode; - mode.area = RGFW_AREA(win->r.w, win->r.h); + RGFW_ASSERT(win != NULL); + + mode.area.w = (u32)win->r.w; + mode.area.h = (u32)win->r.h; return RGFW_monitor_requestMode(mon, mode, RGFW_monitorScale); } @@ -2223,7 +2238,7 @@ RGFW_bool RGFW_monitorModeCompare(RGFW_monitorMode mon, RGFW_monitorMode mon2, R } RGFW_bool RGFW_window_shouldClose(RGFW_window* win) { - return (win == NULL || (win->_flags & RGFW_EVENT_QUIT)|| RGFW_isPressed(win, RGFW_escape)); + return (win == NULL || (win->_flags & RGFW_EVENT_QUIT)|| (win->exitKey && RGFW_isPressed(win, win->exitKey))); } void RGFW_window_setShouldClose(RGFW_window* win, RGFW_bool shouldClose) { @@ -2256,15 +2271,15 @@ RGFW_bool RGFW_window_setIcon(RGFW_window* win, u8* icon, RGFW_area a, i32 chann RGFWDEF void RGFW_captureCursor(RGFW_window* win, RGFW_rect); RGFWDEF void RGFW_releaseCursor(RGFW_window* win); -void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area) { - if ((win->_flags & RGFW_HOLD_MOUSE)) - return; +RGFW_bool RGFW_window_mouseHeld(RGFW_window* win) { return RGFW_BOOL(win->_flags & RGFW_HOLD_MOUSE); } + +void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area) { if (!area.w && !area.h) area = RGFW_AREA(win->r.w / 2, win->r.h / 2); win->_flags |= RGFW_HOLD_MOUSE; - RGFW_captureCursor(win, win->r); + RGFW_captureCursor(win, win->r); RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); } @@ -2464,6 +2479,58 @@ void RGFW_setGLHint(RGFW_glHints hint, i32 value) { if (hint < RGFW_glFinalHint && hint) RGFW_GL_HINTS[hint] = value; } +RGFW_bool RGFW_extensionSupportedStr(const char* extensions, const char* ext, size_t len) { + const char *start = extensions; + const char *where; + const char* terminator; + + if (extensions == NULL || ext == NULL) + return RGFW_FALSE; + + where = strstr(extensions, ext); + while (where) { + terminator = where + len; + if ((where == start || *(where - 1) == ' ') && + (*terminator == ' ' || *terminator == '\0')) { + return RGFW_TRUE; + } + where = RGFW_STRSTR(terminator, ext); + } + + return RGFW_FALSE; +} + +RGFW_bool RGFW_extensionSupported(const char* extension, size_t len) { + #ifdef GL_NUM_EXTENSIONS + if (RGFW_GL_HINTS[RGFW_glMajor] >= 3) { + i32 i; + GLint count = 0; + + RGFW_proc RGFW_glGetStringi = RGFW_getProcAddress("glGetStringi"); + RGFW_proc RGFW_glGetIntegerv = RGFW_getProcAddress("RGFW_glGetIntegerv"); + if (RGFW_glGetIntegerv) + ((void(*)(GLenum, GLint*))RGFW_glGetIntegerv)(GL_NUM_EXTENSIONS, &count); + + for (i = 0; RGFW_glGetStringi && i < count; i++) { + const char* en = ((const char* (*)(u32, u32))RGFW_glGetStringi)(GL_EXTENSIONS, (u32)i); + if (en && RGFW_STRNCMP(en, extension, len) == 0) + return RGFW_TRUE; + } + } else +#endif + { + RGFW_proc RGFW_glGetString = RGFW_getProcAddress("glGetString"); + + if (RGFW_glGetString) { + const char* extensions = ((const char*(*)(u32))RGFW_glGetString)(GL_EXTENSIONS); + if ((extensions != NULL) && RGFW_extensionSupportedStr(extensions, extension, len)) + return RGFW_TRUE; + } + } + + return RGFW_extensionSupportedPlatform(extension, len); +} + /* OPENGL normal only (no EGL / OSMesa) */ #if defined(RGFW_OPENGL) && !defined(RGFW_EGL) && !defined(RGFW_CUSTOM_BACKEND) && !defined(RGFW_WASM) @@ -2824,7 +2891,7 @@ void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { if (RGFW_GL_HINTS[RGFW_glReleaseBehavior] == RGFW_releaseFlush) { RGFW_GL_ADD_ATTRIB(0x2097, 0x2098); } else { - RGFW_GL_ADD_ATTRIB(0x2097, 0x0000); + RGFW_GL_ADD_ATTRIB(0x2096, 0x0000); } RGFW_GL_ADD_ATTRIB(EGL_NONE, EGL_NONE); @@ -2886,6 +2953,11 @@ RGFW_proc RGFW_getProcAddress(const char* procname) { return (RGFW_proc) eglGetProcAddress(procname); } +RGFW_bool RGFW_extensionSupportedPlatform(const char* extension, size_t len) { + const char* extensions = eglQueryString(_RGFW.root->src.EGL_display, EGL_EXTENSIONS); + return extensions != NULL && RGFW_extensionSupportedStr(extensions, extension, len); +} + void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { RGFW_ASSERT(win != NULL); @@ -2946,7 +3018,7 @@ wayland: RGFW_bool RGFW_getVKPresentationSupport(VkInstance instance, VkPhysicalDevice physicalDevice, u32 queueFamilyIndex) { RGFW_ASSERT(instance); - if (_RGFW.windowCount == -1) RGFW_init(); + if (_RGFW.windowCount == -1 || _RGFW_init == RGFW_FALSE) RGFW_init(); #ifdef RGFW_X11 RGFW_GOTO_WAYLAND(0); Visual* visual = DefaultVisual(_RGFW.display, DefaultScreen(_RGFW.display)); @@ -3031,8 +3103,10 @@ This is where OS specific stuff starts RGFW_gamepads_name[index][sizeof(RGFW_gamepads_name[index]) - 1] = 0; u8 j; - for (j = 0; j < 16; j++) - RGFW_gamepadPressed[index][j] = (RGFW_keyState){0, 0}; + for (j = 0; j < 16; j++) { + RGFW_gamepadPressed[index][j].prev = 0; + RGFW_gamepadPressed[index][j].current = 0; + } win->event.type = RGFW_gamepadConnected; @@ -3230,7 +3304,7 @@ void xdg_toplevel_close_handler(void *data, if (win == NULL) win = RGFW_key_win; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_quit, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_quit; e._win = win); RGFW_windowQuitCallback(win); } @@ -3256,9 +3330,9 @@ void pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial, stru RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); RGFW_mouse_win = win; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseEnter, - .point = RGFW_POINT(wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)), - ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_mouseEnter; + e.point = RGFW_POINT(wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)); + e._win = win); RGFW_mouseNotifyCallback(win, win->event.point, RGFW_TRUE); } @@ -3268,9 +3342,9 @@ void pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, stru if (RGFW_mouse_win == win) RGFW_mouse_win = NULL; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseLeave, - .point = win->event.point, - ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_mouseLeave; + e.point = win->event.point; + e._win = win); RGFW_mouseNotifyCallback(win, win->event.point, RGFW_FALSE); } @@ -3278,9 +3352,9 @@ void pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fi RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(x); RGFW_UNUSED(y); RGFW_ASSERT(RGFW_mouse_win != NULL); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mousePosChanged, - .point = RGFW_POINT(wl_fixed_to_double(x), wl_fixed_to_double(y)), - ._win = RGFW_mouse_win}); + RGFW_eventQueuePushEx(e.type = RGFW_mousePosChanged; + e.point = RGFW_POINT(wl_fixed_to_double(x), wl_fixed_to_double(y)); + e._win = RGFW_mouse_win); RGFW_mousePosCallback(RGFW_mouse_win, RGFW_POINT(wl_fixed_to_double(x), wl_fixed_to_double(y)), RGFW_mouse_win->event.vector); } @@ -3297,9 +3371,9 @@ void pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial, uin RGFW_mouseButtons[b].prev = RGFW_mouseButtons[b].current; RGFW_mouseButtons[b].current = RGFW_BOOL(state); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonPressed + RGFW_BOOL(state), - .button = (u8)b, - ._win = RGFW_mouse_win}); + RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonPressed + RGFW_BOOL(state); + e.button = (u8)b; + e._win = RGFW_mouse_win); RGFW_mouseButtonCallback(RGFW_mouse_win, (u8)b, 0, RGFW_BOOL(state)); } void pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { @@ -3308,10 +3382,10 @@ void pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_ double scroll = wl_fixed_to_double(value); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonPressed, - .button = RGFW_mouseScrollUp + (scroll < 0), - .scroll = scroll, - ._win = RGFW_mouse_win}); + RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonPressed; + e.button = RGFW_mouseScrollUp + (scroll < 0); + e.scroll = scroll; + e._win = RGFW_mouse_win); RGFW_mouseButtonCallback(RGFW_mouse_win, RGFW_mouseScrollUp + (scroll < 0), scroll, 1); } @@ -3336,8 +3410,11 @@ void keyboard_enter (void *data, struct wl_keyboard *keyboard, uint32_t serial, RGFW_key_win = (RGFW_window*)wl_surface_get_user_data(surface); RGFW_key_win->_flags |= RGFW_windowFocus; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusIn, ._win = RGFW_key_win}); + RGFW_eventQueuePushEx(e.type = RGFW_focusIn, e._win = RGFW_key_win); RGFW_focusCallback(RGFW_key_win, RGFW_TRUE); + + RGFW_resetKey(); + if ((win->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(win, RGFW_AREA(win->r.w, win->r.h)); } void keyboard_leave (void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); @@ -3346,7 +3423,7 @@ void keyboard_leave (void *data, struct wl_keyboard *keyboard, uint32_t serial, if (RGFW_key_win == win) RGFW_key_win = NULL; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusOut, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e._win = win); win->_flags &= ~(u32)RGFW_windowFocus; RGFW_focusCallback(win, RGFW_FALSE); } @@ -3361,11 +3438,11 @@ void keyboard_key (void *data, struct wl_keyboard *keyboard, uint32_t serial, ui RGFW_keyboard[RGFWkey].prev = RGFW_keyboard[RGFWkey].current; RGFW_keyboard[RGFWkey].current = RGFW_BOOL(state); - RGFW_eventQueuePush((RGFW_event){.type = (u8)(RGFW_keyPressed + state), - .key = (u8)RGFWkey, - .keyChar = (u8)keysym, - .repeat = RGFW_isHeld(RGFW_key_win, (u8)RGFWkey), - ._win = RGFW_key_win}); + RGFW_eventQueuePushEx(e.type = (u8)(RGFW_keyPressed + state); + e.key = (u8)RGFWkey; + e.keyChar = (u8)keysym; + e.repeat = RGFW_isHeld(RGFW_key_win, (u8)RGFWkey); + e._win = RGFW_key_win); RGFW_updateKeyMods(RGFW_key_win, RGFW_BOOL(xkb_keymap_mod_get_index(keymap, "Lock")), RGFW_BOOL(xkb_keymap_mod_get_index(keymap, "Mod2")), RGFW_BOOL(xkb_keymap_mod_get_index(keymap, "ScrollLock"))); RGFW_keyCallback(RGFW_key_win, (u8)RGFWkey, (u8)keysym, RGFW_key_win->event.keyMod, RGFW_BOOL(state)); @@ -3601,6 +3678,10 @@ void RGFW_setXInstName(const char* name) { } #if defined(RGFW_OPENGL) && !defined(RGFW_EGL) +RGFW_bool RGFW_extensionSupportedPlatform(const char * extension, size_t len) { + const char* extensions = glXQueryExtensionsString(_RGFW.display, XDefaultScreen(_RGFW.display)); + return (extensions != NULL) && RGFW_extensionSupportedStr(extensions, extension, len); +} RGFW_proc RGFW_getProcAddress(const char* procname) { return (RGFW_proc) glXGetProcAddress((GLubyte*) procname); } #endif @@ -3802,7 +3883,7 @@ void RGFW_window_getVisual(RGFW_window* win, RGFW_bool software) { if (best_fbc == -1) { RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to get a valid GLX visual"); return; - } + } win->src.bestFbc = fbc[best_fbc]; XVisualInfo* vi = glXGetVisualFromFBConfig(win->src.display, win->src.bestFbc); @@ -3814,6 +3895,7 @@ void RGFW_window_getVisual(RGFW_window* win, RGFW_bool software) { XFree(fbc); win->src.visual = *vi; + XFree(vi); #else RGFW_UNUSED(software); win->src.visual.visual = DefaultVisual(win->src.display, DefaultScreen(win->src.display)); @@ -3888,7 +3970,13 @@ RGFW_UNUSED(win); i32 RGFW_init(void) { - RGFW_GOTO_WAYLAND(1); + RGFW_GOTO_WAYLAND(1); +#if defined(RGFW_C89) || defined(__cplusplus) + if (_RGFW_init) return 0; + _RGFW_init = RGFW_TRUE; + _RGFW.root = NULL; _RGFW.current = NULL; _RGFW.windowCount = -1; _RGFW.eventLen = 0; _RGFW.eventIndex = 0; +#endif + #ifdef RGFW_X11 if (_RGFW.windowCount != -1) return 0; #ifdef RGFW_USE_XDL @@ -3937,6 +4025,7 @@ i32 RGFW_init(void) { XInitThreads(); /*!< init X11 threading */ _RGFW.display = XOpenDisplay(0); XSetWindowAttributes wa; + RGFW_MEMSET(&wa, 0, sizeof(wa)); wa.event_mask = PropertyChangeMask; _RGFW.helperWindow = XCreateWindow(_RGFW.display, XDefaultRootWindow(_RGFW.display), 0, 0, 1, 1, 0, 0, InputOnly, DefaultVisual(_RGFW.display, DefaultScreen(_RGFW.display)), CWEventMask, &wa); @@ -3953,7 +4042,7 @@ i32 RGFW_init(void) { XkbGetNames(_RGFW.display, XkbKeyNamesMask, desc); - memset(&rec, 0, sizeof(rec)); + RGFW_MEMSET(&rec, 0, sizeof(rec)); rec.keycodes = (char*)"evdev"; evdesc = XkbGetKeyboardByName(_RGFW.display, XkbUseCoreKbd, &rec, XkbGBN_KeyNamesMask, XkbGBN_KeyNamesMask, False); /* memo: RGFW_keycodes[x11 keycode] = rgfw keycode */ @@ -3979,7 +4068,7 @@ wayland: _RGFW.wl_display = wl_display_connect(NULL); #endif _RGFW.windowCount = 0; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context initialized"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context initialized"); return 0; } @@ -3999,21 +4088,18 @@ RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowF /* make X window attrubutes */ XSetWindowAttributes swa; + RGFW_MEMSET(&swa, 0, sizeof(swa)); + Colormap cmap; swa.colormap = cmap = XCreateColormap(win->src.display, DefaultRootWindow(win->src.display), win->src.visual.visual, AllocNone); - - swa.background_pixmap = None; - swa.border_pixel = 0; swa.event_mask = event_mask; - swa.background_pixel = 0; - /* create the window */ win->src.window = XCreateWindow(win->src.display, DefaultRootWindow(win->src.display), win->r.x, win->r.y, (u32)win->r.w, (u32)win->r.h, 0, win->src.visual.depth, InputOutput, win->src.visual.visual, - CWColormap | CWBorderPixel | CWBackPixel | CWEventMask, &swa); + CWColormap | CWBorderPixel | CWEventMask, &swa); XFreeColors(win->src.display, cmap, NULL, 0, 0); @@ -4085,9 +4171,11 @@ RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowF RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); RGFW_window_setMouseDefault(win); RGFW_window_setFlags(win, flags); + + win->src.r = win->r; RGFW_window_show(win); - return win; /*return newly created window */ + return win; /*return newly created window */ #endif #ifdef RGFW_WAYLAND wayland: @@ -4625,7 +4713,7 @@ RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { if (version > 5) break; - size_t i; + size_t i; for (i = 0; i < win->event.droppedFilesCount; i++) win->event.droppedFiles[i][0] = '\0'; @@ -4742,7 +4830,11 @@ RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { win->_flags |= RGFW_windowFocus; win->event.type = RGFW_focusIn; RGFW_focusCallback(win, 1); - break; + + + RGFW_resetKey(); + if ((win->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(win, RGFW_AREA(win->r.w, win->r.h)); + break; case FocusOut: if ((win->_flags & RGFW_windowFullscreen)) RGFW_window_minimize(win); @@ -4769,17 +4861,17 @@ RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { case ConfigureNotify: { /* detect resize */ RGFW_window_checkMode(win); - if (E.xconfigure.width != win->r.w || E.xconfigure.height != win->r.h) { + if (E.xconfigure.width != win->src.r.w || E.xconfigure.height != win->src.r.h) { win->event.type = RGFW_windowResized; - win->r = RGFW_RECT(win->r.x, win->r.y, E.xconfigure.width, E.xconfigure.height); + win->src.r = win->r = RGFW_RECT(win->src.r.x, win->src.r.y, E.xconfigure.width, E.xconfigure.height); RGFW_windowResizedCallback(win, win->r); break; } /* detect move */ - if (E.xconfigure.x != win->r.x || E.xconfigure.y != win->r.y) { + if (E.xconfigure.x != win->src.r.x || E.xconfigure.y != win->src.r.y) { win->event.type = RGFW_windowMoved; - win->r = RGFW_RECT(E.xconfigure.x, E.xconfigure.y, win->r.w, win->r.h); + win->src.r = win->r = RGFW_RECT(E.xconfigure.x, E.xconfigure.y, win->src.r.w, win->src.r.h); RGFW_windowMovedCallback(win, win->r); break; } @@ -4876,11 +4968,9 @@ void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { RGFW_ASSERT(win != NULL); - if (a.w == 0 && a.h == 0) - return; - + long flags; XSizeHints hints; - long flags; + RGFW_MEMSET(&hints, 0, sizeof(XSizeHints)); XGetWMNormalHints(win->src.display, win->src.window, &hints, &flags); @@ -4895,11 +4985,9 @@ void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { RGFW_ASSERT(win != NULL); - if (a.w == 0 && a.h == 0) - a = RGFW_getScreenSize(); - + long flags; XSizeHints hints; - long flags; + RGFW_MEMSET(&hints, 0, sizeof(XSizeHints)); XGetWMNormalHints(win->src.display, win->src.window, &hints, &flags); @@ -5058,9 +5146,14 @@ void RGFW_window_setName(RGFW_window* win, const char* name) { XStoreName(win->src.display, win->src.window, name); RGFW_LOAD_ATOM(_NET_WM_NAME); - XChangeProperty( + + char buf[256]; + RGFW_MEMSET(buf, 0, sizeof(buf)); + RGFW_STRNCPY(buf, name, sizeof(buf) - 1); + + XChangeProperty( win->src.display, win->src.window, _NET_WM_NAME, RGFW_XUTF8_STRING, - 8, PropModeReplace, (u8*)name, 256 + 8, PropModeReplace, (u8*)buf, sizeof(buf) ); #endif #ifdef RGFW_WAYLAND @@ -5310,12 +5403,13 @@ void RGFW_window_show(RGFW_window* win) { RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { RGFW_GOTO_WAYLAND(1); - #ifdef RGFW_X11 +#ifdef RGFW_X11 RGFW_init(); - if (XGetSelectionOwner(_RGFW.display, RGFW_XCLIPBOARD) == _RGFW.helperWindow) { - if (str != NULL) - RGFW_STRNCPY(str, _RGFW.clipboard, _RGFW.clipboard_len); - return (RGFW_ssize_t)_RGFW.clipboard_len; + if (XGetSelectionOwner(_RGFW.display, RGFW_XCLIPBOARD) == _RGFW.helperWindow) { + if (str != NULL) + RGFW_STRNCPY(str, _RGFW.clipboard, _RGFW.clipboard_len - 1); + _RGFW.clipboard[_RGFW.clipboard_len - 1] = '\0'; + return (RGFW_ssize_t)_RGFW.clipboard_len - 1; } XEvent event; @@ -5328,25 +5422,25 @@ RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { XConvertSelection(_RGFW.display, RGFW_XCLIPBOARD, RGFW_XUTF8_STRING, XSEL_DATA, _RGFW.helperWindow, CurrentTime); XSync(_RGFW.display, 0); - while (1) { - XNextEvent(_RGFW.display, &event); - if (event.type != SelectionNotify) continue; + while (1) { + XNextEvent(_RGFW.display, &event); + if (event.type != SelectionNotify) continue; - if (event.xselection.selection != RGFW_XCLIPBOARD || event.xselection.property == 0) - return -1; - break; + if (event.xselection.selection != RGFW_XCLIPBOARD || event.xselection.property == 0) + return -1; + break; } XGetWindowProperty(event.xselection.display, event.xselection.requestor, - event.xselection.property, 0L, (~0L), 0, AnyPropertyType, &target, - &format, &sizeN, &N, (u8**) &data); + event.xselection.property, 0L, (~0L), 0, AnyPropertyType, &target, + &format, &sizeN, &N, (u8**) &data); RGFW_ssize_t size; if (sizeN > strCapacity && str != NULL) size = -1; if ((target == RGFW_XUTF8_STRING || target == XA_STRING) && str != NULL) { - RGFW_MEMCPY(str, data, sizeN); + RGFW_MEMCPY(str, data, sizeN); str[sizeN] = '\0'; XFree(data); } else if (str != NULL) size = -1; @@ -5354,11 +5448,11 @@ RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property); size = (RGFW_ssize_t)sizeN; - return size; - #endif - #if defined(RGFW_WAYLAND) - wayland: return 0; - #endif + return size; +#endif +#if defined(RGFW_WAYLAND) +wayland: return 0; +#endif } i32 RGFW_XHandleClipboardSelectionHelper(void) { @@ -5407,9 +5501,10 @@ void RGFW_writeClipboard(const char* text, u32 textLen) { RGFW_FREE(_RGFW.clipboard); _RGFW.clipboard = (char*)RGFW_ALLOC(textLen); - RGFW_ASSERT(_RGFW.clipboard != NULL); + RGFW_ASSERT(_RGFW.clipboard != NULL); - RGFW_STRNCPY(_RGFW.clipboard, text, textLen); + RGFW_STRNCPY(_RGFW.clipboard, text, textLen - 1); + _RGFW.clipboard[textLen - 1] = '\0'; _RGFW.clipboard_len = textLen; #endif #ifdef RGFW_WAYLAND @@ -5543,7 +5638,8 @@ RGFW_monitor RGFW_XCreateMonitor(i32 screen) { RGFW_splitBPP((u32)DefaultDepth(display, DefaultScreen(display)), &monitor.mode); char* name = XDisplayName((const char*)display); - RGFW_MEMCPY(monitor.name, name, 128); + RGFW_STRNCPY(monitor.name, name, sizeof(monitor.name) - 1); + monitor.name[sizeof(monitor.name) - 1] = '\0'; float dpi = XGetSystemContentDPI(display, screen); monitor.pixelRatio = dpi >= 192.0f ? 2 : 1; @@ -5575,7 +5671,8 @@ RGFW_monitor RGFW_XCreateMonitor(i32 screen) { float physW = (float)info->mm_width / 25.4f; float physH = (float)info->mm_height / 25.4f; - RGFW_MEMCPY(monitor.name, info->name, 128); + RGFW_STRNCPY(monitor.name, info->name, sizeof(monitor.name) - 1); + monitor.name[sizeof(monitor.name) - 1] = '\0'; if ((u8)physW && (u8)physH) { monitor.physW = physW; @@ -5690,12 +5787,15 @@ wayland: } RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { - RGFW_ASSERT(win != NULL); + RGFW_monitor mon; + RGFW_MEMSET(&mon, 0, sizeof(mon)); + + RGFW_ASSERT(win != NULL); RGFW_GOTO_WAYLAND(1); #ifdef RGFW_X11 XWindowAttributes attrs; if (!XGetWindowAttributes(win->src.display, win->src.window, &attrs)) { - return (RGFW_monitor){0}; + return mon; } i32 i; @@ -5709,7 +5809,7 @@ RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { #ifdef RGFW_WAYLAND wayland: #endif - return (RGFW_monitor){0}; + return mon; } @@ -5796,7 +5896,7 @@ void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { #endif void RGFW_deinit(void) { - if (_RGFW.windowCount == -1) return; + if (_RGFW.windowCount == -1 || _RGFW_init == RGFW_FALSE) return; #define RGFW_FREE_LIBRARY(x) if (x != NULL) dlclose(x); x = NULL; #ifdef RGFW_X11 /* to save the clipboard on the x server after the window is closed */ @@ -5847,7 +5947,7 @@ void RGFW_deinit(void) { _RGFW.root = NULL; _RGFW.windowCount = -1; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context deinitialized"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context deinitialized"); } void RGFW_window_close(RGFW_window* win) { @@ -6120,7 +6220,20 @@ PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL; #define wglShareLists wglShareListsSRC #endif -#ifdef RGFW_OPENGL +#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) +RGFW_bool RGFW_extensionSupportedPlatform(const char * extension, size_t len) { + const char* extensions = NULL; + + RGFW_proc proc = RGFW_getProcAddress("wglGetExtensionsStringARB"); + RGFW_proc proc2 = RGFW_getProcAddress("wglGetExtensionsStringEXT"); + + if (proc) + extensions = ((const char* (*)(HDC))proc)(wglGetCurrentDC()); + else if (proc2) + extensions = ((const char*(*)(void))proc2)(); + + return extensions != NULL && RGFW_extensionSupportedStr(extensions, extension, len); +} RGFW_proc RGFW_getProcAddress(const char* procname) { RGFW_proc proc = (RGFW_proc)wglGetProcAddress(procname); @@ -6174,14 +6287,14 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) switch (message) { case WM_CLOSE: case WM_QUIT: - RGFW_eventQueuePush((RGFW_event){.type = RGFW_quit, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_quit; e._win = win); RGFW_windowQuitCallback(win); return 0; case WM_ACTIVATE: { RGFW_bool inFocus = RGFW_BOOL(LOWORD(wParam) != WA_INACTIVE); if (inFocus) win->_flags |= RGFW_windowFocus; else win->_flags &= ~ (u32)RGFW_windowFocus; - RGFW_eventQueuePush((RGFW_event){.type = (RGFW_eventType)((u8)RGFW_focusOut - inFocus), ._win = win}); + RGFW_eventQueuePushEx(e.type = (RGFW_eventType)((u8)RGFW_focusOut - inFocus); e._win = win); RGFW_focusCallback(win, inFocus); @@ -6196,7 +6309,7 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_MOVE: win->r.x = windowRect.left; win->r.y = windowRect.top; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMoved, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_windowMoved; e._win = win); RGFW_windowMovedCallback(win, win->r); return DefWindowProcW(hWnd, message, wParam, lParam); case WM_SIZE: { @@ -6224,7 +6337,7 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) win->r.w = windowRect.right - windowRect.left; win->r.h = (windowRect.bottom - windowRect.top) - (i32)win->src.hOffset; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowResized, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e._win = win); RGFW_windowResizedCallback(win, win->r); RGFW_window_checkMode(win); return DefWindowProcW(hWnd, message, wParam, lParam); @@ -6236,7 +6349,7 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) const float scaleX = HIWORD(wParam) / (float) 96; const float scaleY = LOWORD(wParam) / (float) 96; RGFW_scaleUpdatedCallback(win, scaleX, scaleY); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_scaleUpdated, .scaleX = scaleX, .scaleY = scaleY , ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_scaleUpdated; e.scaleX = scaleX; e.scaleY = scaleY; e._win = win); return DefWindowProcW(hWnd, message, wParam, lParam); } #endif @@ -6254,7 +6367,7 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_PAINT: { PAINTSTRUCT ps; BeginPaint(hWnd, &ps); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowRefresh, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_windowRefresh; e._win = win); RGFW_windowRefreshCallback(win); EndPaint(hWnd, &ps); @@ -6307,7 +6420,10 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) __declspec(dllimport) u32 __stdcall timeBeginPeriod(u32 uPeriod); __declspec(dllimport) u32 __stdcall timeEndPeriod(u32 uPeriod); #endif -#define RGFW_PROC_DEF(proc, name) if (name##SRC == NULL && proc != NULL) name##SRC = (PFN_##name)(RGFW_proc)GetProcAddress((proc), (#name)); +#define RGFW_PROC_DEF(proc, name) if (name##SRC == NULL && proc != NULL) { \ + name##SRC = (PFN_##name)(RGFW_proc)GetProcAddress((proc), (#name)); \ + RGFW_ASSERT(name##SRC != NULL); \ + } #ifndef RGFW_NO_XINPUT void RGFW_loadXInput(void); @@ -6322,9 +6438,9 @@ void RGFW_loadXInput(void) { } if (XInputGetStateSRC == NULL) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errFailedFuncLoad, RGFW_DEBUG_CTX(.win = _RGFW.root, .srcError = 0), "Failed to load XInputGetState"); + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errFailedFuncLoad, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to load XInputGetState"); if (XInputGetKeystrokeSRC == NULL) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errFailedFuncLoad, RGFW_DEBUG_CTX(.win = _RGFW.root, .srcError = 0), "Failed to load XInputGetKeystroke"); + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errFailedFuncLoad, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to load XInputGetKeystroke"); } #endif @@ -6333,7 +6449,7 @@ void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area){ win->buffer = buffer; win->bufferSize = area; - BITMAPV5HEADER bi = { 0 }; + BITMAPV5HEADER bi; ZeroMemory(&bi, sizeof(bi)); bi.bV5Size = sizeof(bi); bi.bV5Width = (i32)area.w; @@ -6383,7 +6499,7 @@ void RGFW_captureCursor(RGFW_window* win, RGFW_rect rect) { RegisterRawInputDevices(&id, 1, sizeof(id)); } -#define RGFW_LOAD_LIBRARY(x, lib) if (x == NULL) x = LoadLibraryA(lib) +#define RGFW_LOAD_LIBRARY(x, lib) if (x == NULL) { x = LoadLibraryA(lib); RGFW_ASSERT(x != NULL); } #ifdef RGFW_DIRECTX int RGFW_window_createDXSwapChain(RGFW_window* win, IDXGIFactory* pFactory, IUnknown* pDevice, IDXGISwapChain** swapchain) { @@ -6403,7 +6519,7 @@ int RGFW_window_createDXSwapChain(RGFW_window* win, IDXGIFactory* pFactory, IUnk HRESULT hr = pFactory->lpVtbl->CreateSwapChain(pFactory, (IUnknown*)pDevice, &swapChainDesc, swapchain); if (FAILED(hr)) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errDirectXContext, RGFW_DEBUG_CTX(.win = win, .srcError = hr), "Failed to create DirectX swap chain!"); + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errDirectXContext, RGFW_DEBUG_CTX(win, hr), "Failed to create DirectX swap chain!"); return -2; } @@ -6533,7 +6649,13 @@ void RGFW_window_freeOpenGL(RGFW_window* win) { i32 RGFW_init(void) { - #ifndef RGFW_NO_XINPUT +#if defined(RGFW_C89) || defined(__cplusplus) + if (_RGFW_init) return 0; + _RGFW_init = RGFW_TRUE; + _RGFW.root = NULL; _RGFW.current = NULL; _RGFW.windowCount = -1; _RGFW.eventLen = 0; _RGFW.eventIndex = 0; +#endif + + #ifndef RGFW_NO_XINPUT if (RGFW_XInput_dll == NULL) RGFW_loadXInput(); #endif @@ -6562,7 +6684,6 @@ i32 RGFW_init(void) { #ifndef RGFW_NO_LOAD_WGL RGFW_PROC_DEF(RGFW_wgl_dll, wglCreateContext); RGFW_PROC_DEF(RGFW_wgl_dll, wglDeleteContext); - RGFW_PROC_DEF(RGFW_wgl_dll, wglDeleteContext); RGFW_PROC_DEF(RGFW_wgl_dll, wglGetProcAddress); RGFW_PROC_DEF(RGFW_wgl_dll, wglMakeCurrent); RGFW_PROC_DEF(RGFW_wgl_dll, wglGetCurrentDC); @@ -6574,7 +6695,7 @@ i32 RGFW_init(void) { _RGFW.hiddenMouse = RGFW_loadMouse(RGFW_blk, RGFW_AREA(1, 1), 4); _RGFW.windowCount = 0; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context initialized"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context initialized"); return 1; } @@ -7082,7 +7203,7 @@ RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { break; unsigned size = sizeof(RAWINPUT); - static RAWINPUT raw = {0}; + static RAWINPUT raw; GetRawInputData((HRAWINPUT)msg.lParam, RID_INPUT, &raw, &size, sizeof(RAWINPUTHEADER)); @@ -7233,7 +7354,7 @@ RGFW_monitor win32CreateMonitor(HMONITOR src) { if (!(dd.StateFlags & DISPLAY_DEVICE_ACTIVE)) continue; - DEVMODE dm; + DEVMODEA dm; ZeroMemory(&dm, sizeof(dm)); dm.dmSize = sizeof(dm); @@ -7246,7 +7367,8 @@ RGFW_monitor win32CreateMonitor(HMONITOR src) { mdd.cb = sizeof(mdd); if (EnumDisplayDevicesA(dd.DeviceName, (DWORD)deviceNum, &mdd, 0)) { - RGFW_MEMCPY(monitor.name, mdd.DeviceString, 128); + RGFW_STRNCPY(monitor.name, mdd.DeviceString, sizeof(monitor.name) - 1); + monitor.name[sizeof(monitor.name) - 1] = '\0'; break; } } @@ -7334,13 +7456,14 @@ RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { } RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { - HMONITOR src = MonitorFromPoint((POINT) { mon.x, mon.y }, MONITOR_DEFAULTTOPRIMARY); + POINT p = { mon.x, mon.y }; + HMONITOR src = MonitorFromPoint(p, MONITOR_DEFAULTTOPRIMARY); MONITORINFOEX monitorInfo; monitorInfo.cbSize = sizeof(MONITORINFOEX); GetMonitorInfoA(src, (LPMONITORINFO)&monitorInfo); - DISPLAY_DEVICE dd; + DISPLAY_DEVICEA dd; dd.cb = sizeof(dd); /* Enumerate display devices */ @@ -7349,10 +7472,10 @@ RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW if (!(dd.StateFlags & DISPLAY_DEVICE_ACTIVE)) continue; - if (strcmp(dd.DeviceName, monitorInfo.szDevice) != 0) + if (strcmp(dd.DeviceName, (const char*)monitorInfo.szDevice) != 0) continue; - DEVMODE dm; + DEVMODEA dm; ZeroMemory(&dm, sizeof(dm)); dm.dmSize = sizeof(dm); @@ -7500,7 +7623,7 @@ void RGFW_deinit(void) { RGFW_freeMouse(_RGFW.hiddenMouse); _RGFW.windowCount = -1; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context deinitialized"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context deinitialized"); } @@ -7885,10 +8008,10 @@ typedef RGFW_ENUM(NSUInteger, NSBitmapFormat) { NSBitmapFormatAlphaNonpremultiplied = 1 << 1, /* 0 means is premultiplied */ NSBitmapFormatFloatingpointSamples = 1 << 2, /* 0 is integer */ - NSBitmapFormatSixteenBitLittleEndian API_AVAILABLE(macos(10.10)) = (1 << 8), - NSBitmapFormatThirtyTwoBitLittleEndian API_AVAILABLE(macos(10.10)) = (1 << 9), - NSBitmapFormatSixteenBitBigEndian API_AVAILABLE(macos(10.10)) = (1 << 10), - NSBitmapFormatThirtyTwoBitBigEndian API_AVAILABLE(macos(10.10)) = (1 << 11) + NSBitmapFormatSixteenBitLittleEndian = (1 << 8), + NSBitmapFormatThirtyTwoBitLittleEndian = (1 << 9), + NSBitmapFormatSixteenBitBigEndian = (1 << 10), + NSBitmapFormatThirtyTwoBitBigEndian = (1 << 11) }; id NSBitmapImageRep_initWithBitmapData(unsigned char** planes, NSInteger width, NSInteger height, NSInteger bps, NSInteger spp, bool alpha, bool isPlanar, const char* colorSpaceName, NSBitmapFormat bitmapFormat, NSInteger rowBytes, NSInteger pixelBits); @@ -7907,18 +8030,17 @@ id NSColor_colorWithSRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha ((id)nsclass, func, red, green, blue, alpha); } -#define NS_OPENGL_ENUM_DEPRECATED(minVers, maxVers) API_AVAILABLE(macos(minVers)) typedef RGFW_ENUM(NSInteger, NSOpenGLContextParameter) { - NSOpenGLContextParameterSwapInterval NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 222, /* 1 param. 0 -> Don't sync, 1 -> Sync to vertical retrace */ - NSOpenGLContextParametectxaceOrder NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 235, /* 1 param. 1 -> Above Window (default), -1 -> Below Window */ - NSOpenGLContextParametectxaceOpacity NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 236, /* 1 param. 1-> Surface is opaque (default), 0 -> non-opaque */ - NSOpenGLContextParametectxaceBackingSize NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 304, /* 2 params. Width/height of surface backing size */ - NSOpenGLContextParameterReclaimResources NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 308, /* 0 params. */ - NSOpenGLContextParameterCurrentRendererID NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 309, /* 1 param. Retrieves the current renderer ID */ - NSOpenGLContextParameterGPUVertexProcessing NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 310, /* 1 param. Currently processing vertices with GPU (get) */ - NSOpenGLContextParameterGPUFragmentProcessing NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 311, /* 1 param. Currently processing fragments with GPU (get) */ - NSOpenGLContextParameterHasDrawable NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 314, /* 1 param. Boolean returned if drawable is attached */ - NSOpenGLContextParameterMPSwapsInFlight NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 315, /* 1 param. Max number of swaps queued by the MP GL engine */ + NSOpenGLContextParameterSwapInterval = 222, /* 1 param. 0 -> Don't sync, 1 -> Sync to vertical retrace */ + NSOpenGLContextParametectxaceOrder = 235, /* 1 param. 1 -> Above Window (default), -1 -> Below Window */ + NSOpenGLContextParametectxaceOpacity = 236, /* 1 param. 1-> Surface is opaque (default), 0 -> non-opaque */ + NSOpenGLContextParametectxaceBackingSize = 304, /* 2 params. Width/height of surface backing size */ + NSOpenGLContextParameterReclaimResources = 308, /* 0 params. */ + NSOpenGLContextParameterCurrentRendererID = 309, /* 1 param. Retrieves the current renderer ID */ + NSOpenGLContextParameterGPUVertexProcessing = 310, /* 1 param. Currently processing vertices with GPU (get) */ + NSOpenGLContextParameterGPUFragmentProcessing = 311, /* 1 param. Currently processing fragments with GPU (get) */ + NSOpenGLContextParameterHasDrawable = 314, /* 1 param. Boolean returned if drawable is attached */ + NSOpenGLContextParameterMPSwapsInFlight = 315, /* 1 param. Max number of swaps queued by the MP GL engine */ NSOpenGLContextParameterSwapRectangle API_DEPRECATED("", macos(10.0, 10.14)) = 200, /* 4 params. Set or get the swap rectangle {x, y, w, h} */ NSOpenGLContextParameterSwapRectangleEnable API_DEPRECATED("", macos(10.0, 10.14)) = 201, /* Enable or disable the swap rectangle */ @@ -8065,6 +8187,8 @@ id NSWindow_contentView(id window) { */ #ifdef RGFW_OPENGL +/* MacOS opengl API spares us yet again (there are no extensions) */ +RGFW_bool RGFW_extensionSupportedPlatform(const char * extension, size_t len) { RGFW_UNUSED(extension); RGFW_UNUSED(len); return RGFW_FALSE; } CFBundleRef RGFWnsglFramework = NULL; RGFW_proc RGFW_getProcAddress(const char* procname) { @@ -8091,7 +8215,7 @@ u32 RGFW_OnClose(id self) { if (win == NULL) return true; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_quit, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_quit; e._win = win); RGFW_windowQuitCallback(win); return false; @@ -8115,9 +8239,9 @@ NSDragOperation draggingUpdated(id self, SEL sel, id sender) { return 0; NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(sender, sel_registerName("draggingLocation")); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_DNDInit, - .point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)), - ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_DNDInit; + e.point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); + e._win = win); RGFW_dndInitCallback(win, win->event.point); return NSDragOperationCopy; @@ -8174,15 +8298,17 @@ bool performDragOperation(id self, SEL sel, id sender) { for (i = 0; i < count; i++) { id fileURL = objc_msgSend_arr(fileURLs, sel_registerName("objectAtIndex:"), i); const char *filePath = ((const char* (*)(id, SEL))objc_msgSend)(fileURL, sel_registerName("UTF8String")); - RGFW_MEMCPY(win->event.droppedFiles[i], filePath, RGFW_MAX_PATH); + RGFW_STRNCPY(win->event.droppedFiles[i], filePath, RGFW_MAX_PATH - 1); win->event.droppedFiles[i][RGFW_MAX_PATH - 1] = '\0'; } NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(sender, sel_registerName("draggingLocation")); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_DND, - .point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)), - .droppedFilesCount = (size_t)count, - ._win = win}); - + + win->event.droppedFilesCount = (size_t)count; + RGFW_eventQueuePushEx(e.type = RGFW_DND; + e.point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); + e.droppedFilesCount = (size_t)count; + e._win = win); + RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); return false; @@ -8192,6 +8318,51 @@ bool performDragOperation(id self, SEL sel, id sender) { #include #include +u32 RGFW_osx_getFallbackRefreshRate(CGDirectDisplayID displayID) { + u32 refreshRate = 0; + io_iterator_t it; + io_service_t service; + CFNumberRef indexRef, clockRef, countRef; + uint32_t clock, count; + +#ifdef kIOMainPortDefault + if (IOServiceGetMatchingServices(kIOMainPortDefault, IOServiceMatching("IOFramebuffer"), &it) != 0) +#elif defined(kIOMasterPortDefault) + if (IOServiceGetMatchingServices(kIOMainPortDefault, IOServiceMatching("IOFramebuffer"), &it) != 0) +#endif + return RGFW_FALSE; + + while ((service = IOIteratorNext(it)) != 0) { + uint32_t index; + indexRef = (CFNumberRef)IORegistryEntryCreateCFProperty(service, CFSTR("IOFramebufferOpenGLIndex"), kCFAllocatorDefault, kNilOptions); + if (indexRef == 0) continue; + + if (CFNumberGetValue(indexRef, kCFNumberIntType, &index) && CGOpenGLDisplayMaskToDisplayID(1 << index) == displayID) { + CFRelease(indexRef); + break; + } + + CFRelease(indexRef); + } + + if (service) { + clockRef = (CFNumberRef)IORegistryEntryCreateCFProperty(service, CFSTR("IOFBCurrentPixelClock"), kCFAllocatorDefault, kNilOptions); + if (clockRef) { + if (CFNumberGetValue(clockRef, kCFNumberIntType, &clock) && clock) { + countRef = (CFNumberRef)IORegistryEntryCreateCFProperty(service, CFSTR("IOFBCurrentPixelCount"), kCFAllocatorDefault, kNilOptions); + if (countRef && CFNumberGetValue(countRef, kCFNumberIntType, &count) && count) { + refreshRate = (u32)RGFW_ROUND(clock / (double) count); + CFRelease(countRef); + } + } + CFRelease(clockRef); + } + } + + IOObjectRelease(it); + return refreshRate; +} + IOHIDDeviceRef RGFW_osxControllers[4] = {NULL}; size_t findControllerIndex(IOHIDDeviceRef device) { @@ -8239,10 +8410,10 @@ void RGFW__osxInputValueChangedCallback(void *context, IOReturn result, void *se RGFW_gamepadButtonCallback(_RGFW.root, (u16)index, button, (u8)intValue); RGFW_gamepadPressed[index][button].prev = RGFW_gamepadPressed[index][button].current; RGFW_gamepadPressed[index][button].current = RGFW_BOOL(intValue); - RGFW_eventQueuePush((RGFW_event){.type = intValue ? RGFW_gamepadButtonPressed: RGFW_gamepadButtonReleased, - .button = button, - .gamepad = (u16)index, - ._win = _RGFW.root}); + RGFW_eventQueuePushEx(e.type = intValue ? RGFW_gamepadButtonPressed: RGFW_gamepadButtonReleased; + e.button = button; + e.gamepad = (u16)index; + e._win = _RGFW.root); break; } case kHIDPage_GenericDesktop: { @@ -8264,12 +8435,15 @@ void RGFW__osxInputValueChangedCallback(void *context, IOReturn result, void *se default: return; } - RGFW_eventQueuePush((RGFW_event){.type = RGFW_gamepadAxisMove, - .gamepad = (u16)index, - .axis = {RGFW_gamepadAxes[index][0], RGFW_gamepadAxes[index][1], - RGFW_gamepadAxes[index][2], RGFW_gamepadAxes[index][3]}, - .whichAxis = whichAxis, - ._win = _RGFW.root}); + RGFW_event e; + e.type = RGFW_gamepadAxisMove; + e.gamepad = (u16)index; + e.whichAxis = whichAxis; + e._win = _RGFW.root; + for (size_t i = 0; i < 4; i++) + e.axis[i] = RGFW_gamepadAxes[index][i]; + + RGFW_eventQueuePush(e); RGFW_gamepadAxisCallback(_RGFW.root, (u16)index, RGFW_gamepadAxes[index], 2, whichAxis); } @@ -8313,9 +8487,9 @@ void RGFW__osxDeviceAddedCallback(void* context, IOReturn result, void *sender, RGFW_gamepads[i] = (u16)i; RGFW_gamepadCount++; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_gamepadConnected, - .gamepad = (u16)i, - ._win = _RGFW.root}); + RGFW_eventQueuePushEx(e.type = RGFW_gamepadConnected; + e.gamepad = (u16)i; + e._win = _RGFW.root); RGFW_gamepadCallback(_RGFW.root, (u16)i, 1); break; @@ -8337,9 +8511,9 @@ void RGFW__osxDeviceRemovedCallback(void *context, IOReturn result, void *sender if (index != (size_t)-1) RGFW_osxControllers[index] = NULL; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_gamepadDisconnected, - .gamepad = (u16)index, - ._win = _RGFW.root}); + RGFW_eventQueuePushEx(e.type = RGFW_gamepadDisconnected; + e.gamepad = (u16)index; + e._win = _RGFW.root); RGFW_gamepadCallback(_RGFW.root, (u16)index, 0); RGFW_gamepadCount--; @@ -8418,7 +8592,7 @@ void RGFW__osxWindowDeminiaturize(id self, SEL sel) { if (win == NULL) return; win->_flags |= RGFW_windowMinimize; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowRestored, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_windowRestored; e._win = win); RGFW_windowRestoredCallback(win, win->r); } @@ -8429,7 +8603,7 @@ void RGFW__osxWindowMiniaturize(id self, SEL sel) { if (win == NULL) return; win->_flags &= ~(u32)RGFW_windowMinimize; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMinimized, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_windowMinimized; e._win = win); RGFW_windowMinimizedCallback(win, win->r); } @@ -8441,9 +8615,12 @@ void RGFW__osxWindowBecameKey(id self, SEL sel) { if (win == NULL) return; win->_flags |= RGFW_windowFocus; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusIn, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_focusIn; e._win = win); RGFW_focusCallback(win, RGFW_TRUE); + + RGFW_resetKey(); + if ((win->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(win, RGFW_AREA(win->r.w, win->r.h)); } void RGFW__osxWindowResignKey(id self, SEL sel) { @@ -8453,7 +8630,7 @@ void RGFW__osxWindowResignKey(id self, SEL sel) { if (win == NULL) return; win->_flags &= ~(u32)RGFW_windowFocus; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusOut, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e._win = win); RGFW_focusCallback(win, RGFW_FALSE); } @@ -8470,17 +8647,17 @@ NSSize RGFW__osxWindowResize(id self, SEL sel, NSSize frameSize) { RGFW_monitor mon = RGFW_window_getMonitor(win); if ((i32)mon.mode.area.w == win->r.w && (i32)mon.mode.area.h - 102 <= win->r.h) { win->_flags |= RGFW_windowMaximize; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMaximized, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_windowMaximized; e._win = win); RGFW_windowMaximizedCallback(win, win->r); } else if (win->_flags & RGFW_windowMaximize) { win->_flags &= ~(u32)RGFW_windowMaximize; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowRestored, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_windowRestored; e._win = win); RGFW_windowRestoredCallback(win, win->r); } - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowResized, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e._win = win); RGFW_windowResizedCallback(win, win->r); return frameSize; } @@ -8496,7 +8673,7 @@ void RGFW__osxWindowMove(id self, SEL sel) { win->r.x = (i32) frame.origin.x; win->r.y = (i32) frame.origin.y; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowMoved, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_windowMoved; e._win = win); RGFW_windowMovedCallback(win, win->r); } @@ -8508,7 +8685,7 @@ void RGFW__osxViewDidChangeBackingProperties(id self, SEL _cmd) { RGFW_monitor mon = RGFW_window_getMonitor(win); RGFW_scaleUpdatedCallback(win, mon.scaleX, mon.scaleY); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_scaleUpdated, .scaleX = mon.scaleX, .scaleY = mon.scaleY , ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_scaleUpdated; e.scaleX = mon.scaleX; e.scaleY = mon.scaleY ; e._win = win); } void RGFW__osxDrawRect(id self, SEL _cmd, CGRect rect) { @@ -8517,7 +8694,7 @@ void RGFW__osxDrawRect(id self, SEL _cmd, CGRect rect) { object_getInstanceVariable(self, "RGFW_window", (void**)&win); if (win == NULL) return; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowRefresh, ._win = win}); + RGFW_eventQueuePushEx(e.type = RGFW_windowRefresh; e._win = win); RGFW_windowRefreshCallback(win); } @@ -8614,6 +8791,12 @@ void RGFW_window_freeOpenGL(RGFW_window* win) { i32 RGFW_init(void) { +#if defined(RGFW_C89) || defined(__cplusplus) + if (_RGFW_init) return 0; + _RGFW_init = RGFW_TRUE; + _RGFW.root = NULL; _RGFW.current = NULL; _RGFW.windowCount = -1; _RGFW.eventLen = 0; _RGFW.eventIndex = 0; +#endif + /* NOTE(EimaMei): Why does Apple hate good code? Like wtf, who thought of methods being a great idea??? Imagine a universe, where MacOS had a proper system API (we would probably have like 20% better performance). */ @@ -8635,7 +8818,7 @@ i32 RGFW_init(void) { } _RGFW.windowCount = 0; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context initialized"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context initialized"); return 0; } @@ -8829,20 +9012,20 @@ typedef RGFW_ENUM(u32, NSEventType) { /* various types of events */ NSEventTypeOtherMouseUp = 26, NSEventTypeOtherMouseDragged = 27, /* The following event types are available on some hardware on 10.5.2 and later */ - NSEventTypeGesture API_AVAILABLE(macos(10.5)) = 29, - NSEventTypeMagnify API_AVAILABLE(macos(10.5)) = 30, - NSEventTypeSwipe API_AVAILABLE(macos(10.5)) = 31, - NSEventTypeRotate API_AVAILABLE(macos(10.5)) = 18, - NSEventTypeBeginGesture API_AVAILABLE(macos(10.5)) = 19, - NSEventTypeEndGesture API_AVAILABLE(macos(10.5)) = 20, + NSEventTypeGesture = 29, + NSEventTypeMagnify = 30, + NSEventTypeSwipe = 31, + NSEventTypeRotate = 18, + NSEventTypeBeginGesture = 19, + NSEventTypeEndGesture = 20, - NSEventTypeSmartMagnify API_AVAILABLE(macos(10.8)) = 32, - NSEventTypeQuickLook API_AVAILABLE(macos(10.8)) = 33, + NSEventTypeSmartMagnify = 32, + NSEventTypeQuickLook = 33, - NSEventTypePressure API_AVAILABLE(macos(10.10.3)) = 34, - NSEventTypeDirectTouch API_AVAILABLE(macos(10.10)) = 37, + NSEventTypePressure = 34, + NSEventTypeDirectTouch = 37, - NSEventTypeChangeMode API_AVAILABLE(macos(10.15)) = 38, + NSEventTypeChangeMode = 38, }; typedef unsigned long long NSEventMask; @@ -9424,6 +9607,7 @@ id RGFW_getNSScreenForDisplayID(CGDirectDisplayID display) { return NULL; } +u32 RGFW_osx_getFallbackRefreshRate(CGDirectDisplayID displayID); u32 RGFW_osx_getRefreshRate(CGDirectDisplayID display, CGDisplayModeRef mode) { if (mode) { @@ -9431,13 +9615,13 @@ u32 RGFW_osx_getRefreshRate(CGDirectDisplayID display, CGDisplayModeRef mode) { if (refreshRate != 0) return refreshRate; } - CVDisplayLinkRef link; - CVDisplayLinkCreateWithCGDisplay(display, &link); - const CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link); - if (!(time.flags & kCVTimeIsIndefinite)) - return (u32) (time.timeScale / (double) time.timeValue); - - return 0; +#ifndef RGFW_NO_IOKIT + u32 res = RGFW_osx_getFallbackRefreshRate(display); + if (res != 0) return res; +#else + RGFW_UNUSED(display); +#endif + return 60; } RGFW_monitor RGFW_NSCreateMonitor(CGDirectDisplayID display, id screen) { @@ -9633,7 +9817,7 @@ void RGFW_window_swapBuffers_software(RGFW_window* win) { void RGFW_deinit(void) { _RGFW.windowCount = -1; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context deinitialized"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context deinitialized"); } void RGFW_window_close(RGFW_window* win) { @@ -9646,7 +9830,7 @@ void RGFW_window_close(RGFW_window* win) { RGFW_FREE(win->buffer); #endif - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context deinitialized"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context deinitialized"); _RGFW.windowCount--; if (_RGFW.windowCount == 0) RGFW_deinit(); @@ -9682,15 +9866,15 @@ u64 RGFW_getTimerValue(void) { return (u64)mach_absolute_time(); } */ #ifdef RGFW_WASM -EM_BOOL Emscripten_on_resize(int eventType, const EmscriptenUiEvent* e, void* userData) { +EM_BOOL Emscripten_on_resize(int eventType, const EmscriptenUiEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowResized, ._win = _RGFW.root}); - RGFW_windowResizedCallback(_RGFW.root, RGFW_RECT(0, 0, e->windowInnerWidth, e->windowInnerHeight)); + RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e._win = _RGFW.root); + RGFW_windowResizedCallback(_RGFW.root, RGFW_RECT(0, 0, E->windowInnerWidth, E->windowInnerHeight)); return EM_TRUE; } -EM_BOOL Emscripten_on_fullscreenchange(int eventType, const EmscriptenFullscreenChangeEvent* e, void* userData) { +EM_BOOL Emscripten_on_fullscreenchange(int eventType, const EmscriptenFullscreenChangeEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); static u8 fullscreen = RGFW_FALSE; static RGFW_rect ogRect; @@ -9700,8 +9884,8 @@ EM_BOOL Emscripten_on_fullscreenchange(int eventType, const EmscriptenFullscreen } fullscreen = !fullscreen; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_windowResized, ._win = _RGFW.root}); - _RGFW.root->r = RGFW_RECT(0, 0, e->screenWidth, e->screenHeight); + RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e._win = _RGFW.root); + _RGFW.root->r = RGFW_RECT(0, 0, E->screenWidth, E->screenHeight); EM_ASM("Module.canvas.focus();"); @@ -9728,49 +9912,52 @@ EM_BOOL Emscripten_on_fullscreenchange(int eventType, const EmscriptenFullscreen -EM_BOOL Emscripten_on_focusin(int eventType, const EmscriptenFocusEvent* e, void* userData) { - RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); +EM_BOOL Emscripten_on_focusin(int eventType, const EmscriptenFocusEvent* E, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(E); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusIn, ._win = _RGFW.root}); + RGFW_eventQueuePushEx(e.type = RGFW_focusIn; e._win = _RGFW.root); _RGFW.root->_flags |= RGFW_windowFocus; RGFW_focusCallback(_RGFW.root, 1); + + RGFW_resetKey(); + if ((_RGFW.root->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(_RGFW.root, RGFW_AREA(_RGFW.root->r.w, _RGFW.root->r.h)); return EM_TRUE; } -EM_BOOL Emscripten_on_focusout(int eventType, const EmscriptenFocusEvent* e, void* userData) { - RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); +EM_BOOL Emscripten_on_focusout(int eventType, const EmscriptenFocusEvent* E, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(E); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_focusOut, ._win = _RGFW.root}); + RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e._win = _RGFW.root); _RGFW.root->_flags &= ~(u32)RGFW_windowFocus; RGFW_focusCallback(_RGFW.root, 0); return EM_TRUE; } -EM_BOOL Emscripten_on_mousemove(int eventType, const EmscriptenMouseEvent* e, void* userData) { +EM_BOOL Emscripten_on_mousemove(int eventType, const EmscriptenMouseEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mousePosChanged, - .point = RGFW_POINT(e->targetX, e->targetY), - .vector = RGFW_POINT(e->movementX, e->movementY), - ._win = _RGFW.root}); + RGFW_eventQueuePushEx(e.type = RGFW_mousePosChanged; + e.point = RGFW_POINT(E->targetX, E->targetY); + e.vector = RGFW_POINT(E->movementX, E->movementY); + e._win = _RGFW.root); - _RGFW.root->_lastMousePoint = RGFW_POINT(e->targetX, e->targetY); - RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(e->targetX, e->targetY), RGFW_POINT(e->movementX, e->movementY)); + _RGFW.root->_lastMousePoint = RGFW_POINT(E->targetX, E->targetY); + RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(E->targetX, E->targetY), RGFW_POINT(E->movementX, E->movementY)); return EM_TRUE; } -EM_BOOL Emscripten_on_mousedown(int eventType, const EmscriptenMouseEvent* e, void* userData) { +EM_BOOL Emscripten_on_mousedown(int eventType, const EmscriptenMouseEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - int button = e->button; + int button = E->button; if (button > 2) button += 2; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonPressed, - .point = RGFW_POINT(e->targetX, e->targetY), - .vector = RGFW_POINT(e->movementX, e->movementY), - .button = (u8)button, - .scroll = 0, - ._win = _RGFW.root}); + RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonPressed; + e.point = RGFW_POINT(E->targetX, E->targetY); + e.vector = RGFW_POINT(E->movementX, E->movementY); + e.button = (u8)button; + e.scroll = 0; + e._win = _RGFW.root); RGFW_mouseButtons[button].prev = RGFW_mouseButtons[button].current; RGFW_mouseButtons[button].current = 1; @@ -9778,19 +9965,19 @@ EM_BOOL Emscripten_on_mousedown(int eventType, const EmscriptenMouseEvent* e, vo return EM_TRUE; } -EM_BOOL Emscripten_on_mouseup(int eventType, const EmscriptenMouseEvent* e, void* userData) { +EM_BOOL Emscripten_on_mouseup(int eventType, const EmscriptenMouseEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - int button = e->button; + int button = E->button; if (button > 2) button += 2; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonReleased, - .point = RGFW_POINT(e->targetX, e->targetY), - .vector = RGFW_POINT(e->movementX, e->movementY), - .button = (u8)button, - .scroll = 0, - ._win = _RGFW.root}); + RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonReleased; + e.point = RGFW_POINT(E->targetX, E->targetY); + e.vector = RGFW_POINT(E->movementX, E->movementY); + e.button = (u8)button; + e.scroll = 0; + e._win = _RGFW.root); RGFW_mouseButtons[button].prev = RGFW_mouseButtons[button].current; RGFW_mouseButtons[button].current = 0; @@ -9798,78 +9985,78 @@ EM_BOOL Emscripten_on_mouseup(int eventType, const EmscriptenMouseEvent* e, void return EM_TRUE; } -EM_BOOL Emscripten_on_wheel(int eventType, const EmscriptenWheelEvent* e, void* userData) { +EM_BOOL Emscripten_on_wheel(int eventType, const EmscriptenWheelEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - int button = RGFW_mouseScrollUp + (e->deltaY < 0); - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonPressed, - .button = (u8)button, - .scroll = (double)(e->deltaY < 0 ? 1 : -1), - ._win = _RGFW.root}); + int button = RGFW_mouseScrollUp + (E->deltaY < 0); + RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonPressed; + e.button = (u8)button; + e.scroll = (double)(E->deltaY < 0 ? 1 : -1); + e._win = _RGFW.root); RGFW_mouseButtons[button].prev = RGFW_mouseButtons[button].current; RGFW_mouseButtons[button].current = 1; - RGFW_mouseButtonCallback(_RGFW.root, button, e->deltaY < 0 ? 1 : -1, 1); + RGFW_mouseButtonCallback(_RGFW.root, button, E->deltaY < 0 ? 1 : -1, 1); return EM_TRUE; } -EM_BOOL Emscripten_on_touchstart(int eventType, const EmscriptenTouchEvent* e, void* userData) { +EM_BOOL Emscripten_on_touchstart(int eventType, const EmscriptenTouchEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); size_t i; - for (i = 0; i < (size_t)e->numTouches; i++) { - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonPressed, - .point = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), - .button = RGFW_mouseLeft, - ._win = _RGFW.root}); + for (i = 0; i < (size_t)E->numTouches; i++) { + RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonPressed; + e.point = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); + e.button = RGFW_mouseLeft; + e._win = _RGFW.root); RGFW_mouseButtons[RGFW_mouseLeft].prev = RGFW_mouseButtons[RGFW_mouseLeft].current; RGFW_mouseButtons[RGFW_mouseLeft].current = 1; - _RGFW.root->_lastMousePoint = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY); - RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), _RGFW.root->event.vector); + _RGFW.root->_lastMousePoint = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); + RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY), _RGFW.root->event.vector); RGFW_mouseButtonCallback(_RGFW.root, RGFW_mouseLeft, 0, 1); } return EM_TRUE; } -EM_BOOL Emscripten_on_touchmove(int eventType, const EmscriptenTouchEvent* e, void* userData) { +EM_BOOL Emscripten_on_touchmove(int eventType, const EmscriptenTouchEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); size_t i; - for (i = 0; i < (size_t)e->numTouches; i++) { - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mousePosChanged, - .point = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), - .button = RGFW_mouseLeft, - ._win = _RGFW.root}); + for (i = 0; i < (size_t)E->numTouches; i++) { + RGFW_eventQueuePushEx(e.type = RGFW_mousePosChanged; + e.point = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); + e.button = RGFW_mouseLeft; + e._win = _RGFW.root); - _RGFW.root->_lastMousePoint = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY); - RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), _RGFW.root->event.vector); + _RGFW.root->_lastMousePoint = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); + RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY), _RGFW.root->event.vector); } return EM_TRUE; } -EM_BOOL Emscripten_on_touchend(int eventType, const EmscriptenTouchEvent* e, void* userData) { +EM_BOOL Emscripten_on_touchend(int eventType, const EmscriptenTouchEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); size_t i; - for (i = 0; i < (size_t)e->numTouches; i++) { - RGFW_eventQueuePush((RGFW_event){.type = RGFW_mouseButtonReleased, - .point = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), - .button = RGFW_mouseLeft, - ._win = _RGFW.root}); + for (i = 0; i < (size_t)E->numTouches; i++) { + RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonReleased; + e.point = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); + e.button = RGFW_mouseLeft; + e._win = _RGFW.root); RGFW_mouseButtons[RGFW_mouseLeft].prev = RGFW_mouseButtons[RGFW_mouseLeft].current; RGFW_mouseButtons[RGFW_mouseLeft].current = 0; - _RGFW.root->_lastMousePoint = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY); - RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY), _RGFW.root->event.vector); + _RGFW.root->_lastMousePoint = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); + RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY), _RGFW.root->event.vector); RGFW_mouseButtonCallback(_RGFW.root, RGFW_mouseLeft, 0, 0); } return EM_TRUE; } -EM_BOOL Emscripten_on_touchcancel(int eventType, const EmscriptenTouchEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); return EM_TRUE; } +EM_BOOL Emscripten_on_touchcancel(int eventType, const EmscriptenTouchEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); return EM_TRUE; } EM_BOOL Emscripten_on_gamepad(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); @@ -9879,7 +10066,8 @@ EM_BOOL Emscripten_on_gamepad(int eventType, const EmscriptenGamepadEvent *gamep size_t i = gamepadEvent->index; if (gamepadEvent->connected) { - RGFW_MEMCPY(RGFW_gamepads_name[gamepadEvent->index], gamepadEvent->id, sizeof(RGFW_gamepads_name[gamepadEvent->index])); + RGFW_STRNCPY(RGFW_gamepads_name[gamepadEvent->index], gamepadEvent->id, sizeof(RGFW_gamepads_name[gamepadEvent->index]) - 1); + RGFW_gamepads_name[gamepadEvent->index][sizeof(RGFW_gamepads_name[gamepadEvent->index]) - 1] = '\0'; RGFW_gamepads_type[i] = RGFW_gamepadUnknown; if (RGFW_STRSTR(RGFW_gamepads_name[i], "Microsoft") || RGFW_STRSTR(RGFW_gamepads_name[i], "X-Box")) RGFW_gamepads_type[i] = RGFW_gamepadMicrosoft; @@ -9894,9 +10082,9 @@ EM_BOOL Emscripten_on_gamepad(int eventType, const EmscriptenGamepadEvent *gamep RGFW_gamepadCount--; } - RGFW_eventQueuePush((RGFW_event){.type = (RGFW_eventType)(gamepadEvent->connected ? RGFW_gamepadConnected : RGFW_gamepadConnected), - .gamepad = (u16)gamepadEvent->index, - ._win = _RGFW.root}); + RGFW_eventQueuePushEx(e.type = (RGFW_eventType)(gamepadEvent->connected ? RGFW_gamepadConnected : RGFW_gamepadConnected); + e.gamepad = (u16)gamepadEvent->index; + e._win = _RGFW.root); RGFW_gamepadCallback(_RGFW.root, gamepadEvent->index, gamepadEvent->connected); RGFW_gamepads[gamepadEvent->index] = gamepadEvent->connected; @@ -10022,11 +10210,11 @@ void EMSCRIPTEN_KEEPALIVE RGFW_handleKeyEvent(char* key, char* code, RGFW_bool p if (*((u32*)key) == *((u32*)"Tab")) mappedKey = RGFW_tab; } - RGFW_eventQueuePush((RGFW_event){.type = (RGFW_eventType)(press ? RGFW_keyPressed : RGFW_keyReleased), - .key = (u8)physicalKey, - .keyChar = (u8)mappedKey, - .keyMod = _RGFW.root->event.keyMod, - ._win = _RGFW.root}); + RGFW_eventQueuePushEx(e.type = (RGFW_eventType)(press ? RGFW_keyPressed : RGFW_keyReleased); + e.key = (u8)physicalKey; + e.keyChar = (u8)mappedKey; + e.keyMod = _RGFW.root->event.keyMod; + e._win = _RGFW.root); RGFW_keyboard[physicalKey].prev = RGFW_keyboard[physicalKey].current; RGFW_keyboard[physicalKey].current = press; @@ -10042,9 +10230,10 @@ void EMSCRIPTEN_KEEPALIVE Emscripten_onDrop(size_t count) { if (!(_RGFW.root->_flags & RGFW_windowAllowDND)) return; - RGFW_eventQueuePush((RGFW_event){.type = RGFW_DND, - .droppedFilesCount = count, - ._win = _RGFW.root}); + _RGFW.root->event.droppedFilesCount = count; + RGFW_eventQueuePushEx(e.type = RGFW_DND; + e.droppedFilesCount = count; + e._win = _RGFW.root); RGFW_dndCallback(_RGFW.root, _RGFW.root->event.droppedFiles, count); } @@ -10083,7 +10272,8 @@ void EMSCRIPTEN_KEEPALIVE RGFW_makeSetValue(size_t index, char* file) { /* This seems like a terrible idea, don't replicate this unless you hate yourself or the OS */ /* TODO: find a better way to do this */ - RGFW_MEMCPY((char*)_RGFW.root->event.droppedFiles[index], file, RGFW_MAX_PATH); + RGFW_STRNCPY((char*)_RGFW.root->event.droppedFiles[index], file, RGFW_MAX_PATH - 1); + _RGFW.root->event.droppedFiles[index][RGFW_MAX_PATH - 1] = '\0'; } #include @@ -10151,7 +10341,17 @@ void RGFW_window_freeOpenGL(RGFW_window* win) { #endif } -i32 RGFW_init(void) { _RGFW.windowCount = 0; RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context initialized"); return 0; } +i32 RGFW_init(void) { +#if defined(RGFW_C89) || defined(__cplusplus) + if (_RGFW_init) return 0; + _RGFW_init = RGFW_TRUE; + _RGFW.root = NULL; _RGFW.current = NULL; _RGFW.windowCount = -2; _RGFW.eventLen = 0; _RGFW.eventIndex = 0; +#endif + + _RGFW.windowCount = 0; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context initialized"); + return 0; +} RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { RGFW_window_basic_init(win, rect, flags); @@ -10462,7 +10662,7 @@ void* RGFW_getCurrent_OpenGL(void) { return (void*)emscripten_webgl_get_current_ void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { RGFW_UNUSED(win); RGFW_UNUSED(swapInterval); } #endif -void RGFW_deinit(void) { _RGFW.windowCount = -1; RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, (RGFW_debugContext){0}, "global context deinitialized"); } +void RGFW_deinit(void) { _RGFW.windowCount = -1; RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context deinitialized"); } void RGFW_window_close(RGFW_window* win) { if ((win->_flags & RGFW_windowNoInitAPI) == 0) RGFW_window_freeOpenGL(win); @@ -10491,8 +10691,28 @@ RGFW_area RGFW_getScreenSize(void) { return RGFW_AREA(RGFW_innerWidth(), RGFW_innerHeight()); } +RGFW_bool RGFW_extensionSupportedPlatform(const char* extension, size_t len) { +#ifdef RGFW_OPENGL + return EM_ASM_INT({ + var ext = UTF8ToString($0, $1); + var canvas = document.querySelector('canvas'); + var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); + if (!gl) return 0; + + var supported = gl.getSupportedExtensions(); + return supported && supported.includes(ext) ? 1 : 0; + }, extension, len); +#else + return RGFW_FALSE; +#endif +} + RGFW_proc RGFW_getProcAddress(const char* procname) { - return (RGFW_proc)emscripten_webgl_get_proc_address(procname); +#ifdef RGFW_OPENGL + return (RGFW_proc)emscripten_webgl_get_proc_address(procname); +#else + return NULL +#endif } void RGFW_sleep(u64 milisecond) { diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 8498ddf17..c57ead1d6 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -177,6 +177,7 @@ static const unsigned short keyMappingRGFW[] = { [RGFW_shiftR] = KEY_RIGHT_SHIFT, [RGFW_controlR] = KEY_RIGHT_CONTROL, [RGFW_altR] = KEY_RIGHT_ALT, + [RGFW_superR] = KEY_RIGHT_SUPER, #endif [RGFW_space] = KEY_SPACE, From 296e3af470b5afbc8e3eb30c925166d761974769 Mon Sep 17 00:00:00 2001 From: jestarray <34615798+jestarray@users.noreply.github.com> Date: Sat, 31 May 2025 14:11:00 -0700 Subject: [PATCH 025/242] add const qualifier to ImageDrawTriangleFan and ImageDrawTriangleStrip arguments --- projects/Notepad++/raylib_npp_parser/raylib_to_parse.h | 4 ++-- src/raylib.h | 4 ++-- src/rtextures.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/projects/Notepad++/raylib_npp_parser/raylib_to_parse.h b/projects/Notepad++/raylib_npp_parser/raylib_to_parse.h index 89cce66f9..27e9b79a2 100644 --- a/projects/Notepad++/raylib_npp_parser/raylib_to_parse.h +++ b/projects/Notepad++/raylib_npp_parser/raylib_to_parse.h @@ -440,8 +440,8 @@ RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color c RLAPI void ImageDrawTriangle(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle within an image RLAPI void ImageDrawTriangleEx(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3); // Draw triangle with interpolated colors within an image RLAPI void ImageDrawTriangleLines(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline within an image -RLAPI void ImageDrawTriangleFan(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points within an image (first vertex is the center) -RLAPI void ImageDrawTriangleStrip(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points within an image +RLAPI void ImageDrawTriangleFan(Image *dst, const Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points within an image (first vertex is the center) +RLAPI void ImageDrawTriangleStrip(Image *dst, const Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points within an image RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint); // Draw a source image within a destination image (tint applied to source) RLAPI void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) within an image (destination) RLAPI void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text (custom sprite font) within an image (destination) diff --git a/src/raylib.h b/src/raylib.h index 3601faa25..71d346ea7 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1408,8 +1408,8 @@ RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color c RLAPI void ImageDrawTriangle(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle within an image RLAPI void ImageDrawTriangleEx(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3); // Draw triangle with interpolated colors within an image RLAPI void ImageDrawTriangleLines(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline within an image -RLAPI void ImageDrawTriangleFan(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points within an image (first vertex is the center) -RLAPI void ImageDrawTriangleStrip(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points within an image +RLAPI void ImageDrawTriangleFan(Image *dst, const Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points within an image (first vertex is the center) +RLAPI void ImageDrawTriangleStrip(Image *dst, const Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points within an image RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint); // Draw a source image within a destination image (tint applied to source) RLAPI void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) within an image (destination) RLAPI void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text (custom sprite font) within an image (destination) diff --git a/src/rtextures.c b/src/rtextures.c index 7a404a6de..7a5a5e57e 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -3891,7 +3891,7 @@ void ImageDrawTriangleLines(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Colo } // Draw a triangle fan defined by points within an image (first vertex is the center) -void ImageDrawTriangleFan(Image *dst, Vector2 *points, int pointCount, Color color) +void ImageDrawTriangleFan(Image *dst, const Vector2 *points, int pointCount, Color color) { if (pointCount >= 3) { @@ -3903,7 +3903,7 @@ void ImageDrawTriangleFan(Image *dst, Vector2 *points, int pointCount, Color col } // Draw a triangle strip defined by points within an image -void ImageDrawTriangleStrip(Image *dst, Vector2 *points, int pointCount, Color color) +void ImageDrawTriangleStrip(Image *dst, const Vector2 *points, int pointCount, Color color) { if (pointCount >= 3) { From 58a6846c8f1c11b2e7007109a8455604453c9b61 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 31 May 2025 21:26:50 +0000 Subject: [PATCH 026/242] Update raylib_api.* by CI --- parser/output/raylib_api.json | 4 ++-- parser/output/raylib_api.lua | 4 ++-- parser/output/raylib_api.txt | 4 ++-- parser/output/raylib_api.xml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index efbac71b0..e796478e4 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -8302,7 +8302,7 @@ "name": "dst" }, { - "type": "Vector2 *", + "type": "const Vector2 *", "name": "points" }, { @@ -8325,7 +8325,7 @@ "name": "dst" }, { - "type": "Vector2 *", + "type": "const Vector2 *", "name": "points" }, { diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 08615aa22..a2897a226 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -6141,7 +6141,7 @@ return { returnType = "void", params = { {type = "Image *", name = "dst"}, - {type = "Vector2 *", name = "points"}, + {type = "const Vector2 *", name = "points"}, {type = "int", name = "pointCount"}, {type = "Color", name = "color"} } @@ -6152,7 +6152,7 @@ return { returnType = "void", params = { {type = "Image *", name = "dst"}, - {type = "Vector2 *", name = "points"}, + {type = "const Vector2 *", name = "points"}, {type = "int", name = "pointCount"}, {type = "Color", name = "color"} } diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 2f44d94cc..4de3e267a 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -3185,7 +3185,7 @@ Function 352: ImageDrawTriangleFan() (4 input parameters) Return type: void Description: Draw a triangle fan defined by points within an image (first vertex is the center) Param[1]: dst (type: Image *) - Param[2]: points (type: Vector2 *) + Param[2]: points (type: const Vector2 *) Param[3]: pointCount (type: int) Param[4]: color (type: Color) Function 353: ImageDrawTriangleStrip() (4 input parameters) @@ -3193,7 +3193,7 @@ Function 353: ImageDrawTriangleStrip() (4 input parameters) Return type: void Description: Draw a triangle strip defined by points within an image Param[1]: dst (type: Image *) - Param[2]: points (type: Vector2 *) + Param[2]: points (type: const Vector2 *) Param[3]: pointCount (type: int) Param[4]: color (type: Color) Function 354: ImageDraw() (5 input parameters) diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 52f7f5b1d..e79e55f4f 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -2090,13 +2090,13 @@ - + - + From 19ae6f2c2d5b7789490fcee549337f3fd804359b Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Sat, 31 May 2025 22:58:18 -0700 Subject: [PATCH 027/242] [rshapes] Fix incorrect parameter names in DrawRectangleGradientEx Examining the code shows that the rectangle is drawn winding counterclockwise, starting with the top left. Therefore the colors used should be in the order: topLeft, bottomLeft, bottomRight, topRight. However, the variables actually being used are topLeft, bottomLeft, topRight, bottomRight. I was confused by this as I was getting striping where I didn't expect any. Put another way, the last two parameters are misnamed. This diff swaps the parameter names and their usages. The result is that no runtime behaviour changes: the same parameter order yields the same visual result both before and after this change, but the parameter names now correctly reflect what they are actually used for. You can actually see this in the implementation of DrawRectangleGradientV, which (correctly) passes top, bottom, bottom, top to DrawRectangleGradientEx. --- src/raylib.h | 2 +- src/rshapes.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/raylib.h b/src/raylib.h index fc949a02d..7d24b1410 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1273,7 +1273,7 @@ RLAPI void DrawRectangleRec(Rectangle rec, Color color); RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); // Draw a vertical-gradient-filled rectangle RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); // Draw a horizontal-gradient-filled rectangle -RLAPI void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight); // Draw a gradient-filled rectangle with custom vertex colors +RLAPI void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color bottomRight, Color topRight); // Draw a gradient-filled rectangle with custom vertex colors RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline RLAPI void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color); // Draw rectangle outline with extended parameters RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges diff --git a/src/rshapes.c b/src/rshapes.c index c739f4162..704aede39 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -774,7 +774,7 @@ void DrawRectangleGradientH(int posX, int posY, int width, int height, Color lef } // Draw a gradient-filled rectangle -void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight) +void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color bottomRight, Color topRight) { rlSetTexture(GetShapesTexture().id); Rectangle shapeRect = GetShapesTextureRectangle(); @@ -791,11 +791,11 @@ void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Col rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); rlVertex2f(rec.x, rec.y + rec.height); - rlColor4ub(topRight.r, topRight.g, topRight.b, topRight.a); + rlColor4ub(bottomRight.r, bottomRight.g, bottomRight.b, bottomRight.a); rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); rlVertex2f(rec.x + rec.width, rec.y + rec.height); - rlColor4ub(bottomRight.r, bottomRight.g, bottomRight.b, bottomRight.a); + rlColor4ub(topRight.r, topRight.g, topRight.b, topRight.a); rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); rlVertex2f(rec.x + rec.width, rec.y); rlEnd(); From b52a9f8a0493d4f70842d235a0cb452125109c93 Mon Sep 17 00:00:00 2001 From: garrisonhh Date: Sun, 1 Jun 2025 10:11:42 -0400 Subject: [PATCH 028/242] Add LICENSE to build.zig.zon --- build.zig.zon | 1 + 1 file changed, 1 insertion(+) diff --git a/build.zig.zon b/build.zig.zon index f18d9db84..3bf82afe5 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -23,5 +23,6 @@ "build.zig.zon", "src", "examples", + "LICENSE", }, } From 51958d6e2cb9ac3167b81187f36d6b2149d24a94 Mon Sep 17 00:00:00 2001 From: alqeeu Date: Sun, 1 Jun 2025 17:37:31 +0300 Subject: [PATCH 029/242] changed `RGFW_window_eventWait` timeout to -1 --- src/platforms/rcore_desktop_rgfw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index c57ead1d6..a1694af4a 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -989,7 +989,7 @@ void PollInputEvents(void) if ((CORE.Window.eventWaiting) || (IsWindowState(FLAG_WINDOW_MINIMIZED) && !IsWindowState(FLAG_WINDOW_ALWAYS_RUN))) { - RGFW_window_eventWait(platform.window, 0); // Wait for input events: keyboard/mouse/window events (callbacks) -> Update keys state + RGFW_window_eventWait(platform.window, -1); // Wait for input events: keyboard/mouse/window events (callbacks) -> Update keys state CORE.Time.previous = GetTime(); } From bb5b5434a75b6be87582b7e160415475ae52cb6b Mon Sep 17 00:00:00 2001 From: M374LX Date: Sun, 1 Jun 2025 14:37:01 -0300 Subject: [PATCH 030/242] Update miniaudio to v0.11.22 --- src/external/miniaudio.h | 1813 ++++++++++++++++++++++++++++---------- 1 file changed, 1324 insertions(+), 489 deletions(-) diff --git a/src/external/miniaudio.h b/src/external/miniaudio.h index ad113337e..c74bebeb3 100644 --- a/src/external/miniaudio.h +++ b/src/external/miniaudio.h @@ -1,6 +1,6 @@ /* Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. -miniaudio - v0.11.21 - 2023-11-15 +miniaudio - v0.11.22 - 2025-02-24 David Reid - mackron@gmail.com @@ -12,15 +12,18 @@ GitHub: https://github.com/mackron/miniaudio /* 1. Introduction =============== -miniaudio is a single file library for audio playback and capture. To use it, do the following in -one .c file: +To use miniaudio, include "miniaudio.h": ```c - #define MINIAUDIO_IMPLEMENTATION #include "miniaudio.h" ``` -You can do `#include "miniaudio.h"` in other parts of the program just like any other header. +The implementation is contained in "miniaudio.c". Just compile this like any other source file. You +can include miniaudio.c if you want to compile your project as a single translation unit: + + ```c + #include "miniaudio.c" + ``` miniaudio includes both low level and high level APIs. The low level API is good for those who want to do all of their mixing themselves and only require a light weight interface to the underlying @@ -293,7 +296,7 @@ avoids the same sound being loaded multiple times. The node graph is used for mixing and effect processing. The idea is that you connect a number of nodes into the graph by connecting each node's outputs to another node's inputs. Each node can -implement it's own effect. By chaining nodes together, advanced mixing and effect processing can +implement its own effect. By chaining nodes together, advanced mixing and effect processing can be achieved. The engine encapsulates both the resource manager and the node graph to create a simple, easy to @@ -398,7 +401,7 @@ the be started and/or stopped at a specific time. This can be done with the foll ``` The start/stop time needs to be specified based on the absolute timer which is controlled by the -engine. The current global time time in PCM frames can be retrieved with +engine. The current global time in PCM frames can be retrieved with `ma_engine_get_time_in_pcm_frames()`. The engine's global time can be changed with `ma_engine_set_time_in_pcm_frames()` for synchronization purposes if required. Note that scheduling a start time still requires an explicit call to `ma_sound_start()` before anything will play: @@ -430,11 +433,11 @@ Sounds and sound groups are nodes in the engine's node graph and can be plugged API. This makes it possible to connect sounds and sound groups to effect nodes to produce complex effect chains. -A sound can have it's volume changed with `ma_sound_set_volume()`. If you prefer decibel volume +A sound can have its volume changed with `ma_sound_set_volume()`. If you prefer decibel volume control you can use `ma_volume_db_to_linear()` to convert from decibel representation to linear. Panning and pitching is supported with `ma_sound_set_pan()` and `ma_sound_set_pitch()`. If you know -a sound will never have it's pitch changed with `ma_sound_set_pitch()` or via the doppler effect, +a sound will never have its pitch changed with `ma_sound_set_pitch()` or via the doppler effect, you can specify the `MA_SOUND_FLAG_NO_PITCH` flag when initializing the sound for an optimization. By default, sounds and sound groups have spatialization enabled. If you don't ever want to @@ -483,21 +486,12 @@ link the relevant frameworks but should compile cleanly out of the box with Xcod through the command line requires linking to `-lpthread` and `-lm`. Due to the way miniaudio links to frameworks at runtime, your application may not pass Apple's -notarization process. To fix this there are two options. The first is to use the -`MA_NO_RUNTIME_LINKING` option, like so: - - ```c - #ifdef __APPLE__ - #define MA_NO_RUNTIME_LINKING - #endif - #define MINIAUDIO_IMPLEMENTATION - #include "miniaudio.h" - ``` - -This will require linking with `-framework CoreFoundation -framework CoreAudio -framework AudioToolbox`. -If you get errors about AudioToolbox, try with `-framework AudioUnit` instead. You may get this when -using older versions of iOS. Alternatively, if you would rather keep using runtime linking you can -add the following to your entitlements.xcent file: +notarization process. To fix this there are two options. The first is to compile with +`-DMA_NO_RUNTIME_LINKING` which in turn will require linking with +`-framework CoreFoundation -framework CoreAudio -framework AudioToolbox`. If you get errors about +AudioToolbox, try with `-framework AudioUnit` instead. You may get this when using older versions +of iOS. Alternatively, if you would rather keep using runtime linking you can add the following to +your entitlements.xcent file: ``` com.apple.security.cs.allow-dyld-environment-variables @@ -555,7 +549,7 @@ To run locally, you'll need to use emrun: 2.7. Build Options ------------------ -`#define` these options before including miniaudio.h. +`#define` these options before including miniaudio.c, or pass them as compiler flags: +----------------------------------+--------------------------------------------------------------------+ | Option | Description | @@ -586,6 +580,8 @@ To run locally, you'll need to use emrun: +----------------------------------+--------------------------------------------------------------------+ | MA_NO_WEBAUDIO | Disables the Web Audio backend. | +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_CUSTOM | Disables support for custom backends. | + +----------------------------------+--------------------------------------------------------------------+ | MA_NO_NULL | Disables the null backend. | +----------------------------------+--------------------------------------------------------------------+ | MA_ENABLE_ONLY_SPECIFIC_BACKENDS | Disables all backends by default and requires `MA_ENABLE_*` to | @@ -630,6 +626,9 @@ To run locally, you'll need to use emrun: | MA_ENABLE_WEBAUDIO | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | | | enable the Web Audio backend. | +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_CUSTOM | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable custom backends. | + +----------------------------------+--------------------------------------------------------------------+ | MA_ENABLE_NULL | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | | | enable the null backend. | +----------------------------------+--------------------------------------------------------------------+ @@ -693,11 +692,30 @@ To run locally, you'll need to use emrun: | | You may need to enable this if your target platform does not allow | | | runtime linking via `dlopen()`. | +----------------------------------+--------------------------------------------------------------------+ + | MA_USE_STDINT | (Pass this in as a compiler flag. Do not `#define` this before | + | | miniaudio.c) Forces the use of stdint.h for sized types. | + +----------------------------------+--------------------------------------------------------------------+ | MA_DEBUG_OUTPUT | Enable `printf()` output of debug logs (`MA_LOG_LEVEL_DEBUG`). | +----------------------------------+--------------------------------------------------------------------+ | MA_COINIT_VALUE | Windows only. The value to pass to internal calls to | | | `CoInitializeEx()`. Defaults to `COINIT_MULTITHREADED`. | +----------------------------------+--------------------------------------------------------------------+ + | MA_FORCE_UWP | Windows only. Affects only the WASAPI backend. Will force the | + | | WASAPI backend to use the UWP code path instead of the regular | + | | desktop path. This is normally auto-detected and should rarely be | + | | needed to be used explicitly, but can be useful for debugging. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ON_THREAD_ENTRY | Defines some code that will be executed as soon as an internal | + | | miniaudio-managed thread is created. This will be the first thing | + | | to be executed by the thread entry point. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ON_THREAD_EXIT | Defines some code that will be executed from the entry point of an | + | | internal miniaudio-managed thread upon exit. This will be the last | + | | thing to be executed before the thread's entry point exits. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_THREAD_DEFAULT_STACK_SIZE | If set, specifies the default stack size used by miniaudio-managed | + | | threads. | + +----------------------------------+--------------------------------------------------------------------+ | MA_API | Controls how public APIs should be decorated. Default is `extern`. | +----------------------------------+--------------------------------------------------------------------+ @@ -1309,7 +1327,7 @@ only works for sounds that were initialized with `ma_sound_init_from_file()` and When you initialize a sound, if you specify a sound group the sound will be attached to that group automatically. If you set it to NULL, it will be automatically attached to the engine's endpoint. -If you would instead rather leave the sound unattached by default, you can can specify the +If you would instead rather leave the sound unattached by default, you can specify the `MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT` flag. This is useful if you want to set up a complex node graph. @@ -1686,6 +1704,7 @@ combination of the following flags: MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING ``` When no flags are specified (set to 0), the sound will be fully loaded into memory, but not @@ -1706,6 +1725,14 @@ can instead stream audio data which you can do by specifying the second pages. When a new page needs to be decoded, a job will be posted to the job queue and then subsequently processed in a job thread. +The `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING` flag can be used so that the sound will loop +when it reaches the end by default. It's recommended you use this flag when you want to have a +looping streaming sound. If you try loading a very short sound as a stream, you will get a glitch. +This is because the resource manager needs to pre-fill the initial buffer at initialization time, +and if you don't specify the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING` flag, the resource +manager will assume the sound is not looping and will stop filling the buffer when it reaches the +end, therefore resulting in a discontinuous buffer. + For in-memory sounds, reference counting is used to ensure the data is loaded only once. This means multiple calls to `ma_resource_manager_data_source_init()` with the same file path will result in the file data only being loaded once. Each call to `ma_resource_manager_data_source_init()` must be @@ -1720,7 +1747,7 @@ actual file paths. When `ma_resource_manager_data_source_init()` is called (with `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag), the resource manager will look for these explicitly registered data buffers and, if found, will use it as the backing data for the data source. Note that the resource manager does *not* make a copy of this data so it is up to the -caller to ensure the pointer stays valid for it's lifetime. Use +caller to ensure the pointer stays valid for its lifetime. Use `ma_resource_manager_unregister_data()` to unregister the self-managed data. You can also use `ma_resource_manager_register_file()` and `ma_resource_manager_unregister_file()` to register and unregister a file. It does not make sense to use the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` @@ -2031,7 +2058,7 @@ In the above graph, it starts with two data sources whose outputs are attached t splitter node. It's at this point that the two data sources are mixed. After mixing, the splitter performs it's processing routine and produces two outputs which is simply a duplication of the input stream. One output is attached to a low pass filter, whereas the other output is attached to -a echo/delay. The outputs of the the low pass filter and the echo are attached to the endpoint, and +a echo/delay. The outputs of the low pass filter and the echo are attached to the endpoint, and since they're both connected to the same input bus, they'll be mixed. Each input bus must be configured to accept the same number of channels, but the number of channels @@ -2072,7 +2099,7 @@ data from the graph: ``` When you read audio data, miniaudio starts at the node graph's endpoint node which then pulls in -data from it's input attachments, which in turn recursively pull in data from their inputs, and so +data from its input attachments, which in turn recursively pull in data from their inputs, and so on. At the start of the graph there will be some kind of data source node which will have zero inputs and will instead read directly from a data source. The base nodes don't literally need to read from a `ma_data_source` object, but they will always have some kind of underlying object that @@ -2318,7 +2345,7 @@ You can start and stop a node with the following: By default the node is in a started state, but since it won't be connected to anything won't actually be invoked by the node graph until it's connected. When you stop a node, data will not be -read from any of it's input connections. You can use this property to stop a group of sounds +read from any of its input connections. You can use this property to stop a group of sounds atomically. You can configure the initial state of a node in it's config: @@ -2411,29 +2438,29 @@ audio thread is finished so that control is not handed back to the caller thereb chance to free the node's memory. When the audio thread is processing a node, it does so by reading from each of the output buses of -the node. In order for a node to process data for one of it's output buses, it needs to read from -each of it's input buses, and so on an so forth. It follows that once all output buses of a node +the node. In order for a node to process data for one of its output buses, it needs to read from +each of its input buses, and so on an so forth. It follows that once all output buses of a node are detached, the node as a whole will be disconnected and no further processing will occur unless it's output buses are reattached, which won't be happening when the node is being uninitialized. By having `ma_node_detach_output_bus()` wait until the audio thread is finished with it, we can simplify a few things, at the expense of making `ma_node_detach_output_bus()` a bit slower. By doing this, the implementation of `ma_node_uninit()` becomes trivial - just detach all output -nodes, followed by each of the attachments to each of it's input nodes, and then do any final clean +nodes, followed by each of the attachments to each of its input nodes, and then do any final clean up. With the above design, the worst-case scenario is `ma_node_detach_output_bus()` taking as long as it takes to process the output bus being detached. This will happen if it's called at just the wrong moment where the audio thread has just iterated it and has just started processing. The caller of `ma_node_detach_output_bus()` will stall until the audio thread is finished, which -includes the cost of recursively processing it's inputs. This is the biggest compromise made with -the approach taken by miniaudio for it's lock-free processing system. The cost of detaching nodes +includes the cost of recursively processing its inputs. This is the biggest compromise made with +the approach taken by miniaudio for its lock-free processing system. The cost of detaching nodes earlier in the pipeline (data sources, for example) will be cheaper than the cost of detaching higher level nodes, such as some kind of final post-processing endpoint. If you need to do mass detachments, detach starting from the lowest level nodes and work your way towards the final endpoint node (but don't try detaching the node graph's endpoint). If the audio thread is not running, detachment will be fast and detachment in any order will be the same. The reason nodes need to wait for their input attachments to complete is due to the potential for desyncs between -data sources. If the node was to terminate processing mid way through processing it's inputs, +data sources. If the node was to terminate processing mid way through processing its inputs, there's a chance that some of the underlying data sources will have been read, but then others not. That will then result in a potential desynchronization when detaching and reattaching higher-level nodes. A possible solution to this is to have an option when detaching to terminate processing @@ -2804,7 +2831,7 @@ weights. Custom weights can be passed in as the last parameter of `ma_channel_converter_config_init()`. Predefined channel maps can be retrieved with `ma_channel_map_init_standard()`. This takes a -`ma_standard_channel_map` enum as it's first parameter, which can be one of the following: +`ma_standard_channel_map` enum as its first parameter, which can be one of the following: +-----------------------------------+-----------------------------------------------------------+ | Name | Description | @@ -2890,7 +2917,7 @@ like the following: ma_resample_algorithm_linear); ma_resampler resampler; - ma_result result = ma_resampler_init(&config, &resampler); + ma_result result = ma_resampler_init(&config, NULL, &resampler); if (result != MA_SUCCESS) { // An error occurred... } @@ -3132,7 +3159,7 @@ Biquad filtering is achieved with the `ma_biquad` API. Example: ```c ma_biquad_config config = ma_biquad_config_init(ma_format_f32, channels, b0, b1, b2, a0, a1, a2); - ma_result result = ma_biquad_init(&config, &biquad); + ma_result result = ma_biquad_init(&config, NULL, &biquad); if (result != MA_SUCCESS) { // Error. } @@ -3723,7 +3750,7 @@ extern "C" { #define MA_VERSION_MAJOR 0 #define MA_VERSION_MINOR 11 -#define MA_VERSION_REVISION 21 +#define MA_VERSION_REVISION 22 #define MA_VERSION_STRING MA_XSTRINGIFY(MA_VERSION_MAJOR) "." MA_XSTRINGIFY(MA_VERSION_MINOR) "." MA_XSTRINGIFY(MA_VERSION_REVISION) #if defined(_MSC_VER) && !defined(__clang__) @@ -3740,8 +3767,7 @@ extern "C" { #endif - -#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) || defined(__ppc64__) #define MA_SIZEOF_PTR 8 #else #define MA_SIZEOF_PTR 4 @@ -3805,7 +3831,7 @@ typedef void* ma_handle; typedef void* ma_ptr; /* -ma_proc is annoying because when compiling with GCC we get pendantic warnings about converting +ma_proc is annoying because when compiling with GCC we get pedantic warnings about converting between `void*` and `void (*)()`. We can't use `void (*)()` with MSVC however, because we'll get warning C4191 about "type cast between incompatible function types". To work around this I'm going to use a different data type depending on the compiler. @@ -3999,7 +4025,7 @@ Special wchar_t type to ensure any structures in the public sections that refere consistent size across all platforms. On Windows, wchar_t is 2 bytes, whereas everywhere else it's 4 bytes. Since Windows likes to use -wchar_t for it's IDs, we need a special explicitly sized wchar type that is always 2 bytes on all +wchar_t for its IDs, we need a special explicitly sized wchar type that is always 2 bytes on all platforms. */ #if !defined(MA_POSIX) && defined(MA_WIN32) @@ -4025,7 +4051,7 @@ MA_LOG_LEVEL_INFO callback. MA_LOG_LEVEL_WARNING - Warnings. You should enable this in you development builds and action them when encounted. These + Warnings. You should enable this in you development builds and action them when encountered. These logs usually indicate a potential problem or misconfiguration, but still allow you to keep running. This will never be called from within the data callback. @@ -5457,7 +5483,7 @@ input frames. MA_API ma_result ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); /* -Resets the resampler's timer and clears it's internal cache. +Resets the resampler's timer and clears its internal cache. */ MA_API ma_result ma_resampler_reset(ma_resampler* pResampler); @@ -5678,7 +5704,7 @@ MA_API void ma_channel_map_init_standard(ma_standard_channel_map standardChannel /* Copies a channel map. -Both input and output channel map buffers must have a capacity of at at least `channels`. +Both input and output channel map buffers must have a capacity of at least `channels`. */ MA_API void ma_channel_map_copy(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels); @@ -5817,6 +5843,8 @@ MA_API void ma_data_source_uninit(ma_data_source* pDataSource); MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); /* Must support pFramesOut = NULL in which case a forward seek should be performed. */ MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked); /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, &framesRead); */ MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex); +MA_API ma_result ma_data_source_seek_seconds(ma_data_source* pDataSource, float secondCount, float* pSecondsSeeked); /* Can only seek forward. Abstraction to ma_data_source_seek_pcm_frames() */ +MA_API ma_result ma_data_source_seek_to_second(ma_data_source* pDataSource, float seekPointInSeconds); /* Abstraction to ma_data_source_seek_to_pcm_frame() */ MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor); MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength); /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */ @@ -6182,6 +6210,12 @@ MA_API ma_result ma_event_wait(ma_event* pEvent); Signals the specified auto-reset event. */ MA_API ma_result ma_event_signal(ma_event* pEvent); + + +MA_API ma_result ma_semaphore_init(int initialValue, ma_semaphore* pSemaphore); +MA_API void ma_semaphore_uninit(ma_semaphore* pSemaphore); +MA_API ma_result ma_semaphore_wait(ma_semaphore* pSemaphore); +MA_API ma_result ma_semaphore_release(ma_semaphore* pSemaphore); #endif /* MA_NO_THREADING */ @@ -6273,7 +6307,7 @@ Job Queue /* Slot Allocator -------------- -The idea of the slot allocator is for it to be used in conjunction with a fixed sized buffer. You use the slot allocator to allocator an index that can be used +The idea of the slot allocator is for it to be used in conjunction with a fixed sized buffer. You use the slot allocator to allocate an index that can be used as the insertion point for an object. Slots are reference counted to help mitigate the ABA problem in the lock-free queue we use for tracking jobs. @@ -7006,6 +7040,8 @@ typedef union int nullbackend; /* The null backend uses an integer for device IDs. */ } ma_device_id; +MA_API ma_bool32 ma_device_id_equal(const ma_device_id* pA, const ma_device_id* pB); + typedef struct ma_context_config ma_context_config; typedef struct ma_device_config ma_device_config; @@ -7093,6 +7129,7 @@ struct ma_device_config { const char* pStreamNamePlayback; const char* pStreamNameCapture; + int channelMap; } pulse; struct { @@ -7112,6 +7149,7 @@ struct ma_device_config ma_aaudio_allowed_capture_policy allowedCapturePolicy; ma_bool32 noAutoStartAfterReroute; ma_bool32 enableCompatibilityWorkarounds; + ma_bool32 allowSetBufferCapacity; } aaudio; }; @@ -7184,7 +7222,7 @@ and on output returns detailed information about the device in `ma_device_info`. case when the device ID is NULL, in which case information about the default device needs to be retrieved. Once the context has been created and the device ID retrieved (if using anything other than the default device), the device can be created. -This is a little bit more complicated than initialization of the context due to it's more complicated configuration. When initializing a +This is a little bit more complicated than initialization of the context due to its more complicated configuration. When initializing a device, a duplex device may be requested. This means a separate data format needs to be specified for both playback and capture. On input, the data format is set to what the application wants. On output it's set to the native format which should match as closely as possible to the requested format. The conversion between the format requested by the application and the device's native format will be handled @@ -7205,10 +7243,10 @@ asynchronous reading and writing, `onDeviceStart()` and `onDeviceStop()` should The handling of data delivery between the application and the device is the most complicated part of the process. To make this a bit easier, some helper callbacks are available. If the backend uses a blocking read/write style of API, the `onDeviceRead()` and `onDeviceWrite()` callbacks can optionally be implemented. These are blocking and work just like reading and writing from a file. If the -backend uses a callback for data delivery, that callback must call `ma_device_handle_backend_data_callback()` from within it's callback. +backend uses a callback for data delivery, that callback must call `ma_device_handle_backend_data_callback()` from within its callback. This allows miniaudio to then process any necessary data conversion and then pass it to the miniaudio data callback. -If the backend requires absolute flexibility with it's data delivery, it can optionally implement the `onDeviceDataLoop()` callback +If the backend requires absolute flexibility with its data delivery, it can optionally implement the `onDeviceDataLoop()` callback which will allow it to implement the logic that will run on the audio thread. This is much more advanced and is completely optional. The audio thread should run data delivery logic in a loop while `ma_device_get_state() == ma_device_state_started` and no errors have been @@ -7248,6 +7286,10 @@ struct ma_context_config void* pUserData; ma_allocation_callbacks allocationCallbacks; struct + { + ma_handle hWnd; /* HWND. Optional window handle to pass into SetCooperativeLevel(). Will default to the foreground window, and if that fails, the desktop window. */ + } dsound; + struct { ma_bool32 useVerboseDeviceEnumeration; } alsa; @@ -7336,6 +7378,7 @@ struct ma_context #ifdef MA_SUPPORT_DSOUND struct { + ma_handle hWnd; /* Can be null. */ ma_handle hDSoundDLL; ma_proc DirectSoundCreate; ma_proc DirectSoundEnumerateA; @@ -7942,6 +7985,7 @@ struct ma_device { /*AAudioStream**/ ma_ptr pStreamPlayback; /*AAudioStream**/ ma_ptr pStreamCapture; + ma_mutex rerouteLock; ma_aaudio_usage usage; ma_aaudio_content_type contentType; ma_aaudio_input_preset inputPreset; @@ -8365,6 +8409,10 @@ Retrieves basic information about every active playback and/or capture device. This function will allocate memory internally for the device lists and return a pointer to them through the `ppPlaybackDeviceInfos` and `ppCaptureDeviceInfos` parameters. If you do not want to incur the overhead of these allocations consider using `ma_context_enumerate_devices()` which will instead use a callback. +Note that this only retrieves the ID and name/description of the device. The reason for only retrieving basic information is that it would otherwise require +opening the backend device in order to probe it for more detailed information which can be inefficient. Consider using `ma_context_get_device_info()` for this, +but don't call it from within the enumeration callback. + Parameters ---------- @@ -8406,7 +8454,7 @@ The returned pointers will become invalid upon the next call this this function, See Also -------- -ma_context_get_devices() +ma_context_enumerate_devices() */ MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** ppPlaybackDeviceInfos, ma_uint32* pPlaybackDeviceCount, ma_device_info** ppCaptureDeviceInfos, ma_uint32* pCaptureDeviceCount); @@ -8545,7 +8593,7 @@ from a microphone. Whether or not you should send or receive data from the devic playback, capture, full-duplex or loopback. (Note that loopback mode is only supported on select backends.) Sending and receiving audio data to and from the device is done via a callback which is fired by miniaudio at periodic time intervals. -The frequency at which data is delivered to and from a device depends on the size of it's period. The size of the period can be defined in terms of PCM frames +The frequency at which data is delivered to and from a device depends on the size of its period. The size of the period can be defined in terms of PCM frames or milliseconds, whichever is more convenient. Generally speaking, the smaller the period, the lower the latency at the expense of higher CPU usage and increased risk of glitching due to the more frequent and granular data deliver intervals. The size of a period will depend on your requirements, but miniaudio's defaults should work fine for most scenarios. If you're building a game you should leave this fairly small, whereas if you're building a simple @@ -8619,7 +8667,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c performanceProfile A hint to miniaudio as to the performance requirements of your program. Can be either `ma_performance_profile_low_latency` (default) or - `ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at it's default value. + `ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at its default value. noPreSilencedOutputBuffer When set to true, the contents of the output buffer passed into the data callback will be left undefined. When set to false (default), the contents of @@ -8659,7 +8707,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c A pointer that will passed to callbacks in pBackendVTable. resampling.linear.lpfOrder - The linear resampler applies a low-pass filter as part of it's processing for anti-aliasing. This setting controls the order of the filter. The higher + The linear resampler applies a low-pass filter as part of its processing for anti-aliasing. This setting controls the order of the filter. The higher the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is `MA_MAX_FILTER_ORDER`. The default value is `min(4, MA_MAX_FILTER_ORDER)`. @@ -8736,6 +8784,9 @@ then be set directly on the structure. Below are the members of the `ma_device_c pulse.pStreamNameCapture PulseAudio only. Sets the stream name for capture. + pulse.channelMap + PulseAudio only. Sets the channel map that is requested from PulseAudio. See MA_PA_CHANNEL_MAP_* constants. Defaults to MA_PA_CHANNEL_MAP_AIFF. + coreaudio.allowNominalSampleRateChange Core Audio only. Desktop only. When enabled, allows the sample rate of the device to be changed at the operating system level. This is disabled by default in order to prevent intrusive changes to the user's system. This is useful if you want to use a sample rate @@ -8909,7 +8960,7 @@ Unsafe. It is not safe to call this inside any callback. Remarks ------- -You only need to use this function if you want to configure the context differently to it's defaults. You should never use this function if you want to manage +You only need to use this function if you want to configure the context differently to its defaults. You should never use this function if you want to manage your own context. See the documentation for `ma_context_init()` for information on the different context configuration options. @@ -9674,7 +9725,7 @@ Utilities ************************************************************************************************************************************************************/ /* -Calculates a buffer size in milliseconds from the specified number of frames and sample rate. +Calculates a buffer size in milliseconds (rounded up) from the specified number of frames and sample rate. */ MA_API ma_uint32 ma_calculate_buffer_size_in_milliseconds_from_frames(ma_uint32 bufferSizeInFrames, ma_uint32 sampleRate); @@ -9931,7 +9982,7 @@ struct ma_decoder void* pInputCache; /* In input format. Can be null if it's not needed. */ ma_uint64 inputCacheCap; /* The capacity of the input cache. */ ma_uint64 inputCacheConsumed; /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */ - ma_uint64 inputCacheRemaining; /* The number of valid frames remaining in the cahce. */ + ma_uint64 inputCacheRemaining; /* The number of valid frames remaining in the cache. */ ma_allocation_callbacks allocationCallbacks; union { @@ -9972,7 +10023,7 @@ This is not thread safe without your own synchronization. MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); /* -Seeks to a PCM frame based on it's absolute index. +Seeks to a PCM frame based on its absolute index. This is not thread safe without your own synchronization. */ @@ -10235,7 +10286,8 @@ typedef enum MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE = 0x00000002, /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */ MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC = 0x00000004, /* When set, the resource manager will load the data source asynchronously. */ MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT = 0x00000008, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */ - MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH = 0x00000010 /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */ + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH = 0x00000010, /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */ + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING = 0x00000020 /* When set, configures the data source to loop by default. */ } ma_resource_manager_data_source_flags; @@ -10303,8 +10355,8 @@ typedef struct ma_uint64 rangeEndInPCMFrames; ma_uint64 loopPointBegInPCMFrames; ma_uint64 loopPointEndInPCMFrames; - ma_bool32 isLooping; ma_uint32 flags; + ma_bool32 isLooping; /* Deprecated. Use the MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING flag in `flags` instead. */ } ma_resource_manager_data_source_config; MA_API ma_resource_manager_data_source_config ma_resource_manager_data_source_config_init(void); @@ -10547,6 +10599,16 @@ Node Graph /* Use this when the bus count is determined by the node instance rather than the vtable. */ #define MA_NODE_BUS_COUNT_UNKNOWN 255 + +/* For some internal memory management of ma_node_graph. */ +typedef struct +{ + size_t offset; + size_t sizeInBytes; + unsigned char _data[1]; +} ma_stack; + + typedef struct ma_node_graph ma_node_graph; typedef void ma_node; @@ -10586,7 +10648,7 @@ typedef struct void (* onProcess)(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut); /* - A callback for retrieving the number of a input frames that are required to output the + A callback for retrieving the number of input frames that are required to output the specified number of output frames. You would only want to implement this when the node performs resampling. This is optional, even for nodes that perform resampling, but it does offer a small reduction in latency as it allows miniaudio to calculate the exact number of input frames @@ -10671,10 +10733,14 @@ typedef struct ma_node_base ma_node_base; struct ma_node_base { /* These variables are set once at startup. */ - ma_node_graph* pNodeGraph; /* The graph this node belongs to. */ + ma_node_graph* pNodeGraph; /* The graph this node belongs to. */ const ma_node_vtable* vtable; - float* pCachedData; /* Allocated on the heap. Fixed size. Needs to be stored on the heap because reading from output buses is done in separate function calls. */ - ma_uint16 cachedDataCapInFramesPerBus; /* The capacity of the input data cache in frames, per bus. */ + ma_uint32 inputBusCount; + ma_uint32 outputBusCount; + ma_node_input_bus* pInputBuses; + ma_node_output_bus* pOutputBuses; + float* pCachedData; /* Allocated on the heap. Fixed size. Needs to be stored on the heap because reading from output buses is done in separate function calls. */ + ma_uint16 cachedDataCapInFramesPerBus; /* The capacity of the input data cache in frames, per bus. */ /* These variables are read and written only from the audio thread. */ ma_uint16 cachedFrameCountOut; @@ -10682,13 +10748,9 @@ struct ma_node_base ma_uint16 consumedFrameCountIn; /* These variables are read and written between different threads. */ - MA_ATOMIC(4, ma_node_state) state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */ - MA_ATOMIC(8, ma_uint64) stateTimes[2]; /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */ - MA_ATOMIC(8, ma_uint64) localTime; /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */ - ma_uint32 inputBusCount; - ma_uint32 outputBusCount; - ma_node_input_bus* pInputBuses; - ma_node_output_bus* pOutputBuses; + MA_ATOMIC(4, ma_node_state) state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */ + MA_ATOMIC(8, ma_uint64) stateTimes[2]; /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */ + MA_ATOMIC(8, ma_uint64) localTime; /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */ /* Memory management. */ ma_node_input_bus _inputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT]; @@ -10724,7 +10786,8 @@ MA_API ma_result ma_node_set_time(ma_node* pNode, ma_uint64 localTime); typedef struct { ma_uint32 channels; - ma_uint16 nodeCacheCapInFrames; + ma_uint32 processingSizeInFrames; /* This is the preferred processing size for node processing callbacks unless overridden by a node itself. Can be 0 in which case it will be based on the frame count passed into ma_node_graph_read_pcm_frames(), but will not be well defined. */ + size_t preMixStackSizeInBytes; /* Defaults to 512KB per channel. Reducing this will save memory, but the depth of your node graph will be more restricted. */ } ma_node_graph_config; MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels); @@ -10735,10 +10798,15 @@ struct ma_node_graph /* Immutable. */ ma_node_base base; /* The node graph itself is a node so it can be connected as an input to different node graph. This has zero inputs and calls ma_node_graph_read_pcm_frames() to generate it's output. */ ma_node_base endpoint; /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */ - ma_uint16 nodeCacheCapInFrames; + float* pProcessingCache; /* This will be allocated when processingSizeInFrames is non-zero. This is needed because ma_node_graph_read_pcm_frames() can be called with a variable number of frames, and we may need to do some buffering in situations where the caller requests a frame count that's not a multiple of processingSizeInFrames. */ + ma_uint32 processingCacheFramesRemaining; + ma_uint32 processingSizeInFrames; /* Read and written by multiple threads. */ MA_ATOMIC(4, ma_bool32) isReading; + + /* Modified only by the audio thread. */ + ma_stack* pPreMixStack; }; MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph); @@ -11023,6 +11091,7 @@ typedef enum MA_SOUND_FLAG_ASYNC = 0x00000004, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */ MA_SOUND_FLAG_WAIT_INIT = 0x00000008, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */ MA_SOUND_FLAG_UNKNOWN_LENGTH = 0x00000010, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */ + MA_SOUND_FLAG_LOOPING = 0x00000020, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING */ /* ma_sound specific flags. */ MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT = 0x00001000, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */ @@ -11062,7 +11131,7 @@ MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_e /* Base node object for both ma_sound and ma_sound_group. */ typedef struct { - ma_node_base baseNode; /* Must be the first member for compatiblity with the ma_node API. */ + ma_node_base baseNode; /* Must be the first member for compatibility with the ma_node API. */ ma_engine* pEngine; /* A pointer to the engine. Set based on the value from the config. */ ma_uint32 sampleRate; /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */ ma_uint32 volumeSmoothTimeInPCMFrames; @@ -11122,13 +11191,13 @@ typedef struct ma_uint64 rangeEndInPCMFrames; ma_uint64 loopPointBegInPCMFrames; ma_uint64 loopPointEndInPCMFrames; - ma_bool32 isLooping; ma_sound_end_proc endCallback; /* Fired when the sound reaches the end. Will be fired from the audio thread. Do not restart, uninitialize or otherwise change the state of the sound from here. Instead fire an event or set a variable to indicate to a different thread to change the start of the sound. Will not be fired in response to a scheduled stop with ma_sound_set_stop_time_*(). */ void* pEndCallbackUserData; #ifndef MA_NO_RESOURCE_MANAGER ma_resource_manager_pipeline_notifications initNotifications; #endif ma_fence* pDoneFence; /* Deprecated. Use initNotifications instead. Released when the resource manager has finished decoding the entire sound. Not used with streams. */ + ma_bool32 isLooping; /* Deprecated. Use the MA_SOUND_FLAG_LOOPING flag in `flags` instead. */ } ma_sound_config; MA_API ma_sound_config ma_sound_config_init(void); /* Deprecated. Will be removed in version 0.12. Use ma_sound_config_2() instead. */ @@ -11192,6 +11261,7 @@ typedef struct ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */ ma_uint32 gainSmoothTimeInMilliseconds; /* When set to 0, gainSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */ ma_uint32 defaultVolumeSmoothTimeInPCMFrames; /* Defaults to 0. Controls the default amount of smoothing to apply to volume changes to sounds. High values means more smoothing at the expense of high latency (will take longer to reach the new volume). */ + ma_uint32 preMixStackSizeInBytes; /* A stack is used for internal processing in the node graph. This allows you to configure the size of this stack. Smaller values will reduce the maximum depth of your node graph. You should rarely need to modify this. */ ma_allocation_callbacks allocationCallbacks; ma_bool32 noAutoStart; /* When set to true, requires an explicit call to ma_engine_start(). This is false by default, meaning the engine will be started automatically in ma_engine_init(). */ ma_bool32 noDevice; /* When set to true, don't create a default device. ma_engine_read_pcm_frames() can be called manually to read data. */ @@ -11206,12 +11276,12 @@ MA_API ma_engine_config ma_engine_config_init(void); struct ma_engine { - ma_node_graph nodeGraph; /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */ + ma_node_graph nodeGraph; /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */ #if !defined(MA_NO_RESOURCE_MANAGER) ma_resource_manager* pResourceManager; #endif #if !defined(MA_NO_DEVICE_IO) - ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */ + ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */ #endif ma_log* pLog; ma_uint32 sampleRate; @@ -11220,10 +11290,10 @@ struct ma_engine ma_allocation_callbacks allocationCallbacks; ma_bool8 ownsResourceManager; ma_bool8 ownsDevice; - ma_spinlock inlinedSoundLock; /* For synchronizing access so the inlined sound list. */ - ma_sound_inlined* pInlinedSoundHead; /* The first inlined sound. Inlined sounds are tracked in a linked list. */ - MA_ATOMIC(4, ma_uint32) inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */ - ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. */ + ma_spinlock inlinedSoundLock; /* For synchronizing access to the inlined sound list. */ + ma_sound_inlined* pInlinedSoundHead; /* The first inlined sound. Inlined sounds are tracked in a linked list. */ + MA_ATOMIC(4, ma_uint32) inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */ + ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. */ ma_uint32 defaultVolumeSmoothTimeInPCMFrames; ma_mono_expansion_mode monoExpansionMode; ma_engine_process_proc onProcess; @@ -11348,6 +11418,7 @@ MA_API void ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping); MA_API ma_bool32 ma_sound_is_looping(const ma_sound* pSound); MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound); MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameIndex); /* Just a wrapper around ma_data_source_seek_to_pcm_frame(). */ +MA_API ma_result ma_sound_seek_to_second(ma_sound* pSound, float seekPointInSeconds); /* Abstraction to ma_sound_seek_to_pcm_frame() */ MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor); MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength); @@ -13861,7 +13932,7 @@ static ma_uint32 ma_ffs_32(ma_uint32 x) /* Just a naive implementation just to get things working for now. Will optimize this later. */ for (i = 0; i < 32; i += 1) { - if ((x & (1 << i)) != 0) { + if ((x & (1U << i)) != 0) { return i; } } @@ -14024,7 +14095,7 @@ static MA_INLINE ma_int32 ma_dither_s32(ma_dither_mode ditherMode, ma_int32 dith Atomics **************************************************************************************************************************************************************/ -/* ma_atomic.h begin */ +/* c89atomic.h begin */ #ifndef ma_atomic_h #if defined(__cplusplus) extern "C" { @@ -14750,12 +14821,12 @@ typedef int ma_atomic_memory_order; typedef ma_uint8 ma_atomic_flag; #define ma_atomic_flag_test_and_set_explicit(ptr, order) (ma_bool32)ma_atomic_test_and_set_explicit_8(ptr, order) #define ma_atomic_flag_clear_explicit(ptr, order) ma_atomic_clear_explicit_8(ptr, order) - #define c89atoimc_flag_load_explicit(ptr, order) ma_atomic_load_explicit_8(ptr, order) + #define ma_atomic_flag_load_explicit(ptr, order) ma_atomic_load_explicit_8(ptr, order) #else typedef ma_uint32 ma_atomic_flag; #define ma_atomic_flag_test_and_set_explicit(ptr, order) (ma_bool32)ma_atomic_test_and_set_explicit_32(ptr, order) #define ma_atomic_flag_clear_explicit(ptr, order) ma_atomic_clear_explicit_32(ptr, order) - #define c89atoimc_flag_load_explicit(ptr, order) ma_atomic_load_explicit_32(ptr, order) + #define ma_atomic_flag_load_explicit(ptr, order) ma_atomic_load_explicit_32(ptr, order) #endif #elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) #define MA_ATOMIC_HAS_NATIVE_COMPARE_EXCHANGE @@ -14836,15 +14907,24 @@ typedef int ma_atomic_memory_order; __atomic_compare_exchange_n(dst, &expected, desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); return expected; } + #if defined(__clang__) + #pragma clang diagnostic push + #if __clang_major__ >= 8 + #pragma clang diagnostic ignored "-Watomic-alignment" + #endif + #endif static MA_INLINE ma_uint64 ma_atomic_compare_and_swap_64(volatile ma_uint64* dst, ma_uint64 expected, ma_uint64 desired) { __atomic_compare_exchange_n(dst, &expected, desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); return expected; } + #if defined(__clang__) + #pragma clang diagnostic pop + #endif typedef ma_uint8 ma_atomic_flag; #define ma_atomic_flag_test_and_set_explicit(dst, order) (ma_bool32)__atomic_test_and_set(dst, order) #define ma_atomic_flag_clear_explicit(dst, order) __atomic_clear(dst, order) - #define c89atoimc_flag_load_explicit(ptr, order) ma_atomic_load_explicit_8(ptr, order) + #define ma_atomic_flag_load_explicit(ptr, order) ma_atomic_load_explicit_8(ptr, order) #else #define ma_atomic_memory_order_relaxed 1 #define ma_atomic_memory_order_consume 2 @@ -15358,7 +15438,7 @@ typedef int ma_atomic_memory_order; typedef ma_uint8 ma_atomic_flag; #define ma_atomic_flag_test_and_set_explicit(ptr, order) (ma_bool32)ma_atomic_test_and_set_explicit_8(ptr, order) #define ma_atomic_flag_clear_explicit(ptr, order) ma_atomic_clear_explicit_8(ptr, order) - #define c89atoimc_flag_load_explicit(ptr, order) ma_atomic_load_explicit_8(ptr, order) + #define ma_atomic_flag_load_explicit(ptr, order) ma_atomic_load_explicit_8(ptr, order) #endif #if !defined(MA_ATOMIC_HAS_NATIVE_COMPARE_EXCHANGE) #if defined(MA_ATOMIC_HAS_8) @@ -15883,7 +15963,7 @@ static MA_INLINE void ma_atomic_spinlock_lock(volatile ma_atomic_spinlock* pSpin if (ma_atomic_flag_test_and_set_explicit(pSpinlock, ma_atomic_memory_order_acquire) == 0) { break; } - while (c89atoimc_flag_load_explicit(pSpinlock, ma_atomic_memory_order_relaxed) == 1) { + while (ma_atomic_flag_load_explicit(pSpinlock, ma_atomic_memory_order_relaxed) == 1) { } } } @@ -15898,7 +15978,7 @@ static MA_INLINE void ma_atomic_spinlock_unlock(volatile ma_atomic_spinlock* pSp } #endif #endif -/* ma_atomic.h end */ +/* c89atomic.h end */ #define MA_ATOMIC_SAFE_TYPE_IMPL(c89TypeExtension, type) \ static MA_INLINE ma_##type ma_atomic_##type##_get(ma_atomic_##type* x) \ @@ -16096,7 +16176,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority int result; pthread_attr_t* pAttr = NULL; -#if !defined(__EMSCRIPTEN__) +#if !defined(__EMSCRIPTEN__) && !defined(__3DS__) /* Try setting the thread priority. It's not critical if anything fails here. */ pthread_attr_t attr; if (pthread_attr_init(&attr) == 0) { @@ -16142,19 +16222,34 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority if (priority == ma_thread_priority_idle) { sched.sched_priority = priorityMin; } else if (priority == ma_thread_priority_realtime) { - sched.sched_priority = priorityMax; - } else { - sched.sched_priority += ((int)priority + 5) * priorityStep; /* +5 because the lowest priority is -5. */ - if (sched.sched_priority < priorityMin) { - sched.sched_priority = priorityMin; + #if defined(MA_PTHREAD_REALTIME_THREAD_PRIORITY) + { + sched.sched_priority = MA_PTHREAD_REALTIME_THREAD_PRIORITY; } - if (sched.sched_priority > priorityMax) { + #else + { sched.sched_priority = priorityMax; } + #endif + } else { + sched.sched_priority += ((int)priority + 5) * priorityStep; /* +5 because the lowest priority is -5. */ } - /* I'm not treating a failure of setting the priority as a critical error so not checking the return value here. */ - pthread_attr_setschedparam(&attr, &sched); + if (sched.sched_priority < priorityMin) { + sched.sched_priority = priorityMin; + } + if (sched.sched_priority > priorityMax) { + sched.sched_priority = priorityMax; + } + + /* I'm not treating a failure of setting the priority as a critical error so not aborting on failure here. */ + if (pthread_attr_setschedparam(&attr, &sched) == 0) { + #if !defined(MA_ANDROID) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28) + { + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + } + #endif + } } } } @@ -16187,7 +16282,7 @@ static void ma_thread_wait__posix(ma_thread* pThread) static ma_result ma_mutex_init__posix(ma_mutex* pMutex) { int result; - + if (pMutex == NULL) { return MA_INVALID_ARGS; } @@ -17406,7 +17501,7 @@ static ma_job_proc g_jobVTable[MA_JOB_TYPE_COUNT] = /* Device. */ #if !defined(MA_NO_DEVICE_IO) - ma_job_process__device__aaudio_reroute /*MA_JOB_TYPE_DEVICE_AAUDIO_REROUTE*/ + ma_job_process__device__aaudio_reroute /* MA_JOB_TYPE_DEVICE_AAUDIO_REROUTE */ #endif }; @@ -17751,7 +17846,7 @@ MA_API ma_result ma_job_queue_next(ma_job_queue* pQueue, ma_job* pJob) is stored. One thread can fall through to the freeing of this item while another is still using "head" for the retrieval of the "next" variable. - The slot allocator might need to make use of some reference counting to ensure it's only truely freed when + The slot allocator might need to make use of some reference counting to ensure it's only truly freed when there are no more references to the item. This must be fixed before removing these locks. */ @@ -17859,7 +17954,16 @@ MA_API void ma_dlclose(ma_log* pLog, ma_handle handle) #ifdef MA_WIN32 FreeLibrary((HMODULE)handle); #else - dlclose((void*)handle); + /* Hack for Android bug (see https://github.com/android/ndk/issues/360). Calling dlclose() pre-API 28 may segfault. */ + #if !defined(MA_ANDROID) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28) + { + dlclose((void*)handle); + } + #else + { + (void)handle; + } + #endif #endif (void)pLog; @@ -17880,12 +17984,12 @@ MA_API ma_proc ma_dlsym(ma_log* pLog, ma_handle handle, const char* symbol) #ifdef _WIN32 proc = (ma_proc)GetProcAddress((HMODULE)handle, symbol); #else -#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) +#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) || defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" #endif proc = (ma_proc)dlsym((void*)handle, symbol); -#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) +#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) || defined(__clang__) #pragma GCC diagnostic pop #endif #endif @@ -17923,9 +18027,13 @@ DEVICE I/O #endif #endif +#ifdef MA_APPLE + #include +#endif + #ifndef MA_NO_DEVICE_IO -#if defined(MA_APPLE) && (__MAC_OS_X_VERSION_MIN_REQUIRED < 101200) +#if defined(MA_APPLE) && (MAC_OS_X_VERSION_MIN_REQUIRED < 101200) #include /* For mach_absolute_time() */ #endif @@ -17939,6 +18047,10 @@ DEVICE I/O #endif #endif +/* This must be set to at least 26. */ +#ifndef MA_AAUDIO_MIN_ANDROID_SDK_VERSION +#define MA_AAUDIO_MIN_ANDROID_SDK_VERSION 27 +#endif MA_API void ma_device_info_add_native_data_format(ma_device_info* pDeviceInfo, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 flags) @@ -18085,7 +18197,7 @@ MA_API ma_bool32 ma_is_backend_enabled(ma_backend backend) #if defined(MA_HAS_AAUDIO) #if defined(MA_ANDROID) { - return ma_android_sdk_version() >= 26; + return ma_android_sdk_version() >= MA_AAUDIO_MIN_ANDROID_SDK_VERSION; } #else return MA_FALSE; @@ -18402,7 +18514,6 @@ typedef LONG (WINAPI * MA_PFN_RegCloseKey)(HKEY hKey); typedef LONG (WINAPI * MA_PFN_RegQueryValueExA)(HKEY hKey, const char* lpValueName, DWORD* lpReserved, DWORD* lpType, BYTE* lpData, DWORD* lpcbData); #endif /* MA_WIN32_DESKTOP */ - MA_API size_t ma_strlen_WCHAR(const WCHAR* str) { size_t len = 0; @@ -18487,7 +18598,7 @@ Timing return (double)(counter.QuadPart - pTimer->counter) / g_ma_TimerFrequency.QuadPart; } -#elif defined(MA_APPLE) && (__MAC_OS_X_VERSION_MIN_REQUIRED < 101200) +#elif defined(MA_APPLE) && (MAC_OS_X_VERSION_MIN_REQUIRED < 101200) static ma_uint64 g_ma_TimerFrequency = 0; static void ma_timer_init(ma_timer* pTimer) { @@ -18670,11 +18781,16 @@ static void ma_device__on_notification_rerouted(ma_device* pDevice) #endif #if defined(MA_EMSCRIPTEN) -EMSCRIPTEN_KEEPALIVE -void ma_device__on_notification_unlocked(ma_device* pDevice) +#ifdef __cplusplus +extern "C" { +#endif +void EMSCRIPTEN_KEEPALIVE ma_device__on_notification_unlocked(ma_device* pDevice) { ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_unlocked)); } +#ifdef __cplusplus +} +#endif #endif @@ -18802,7 +18918,7 @@ static void ma_device__handle_data_callback(ma_device* pDevice, void* pFramesOut unsigned int prevDenormalState = ma_device_disable_denormals(pDevice); { /* Volume control of input makes things a bit awkward because the input buffer is read-only. We'll need to use a temp buffer and loop in this case. */ - if (pFramesIn != NULL && masterVolumeFactor < 1) { + if (pFramesIn != NULL && masterVolumeFactor != 1) { ma_uint8 tempFramesIn[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; ma_uint32 bpfCapture = ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); ma_uint32 bpfPlayback = ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); @@ -18825,7 +18941,7 @@ static void ma_device__handle_data_callback(ma_device* pDevice, void* pFramesOut /* Volume control and clipping for playback devices. */ if (pFramesOut != NULL) { - if (masterVolumeFactor < 1) { + if (masterVolumeFactor != 1) { if (pFramesIn == NULL) { /* <-- In full-duplex situations, the volume will have been applied to the input samples before the data callback. Applying it again post-callback will incorrectly compound it. */ ma_apply_volume_factor_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels, masterVolumeFactor); } @@ -18837,6 +18953,11 @@ static void ma_device__handle_data_callback(ma_device* pDevice, void* pFramesOut } } ma_device_restore_denormals(pDevice, prevDenormalState); + } else { + /* No data callback. Just silence the output. */ + if (pFramesOut != NULL) { + ma_silence_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels); + } } } @@ -18922,9 +19043,7 @@ static void ma_device__read_frames_from_client(ma_device* pDevice, ma_uint32 fra framesToReadThisIterationIn = requiredInputFrameCount; } - if (framesToReadThisIterationIn > 0) { - ma_device__handle_data_callback(pDevice, pIntermediaryBuffer, NULL, (ma_uint32)framesToReadThisIterationIn); - } + ma_device__handle_data_callback(pDevice, pIntermediaryBuffer, NULL, (ma_uint32)framesToReadThisIterationIn); /* At this point we have our decoded data in input format and now we need to convert to output format. Note that even if we didn't read any @@ -18965,7 +19084,7 @@ static void ma_device__send_frames_to_client(ma_device* pDevice, ma_uint32 frame ma_uint64 totalClientFramesProcessed = 0; const void* pRunningFramesInDeviceFormat = pFramesInDeviceFormat; - /* We just keep going until we've exhaused all of our input frames and cannot generate any more output frames. */ + /* We just keep going until we've exhausted all of our input frames and cannot generate any more output frames. */ for (;;) { ma_uint64 deviceFramesProcessedThisIteration; ma_uint64 clientFramesProcessedThisIteration; @@ -19248,7 +19367,7 @@ static ma_result ma_device_audio_thread__default_read_write(ma_device* pDevice) } /* - If we weren't able to generate any output frames it must mean we've exhaused all of our input. The only time this would not be the case is if capturedClientData was too small + If we weren't able to generate any output frames it must mean we've exhausted all of our input. The only time this would not be the case is if capturedClientData was too small which should never be the case when it's of the size MA_DATA_CONVERTER_STACK_BUFFER_SIZE. */ if (capturedClientFramesToProcessThisIteration == 0) { @@ -19451,7 +19570,7 @@ static ma_result ma_device_do_operation__null(ma_device* pDevice, ma_uint32 oper /* The first thing to do is wait for an operation slot to become available. We only have a single slot for this, but we could extend this later - to support queing of operations. + to support queuing of operations. */ result = ma_semaphore_wait(&pDevice->null_device.operationSemaphore); if (result != MA_SUCCESS) { @@ -21268,7 +21387,7 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context } /* - Exlcusive Mode. We repeatedly call IsFormatSupported() here. This is not currently supported on + Exclusive Mode. We repeatedly call IsFormatSupported() here. This is not currently supported on UWP. Failure to retrieve the exclusive mode format is not considered an error, so from here on out, MA_SUCCESS is guaranteed to be returned. */ @@ -21473,10 +21592,23 @@ static ma_result ma_context_get_MMDevice__wasapi(ma_context* pContext, ma_device MA_ASSERT(pContext != NULL); MA_ASSERT(ppMMDevice != NULL); + /* + This weird COM init/uninit here is a hack to work around a crash when changing devices. What is happening is + WASAPI fires a callback from another thread when the device is changed. It's from that thread where this + function is getting called. What I'm suspecting is that the other thread is not initializing COM which in turn + results in CoCreateInstance() failing. + + The community has reported that this seems to fix the crash. There are future plans to move all WASAPI operation + over to a single thread to make everything safer, but in the meantime while we wait for that to come online I'm + happy enough to use this hack instead. + */ ma_CoInitializeEx(pContext, NULL, MA_COINIT_VALUE); - hr = ma_CoCreateInstance(pContext, &MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); + { + hr = ma_CoCreateInstance(pContext, &MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); + } ma_CoUninitialize(pContext); - if (FAILED(hr)) { + + if (FAILED(hr)) { /* <-- This is checking the call above to ma_CoCreateInstance(). */ ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create IMMDeviceEnumerator.\n"); return ma_result_from_HRESULT(hr); } @@ -21508,7 +21640,7 @@ static ma_result ma_context_get_device_id_from_MMDevice__wasapi(ma_context* pCon size_t idlen = ma_strlen_WCHAR(pDeviceIDString); if (idlen+1 > ma_countof(pDeviceID->wasapi)) { ma_CoTaskMemFree(pContext, pDeviceIDString); - MA_ASSERT(MA_FALSE); /* NOTE: If this is triggered, please report it. It means the format of the ID must haved change and is too long to fit in our fixed sized buffer. */ + MA_ASSERT(MA_FALSE); /* NOTE: If this is triggered, please report it. It means the format of the ID must have changed and is too long to fit in our fixed sized buffer. */ return MA_ERROR; } @@ -21952,12 +22084,16 @@ static ma_result ma_device_uninit__wasapi(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); -#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) - if (pDevice->wasapi.pDeviceEnumerator) { - ((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator)->lpVtbl->UnregisterEndpointNotificationCallback((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator, &pDevice->wasapi.notificationClient); - ma_IMMDeviceEnumerator_Release((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator); + #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) + { + if (pDevice->wasapi.pDeviceEnumerator) { + ((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator)->lpVtbl->UnregisterEndpointNotificationCallback((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator, &pDevice->wasapi.notificationClient); + ma_IMMDeviceEnumerator_Release((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator); + } + + ma_mutex_uninit(&pDevice->wasapi.rerouteLock); } -#endif + #endif if (pDevice->wasapi.pRenderClient) { if (pDevice->wasapi.pMappedBufferPlayback != NULL) { @@ -22258,7 +22394,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device MA_REFERENCE_TIME bufferDuration = periodDurationInMicroseconds * pData->periodsOut * 10; /* - If the periodicy is too small, Initialize() will fail with AUDCLNT_E_INVALID_DEVICE_PERIOD. In this case we should just keep increasing + If the periodicity is too small, Initialize() will fail with AUDCLNT_E_INVALID_DEVICE_PERIOD. In this case we should just keep increasing it and trying it again. */ hr = E_FAIL; @@ -22268,7 +22404,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device if (bufferDuration > 500*10000) { break; } else { - if (bufferDuration == 0) { /* <-- Just a sanity check to prevent an infinit loop. Should never happen, but it makes me feel better. */ + if (bufferDuration == 0) { /* <-- Just a sanity check to prevent an infinite loop. Should never happen, but it makes me feel better. */ break; } @@ -23007,6 +23143,14 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) } if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { + /* If we have a mapped buffer we need to release it. */ + if (pDevice->wasapi.pMappedBufferCapture != NULL) { + ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); + pDevice->wasapi.pMappedBufferCapture = NULL; + pDevice->wasapi.mappedBufferCaptureCap = 0; + pDevice->wasapi.mappedBufferCaptureLen = 0; + } + hr = ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); if (FAILED(hr)) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal capture device."); @@ -23020,31 +23164,34 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) return ma_result_from_HRESULT(hr); } - /* If we have a mapped buffer we need to release it. */ - if (pDevice->wasapi.pMappedBufferCapture != NULL) { - ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); - pDevice->wasapi.pMappedBufferCapture = NULL; - pDevice->wasapi.mappedBufferCaptureCap = 0; - pDevice->wasapi.mappedBufferCaptureLen = 0; - } - ma_atomic_bool32_set(&pDevice->wasapi.isStartedCapture, MA_FALSE); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + if (pDevice->wasapi.pMappedBufferPlayback != NULL) { + ma_silence_pcm_frames( + ma_offset_pcm_frames_ptr(pDevice->wasapi.pMappedBufferPlayback, pDevice->wasapi.mappedBufferPlaybackLen, pDevice->playback.internalFormat, pDevice->playback.internalChannels), + pDevice->wasapi.mappedBufferPlaybackCap - pDevice->wasapi.mappedBufferPlaybackLen, + pDevice->playback.internalFormat, pDevice->playback.internalChannels + ); + ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0); + pDevice->wasapi.pMappedBufferPlayback = NULL; + pDevice->wasapi.mappedBufferPlaybackCap = 0; + pDevice->wasapi.mappedBufferPlaybackLen = 0; + } + /* The buffer needs to be drained before stopping the device. Not doing this will result in the last few frames not getting output to the speakers. This is a problem for very short sounds because it'll result in a significant portion of it not getting played. */ if (ma_atomic_bool32_get(&pDevice->wasapi.isStartedPlayback)) { /* We need to make sure we put a timeout here or else we'll risk getting stuck in a deadlock in some cases. */ - DWORD waitTime = pDevice->wasapi.actualBufferSizeInFramesPlayback / pDevice->playback.internalSampleRate; + DWORD waitTime = (pDevice->wasapi.actualBufferSizeInFramesPlayback * 1000) / pDevice->playback.internalSampleRate; if (pDevice->playback.shareMode == ma_share_mode_exclusive) { WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, waitTime); - } - else { - ma_uint32 prevFramesAvaialablePlayback = (ma_uint32)-1; + } else { + ma_uint32 prevFramesAvailablePlayback = (ma_uint32)-1; ma_uint32 framesAvailablePlayback; for (;;) { result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &framesAvailablePlayback); @@ -23060,13 +23207,13 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) Just a safety check to avoid an infinite loop. If this iteration results in a situation where the number of available frames has not changed, get out of the loop. I don't think this should ever happen, but I think it's nice to have just in case. */ - if (framesAvailablePlayback == prevFramesAvaialablePlayback) { + if (framesAvailablePlayback == prevFramesAvailablePlayback) { break; } - prevFramesAvaialablePlayback = framesAvailablePlayback; + prevFramesAvailablePlayback = framesAvailablePlayback; - WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, waitTime * 1000); ResetEvent((HANDLE)pDevice->wasapi.hEventPlayback); /* Manual reset. */ + WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, waitTime); } } } @@ -23078,19 +23225,20 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) } /* The audio client needs to be reset otherwise restarting will fail. */ - hr = ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + { + ma_int32 retries = 5; + + while ((hr = ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback)) == MA_AUDCLNT_E_BUFFER_OPERATION_PENDING && retries > 0) { + ma_sleep(10); + retries -= 1; + } + } + if (FAILED(hr)) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal playback device."); return ma_result_from_HRESULT(hr); } - if (pDevice->wasapi.pMappedBufferPlayback != NULL) { - ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0); - pDevice->wasapi.pMappedBufferPlayback = NULL; - pDevice->wasapi.mappedBufferPlaybackCap = 0; - pDevice->wasapi.mappedBufferPlaybackLen = 0; - } - ma_atomic_bool32_set(&pDevice->wasapi.isStartedPlayback, MA_FALSE); } @@ -23657,6 +23805,13 @@ DirectSound Backend #define MA_DSBPLAY_TERMINATEBY_DISTANCE 0x00000010 #define MA_DSBPLAY_TERMINATEBY_PRIORITY 0x00000020 +#define MA_DSBSTATUS_PLAYING 0x00000001 +#define MA_DSBSTATUS_BUFFERLOST 0x00000002 +#define MA_DSBSTATUS_LOOPING 0x00000004 +#define MA_DSBSTATUS_LOCHARDWARE 0x00000008 +#define MA_DSBSTATUS_LOCSOFTWARE 0x00000010 +#define MA_DSBSTATUS_TERMINATED 0x00000020 + #define MA_DSCBSTART_LOOPING 0x00000001 typedef struct @@ -24026,9 +24181,12 @@ static ma_result ma_context_create_IDirectSound__dsound(ma_context* pContext, ma } /* The cooperative level must be set before doing anything else. */ - hWnd = ((MA_PFN_GetForegroundWindow)pContext->win32.GetForegroundWindow)(); + hWnd = (HWND)pContext->dsound.hWnd; if (hWnd == 0) { - hWnd = ((MA_PFN_GetDesktopWindow)pContext->win32.GetDesktopWindow)(); + hWnd = ((MA_PFN_GetForegroundWindow)pContext->win32.GetForegroundWindow)(); + if (hWnd == 0) { + hWnd = ((MA_PFN_GetDesktopWindow)pContext->win32.GetDesktopWindow)(); + } } hr = ma_IDirectSound_SetCooperativeLevel(pDirectSound, hWnd, (shareMode == ma_share_mode_exclusive) ? MA_DSSCL_EXCLUSIVE : MA_DSSCL_PRIORITY); @@ -24532,8 +24690,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf } /* - Unfortunately DirectSound uses different APIs and data structures for playback and catpure devices. We need to initialize - the capture device first because we'll want to match it's buffer size and period count on the playback side if we're using + Unfortunately DirectSound uses different APIs and data structures for playback and capture devices. We need to initialize + the capture device first because we'll want to match its buffer size and period count on the playback side if we're using full-duplex mode. */ if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { @@ -24816,6 +24974,7 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) ma_bool32 isPlaybackDeviceStarted = MA_FALSE; ma_uint32 framesWrittenToPlaybackDevice = 0; /* For knowing whether or not the playback device needs to be started. */ ma_uint32 waitTimeInMilliseconds = 1; + DWORD playbackBufferStatus = 0; MA_ASSERT(pDevice != NULL); @@ -25144,6 +25303,20 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) break; } + hr = ma_IDirectSoundBuffer_GetStatus((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, &playbackBufferStatus); + if (SUCCEEDED(hr) && (playbackBufferStatus & MA_DSBSTATUS_PLAYING) == 0 && isPlaybackDeviceStarted) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[DirectSound] Attempting to resume audio due to state: %d.", (int)playbackBufferStatus); + hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); + if (FAILED(hr)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed after attempting to resume from state %d.", (int)playbackBufferStatus); + return ma_result_from_HRESULT(hr); + } + + isPlaybackDeviceStarted = MA_TRUE; + ma_sleep(waitTimeInMilliseconds); + continue; + } + if (physicalPlayCursorInBytes < prevPlayCursorInBytesPlayback) { physicalPlayCursorLoopFlagPlayback = !physicalPlayCursorLoopFlagPlayback; } @@ -25345,6 +25518,8 @@ static ma_result ma_context_init__dsound(ma_context* pContext, const ma_context_ return MA_API_NOT_FOUND; } + pContext->dsound.hWnd = pConfig->dsound.hWnd; + pCallbacks->onContextInit = ma_context_init__dsound; pCallbacks->onContextUninit = ma_context_uninit__dsound; pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__dsound; @@ -25667,7 +25842,7 @@ static ma_result ma_context_get_device_info_from_WAVECAPS(ma_context* pContext, - If the name GUID is not present in the registry we'll also need to stick to the original 31 characters. - I like consistency, so I want the returned device names to be consistent with those returned by WASAPI and DirectSound. The problem, however is that WASAPI and DirectSound use " ()" format (such as "Speakers (High Definition Audio)"), - but WinMM does not specificy the component name. From my admittedly limited testing, I've notice the component name seems to + but WinMM does not specify the component name. From my admittedly limited testing, I've notice the component name seems to usually fit within the 31 characters of the fixed sized buffer, so what I'm going to do is parse that string for the component name, and then concatenate the name from the registry. */ @@ -25935,7 +26110,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi return MA_DEVICE_TYPE_NOT_SUPPORTED; } - /* No exlusive mode with WinMM. */ + /* No exclusive mode with WinMM. */ if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { return MA_SHARE_MODE_NOT_SUPPORTED; @@ -25957,7 +26132,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi /* We use an event to know when a new fragment needs to be enqueued. */ pDevice->winmm.hEventCapture = (ma_handle)CreateEventA(NULL, TRUE, TRUE, NULL); if (pDevice->winmm.hEventCapture == NULL) { - errorMsg = "[WinMM] Failed to create event for fragment enqueing for the capture device.", errorCode = ma_result_from_GetLastError(GetLastError()); + errorMsg = "[WinMM] Failed to create event for fragment enqueuing for the capture device.", errorCode = ma_result_from_GetLastError(GetLastError()); goto on_error; } @@ -25995,7 +26170,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi /* We use an event to know when a new fragment needs to be enqueued. */ pDevice->winmm.hEventPlayback = (ma_handle)CreateEventA(NULL, TRUE, TRUE, NULL); if (pDevice->winmm.hEventPlayback == NULL) { - errorMsg = "[WinMM] Failed to create event for fragment enqueing for the playback device.", errorCode = ma_result_from_GetLastError(GetLastError()); + errorMsg = "[WinMM] Failed to create event for fragment enqueuing for the playback device.", errorCode = ma_result_from_GetLastError(GetLastError()); goto on_error; } @@ -27117,7 +27292,7 @@ static ma_result ma_context_open_pcm__alsa(ma_context* pContext, ma_share_mode s /* We're trying to open a specific device. There's a few things to consider here: - miniaudio recongnizes a special format of device id that excludes the "hw", "dmix", etc. prefix. It looks like this: ":0,0", ":0,1", etc. When + miniaudio recognizes a special format of device id that excludes the "hw", "dmix", etc. prefix. It looks like this: ":0,0", ":0,1", etc. When an ID of this format is specified, it indicates to miniaudio that it can try different combinations of plugins ("hw", "dmix", etc.) until it finds an appropriate one that works. This comes in very handy when trying to open a device in shared mode ("dmix"), vs exclusive mode ("hw"). */ @@ -27216,7 +27391,7 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu /* At this point, hwid looks like "hw:0,0". In simplified enumeration mode, we actually want to strip off the plugin name so it looks like ":0,0". The reason for this is that this special format is detected at device - initialization time and is used as an indicator to try and use the most appropriate plugin depending on the + initialization time and is used as an indicator to try to use the most appropriate plugin depending on the device type and sharing mode. */ char* dst = hwid; @@ -27395,7 +27570,7 @@ static void ma_context_iterate_rates_and_add_native_data_format__alsa(ma_context ((ma_snd_pcm_hw_params_get_rate_min_proc)pContext->alsa.snd_pcm_hw_params_get_rate_min)(pHWParams, &minSampleRate, &sampleRateDir); ((ma_snd_pcm_hw_params_get_rate_max_proc)pContext->alsa.snd_pcm_hw_params_get_rate_max)(pHWParams, &maxSampleRate, &sampleRateDir); - /* Make sure our sample rates are clamped to sane values. Stupid devices like "pulse" will reports rates like "1" which is ridiculus. */ + /* Make sure our sample rates are clamped to sane values. Stupid devices like "pulse" will reports rates like "1" which is ridiculous. */ minSampleRate = ma_clamp(minSampleRate, (unsigned int)ma_standard_sample_rate_min, (unsigned int)ma_standard_sample_rate_max); maxSampleRate = ma_clamp(maxSampleRate, (unsigned int)ma_standard_sample_rate_min, (unsigned int)ma_standard_sample_rate_max); @@ -27471,10 +27646,10 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic /* Some ALSA devices can support many permutations of formats, channels and rates. We only support a fixed number of permutations which means we need to employ some strategies to ensure the best - combinations are returned. An example is the "pulse" device which can do it's own data conversion + combinations are returned. An example is the "pulse" device which can do its own data conversion in software and as a result can support any combination of format, channels and rate. - We want to ensure the the first data formats are the best. We have a list of favored sample + We want to ensure that the first data formats are the best. We have a list of favored sample formats and sample rates, so these will be the basis of our iteration. */ @@ -28052,7 +28227,21 @@ static ma_result ma_device_start__alsa(ma_device* pDevice) } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - /* Don't need to do anything for playback because it'll be started automatically when enough data has been written. */ + /* + When data is written to the device we wait for the device to get ready to receive data with poll(). In my testing + I have observed that poll() can sometimes block forever unless the device is started explicitly with snd_pcm_start() + or some data is written with snd_pcm_writei(). + + To resolve this I've decided to do an explicit start with snd_pcm_start(). The problem with this is that the device + is started without any data in the internal buffer which will result in an immediate underrun. If instead we were + to call into snd_pcm_writei() in an attempt to prevent the underrun, we would run the risk of a weird deadlock + issue as documented inside ma_device_write__alsa(). + */ + resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback); + if (resultALSA < 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start playback device."); + return ma_result_from_errno(-resultALSA); + } } return MA_SUCCESS; @@ -28065,6 +28254,7 @@ static ma_result ma_device_stop__alsa(ma_device* pDevice) a small chance that our wakeupfd has not been cleared. We'll clear that out now if applicable. */ int resultPoll; + int resultRead; if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device...\n"); @@ -28079,12 +28269,15 @@ static ma_result ma_device_stop__alsa(ma_device* pDevice) ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device successful.\n"); } - /* Clear the wakeupfd. */ - resultPoll = poll((struct pollfd*)pDevice->alsa.pPollDescriptorsCapture, 1, 0); - if (resultPoll > 0) { - ma_uint64 t; - read(((struct pollfd*)pDevice->alsa.pPollDescriptorsCapture)[0].fd, &t, sizeof(t)); - } + /* Clear the wakeupfd. */ + resultPoll = poll((struct pollfd*)pDevice->alsa.pPollDescriptorsCapture, 1, 0); + if (resultPoll > 0) { + ma_uint64 t; + resultRead = read(((struct pollfd*)pDevice->alsa.pPollDescriptorsCapture)[0].fd, &t, sizeof(t)); + if (resultRead != sizeof(t)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Failed to read from capture wakeupfd. read() = %d\n", resultRead); + } + } } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { @@ -28101,12 +28294,14 @@ static ma_result ma_device_stop__alsa(ma_device* pDevice) } /* Clear the wakeupfd. */ - resultPoll = poll((struct pollfd*)pDevice->alsa.pPollDescriptorsPlayback, 1, 0); - if (resultPoll > 0) { - ma_uint64 t; - read(((struct pollfd*)pDevice->alsa.pPollDescriptorsPlayback)[0].fd, &t, sizeof(t)); - } - + resultPoll = poll((struct pollfd*)pDevice->alsa.pPollDescriptorsPlayback, 1, 0); + if (resultPoll > 0) { + ma_uint64 t; + resultRead = read(((struct pollfd*)pDevice->alsa.pPollDescriptorsPlayback)[0].fd, &t, sizeof(t)); + if (resultRead != sizeof(t)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Failed to read from playback wakeupfd. read() = %d\n", resultRead); + } + } } return MA_SUCCESS; @@ -28119,13 +28314,20 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, st int resultALSA; int resultPoll = poll(pPollDescriptors, pollDescriptorCount, -1); if (resultPoll < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] poll() failed.\n"); - return ma_result_from_errno(errno); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[ALSA] poll() failed.\n"); + + /* + There have been reports that poll() is returning an error randomly and that instead of + returning an error, simply trying again will work. I'm experimenting with adopting this + advice. + */ + continue; + /*return ma_result_from_errno(errno);*/ } /* Before checking the ALSA poll descriptor flag we need to check if the wakeup descriptor - has had it's POLLIN flag set. If so, we need to actually read the data and then exit + has had it's POLLIN flag set. If so, we need to actually read the data and then exit the function. The wakeup descriptor will be the first item in the descriptors buffer. */ if ((pPollDescriptors[0].revents & POLLIN) != 0) { @@ -28154,7 +28356,7 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, st ma_snd_pcm_state_t state = ((ma_snd_pcm_state_proc)pDevice->pContext->alsa.snd_pcm_state)(pPCM); if (state == MA_SND_PCM_STATE_XRUN) { /* The PCM is in a xrun state. This will be recovered from at a higher level. We can disregard this. */ - } else { + } else { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[ALSA] POLLERR detected. status = %d\n", ((ma_snd_pcm_state_proc)pDevice->pContext->alsa.snd_pcm_state)(pPCM)); } } @@ -28587,7 +28789,7 @@ static ma_result ma_context_init__alsa(ma_context* pContext, const ma_context_co return MA_SUCCESS; } -#endif /* ALSA */ +#endif /* MA_HAS_ALSA */ @@ -28598,7 +28800,7 @@ PulseAudio Backend ******************************************************************************/ #ifdef MA_HAS_PULSEAUDIO /* -The PulseAudio API, along with Apple's Core Audio, is the worst of the maintream audio APIs. This is a brief description of what's going on +The PulseAudio API, along with Apple's Core Audio, is the worst of the mainstream audio APIs. This is a brief description of what's going on in the PulseAudio backend. I apologize if this gets a bit ranty for your liking - you might want to skip this discussion. PulseAudio has something they call the "Simple API", which unfortunately isn't suitable for miniaudio. I've not seen anywhere where it @@ -28613,7 +28815,7 @@ get fun, and I don't mean that in a good way... The problems start with the very name of the API - "asynchronous". Yes, this is an asynchronous oriented API which means your commands don't immediately take effect. You instead need to issue your commands, and then wait for them to complete. The waiting mechanism is -enabled through the use of a "main loop". In the asychronous API you cannot get away from the main loop, and the main loop is where almost +enabled through the use of a "main loop". In the asynchronous API you cannot get away from the main loop, and the main loop is where almost all of PulseAudio's problems stem from. When you first initialize PulseAudio you need an object referred to as "main loop". You can implement this yourself by defining your own @@ -28663,7 +28865,7 @@ because PulseAudio takes it literally, specifically the "can be". You would thin writing and reading data to and from the stream, and that would be right, except when it's not. When you initialize the stream, you can set a flag that tells PulseAudio to not start the stream automatically. This is required because miniaudio does not auto-start devices straight after initialization - you need to call `ma_device_start()` manually. The problem is that even when this flag is specified, -PulseAudio will immediately fire it's write or read callback. This is *technically* correct (based on the wording in the documentation) +PulseAudio will immediately fire its write or read callback. This is *technically* correct (based on the wording in the documentation) because indeed, data *can* be written at this point. The problem is that it's not *practical*. It makes sense that the write/read callback would be where a program will want to write or read data to or from the stream, but when it's called before the application has even requested that the stream be started, it's just not practical because the program probably isn't ready for any kind of data delivery at @@ -30041,16 +30243,18 @@ static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFra static ma_pa_stream* ma_device__pa_stream_new__pulse(ma_device* pDevice, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap) { - static int g_StreamCounter = 0; + static ma_atomic_uint32 g_StreamCounter = { 0 }; char actualStreamName[256]; if (pStreamName != NULL) { ma_strncpy_s(actualStreamName, sizeof(actualStreamName), pStreamName, (size_t)-1); } else { - ma_strcpy_s(actualStreamName, sizeof(actualStreamName), "miniaudio:"); - ma_itoa_s(g_StreamCounter, actualStreamName + 8, sizeof(actualStreamName)-8, 10); /* 8 = strlen("miniaudio:") */ + const char* pBaseName = "miniaudio:"; + size_t baseNameLen = strlen(pBaseName); + ma_strcpy_s(actualStreamName, sizeof(actualStreamName), pBaseName); + ma_itoa_s((int)ma_atomic_uint32_get(&g_StreamCounter), actualStreamName + baseNameLen, sizeof(actualStreamName)-baseNameLen, 10); } - g_StreamCounter += 1; + ma_atomic_uint32_fetch_add(&g_StreamCounter, 1); return ((ma_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((ma_pa_context*)pDevice->pulse.pPulseContext, actualStreamName, ss, cmap); } @@ -30304,6 +30508,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi ma_pa_buffer_attr attr; const ma_pa_sample_spec* pActualSS = NULL; const ma_pa_buffer_attr* pActualAttr = NULL; + const ma_pa_channel_map* pActualChannelMap = NULL; ma_uint32 iChannel; ma_pa_stream_flags_t streamFlags; @@ -30364,7 +30569,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi } /* Use a default channel map. */ - ((ma_pa_channel_map_init_extend_proc)pDevice->pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, MA_PA_CHANNEL_MAP_DEFAULT); + ((ma_pa_channel_map_init_extend_proc)pDevice->pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, pConfig->pulse.channelMap); /* Use the requested sample rate if one was specified. */ if (pDescriptorCapture->sampleRate != 0) { @@ -30453,7 +30658,12 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi goto on_error4; } + /* Internal channel map. */ + pActualChannelMap = ((ma_pa_stream_get_channel_map_proc)pDevice->pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamCapture); + if (pActualChannelMap == NULL) { + pActualChannelMap = &cmap; /* Fallback just in case. */ + } /* Bug in PipeWire. There have been reports that PipeWire is returning AUX channels when reporting @@ -30463,8 +30673,8 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi fixed sooner than later. I might remove this hack later. */ if (pDescriptorCapture->channels > 2) { - for (iChannel = 0; iChannel < pDescriptorCapture->channels; ++iChannel) { - pDescriptorCapture->channelMap[iChannel] = ma_channel_position_from_pulse(cmap.map[iChannel]); + for (iChannel = 0; iChannel < pDescriptorCapture->channels; iChannel += 1) { + pDescriptorCapture->channelMap[iChannel] = ma_channel_position_from_pulse(pActualChannelMap->map[iChannel]); } } else { /* Hack for mono and stereo. */ @@ -30511,7 +30721,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi } /* Use a default channel map. */ - ((ma_pa_channel_map_init_extend_proc)pDevice->pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, MA_PA_CHANNEL_MAP_DEFAULT); + ((ma_pa_channel_map_init_extend_proc)pDevice->pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, pConfig->pulse.channelMap); /* Use the requested sample rate if one was specified. */ @@ -30605,7 +30815,12 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi goto on_error4; } + /* Internal channel map. */ + pActualChannelMap = ((ma_pa_stream_get_channel_map_proc)pDevice->pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); + if (pActualChannelMap == NULL) { + pActualChannelMap = &cmap; /* Fallback just in case. */ + } /* Bug in PipeWire. There have been reports that PipeWire is returning AUX channels when reporting @@ -30615,8 +30830,8 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi fixed sooner than later. I might remove this hack later. */ if (pDescriptorPlayback->channels > 2) { - for (iChannel = 0; iChannel < pDescriptorPlayback->channels; ++iChannel) { - pDescriptorPlayback->channelMap[iChannel] = ma_channel_position_from_pulse(cmap.map[iChannel]); + for (iChannel = 0; iChannel < pDescriptorPlayback->channels; iChannel += 1) { + pDescriptorPlayback->channelMap[iChannel] = ma_channel_position_from_pulse(pActualChannelMap->map[iChannel]); } } else { /* Hack for mono and stereo. */ @@ -31769,7 +31984,7 @@ static ma_result ma_context_init__jack(ma_context* pContext, const ma_context_co return MA_SUCCESS; } -#endif /* JACK */ +#endif /* MA_HAS_JACK */ @@ -31860,7 +32075,7 @@ that supports this level of detail. There was some public domain sample code I s and AudioUnit APIs, but I couldn't see anything that gave low-level control over device selection and capabilities (the distinction between playback and capture in particular). Therefore, miniaudio is using the AudioObject API. -Most (all?) functions in the AudioObject API take a AudioObjectID as it's input. This is the device identifier. When +Most (all?) functions in the AudioObject API take a AudioObjectID as its input. This is the device identifier. When retrieving global information, such as the device list, you use kAudioObjectSystemObject. When retrieving device-specific data, you pass in the ID for that device. In order to retrieve device-specific IDs you need to enumerate over each of the devices. This is done using the AudioObjectGetPropertyDataSize() and AudioObjectGetPropertyData() APIs which seem to be @@ -32195,6 +32410,12 @@ static ma_result ma_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* #define AUDIO_OBJECT_PROPERTY_ELEMENT kAudioObjectPropertyElementMaster #endif +/* kAudioDevicePropertyScope* were renamed to kAudioObjectPropertyScope* in 10.8. */ +#if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8) +#define kAudioObjectPropertyScopeInput kAudioDevicePropertyScopeInput +#define kAudioObjectPropertyScopeOutput kAudioDevicePropertyScopeOutput +#endif + static ma_result ma_get_device_object_ids__coreaudio(ma_context* pContext, UInt32* pDeviceCount, AudioObjectID** ppDeviceObjectIDs) /* NOTE: Free the returned buffer with ma_free(). */ { AudioObjectPropertyAddress propAddressDevices; @@ -32784,7 +33005,7 @@ static ma_result ma_find_best_format__coreaudio(ma_context* pContext, AudioObjec desiredSampleRate = sampleRate; if (desiredSampleRate == 0) { - desiredSampleRate = pOrigFormat->mSampleRate; + desiredSampleRate = (ma_uint32)pOrigFormat->mSampleRate; } desiredChannelCount = channels; @@ -33427,7 +33648,7 @@ static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFl } } else { /* This is the deinterleaved case. We need to update each buffer in groups of internalChannels. This assumes each buffer is the same size. */ - MA_ASSERT(pDevice->playback.internalChannels <= MA_MAX_CHANNELS); /* This should heve been validated at initialization time. */ + MA_ASSERT(pDevice->playback.internalChannels <= MA_MAX_CHANNELS); /* This should have been validated at initialization time. */ /* For safety we'll check that the internal channels is a multiple of the buffer count. If it's not it means something @@ -33518,11 +33739,12 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla */ for (iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { pRenderedBufferList->mBuffers[iBuffer].mDataByteSize = pDevice->coreaudio.audioBufferCapInFrames * ma_get_bytes_per_sample(pDevice->capture.internalFormat) * pRenderedBufferList->mBuffers[iBuffer].mNumberChannels; + /*printf("DEBUG: nDataByteSize = %d\n", (int)pRenderedBufferList->mBuffers[iBuffer].mDataByteSize);*/ } status = ((ma_AudioUnitRender_proc)pDevice->pContext->coreaudio.AudioUnitRender)((AudioUnit)pDevice->coreaudio.audioUnitCapture, pActionFlags, pTimeStamp, busNumber, frameCount, pRenderedBufferList); if (status != noErr) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " ERROR: AudioUnitRender() failed with %d.\n", (int)status); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "ERROR: AudioUnitRender() failed with %d.\n", (int)status); return status; } @@ -33758,7 +33980,7 @@ static ma_result ma_context__init_device_tracking__coreaudio(ma_context* pContex ma_spinlock_lock(&g_DeviceTrackingInitLock_CoreAudio); { - /* Don't do anything if we've already initializd device tracking. */ + /* Don't do anything if we've already initialized device tracking. */ if (g_DeviceTrackingInitCounter_CoreAudio == 0) { AudioObjectPropertyAddress propAddress; propAddress.mScope = kAudioObjectPropertyScopeGlobal; @@ -34070,11 +34292,11 @@ typedef struct static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_init_internal_data__coreaudio* pData, void* pDevice_DoNotReference) /* <-- pDevice is typed as void* intentionally so as to avoid accidentally referencing it. */ { - ma_result result; + ma_result result = MA_SUCCESS; OSStatus status; UInt32 enableIOFlag; AudioStreamBasicDescription bestFormat; - UInt32 actualPeriodSizeInFrames; + ma_uint32 actualPeriodSizeInFrames; AURenderCallbackStruct callbackInfo; #if defined(MA_APPLE_DESKTOP) AudioObjectID deviceObjectID; @@ -34226,7 +34448,7 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev returning a result code of -10863. I have also tried changing the format directly on the input scope on the input bus, but this just results in `ca_require: IsStreamFormatWritable(inScope, inElement) NotWritable` when trying to set the format. - Something that does seem to work, however, has been setting the nominal sample rate on the deivce object. The problem with + Something that does seem to work, however, has been setting the nominal sample rate on the device object. The problem with this, however, is that it actually changes the sample rate at the operating system level and not just the application. This could be intrusive to the user, however, so I don't think it's wise to make this the default. Instead I'm making this a configuration option. When the `coreaudio.allowNominalSampleRateChange` config option is set to true, changing the sample @@ -34277,15 +34499,28 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev /* I've had a report that the channel count returned by AudioUnitGetProperty above is inconsistent with AVAudioSession outputNumberOfChannels. I'm going to try using the AVAudioSession values instead. + + UPDATE 20/02/2025: + When testing on the simulator with an iPhone 15 and iOS 17 I get an error when initializing the audio + unit if set the input channels to pAudioSession.inputNumberOfChannels. What is happening is the channel + count returned from AudioUnitGetProperty() above is set to 2, but pAudioSession is reporting a channel + count of 1. When this happens, the call to AudioUnitSetProprty() below just down below will succeed, but + AudioUnitInitialize() further down will fail. The only solution I have come up with is to not set the + channel count to pAudioSession.inputNumberOfChannels. */ if (deviceType == ma_device_type_playback) { bestFormat.mChannelsPerFrame = (UInt32)pAudioSession.outputNumberOfChannels; } + + #if 0 if (deviceType == ma_device_type_capture) { + /*printf("DEBUG: bestFormat.mChannelsPerFrame = %d; pAudioSession.inputNumberOfChannels = %d\n", (int)bestFormat.mChannelsPerFrame, (int)pAudioSession.inputNumberOfChannels);*/ bestFormat.mChannelsPerFrame = (UInt32)pAudioSession.inputNumberOfChannels; } + #endif } + status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, sizeof(bestFormat)); if (status != noErr) { ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); @@ -34305,7 +34540,7 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev } pData->channelsOut = bestFormat.mChannelsPerFrame; - pData->sampleRateOut = bestFormat.mSampleRate; + pData->sampleRateOut = (ma_uint32)bestFormat.mSampleRate; } /* Clamp the channel count for safety. */ @@ -34612,7 +34847,7 @@ static ma_result ma_device_init__coreaudio(ma_device* pDevice, const ma_device_c ma_get_AudioObject_uid(pDevice->pContext, pDevice->coreaudio.deviceObjectIDCapture, sizeof(pDevice->capture.id.coreaudio), pDevice->capture.id.coreaudio); /* - If we are using the default device we'll need to listen for changes to the system's default device so we can seemlessly + If we are using the default device we'll need to listen for changes to the system's default device so we can seamlessly switch the device in the background. */ if (pConfig->capture.pDeviceID == NULL) { @@ -34676,7 +34911,7 @@ static ma_result ma_device_init__coreaudio(ma_device* pDevice, const ma_device_c ma_get_AudioObject_uid(pDevice->pContext, pDevice->coreaudio.deviceObjectIDPlayback, sizeof(pDevice->playback.id.coreaudio), pDevice->playback.id.coreaudio); /* - If we are using the default device we'll need to listen for changes to the system's default device so we can seemlessly + If we are using the default device we'll need to listen for changes to the system's default device so we can seamlessly switch the device in the background. */ if (pDescriptorPlayback->pDeviceID == NULL && (pConfig->deviceType != ma_device_type_duplex || pDescriptorCapture->pDeviceID != NULL)) { @@ -34994,7 +35229,7 @@ static ma_result ma_context_init__coreaudio(ma_context* pContext, const ma_conte return MA_SUCCESS; } -#endif /* Core Audio */ +#endif /* MA_HAS_COREAUDIO */ @@ -35486,7 +35721,7 @@ static ma_result ma_device_uninit__sndio(ma_device* pDevice) ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)pDevice->sndio.handleCapture); } - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)pDevice->sndio.handlePlayback); } @@ -35841,7 +36076,7 @@ static ma_result ma_context_init__sndio(ma_context* pContext, const ma_context_c (void)pConfig; return MA_SUCCESS; } -#endif /* sndio */ +#endif /* MA_HAS_SNDIO */ @@ -35859,6 +36094,10 @@ audio(4) Backend #include #include +#ifdef __NetBSD__ +#include +#endif + #if defined(__OpenBSD__) #include #if defined(OpenBSD) && OpenBSD >= 201709 @@ -36078,7 +36317,7 @@ static ma_result ma_context_get_device_info_from_fd__audio4(ma_context* pContext ma_uint32 channels; ma_uint32 sampleRate; -#ifdef __NetBSD__ +#if defined(__NetBSD__) && (__NetBSD_Version__ >= 900000000) if (ioctl(fd, AUDIO_GETFORMAT, &fdInfo) < 0) { return MA_ERROR; } @@ -36364,7 +36603,7 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c /* We're using a default device. Get the info from the /dev/audioctl file instead of /dev/audio. */ int fdctl = open(pDefaultDeviceCtlNames[iDefaultDevice], fdFlags, 0); if (fdctl != -1) { -#ifdef __NetBSD__ +#if defined(__NetBSD__) && (__NetBSD_Version__ >= 900000000) fdInfoResult = ioctl(fdctl, AUDIO_GETFORMAT, &fdInfo); #else fdInfoResult = ioctl(fdctl, AUDIO_GETINFO, &fdInfo); @@ -36735,7 +36974,7 @@ static ma_result ma_context_init__audio4(ma_context* pContext, const ma_context_ return MA_SUCCESS; } -#endif /* audio4 */ +#endif /* MA_HAS_AUDIO4 */ /****************************************************************************** @@ -37098,7 +37337,7 @@ static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_conf } /* - The OSS documantation is very clear about the order we should be initializing the device's properties: + The OSS documentation is very clear about the order we should be initializing the device's properties: 1) Format 2) Channels 3) Sample rate. @@ -37366,7 +37605,7 @@ static ma_result ma_context_init__oss(ma_context* pContext, const ma_context_con return MA_SUCCESS; } -#endif /* OSS */ +#endif /* MA_HAS_OSS */ @@ -37379,7 +37618,9 @@ AAudio Backend ******************************************************************************/ #ifdef MA_HAS_AAUDIO -/*#include */ +#ifdef MA_NO_RUNTIME_LINKING + #include +#endif typedef int32_t ma_aaudio_result_t; typedef int32_t ma_aaudio_direction_t; @@ -37592,9 +37833,7 @@ static void ma_stream_error_callback__aaudio(ma_AAudioStream* pStream, void* pUs MA_ASSERT(pDevice != NULL); (void)error; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] ERROR CALLBACK: error=%d, AAudioStream_getState()=%d\n", error, ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream)); - /* When we get an error, we'll assume that the stream is in an erroneous state and needs to be restarted. From the documentation, we cannot do this from the error callback. Therefore we are going to use an event thread for the AAudio backend to do this @@ -37622,7 +37861,9 @@ static ma_aaudio_data_callback_result_t ma_stream_data_callback_capture__aaudio( ma_device* pDevice = (ma_device*)pUserData; MA_ASSERT(pDevice != NULL); - ma_device_handle_backend_data_callback(pDevice, NULL, pAudioData, frameCount); + if (frameCount > 0) { + ma_device_handle_backend_data_callback(pDevice, NULL, pAudioData, (ma_uint32)frameCount); + } (void)pStream; return MA_AAUDIO_CALLBACK_RESULT_CONTINUE; @@ -37633,7 +37874,14 @@ static ma_aaudio_data_callback_result_t ma_stream_data_callback_playback__aaudio ma_device* pDevice = (ma_device*)pUserData; MA_ASSERT(pDevice != NULL); - ma_device_handle_backend_data_callback(pDevice, pAudioData, NULL, frameCount); + /* + I've had a report that AAudio can sometimes post a frame count of 0. We need to check for that here + so we don't get any errors at a deeper level. I'm doing the same with the capture side for safety, + though I've not yet had any reports about that one. + */ + if (frameCount > 0) { + ma_device_handle_backend_data_callback(pDevice, pAudioData, NULL, (ma_uint32)frameCount); + } (void)pStream; return MA_AAUDIO_CALLBACK_RESULT_CONTINUE; @@ -37668,32 +37916,25 @@ static ma_result ma_create_and_configure_AAudioStreamBuilder__aaudio(ma_context* ((MA_PFN_AAudioStreamBuilder_setSampleRate)pContext->aaudio.AAudioStreamBuilder_setSampleRate)(pBuilder, pDescriptor->sampleRate); } - if (deviceType == ma_device_type_capture) { - if (pDescriptor->channels != 0) { - ((MA_PFN_AAudioStreamBuilder_setChannelCount)pContext->aaudio.AAudioStreamBuilder_setChannelCount)(pBuilder, pDescriptor->channels); - } - if (pDescriptor->format != ma_format_unknown) { - ((MA_PFN_AAudioStreamBuilder_setFormat)pContext->aaudio.AAudioStreamBuilder_setFormat)(pBuilder, (pDescriptor->format == ma_format_s16) ? MA_AAUDIO_FORMAT_PCM_I16 : MA_AAUDIO_FORMAT_PCM_FLOAT); - } - } else { - if (pDescriptor->channels != 0) { - ((MA_PFN_AAudioStreamBuilder_setChannelCount)pContext->aaudio.AAudioStreamBuilder_setChannelCount)(pBuilder, pDescriptor->channels); - } - if (pDescriptor->format != ma_format_unknown) { - ((MA_PFN_AAudioStreamBuilder_setFormat)pContext->aaudio.AAudioStreamBuilder_setFormat)(pBuilder, (pDescriptor->format == ma_format_s16) ? MA_AAUDIO_FORMAT_PCM_I16 : MA_AAUDIO_FORMAT_PCM_FLOAT); - } + if (pDescriptor->channels != 0) { + ((MA_PFN_AAudioStreamBuilder_setChannelCount)pContext->aaudio.AAudioStreamBuilder_setChannelCount)(pBuilder, pDescriptor->channels); + } + + if (pDescriptor->format != ma_format_unknown) { + ((MA_PFN_AAudioStreamBuilder_setFormat)pContext->aaudio.AAudioStreamBuilder_setFormat)(pBuilder, (pDescriptor->format == ma_format_s16) ? MA_AAUDIO_FORMAT_PCM_I16 : MA_AAUDIO_FORMAT_PCM_FLOAT); } /* - There have been reports where setting the frames per data callback results in an error - later on from Android. To address this, I'm experimenting with simply not setting it on - anything from Android 11 and earlier. Suggestions welcome on how we might be able to make - this more targetted. + There have been reports where setting the frames per data callback results in an error. + In particular, re-routing may inadvertently switch from low-latency mode, resulting in a less stable + stream from the legacy path (AudioStreamLegacy). To address this, we simply don't set the value. It + can still be set if it's explicitly requested via the aaudio.allowSetBufferCapacity variable in the + device config. */ - if (!pConfig->aaudio.enableCompatibilityWorkarounds || ma_android_sdk_version() > 30) { + if ((!pConfig->aaudio.enableCompatibilityWorkarounds || ma_android_sdk_version() > 30) && pConfig->aaudio.allowSetBufferCapacity) { /* - AAudio is annoying when it comes to it's buffer calculation stuff because it doesn't let you + AAudio is annoying when it comes to its buffer calculation stuff because it doesn't let you retrieve the actual sample rate until after you've opened the stream. But you need to configure the buffer capacity before you open the stream... :/ @@ -37727,7 +37968,11 @@ static ma_result ma_create_and_configure_AAudioStreamBuilder__aaudio(ma_context* ((MA_PFN_AAudioStreamBuilder_setDataCallback)pContext->aaudio.AAudioStreamBuilder_setDataCallback)(pBuilder, ma_stream_data_callback_playback__aaudio, (void*)pDevice); } - /* Not sure how this affects things, but since there's a mapping between miniaudio's performance profiles and AAudio's performance modes, let go ahead and set it. */ + /* + If we set AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, we allow for MMAP (non-legacy path). + Since there's a mapping between miniaudio's performance profiles and AAudio's performance modes, let's use it. + Beware though, with a conservative performance profile, AAudio will indeed take the legacy path. + */ ((MA_PFN_AAudioStreamBuilder_setPerformanceMode)pContext->aaudio.AAudioStreamBuilder_setPerformanceMode)(pBuilder, (pConfig->performanceProfile == ma_performance_profile_low_latency) ? MA_AAUDIO_PERFORMANCE_MODE_LOW_LATENCY : MA_AAUDIO_PERFORMANCE_MODE_NONE); /* We need to set an error callback to detect device changes. */ @@ -37763,6 +38008,9 @@ static ma_result ma_open_stream_basic__aaudio(ma_context* pContext, const ma_dev return result; } + /* Let's give AAudio a hint to avoid the legacy path (AudioStreamLegacy). */ + ((MA_PFN_AAudioStreamBuilder_setPerformanceMode)pContext->aaudio.AAudioStreamBuilder_setPerformanceMode)(pBuilder, MA_AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + return ma_open_stream_and_close_builder__aaudio(pContext, pBuilder, ppStream); } @@ -37787,6 +38035,10 @@ static ma_result ma_open_stream__aaudio(ma_device* pDevice, const ma_device_conf static ma_result ma_close_stream__aaudio(ma_context* pContext, ma_AAudioStream* pStream) { + if (pStream == NULL) { + return MA_INVALID_ARGS; + } + return ma_result_from_aaudio(((MA_PFN_AAudioStream_close)pContext->aaudio.AAudioStream_close)(pStream)); } @@ -37913,20 +38165,36 @@ static ma_result ma_context_get_device_info__aaudio(ma_context* pContext, ma_dev return MA_SUCCESS; } +static ma_result ma_close_streams__aaudio(ma_device* pDevice) +{ + MA_ASSERT(pDevice != NULL); + + /* When re-routing, streams may have been closed and never re-opened. Hence the extra checks below. */ + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); + pDevice->aaudio.pStreamCapture = NULL; + } + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); + pDevice->aaudio.pStreamPlayback = NULL; + } + + return MA_SUCCESS; +} static ma_result ma_device_uninit__aaudio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); - pDevice->aaudio.pStreamCapture = NULL; + /* Wait for any rerouting to finish before attempting to close the streams. */ + ma_mutex_lock(&pDevice->aaudio.rerouteLock); + { + ma_close_streams__aaudio(pDevice); } + ma_mutex_unlock(&pDevice->aaudio.rerouteLock); - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); - pDevice->aaudio.pStreamPlayback = NULL; - } + /* Destroy re-routing lock. */ + ma_mutex_uninit(&pDevice->aaudio.rerouteLock); return MA_SUCCESS; } @@ -37978,7 +38246,7 @@ static ma_result ma_device_init_by_type__aaudio(ma_device* pDevice, const ma_dev return MA_SUCCESS; } -static ma_result ma_device_init__aaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +static ma_result ma_device_init_streams__aaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { ma_result result; @@ -38011,6 +38279,25 @@ static ma_result ma_device_init__aaudio(ma_device* pDevice, const ma_device_conf return MA_SUCCESS; } +static ma_result ma_device_init__aaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +{ + ma_result result; + + MA_ASSERT(pDevice != NULL); + + result = ma_device_init_streams__aaudio(pDevice, pConfig, pDescriptorPlayback, pDescriptorCapture); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_mutex_init(&pDevice->aaudio.rerouteLock); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + static ma_result ma_device_start_stream__aaudio(ma_device* pDevice, ma_AAudioStream* pStream) { ma_aaudio_result_t resultAA; @@ -38018,12 +38305,16 @@ static ma_result ma_device_start_stream__aaudio(ma_device* pDevice, ma_AAudioStr MA_ASSERT(pDevice != NULL); + if (pStream == NULL) { + return MA_INVALID_ARGS; + } + resultAA = ((MA_PFN_AAudioStream_requestStart)pDevice->pContext->aaudio.AAudioStream_requestStart)(pStream); if (resultAA != MA_AAUDIO_OK) { return ma_result_from_aaudio(resultAA); } - /* Do we actually need to wait for the device to transition into it's started state? */ + /* Do we actually need to wait for the device to transition into its started state? */ /* The device should be in either a starting or started state. If it's not set to started we need to wait for it to transition. It should go from starting to started. */ currentState = ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream); @@ -38050,6 +38341,10 @@ static ma_result ma_device_stop_stream__aaudio(ma_device* pDevice, ma_AAudioStre MA_ASSERT(pDevice != NULL); + if (pStream == NULL) { + return MA_INVALID_ARGS; + } + /* From the AAudio documentation: @@ -38135,22 +38430,20 @@ static ma_result ma_device_stop__aaudio(ma_device* pDevice) static ma_result ma_device_reinit__aaudio(ma_device* pDevice, ma_device_type deviceType) { ma_result result; + int32_t retries = 0; MA_ASSERT(pDevice != NULL); - /* The first thing to do is close the streams. */ - if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { - ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); - pDevice->aaudio.pStreamCapture = NULL; - } - - if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { - ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); - pDevice->aaudio.pStreamPlayback = NULL; - } - - /* Now we need to reinitialize each streams. The hardest part with this is just filling output the config and descriptors. */ + /* + TODO: Stop retrying if main thread is about to uninit device. + */ + ma_mutex_lock(&pDevice->aaudio.rerouteLock); { +error_disconnected: + /* The first thing to do is close the streams. */ + ma_close_streams__aaudio(pDevice); + + /* Now we need to reinitialize each streams. The hardest part with this is just filling output the config and descriptors. */ ma_device_config deviceConfig; ma_device_descriptor descriptorPlayback; ma_device_descriptor descriptorCapture; @@ -38199,15 +38492,17 @@ static ma_result ma_device_reinit__aaudio(ma_device* pDevice, ma_device_type dev descriptorPlayback.periodCount = deviceConfig.periods; } - result = ma_device_init__aaudio(pDevice, &deviceConfig, &descriptorPlayback, &descriptorCapture); + result = ma_device_init_streams__aaudio(pDevice, &deviceConfig, &descriptorPlayback, &descriptorCapture); if (result != MA_SUCCESS) { - return result; + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[AAudio] Failed to create stream after route change."); + goto done; } result = ma_device_post_init(pDevice, deviceType, &descriptorPlayback, &descriptorCapture); if (result != MA_SUCCESS) { - ma_device_uninit__aaudio(pDevice); - return result; + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[AAudio] Failed to initialize device after route change."); + ma_close_streams__aaudio(pDevice); + goto done; } /* We'll only ever do this in response to a reroute. */ @@ -38216,14 +38511,29 @@ static ma_result ma_device_reinit__aaudio(ma_device* pDevice, ma_device_type dev /* If the device is started, start the streams. Maybe make this configurable? */ if (ma_device_get_state(pDevice) == ma_device_state_started) { if (pDevice->aaudio.noAutoStartAfterReroute == MA_FALSE) { - ma_device_start__aaudio(pDevice); + result = ma_device_start__aaudio(pDevice); + if (result != MA_SUCCESS) { + /* We got disconnected! Retry a few times, until we find a connected device! */ + retries += 1; + if (retries <= 3) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Failed to start stream after route change, retrying(%d)", retries); + goto error_disconnected; + } + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Failed to start stream after route change."); + goto done; + } } else { ma_device_stop(pDevice); /* Do a full device stop so we set internal state correctly. */ } } - - return MA_SUCCESS; + + result = MA_SUCCESS; } +done: + /* Re-routing done */ + ma_mutex_unlock(&pDevice->aaudio.rerouteLock); + + return result; } static ma_result ma_device_get_info__aaudio(ma_device* pDevice, ma_device_type type, ma_device_info* pDeviceInfo) @@ -38234,12 +38544,12 @@ static ma_result ma_device_get_info__aaudio(ma_device* pDevice, ma_device_type t MA_ASSERT(type != ma_device_type_duplex); MA_ASSERT(pDeviceInfo != NULL); - if (type == ma_device_type_playback) { + if (type == ma_device_type_capture) { pStream = (ma_AAudioStream*)pDevice->aaudio.pStreamCapture; pDeviceInfo->id.aaudio = pDevice->capture.id.aaudio; ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); /* Only supporting default devices. */ } - if (type == ma_device_type_capture) { + if (type == ma_device_type_playback) { pStream = (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback; pDeviceInfo->id.aaudio = pDevice->playback.id.aaudio; ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); /* Only supporting default devices. */ @@ -38272,6 +38582,7 @@ static ma_result ma_context_uninit__aaudio(ma_context* pContext) static ma_result ma_context_init__aaudio(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { +#if !defined(MA_NO_RUNTIME_LINKING) size_t i; const char* libNames[] = { "libaaudio.so" @@ -38317,7 +38628,39 @@ static ma_result ma_context_init__aaudio(ma_context* pContext, const ma_context_ pContext->aaudio.AAudioStream_getFramesPerBurst = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_getFramesPerBurst"); pContext->aaudio.AAudioStream_requestStart = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_requestStart"); pContext->aaudio.AAudioStream_requestStop = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_requestStop"); - +#else + pContext->aaudio.AAudio_createStreamBuilder = (ma_proc)AAudio_createStreamBuilder; + pContext->aaudio.AAudioStreamBuilder_delete = (ma_proc)AAudioStreamBuilder_delete; + pContext->aaudio.AAudioStreamBuilder_setDeviceId = (ma_proc)AAudioStreamBuilder_setDeviceId; + pContext->aaudio.AAudioStreamBuilder_setDirection = (ma_proc)AAudioStreamBuilder_setDirection; + pContext->aaudio.AAudioStreamBuilder_setSharingMode = (ma_proc)AAudioStreamBuilder_setSharingMode; + pContext->aaudio.AAudioStreamBuilder_setFormat = (ma_proc)AAudioStreamBuilder_setFormat; + pContext->aaudio.AAudioStreamBuilder_setChannelCount = (ma_proc)AAudioStreamBuilder_setChannelCount; + pContext->aaudio.AAudioStreamBuilder_setSampleRate = (ma_proc)AAudioStreamBuilder_setSampleRate; + pContext->aaudio.AAudioStreamBuilder_setBufferCapacityInFrames = (ma_proc)AAudioStreamBuilder_setBufferCapacityInFrames; + pContext->aaudio.AAudioStreamBuilder_setFramesPerDataCallback = (ma_proc)AAudioStreamBuilder_setFramesPerDataCallback; + pContext->aaudio.AAudioStreamBuilder_setDataCallback = (ma_proc)AAudioStreamBuilder_setDataCallback; + pContext->aaudio.AAudioStreamBuilder_setErrorCallback = (ma_proc)AAudioStreamBuilder_setErrorCallback; + pContext->aaudio.AAudioStreamBuilder_setPerformanceMode = (ma_proc)AAudioStreamBuilder_setPerformanceMode; + pContext->aaudio.AAudioStreamBuilder_setUsage = (ma_proc)AAudioStreamBuilder_setUsage; + pContext->aaudio.AAudioStreamBuilder_setContentType = (ma_proc)AAudioStreamBuilder_setContentType; + pContext->aaudio.AAudioStreamBuilder_setInputPreset = (ma_proc)AAudioStreamBuilder_setInputPreset; + #if defined(__ANDROID_API__) && __ANDROID_API__ >= 29 + pContext->aaudio.AAudioStreamBuilder_setAllowedCapturePolicy = (ma_proc)AAudioStreamBuilder_setAllowedCapturePolicy; + #endif + pContext->aaudio.AAudioStreamBuilder_openStream = (ma_proc)AAudioStreamBuilder_openStream; + pContext->aaudio.AAudioStream_close = (ma_proc)AAudioStream_close; + pContext->aaudio.AAudioStream_getState = (ma_proc)AAudioStream_getState; + pContext->aaudio.AAudioStream_waitForStateChange = (ma_proc)AAudioStream_waitForStateChange; + pContext->aaudio.AAudioStream_getFormat = (ma_proc)AAudioStream_getFormat; + pContext->aaudio.AAudioStream_getChannelCount = (ma_proc)AAudioStream_getChannelCount; + pContext->aaudio.AAudioStream_getSampleRate = (ma_proc)AAudioStream_getSampleRate; + pContext->aaudio.AAudioStream_getBufferCapacityInFrames = (ma_proc)AAudioStream_getBufferCapacityInFrames; + pContext->aaudio.AAudioStream_getFramesPerDataCallback = (ma_proc)AAudioStream_getFramesPerDataCallback; + pContext->aaudio.AAudioStream_getFramesPerBurst = (ma_proc)AAudioStream_getFramesPerBurst; + pContext->aaudio.AAudioStream_requestStart = (ma_proc)AAudioStream_requestStart; + pContext->aaudio.AAudioStream_requestStop = (ma_proc)AAudioStream_requestStop; +#endif pCallbacks->onContextInit = ma_context_init__aaudio; pCallbacks->onContextUninit = ma_context_uninit__aaudio; @@ -38355,6 +38698,7 @@ static ma_result ma_context_init__aaudio(ma_context* pContext, const ma_context_ static ma_result ma_job_process__device__aaudio_reroute(ma_job* pJob) { + ma_result result; ma_device* pDevice; MA_ASSERT(pJob != NULL); @@ -38363,7 +38707,18 @@ static ma_result ma_job_process__device__aaudio_reroute(ma_job* pJob) MA_ASSERT(pDevice != NULL); /* Here is where we need to reroute the device. To do this we need to uninitialize the stream and reinitialize it. */ - return ma_device_reinit__aaudio(pDevice, (ma_device_type)pJob->data.device.aaudio.reroute.deviceType); + result = ma_device_reinit__aaudio(pDevice, (ma_device_type)pJob->data.device.aaudio.reroute.deviceType); + if (result != MA_SUCCESS) { + /* + Getting here means we failed to reroute the device. The best thing I can think of here is to + just stop the device. + */ + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[AAudio] Stopping device due to reroute failure."); + ma_device_stop(pDevice); + return result; + } + + return MA_SUCCESS; } #else /* Getting here means there is no AAudio backend so we need a no-op job implementation. */ @@ -39649,6 +40004,10 @@ Web Audio Backend #if (__EMSCRIPTEN_major__ > 3) || (__EMSCRIPTEN_major__ == 3 && (__EMSCRIPTEN_minor__ > 1 || (__EMSCRIPTEN_minor__ == 1 && __EMSCRIPTEN_tiny__ >= 32))) #include #define MA_SUPPORT_AUDIO_WORKLETS + + #if (__EMSCRIPTEN_major__ > 3) || (__EMSCRIPTEN_major__ == 3 && (__EMSCRIPTEN_minor__ > 1 || (__EMSCRIPTEN_minor__ == 1 && __EMSCRIPTEN_tiny__ >= 70))) + #define MA_SUPPORT_AUDIO_WORKLETS_VARIABLE_BUFFER_SIZE + #endif #endif /* @@ -39660,7 +40019,7 @@ TODO: Version 0.12: Swap this logic around so that AudioWorklets are used by def /* The thread stack size must be a multiple of 16. */ #ifndef MA_AUDIO_WORKLETS_THREAD_STACK_SIZE -#define MA_AUDIO_WORKLETS_THREAD_STACK_SIZE 16384 +#define MA_AUDIO_WORKLETS_THREAD_STACK_SIZE 131072 #endif #if defined(MA_USE_AUDIO_WORKLETS) @@ -39786,12 +40145,14 @@ static ma_result ma_device_uninit__webaudio(ma_device* pDevice) #if defined(MA_USE_AUDIO_WORKLETS) { EM_ASM({ - var device = miniaudio.get_device_by_index($0); + var device = window.miniaudio.get_device_by_index($0); if (device.streamNode !== undefined) { device.streamNode.disconnect(); device.streamNode = undefined; } + + device.pDevice = undefined; }, pDevice->webaudio.deviceIndex); emscripten_destroy_web_audio_node(pDevice->webaudio.audioWorklet); @@ -39801,7 +40162,7 @@ static ma_result ma_device_uninit__webaudio(ma_device* pDevice) #else { EM_ASM({ - var device = miniaudio.get_device_by_index($0); + var device = window.miniaudio.get_device_by_index($0); /* Make sure all nodes are disconnected and marked for collection. */ if (device.scriptNode !== undefined) { @@ -39828,7 +40189,7 @@ static ma_result ma_device_uninit__webaudio(ma_device* pDevice) /* Clean up the device on the JS side. */ EM_ASM({ - miniaudio.untrack_device_by_index($0); + window.miniaudio.untrack_device_by_index($0); }, pDevice->webaudio.deviceIndex); ma_free(pDevice->webaudio.pIntermediaryBuffer, &pDevice->pContext->allocationCallbacks); @@ -39894,10 +40255,6 @@ static EM_BOOL ma_audio_worklet_process_callback__webaudio(int inputCount, const (void)paramCount; (void)pParams; - if (ma_device_get_state(pDevice) != ma_device_state_started) { - return EM_TRUE; - } - /* The Emscripten documentation says that it'll always be 128 frames being passed in. Hard coding it like that feels like a very bad idea to me. Even if it's hard coded in the backend, the API and documentation should always refer @@ -39906,7 +40263,20 @@ static EM_BOOL ma_audio_worklet_process_callback__webaudio(int inputCount, const Unfortunately the audio data is not interleaved so we'll need to convert it before we give the data to miniaudio for further processing. */ - frameCount = 128; + if (pDevice->type == ma_device_type_playback) { + frameCount = pDevice->playback.internalPeriodSizeInFrames; + } else { + frameCount = pDevice->capture.internalPeriodSizeInFrames; + } + + if (ma_device_get_state(pDevice) != ma_device_state_started) { + /* Fill the output buffer with zero to avoid a noise sound */ + for (int i = 0; i < outputCount; i += 1) { + MA_ZERO_MEMORY(pOutputs[i].data, pOutputs[i].numberOfChannels * frameCount * sizeof(float)); + } + + return EM_TRUE; + } if (inputCount > 0) { /* Input data needs to be interleaved before we hand it to the client. */ @@ -39961,7 +40331,7 @@ static void ma_audio_worklet_processor_created__webaudio(EMSCRIPTEN_WEBAUDIO_T a count from MediaStreamAudioSourceNode (what we use for capture)? The only way to have control is to configure an output channel count on the capture side. This is slightly confusing for capture mode because intuitively you wouldn't actually connect an output to an input-only node, but this is what we'll have to do in order to have - proper control over the channel count. In the capture case, we'll have to output silence to it's output node. + proper control over the channel count. In the capture case, we'll have to output silence to its output node. */ if (pParameters->pConfig->deviceType == ma_device_type_capture) { channels = (int)((pParameters->pDescriptorCapture->channels > 0) ? pParameters->pDescriptorCapture->channels : MA_DEFAULT_CHANNELS); @@ -39984,7 +40354,15 @@ static void ma_audio_worklet_processor_created__webaudio(EMSCRIPTEN_WEBAUDIO_T a Now that we know the channel count to use we can allocate the intermediary buffer. The intermediary buffer is used for interleaving and deinterleaving. */ - intermediaryBufferSizeInFrames = 128; + #if defined(MA_SUPPORT_AUDIO_WORKLETS_VARIABLE_BUFFER_SIZE) + { + intermediaryBufferSizeInFrames = (size_t)emscripten_audio_context_quantum_size(audioContext); + } + #else + { + intermediaryBufferSizeInFrames = 128; + } + #endif pParameters->pDevice->webaudio.pIntermediaryBuffer = (float*)ma_malloc(intermediaryBufferSizeInFrames * (ma_uint32)channels * sizeof(float), &pParameters->pDevice->pContext->allocationCallbacks); if (pParameters->pDevice->webaudio.pIntermediaryBuffer == NULL) { @@ -39993,7 +40371,6 @@ static void ma_audio_worklet_processor_created__webaudio(EMSCRIPTEN_WEBAUDIO_T a return; } - pParameters->pDevice->webaudio.audioWorklet = emscripten_create_wasm_audio_worklet_node(audioContext, "miniaudio", &audioWorkletOptions, &ma_audio_worklet_process_callback__webaudio, pParameters->pDevice); /* With the audio worklet initialized we can now attach it to the graph. */ @@ -40133,7 +40510,6 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co /* It's not clear if this can return an error. None of the tests in the Emscripten repository check for this, so neither am I for now. */ pDevice->webaudio.audioContext = emscripten_create_audio_context(&audioContextAttributes); - /* With the context created we can now create the worklet. We can only have a single worklet per audio context which means we'll need to craft this appropriately to handle duplex devices correctly. @@ -40182,11 +40558,12 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co /* We need to add an entry to the miniaudio.devices list on the JS side so we can do some JS/C interop. */ pDevice->webaudio.deviceIndex = EM_ASM_INT({ - return miniaudio.track_device({ + return window.miniaudio.track_device({ webaudio: emscriptenGetAudioObject($0), - state: 1 /* 1 = ma_device_state_stopped */ + state: 1, /* 1 = ma_device_state_stopped */ + pDevice: $1 }); - }, pDevice->webaudio.audioContext); + }, pDevice->webaudio.audioContext, pDevice); return MA_SUCCESS; } @@ -40198,7 +40575,7 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co ma_uint32 sampleRate; ma_uint32 periodSizeInFrames; - /* The channel count will depend on the device type. If it's a capture, use it's, otherwise use the playback side. */ + /* The channel count will depend on the device type. If it's a capture, use its, otherwise use the playback side. */ if (pConfig->deviceType == ma_device_type_capture) { channels = (pDescriptorCapture->channels > 0) ? pDescriptorCapture->channels : MA_DEFAULT_CHANNELS; } else { @@ -40267,11 +40644,11 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co /* The node processing callback. */ device.scriptNode.onaudioprocess = function(e) { if (device.intermediaryBufferView == null || device.intermediaryBufferView.length == 0) { - device.intermediaryBufferView = new Float32Array(Module.HEAPF32.buffer, pIntermediaryBuffer, bufferSize * channels); + device.intermediaryBufferView = new Float32Array(HEAPF32.buffer, pIntermediaryBuffer, bufferSize * channels); } /* Do the capture side first. */ - if (deviceType == miniaudio.device_type.capture || deviceType == miniaudio.device_type.duplex) { + if (deviceType == window.miniaudio.device_type.capture || deviceType == window.miniaudio.device_type.duplex) { /* The data must be interleaved before being processed miniaudio. */ for (var iChannel = 0; iChannel < channels; iChannel += 1) { var inputBuffer = e.inputBuffer.getChannelData(iChannel); @@ -40285,7 +40662,7 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co _ma_device_process_pcm_frames_capture__webaudio(pDevice, bufferSize, pIntermediaryBuffer); } - if (deviceType == miniaudio.device_type.playback || deviceType == miniaudio.device_type.duplex) { + if (deviceType == window.miniaudio.device_type.playback || deviceType == window.miniaudio.device_type.duplex) { _ma_device_process_pcm_frames_playback__webaudio(pDevice, bufferSize, pIntermediaryBuffer); for (var iChannel = 0; iChannel < e.outputBuffer.numberOfChannels; ++iChannel) { @@ -40305,7 +40682,7 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co }; /* Now we need to connect our node to the graph. */ - if (deviceType == miniaudio.device_type.capture || deviceType == miniaudio.device_type.duplex) { + if (deviceType == window.miniaudio.device_type.capture || deviceType == window.miniaudio.device_type.duplex) { navigator.mediaDevices.getUserMedia({audio:true, video:false}) .then(function(stream) { device.streamNode = device.webaudio.createMediaStreamSource(stream); @@ -40317,13 +40694,13 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co }); } - if (deviceType == miniaudio.device_type.playback) { + if (deviceType == window.miniaudio.device_type.playback) { device.scriptNode.connect(device.webaudio.destination); } device.pDevice = pDevice; - return miniaudio.track_device(device); + return window.miniaudio.track_device(device); }, pConfig->deviceType, channels, sampleRate, periodSizeInFrames, pDevice->webaudio.pIntermediaryBuffer, pDevice); if (deviceIndex < 0) { @@ -40333,7 +40710,7 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co pDevice->webaudio.deviceIndex = deviceIndex; /* Grab the sample rate from the audio context directly. */ - sampleRate = (ma_uint32)EM_ASM_INT({ return miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); + sampleRate = (ma_uint32)EM_ASM_INT({ return window.miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); if (pDescriptorCapture != NULL) { pDescriptorCapture->format = ma_format_f32; @@ -40363,9 +40740,9 @@ static ma_result ma_device_start__webaudio(ma_device* pDevice) MA_ASSERT(pDevice != NULL); EM_ASM({ - var device = miniaudio.get_device_by_index($0); + var device = window.miniaudio.get_device_by_index($0); device.webaudio.resume(); - device.state = miniaudio.device_state.started; + device.state = window.miniaudio.device_state.started; }, pDevice->webaudio.deviceIndex); return MA_SUCCESS; @@ -40385,9 +40762,9 @@ static ma_result ma_device_stop__webaudio(ma_device* pDevice) do any kind of explicit draining. */ EM_ASM({ - var device = miniaudio.get_device_by_index($0); + var device = window.miniaudio.get_device_by_index($0); device.webaudio.suspend(); - device.state = miniaudio.device_state.stopped; + device.state = window.miniaudio.device_state.stopped; }, pDevice->webaudio.deviceIndex); ma_device__on_notification_stopped(pDevice); @@ -40405,6 +40782,10 @@ static ma_result ma_context_uninit__webaudio(ma_context* pContext) /* Remove the global miniaudio object from window if there are no more references to it. */ EM_ASM({ if (typeof(window.miniaudio) !== 'undefined') { + miniaudio.unlock_event_types.map(function(event_type) { + document.removeEventListener(event_type, miniaudio.unlock, true); + }); + window.miniaudio.referenceCount -= 1; if (window.miniaudio.referenceCount === 0) { delete window.miniaudio; @@ -40446,6 +40827,7 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex window.miniaudio.device_state.started = $4; /* Device cache for mapping devices to indexes for JavaScript/C interop. */ + let miniaudio = window.miniaudio; miniaudio.devices = []; miniaudio.track_device = function(device) { @@ -40497,13 +40879,13 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex var device = miniaudio.devices[i]; if (device != null && device.webaudio != null && - device.state === window.miniaudio.device_state.started) { + device.state === miniaudio.device_state.started) { device.webaudio.resume().then(() => { - Module._ma_device__on_notification_unlocked(device.pDevice); - }, - (error) => {console.error("Failed to resume audiocontext", error); - }); + _ma_device__on_notification_unlocked(device.pDevice); + }, + (error) => {console.error("Failed to resume audiocontext", error); + }); } } miniaudio.unlock_event_types.map(function(event_type) { @@ -40539,7 +40921,7 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex return MA_SUCCESS; } -#endif /* Web Audio */ +#endif /* MA_HAS_WEBAUDIO */ @@ -40818,7 +41200,7 @@ MA_API ma_result ma_device_post_init(ma_device* pDevice, ma_device_type deviceTy ma_device_info deviceInfo; if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { - result = ma_device_get_info(pDevice, (deviceType == ma_device_type_loopback) ? ma_device_type_playback : ma_device_type_capture, &deviceInfo); + result = ma_device_get_info(pDevice, ma_device_type_capture, &deviceInfo); if (result == MA_SUCCESS) { ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), deviceInfo.name, (size_t)-1); } else { @@ -40865,7 +41247,7 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) #endif /* - When the device is being initialized it's initial state is set to ma_device_state_uninitialized. Before returning from + When the device is being initialized its initial state is set to ma_device_state_uninitialized. Before returning from ma_device_init(), the state needs to be set to something valid. In miniaudio the device's default state immediately after initialization is stopped, so therefore we need to mark the device as such. miniaudio will wait on the worker thread to signal an event to know when the worker thread is ready for action. @@ -41210,6 +41592,24 @@ MA_API ma_result ma_device_job_thread_next(ma_device_job_thread* pJobThread, ma_ } +MA_API ma_bool32 ma_device_id_equal(const ma_device_id* pA, const ma_device_id* pB) +{ + size_t i; + + if (pA == NULL || pB == NULL) { + return MA_FALSE; + } + + for (i = 0; i < sizeof(ma_device_id); i += 1) { + if (((const char*)pA)[i] != ((const char*)pB)[i]) { + return MA_FALSE; + } + } + + return MA_TRUE; +} + + MA_API ma_context_config ma_context_config_init(void) { @@ -41983,7 +42383,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC return result; } - /* Wait for the worker thread to put the device into it's stopped state for real. */ + /* Wait for the worker thread to put the device into its stopped state for real. */ ma_event_wait(&pDevice->stopEvent); MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped); } else { @@ -42009,7 +42409,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[%s]\n", ma_get_backend_name(pDevice->pContext->backend)); if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; - ma_device_get_name(pDevice, (pDevice->type == ma_device_type_loopback) ? ma_device_type_playback : ma_device_type_capture, name, sizeof(name), NULL); + ma_device_get_name(pDevice, ma_device_type_capture, name, sizeof(name), NULL); ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " %s (%s)\n", name, "Capture"); ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Format: %s -> %s\n", ma_get_format_name(pDevice->capture.internalFormat), ma_get_format_name(pDevice->capture.format)); @@ -42262,6 +42662,17 @@ MA_API ma_result ma_device_get_info(ma_device* pDevice, ma_device_type type, ma_ if (type == ma_device_type_playback) { return ma_context_get_device_info(pDevice->pContext, type, pDevice->playback.pID, pDeviceInfo); } else { + /* + Here we're getting the capture side, which is the branch we'll be entering for a loopback + device, since loopback is capturing. However, if the device is using the default device ID, + it won't get the correct information because it'll think we're asking for the default + capture device, where in fact for loopback we want the default *playback* device. We'll do + a bit of a hack here to make sure we get the correct info. + */ + if (pDevice->type == ma_device_type_loopback && pDevice->capture.pID == NULL) { + type = ma_device_type_playback; + } + return ma_context_get_device_info(pDevice->pContext, type, pDevice->capture.pID, pDeviceInfo); } } @@ -42323,6 +42734,15 @@ MA_API ma_result ma_device_start(ma_device* pDevice) ma_mutex_lock(&pDevice->startStopLock); { + /* + We need to check again if the device is in a started state because it's possible for one thread to have started the device + while another was waiting on the mutex. + */ + if (ma_device_get_state(pDevice) == ma_device_state_started) { + ma_mutex_unlock(&pDevice->startStopLock); + return MA_SUCCESS; /* Already started. */ + } + /* Starting and stopping are wrapped in a mutex which means we can assert that the device is in a stopped or paused state. */ MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped); @@ -42383,6 +42803,15 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) ma_mutex_lock(&pDevice->startStopLock); { + /* + We need to check again if the device is in a stopped state because it's possible for one thread to have stopped the device + while another was waiting on the mutex. + */ + if (ma_device_get_state(pDevice) == ma_device_state_stopped) { + ma_mutex_unlock(&pDevice->startStopLock); + return MA_SUCCESS; /* Already stopped. */ + } + /* Starting and stopping are wrapped in a mutex which means we can assert that the device is in a started or paused state. */ MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_started); @@ -42401,7 +42830,7 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) } else { /* Synchronous backends. The stop callback is always called from the worker thread. Do not call the stop callback here. If - the backend is implementing it's own audio thread loop we'll need to wake it up if required. Note that we need to make + the backend is implementing its own audio thread loop we'll need to wake it up if required. Note that we need to make sure the state of the device is *not* playing right now, which it shouldn't be since we set it above. This is super important though, so I'm asserting it here as well for extra safety in case we accidentally change something later. */ @@ -42518,6 +42947,15 @@ MA_API ma_result ma_device_handle_backend_data_callback(ma_device* pDevice, void return MA_INVALID_ARGS; } + /* + There is an assert deeper in the code that checks that frameCount > 0. Since this is a public facing + API we'll need to check for that here. I've had reports that AAudio can sometimes post a frame count + of 0. + */ + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pDevice->type == ma_device_type_duplex) { if (pInput != NULL) { ma_device__handle_duplex_callback_capture(pDevice, frameCount, pInput, &pDevice->duplexRB.rb); @@ -42592,7 +43030,7 @@ MA_API ma_uint32 ma_calculate_buffer_size_in_milliseconds_from_frames(ma_uint32 return 0; } - return bufferSizeInFrames*1000 / sampleRate; + return (bufferSizeInFrames*1000 + (sampleRate - 1)) / sampleRate; } MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_milliseconds(ma_uint32 bufferSizeInMilliseconds, ma_uint32 sampleRate) @@ -47420,7 +47858,7 @@ static ma_result ma_bpf_get_heap_layout(const ma_bpf_config* pConfig, ma_bpf_hea return MA_INVALID_ARGS; } - bpf2Count = pConfig->channels / 2; + bpf2Count = pConfig->order / 2; pHeapLayout->sizeInBytes = 0; @@ -49478,7 +49916,7 @@ MA_API float ma_fader_get_current_volume(const ma_fader* pFader) } else if ((ma_uint64)pFader->cursorInFrames >= pFader->lengthInFrames) { /* Safe case because the < 0 case was checked above. */ return pFader->volumeEnd; } else { - /* The cursor is somewhere inside the fading period. We can figure this out with a simple linear interpoluation between volumeBeg and volumeEnd based on our cursor position. */ + /* The cursor is somewhere inside the fading period. We can figure this out with a simple linear interpolation between volumeBeg and volumeEnd based on our cursor position. */ return ma_mix_f32_fast(pFader->volumeBeg, pFader->volumeEnd, (ma_uint32)pFader->cursorInFrames / (float)((ma_uint32)pFader->lengthInFrames)); /* Safe cast to uint32 because we clamp it in ma_fader_process_pcm_frames(). */ } } @@ -49701,9 +50139,9 @@ static float ma_attenuation_exponential(float distance, float minDistance, float /* -Dopper Effect calculation taken from the OpenAL spec, with two main differences: +Doppler Effect calculation taken from the OpenAL spec, with two main differences: - 1) The source to listener vector will have already been calcualted at an earlier step so we can + 1) The source to listener vector will have already been calculated at an earlier step so we can just use that directly. We need only the position of the source relative to the origin. 2) We don't scale by a frequency because we actually just want the ratio which we'll plug straight @@ -49742,7 +50180,7 @@ static void ma_get_default_channel_map_for_spatializer(ma_channel* pChannelMap, Special case for stereo. Want to default the left and right speakers to side left and side right so that they're facing directly down the X axis rather than slightly forward. Not doing this will result in sounds being quieter when behind the listener. This might - actually be good for some scenerios, but I don't think it's an appropriate default because + actually be good for some scenarios, but I don't think it's an appropriate default because it can be a bit unexpected. */ if (channelCount == 2) { @@ -50076,7 +50514,7 @@ MA_API ma_spatializer_config ma_spatializer_config_init(ma_uint32 channelsIn, ma config.maxDistance = MA_FLT_MAX; config.rolloff = 1; config.coneInnerAngleInRadians = 6.283185f; /* 360 degrees. */ - config.coneOuterAngleInRadians = 6.283185f; /* 360 degress. */ + config.coneOuterAngleInRadians = 6.283185f; /* 360 degrees. */ config.coneOuterGain = 0.0f; config.dopplerFactor = 1; config.directionalAttenuationFactor = 1; @@ -50310,7 +50748,7 @@ static float ma_calculate_angular_gain(ma_vec3f dirA, ma_vec3f dirB, float coneI To do cone attenuation, I'm just using the same math that we'd use to implement a basic spotlight in OpenGL. We just need to get the direction from the source to the listener and then do a dot product against that and the direction of the spotlight. Then we just compare that dot product against the cosine of the inner and outer - angles. If the dot product is greater than the the outer angle, we just use coneOuterGain. If it's less than + angles. If the dot product is greater than the outer angle, we just use coneOuterGain. If it's less than the inner angle, we just use a gain of 1. Otherwise we linearly interpolate between 1 and coneOuterGain. */ if (coneInnerAngleInRadians < 6.283185f) { @@ -50380,7 +50818,7 @@ MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, ma_vec3f relativePosNormalized; ma_vec3f relativePos; /* The position relative to the listener. */ ma_vec3f relativeDir; /* The direction of the sound, relative to the listener. */ - ma_vec3f listenerVel; /* The volocity of the listener. For doppler pitch calculation. */ + ma_vec3f listenerVel; /* The velocity of the listener. For doppler pitch calculation. */ float speedOfSound; float distance = 0; float gain = 1; @@ -50461,11 +50899,11 @@ MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, To do cone attenuation, I'm just using the same math that we'd use to implement a basic spotlight in OpenGL. We just need to get the direction from the source to the listener and then do a dot product against that and the direction of the spotlight. Then we just compare that dot product against the cosine of the inner and outer - angles. If the dot product is greater than the the outer angle, we just use coneOuterGain. If it's less than + angles. If the dot product is greater than the outer angle, we just use coneOuterGain. If it's less than the inner angle, we just use a gain of 1. Otherwise we linearly interpolate between 1 and coneOuterGain. */ if (distance > 0) { - /* Source anglular gain. */ + /* Source angular gain. */ float spatializerConeInnerAngle; float spatializerConeOuterAngle; float spatializerConeOuterGain; @@ -50977,7 +51415,7 @@ MA_API void ma_spatializer_get_relative_position_and_direction(const ma_spatiali listenerDirection = ma_spatializer_listener_get_direction(pListener); /* - We need to calcualte the right vector from our forward and up vectors. This is done with + We need to calculate the right vector from our forward and up vectors. This is done with a cross product. */ axisZ = ma_vec3f_normalize(listenerDirection); /* Normalization required here because we can't trust the caller. */ @@ -51123,7 +51561,7 @@ static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pRes lpfConfig = ma_lpf_config_init(pResampler->config.format, pResampler->config.channels, lpfSampleRate, lpfCutoffFrequency, pResampler->config.lpfOrder); /* - If the resampler is alreay initialized we don't want to do a fresh initialization of the low-pass filter because it will result in the cached frames + If the resampler is already initialized we don't want to do a fresh initialization of the low-pass filter because it will result in the cached frames getting cleared. Instead we re-initialize the filter which will maintain any cached frames. */ if (isResamplerAlreadyInitialized) { @@ -51818,7 +52256,7 @@ MA_API ma_result ma_linear_resampler_get_expected_output_frame_count(const ma_li preliminaryInputFrameCount = (pResampler->inTimeInt + outputFrameCount*pResampler->inAdvanceInt ) + preliminaryInputFrameCountFromFrac; /* - If the total number of *whole* input frames that would be required to generate our preliminary output frame count is greather than + If the total number of *whole* input frames that would be required to generate our preliminary output frame count is greater than the amount of whole input frames we have available as input we need to *not* add an extra output frame as there won't be enough data to actually process. Otherwise we need to add the extra output frame. */ @@ -51856,7 +52294,7 @@ MA_API ma_result ma_linear_resampler_reset(ma_linear_resampler* pResampler) } } - /* The low pass filter needs to have it's cache reset. */ + /* The low pass filter needs to have its cache reset. */ ma_lpf_clear_cache(&pResampler->lpf); return MA_SUCCESS; @@ -52373,19 +52811,19 @@ static float ma_calculate_channel_position_rectangular_weight(ma_channel channel of contribution to apply to the side/left and back/left speakers, however, is a bit more complicated. Imagine the front/left speaker as emitting audio from two planes - the front plane and the left plane. You can think of the front/left - speaker emitting half of it's total volume from the front, and the other half from the left. Since part of it's volume is being emitted + speaker emitting half of its total volume from the front, and the other half from the left. Since part of its volume is being emitted from the left side, and the side/left and back/left channels also emit audio from the left plane, one would expect that they would receive some amount of contribution from front/left speaker. The amount of contribution depends on how many planes are shared between the two speakers. Note that in the examples below I've added a top/front/left speaker as an example just to show how the math works across 3 spatial dimensions. The first thing to do is figure out how each speaker's volume is spread over each of plane: - - front/left: 2 planes (front and left) = 1/2 = half it's total volume on each plane + - front/left: 2 planes (front and left) = 1/2 = half its total volume on each plane - side/left: 1 plane (left only) = 1/1 = entire volume from left plane - - back/left: 2 planes (back and left) = 1/2 = half it's total volume on each plane - - top/front/left: 3 planes (top, front and left) = 1/3 = one third it's total volume on each plane + - back/left: 2 planes (back and left) = 1/2 = half its total volume on each plane + - top/front/left: 3 planes (top, front and left) = 1/3 = one third its total volume on each plane - The amount of volume each channel contributes to each of it's planes is what controls how much it is willing to given and take to other + The amount of volume each channel contributes to each of its planes is what controls how much it is willing to given and take to other channels on the same plane. The volume that is willing to the given by one channel is multiplied by the volume that is willing to be taken by the other to produce the final contribution. */ @@ -52496,12 +52934,7 @@ static ma_channel_conversion_path ma_channel_map_get_conversion_path(const ma_ch ma_uint32 iChannelIn; ma_bool32 areAllChannelPositionsPresent = MA_TRUE; for (iChannelIn = 0; iChannelIn < channelsIn; ++iChannelIn) { - ma_bool32 isInputChannelPositionInOutput = MA_FALSE; - if (ma_channel_map_contains_channel_position(channelsOut, pChannelMapOut, ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn))) { - isInputChannelPositionInOutput = MA_TRUE; - break; - } - + ma_bool32 isInputChannelPositionInOutput = ma_channel_map_contains_channel_position(channelsOut, pChannelMapOut, ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn)); if (!isInputChannelPositionInOutput) { areAllChannelPositionsPresent = MA_FALSE; break; @@ -52528,8 +52961,8 @@ static ma_result ma_channel_map_build_shuffle_table(const ma_channel* pChannelMa } /* - When building the shuffle table we just do a 1:1 mapping based on the first occurance of a channel. If the - input channel has more than one occurance of a channel position, the second one will be ignored. + When building the shuffle table we just do a 1:1 mapping based on the first occurrence of a channel. If the + input channel has more than one occurrence of a channel position, the second one will be ignored. */ for (iChannelOut = 0; iChannelOut < channelCountOut; iChannelOut += 1) { ma_channel channelOut; @@ -54824,7 +55257,7 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co Before doing any processing we need to determine how many frames we should try processing this iteration, for both input and output. The resampler requires us to perform format and channel conversion before passing any data into it. If we get our input count wrong, we'll - end up peforming redundant pre-processing. This isn't the end of the world, but it does + end up performing redundant pre-processing. This isn't the end of the world, but it does result in some inefficiencies proportionate to how far our estimates are off. If the resampler has a means to calculate exactly how much we'll need, we'll use that. @@ -55994,7 +56427,7 @@ MA_API const char* ma_channel_position_to_string(ma_channel channel) case MA_CHANNEL_LFE : return "CHANNEL_LFE"; case MA_CHANNEL_BACK_LEFT : return "CHANNEL_BACK_LEFT"; case MA_CHANNEL_BACK_RIGHT : return "CHANNEL_BACK_RIGHT"; - case MA_CHANNEL_FRONT_LEFT_CENTER : return "CHANNEL_FRONT_LEFT_CENTER "; + case MA_CHANNEL_FRONT_LEFT_CENTER : return "CHANNEL_FRONT_LEFT_CENTER"; case MA_CHANNEL_FRONT_RIGHT_CENTER: return "CHANNEL_FRONT_RIGHT_CENTER"; case MA_CHANNEL_BACK_CENTER : return "CHANNEL_BACK_CENTER"; case MA_CHANNEL_SIDE_LEFT : return "CHANNEL_SIDE_LEFT"; @@ -56299,13 +56732,9 @@ MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes) newReadOffsetLoopFlag ^= 0x80000000; } - ma_atomic_exchange_32(&pRB->encodedReadOffset, ma_rb__construct_offset(newReadOffsetLoopFlag, newReadOffsetInBytes)); + ma_atomic_exchange_32(&pRB->encodedReadOffset, ma_rb__construct_offset(newReadOffsetInBytes, newReadOffsetLoopFlag)); - if (ma_rb_pointer_distance(pRB) == 0) { - return MA_AT_END; - } else { - return MA_SUCCESS; - } + return MA_SUCCESS; } MA_API ma_result ma_rb_acquire_write(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut) @@ -56385,13 +56814,9 @@ MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes) newWriteOffsetLoopFlag ^= 0x80000000; } - ma_atomic_exchange_32(&pRB->encodedWriteOffset, ma_rb__construct_offset(newWriteOffsetLoopFlag, newWriteOffsetInBytes)); + ma_atomic_exchange_32(&pRB->encodedWriteOffset, ma_rb__construct_offset(newWriteOffsetInBytes, newWriteOffsetLoopFlag)); - if (ma_rb_pointer_distance(pRB) == 0) { - return MA_AT_END; - } else { - return MA_SUCCESS; - } + return MA_SUCCESS; } MA_API ma_result ma_rb_seek_read(ma_rb* pRB, size_t offsetInBytes) @@ -56614,6 +57039,16 @@ static ma_result ma_pcm_rb_data_source__on_read(ma_data_source* pDataSource, voi totalFramesRead += mappedFrameCount; } + /* + There is no notion of an "end" in a ring buffer. If we didn't have enough data to fill the requested frame + count we'll need to pad with silence. If we don't do this, totalFramesRead might equal 0 which will result + in the data source layer at a higher level translating this to MA_AT_END which is incorrect for a ring buffer. + */ + if (totalFramesRead < frameCount) { + ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, pRB->format, pRB->channels), (frameCount - totalFramesRead), pRB->format, pRB->channels); + totalFramesRead = frameCount; + } + *pFramesRead = totalFramesRead; return MA_SUCCESS; } @@ -57162,6 +57597,10 @@ MA_API ma_result ma_data_source_init(const ma_data_source_config* pConfig, ma_da return MA_INVALID_ARGS; } + if (pConfig->vtable == NULL) { + return MA_INVALID_ARGS; + } + pDataSourceBase->vtable = pConfig->vtable; pDataSourceBase->rangeBegInFrames = MA_DATA_SOURCE_DEFAULT_RANGE_BEG; pDataSourceBase->rangeEndInFrames = MA_DATA_SOURCE_DEFAULT_RANGE_END; @@ -57212,6 +57651,58 @@ static ma_result ma_data_source_resolve_current(ma_data_source* pDataSource, ma_ return MA_SUCCESS; } +static ma_result ma_data_source_read_pcm_frames_from_backend(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + + MA_ASSERT(pDataSourceBase != NULL); + MA_ASSERT(pDataSourceBase->vtable != NULL); + MA_ASSERT(pDataSourceBase->vtable->onRead != NULL); + MA_ASSERT(pFramesRead != NULL); + + if (pFramesOut != NULL) { + return pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, pFramesRead); + } else { + /* + No output buffer. Probably seeking forward. Read and discard. Can probably optimize this in terms of + onSeek and onGetCursor, but need to keep in mind that the data source may not implement these functions. + */ + ma_result result; + ma_uint64 framesRead; + ma_format format; + ma_uint32 channels; + ma_uint64 discardBufferCapInFrames; + ma_uint8 pDiscardBuffer[4096]; + + result = ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0); + if (result != MA_SUCCESS) { + return result; + } + + discardBufferCapInFrames = sizeof(pDiscardBuffer) / ma_get_bytes_per_frame(format, channels); + + framesRead = 0; + while (framesRead < frameCount) { + ma_uint64 framesReadThisIteration = 0; + ma_uint64 framesToRead = frameCount - framesRead; + if (framesToRead > discardBufferCapInFrames) { + framesToRead = discardBufferCapInFrames; + } + + result = pDataSourceBase->vtable->onRead(pDataSourceBase, pDiscardBuffer, framesToRead, &framesReadThisIteration); + if (result != MA_SUCCESS) { + return result; + } + + framesRead += framesReadThisIteration; + } + + *pFramesRead = framesRead; + + return MA_SUCCESS; + } +} + static ma_result ma_data_source_read_pcm_frames_within_range(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; @@ -57227,9 +57718,11 @@ static ma_result ma_data_source_read_pcm_frames_within_range(ma_data_source* pDa return MA_INVALID_ARGS; } + MA_ASSERT(pDataSourceBase->vtable != NULL); + if ((pDataSourceBase->vtable->flags & MA_DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT) != 0 || (pDataSourceBase->rangeEndInFrames == ~((ma_uint64)0) && (pDataSourceBase->loopEndInFrames == ~((ma_uint64)0) || loop == MA_FALSE))) { /* Either the data source is self-managing the range, or no range is set - just read like normal. The data source itself will tell us when the end is reached. */ - result = pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); + result = ma_data_source_read_pcm_frames_from_backend(pDataSource, pFramesOut, frameCount, &framesRead); } else { /* Need to clamp to within the range. */ ma_uint64 relativeCursor; @@ -57238,7 +57731,7 @@ static ma_result ma_data_source_read_pcm_frames_within_range(ma_data_source* pDa result = ma_data_source_get_cursor_in_pcm_frames(pDataSourceBase, &relativeCursor); if (result != MA_SUCCESS) { /* Failed to retrieve the cursor. Cannot read within a range or loop points. Just read like normal - this may happen for things like noise data sources where it doesn't really matter. */ - result = pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); + result = ma_data_source_read_pcm_frames_from_backend(pDataSource, pFramesOut, frameCount, &framesRead); } else { ma_uint64 rangeBeg; ma_uint64 rangeEnd; @@ -57266,7 +57759,7 @@ static ma_result ma_data_source_read_pcm_frames_within_range(ma_data_source* pDa MA_AT_END so the higher level function can know about it. */ if (frameCount > 0) { - result = pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); + result = ma_data_source_read_pcm_frames_from_backend(pDataSource, pFramesOut, frameCount, &framesRead); } else { result = MA_AT_END; /* The cursor is sitting on the end of the range which means we're at the end. */ } @@ -57348,7 +57841,7 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi totalFramesProcessed += framesProcessed; /* - If we encounted an error from the read callback, make sure it's propagated to the caller. The caller may need to know whether or not MA_BUSY is returned which is + If we encountered an error from the read callback, make sure it's propagated to the caller. The caller may need to know whether or not MA_BUSY is returned which is not necessarily considered an error. */ if (result != MA_SUCCESS && result != MA_AT_END) { @@ -57439,7 +57932,7 @@ MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, m ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; if (pDataSourceBase == NULL) { - return MA_SUCCESS; + return MA_INVALID_ARGS; } if (pDataSourceBase->vtable->onSeek == NULL) { @@ -57447,12 +57940,61 @@ MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, m } if (frameIndex > pDataSourceBase->rangeEndInFrames) { - return MA_INVALID_OPERATION; /* Trying to seek to far forward. */ + return MA_INVALID_OPERATION; /* Trying to seek too far forward. */ } + MA_ASSERT(pDataSourceBase->vtable != NULL); + return pDataSourceBase->vtable->onSeek(pDataSource, pDataSourceBase->rangeBegInFrames + frameIndex); } +MA_API ma_result ma_data_source_seek_seconds(ma_data_source* pDataSource, float secondCount, float* pSecondsSeeked) +{ + ma_uint64 frameCount; + ma_uint64 framesSeeked = 0; + ma_uint32 sampleRate; + ma_result result; + + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_data_source_get_data_format(pDataSource, NULL, NULL, &sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; + } + + /* We need PCM frames instead of seconds */ + frameCount = (ma_uint64)(secondCount * sampleRate); + + result = ma_data_source_seek_pcm_frames(pDataSource, frameCount, &framesSeeked); + + /* VC6 doesn't support division between unsigned 64-bit integer and floating point number. Signed integer needed. This shouldn't affect anything in practice */ + *pSecondsSeeked = (ma_int64)framesSeeked / (float)sampleRate; + return result; +} + +MA_API ma_result ma_data_source_seek_to_second(ma_data_source* pDataSource, float seekPointInSeconds) +{ + ma_uint64 frameIndex; + ma_uint32 sampleRate; + ma_result result; + + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_data_source_get_data_format(pDataSource, NULL, NULL, &sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; + } + + /* We need PCM frames instead of seconds */ + frameIndex = (ma_uint64)(seekPointInSeconds * sampleRate); + + return ma_data_source_seek_to_pcm_frame(pDataSource, frameIndex); +} + MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; @@ -57479,6 +58021,8 @@ MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_ return MA_INVALID_ARGS; } + MA_ASSERT(pDataSourceBase->vtable != NULL); + if (pDataSourceBase->vtable->onGetDataFormat == NULL) { return MA_NOT_IMPLEMENTED; } @@ -57519,6 +58063,8 @@ MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSo return MA_SUCCESS; } + MA_ASSERT(pDataSourceBase->vtable != NULL); + if (pDataSourceBase->vtable->onGetCursor == NULL) { return MA_NOT_IMPLEMENTED; } @@ -57552,6 +58098,8 @@ MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSo return MA_INVALID_ARGS; } + MA_ASSERT(pDataSourceBase->vtable != NULL); + /* If we have a range defined we'll use that to determine the length. This is one of rare times where we'll actually trust the caller. If they've set the range, I think it's mostly safe to @@ -57639,6 +58187,8 @@ MA_API ma_result ma_data_source_set_looping(ma_data_source* pDataSource, ma_bool ma_atomic_exchange_32(&pDataSourceBase->isLooping, isLooping); + MA_ASSERT(pDataSourceBase->vtable != NULL); + /* If there's no callback for this just treat it as a successful no-op. */ if (pDataSourceBase->vtable->onSetLooping == NULL) { return MA_SUCCESS; @@ -57676,7 +58226,7 @@ MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSou /* We may need to adjust the position of the cursor to ensure it's clamped to the range. Grab it now - so we can calculate it's absolute position before we change the range. + so we can calculate its absolute position before we change the range. */ result = ma_data_source_get_cursor_in_pcm_frames(pDataSource, &relativeCursor); if (result == MA_SUCCESS) { @@ -57710,7 +58260,7 @@ MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSou /* Seek to within range. Note that our seek positions here are relative to the new range. We don't want - do do this if we failed to retrieve the cursor earlier on because it probably means the data source + to do this if we failed to retrieve the cursor earlier on because it probably means the data source has no notion of a cursor. In practice the seek would probably fail (which we silently ignore), but I'm just not even going to attempt it. */ @@ -57729,6 +58279,13 @@ MA_API void ma_data_source_get_range_in_pcm_frames(const ma_data_source* pDataSo { const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; + if (pRangeBegInFrames != NULL) { + *pRangeBegInFrames = 0; + } + if (pRangeEndInFrames != NULL) { + *pRangeEndInFrames = 0; + } + if (pDataSource == NULL) { return; } @@ -57773,6 +58330,13 @@ MA_API void ma_data_source_get_loop_point_in_pcm_frames(const ma_data_source* pD { const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; + if (pLoopBegInFrames != NULL) { + *pLoopBegInFrames = 0; + } + if (pLoopEndInFrames != NULL) { + *pLoopEndInFrames = 0; + } + if (pDataSource == NULL) { return; } @@ -59167,7 +59731,7 @@ static ma_result ma_default_vfs_seek__win32(ma_vfs* pVFS, ma_vfs_file file, ma_i result = ma_SetFilePointerEx((HANDLE)file, liDistanceToMove, NULL, dwMoveMethod); } else if (ma_SetFilePointer != NULL) { /* No SetFilePointerEx() so restrict to 31 bits. */ - if (origin > 0x7FFFFFFF) { + if (offset > 0x7FFFFFFF) { return MA_OUT_OF_RANGE; } @@ -59377,7 +59941,7 @@ static ma_result ma_default_vfs_seek__stdio(ma_vfs* pVFS, ma_vfs_file file, ma_i result = _fseeki64((FILE*)file, offset, whence); #else /* No _fseeki64() so restrict to 31 bits. */ - if (origin > 0x7FFFFFFF) { + if (offset > 0x7FFFFFFF) { return MA_OUT_OF_RANGE; } @@ -59770,7 +60334,7 @@ extern "C" { #define MA_DR_WAV_XSTRINGIFY(x) MA_DR_WAV_STRINGIFY(x) #define MA_DR_WAV_VERSION_MAJOR 0 #define MA_DR_WAV_VERSION_MINOR 13 -#define MA_DR_WAV_VERSION_REVISION 13 +#define MA_DR_WAV_VERSION_REVISION 18 #define MA_DR_WAV_VERSION_STRING MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MAJOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MINOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_REVISION) #include #define MA_DR_WAVE_FORMAT_PCM 0x1 @@ -60190,7 +60754,7 @@ extern "C" { #define MA_DR_FLAC_XSTRINGIFY(x) MA_DR_FLAC_STRINGIFY(x) #define MA_DR_FLAC_VERSION_MAJOR 0 #define MA_DR_FLAC_VERSION_MINOR 12 -#define MA_DR_FLAC_VERSION_REVISION 42 +#define MA_DR_FLAC_VERSION_REVISION 43 #define MA_DR_FLAC_VERSION_STRING MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_MAJOR) "." MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_MINOR) "." MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_REVISION) #include #if defined(_MSC_VER) && _MSC_VER >= 1700 @@ -60477,7 +61041,7 @@ extern "C" { #define MA_DR_MP3_XSTRINGIFY(x) MA_DR_MP3_STRINGIFY(x) #define MA_DR_MP3_VERSION_MAJOR 0 #define MA_DR_MP3_VERSION_MINOR 6 -#define MA_DR_MP3_VERSION_REVISION 38 +#define MA_DR_MP3_VERSION_REVISION 40 #define MA_DR_MP3_VERSION_STRING MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_MAJOR) "." MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_MINOR) "." MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_REVISION) #include #define MA_DR_MP3_MAX_PCM_FRAMES_PER_MP3_FRAME 1152 @@ -60639,7 +61203,7 @@ MA_API ma_decoder_config ma_decoder_config_init(ma_format outputFormat, ma_uint3 return config; } -MA_API ma_decoder_config ma_decoder_config_init_default() +MA_API ma_decoder_config ma_decoder_config_init_default(void) { return ma_decoder_config_init(ma_format_unknown, 0, 0); } @@ -63232,7 +63796,7 @@ MA_API ma_result ma_stbvorbis_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_ #if !defined(MA_NO_VORBIS) { /* - stb_vorbis lacks a callback based API for it's pulling API which means we're stuck with the + stb_vorbis lacks a callback based API for its pulling API which means we're stuck with the pushing API. In order for us to be able to successfully initialize the decoder we need to supply it with enough data. We need to keep loading data until we have enough. */ @@ -63313,7 +63877,7 @@ MA_API ma_result ma_stbvorbis_init_memory(const void* pData, size_t dataSize, co { (void)pAllocationCallbacks; - /* stb_vorbis uses an int as it's size specifier, restricting it to 32-bit even on 64-bit systems. *sigh*. */ + /* stb_vorbis uses an int as its size specifier, restricting it to 32-bit even on 64-bit systems. *sigh*. */ if (dataSize > INT_MAX) { return MA_TOO_BIG; } @@ -63403,7 +63967,7 @@ MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFram /* The first thing to do is read from any already-cached frames. */ ma_uint32 framesToReadFromCache = (ma_uint32)ma_min(pVorbis->push.framesRemaining, (frameCount - totalFramesRead)); /* Safe cast because pVorbis->framesRemaining is 32-bit. */ - /* The output pointer can be null in which case we just treate it as a seek. */ + /* The output pointer can be null in which case we just treat it as a seek. */ if (pFramesOut != NULL) { ma_uint64 iFrame; for (iFrame = 0; iFrame < framesToReadFromCache; iFrame += 1) { @@ -63477,7 +64041,7 @@ MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFram } } - /* If we don't have a success code at this point it means we've encounted an error or the end of the file has been reached (probably the latter). */ + /* If we don't have a success code at this point it means we've encountered an error or the end of the file has been reached (probably the latter). */ if (result != MA_SUCCESS) { break; } @@ -64291,8 +64855,7 @@ MA_API ma_result ma_decoder_init_memory(const void* pData, size_t dataSize, cons #if defined(MA_HAS_WAV) || \ defined(MA_HAS_MP3) || \ defined(MA_HAS_FLAC) || \ - defined(MA_HAS_VORBIS) || \ - defined(MA_HAS_OPUS) + defined(MA_HAS_VORBIS) #define MA_HAS_PATH_API #endif @@ -65107,7 +65670,7 @@ MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesO } else { /* Getting here means we need to do data conversion. If we're seeking forward and are _not_ doing resampling we can run this in a fast path. If we're doing resampling we - need to run through each sample because we need to ensure it's internal cache is updated. + need to run through each sample because we need to ensure its internal cache is updated. */ if (pFramesOut == NULL && pDecoder->converter.hasResampler == MA_FALSE) { result = ma_data_source_read_pcm_frames(pDecoder->pBackend, NULL, frameCount, &totalFramesReadOut); @@ -65197,8 +65760,17 @@ MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesO if (requiredInputFrameCount > 0) { result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pIntermediaryBuffer, framesToReadThisIterationIn, &framesReadThisIterationIn); + + /* + Note here that even if we've reached the end, we don't want to abort because there might be more output frames needing to be + generated from cached input data, which might happen if resampling is being performed. + */ + if (result != MA_SUCCESS && result != MA_AT_END) { + break; + } } else { framesReadThisIterationIn = 0; + pIntermediaryBuffer[0] = 0; /* <-- This is just to silence a static analysis warning. */ } /* @@ -66679,7 +67251,7 @@ MA_API ma_result ma_noise_set_type(ma_noise* pNoise, ma_noise_type type) /* This function should never have been implemented in the first place. Changing the type dynamically is not - supported. Instead you need to uninitialize and reinitiailize a fresh `ma_noise` object. This function + supported. Instead you need to uninitialize and reinitialize a fresh `ma_noise` object. This function will be removed in version 0.12. */ MA_ASSERT(MA_FALSE); @@ -67725,7 +68297,7 @@ MA_API ma_result ma_resource_manager_init(const ma_resource_manager_config* pCon pResourceManager->config.pVFS = &pResourceManager->defaultVFS; } - /* If threading has been disabled at compile time, enfore it at run time as well. */ + /* If threading has been disabled at compile time, enforce it at run time as well. */ #ifdef MA_NO_THREADING { pResourceManager->config.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING; @@ -67762,15 +68334,17 @@ MA_API ma_result ma_resource_manager_init(const ma_resource_manager_config* pCon /* Custom decoding backends. */ if (pConfig->ppCustomDecodingBackendVTables != NULL && pConfig->customDecodingBackendCount > 0) { size_t sizeInBytes = sizeof(*pResourceManager->config.ppCustomDecodingBackendVTables) * pConfig->customDecodingBackendCount; + ma_decoding_backend_vtable** ppCustomDecodingBackendVTables; - pResourceManager->config.ppCustomDecodingBackendVTables = (ma_decoding_backend_vtable**)ma_malloc(sizeInBytes, &pResourceManager->config.allocationCallbacks); + ppCustomDecodingBackendVTables = (ma_decoding_backend_vtable**)ma_malloc(sizeInBytes, &pResourceManager->config.allocationCallbacks); if (pResourceManager->config.ppCustomDecodingBackendVTables == NULL) { ma_job_queue_uninit(&pResourceManager->jobQueue, &pResourceManager->config.allocationCallbacks); return MA_OUT_OF_MEMORY; } - MA_COPY_MEMORY(pResourceManager->config.ppCustomDecodingBackendVTables, pConfig->ppCustomDecodingBackendVTables, sizeInBytes); + MA_COPY_MEMORY(ppCustomDecodingBackendVTables, pConfig->ppCustomDecodingBackendVTables, sizeInBytes); + pResourceManager->config.ppCustomDecodingBackendVTables = ppCustomDecodingBackendVTables; pResourceManager->config.customDecodingBackendCount = pConfig->customDecodingBackendCount; pResourceManager->config.pCustomDecodingBackendUserData = pConfig->pCustomDecodingBackendUserData; } @@ -67821,7 +68395,7 @@ static void ma_resource_manager_delete_all_data_buffer_nodes(ma_resource_manager ma_resource_manager_data_buffer_node* pDataBufferNode = pResourceManager->pRootDataBufferNode; ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); - /* The data buffer has been removed from the BST, so now we need to free it's data. */ + /* The data buffer has been removed from the BST, so now we need to free its data. */ ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode); } } @@ -67834,7 +68408,7 @@ MA_API void ma_resource_manager_uninit(ma_resource_manager* pResourceManager) /* Job threads need to be killed first. To do this we need to post a quit message to the message queue and then wait for the thread. The quit message will never be removed from the - queue which means it will never not be returned after being encounted for the first time which means all threads will eventually receive it. + queue which means it will never not be returned after being encountered for the first time which means all threads will eventually receive it. */ ma_resource_manager_post_job_quit(pResourceManager); @@ -67874,7 +68448,7 @@ MA_API void ma_resource_manager_uninit(ma_resource_manager* pResourceManager) #endif } - ma_free(pResourceManager->config.ppCustomDecodingBackendVTables, &pResourceManager->config.allocationCallbacks); + ma_free((ma_decoding_backend_vtable**)pResourceManager->config.ppCustomDecodingBackendVTables, &pResourceManager->config.allocationCallbacks); /* <-- Naughty const-cast, but this is safe. */ if (pResourceManager->config.pLog == &pResourceManager->log) { ma_log_uninit(&pResourceManager->log); @@ -68292,7 +68866,7 @@ static ma_result ma_resource_manager_data_buffer_node_decode_next_page(ma_resour } result = ma_decoder_read_pcm_frames(pDecoder, pPage->pAudioData, framesToTryReading, &framesRead); - if (framesRead > 0) { + if (result == MA_SUCCESS && framesRead > 0) { pPage->sizeInFrames = framesRead; result = ma_paged_audio_buffer_data_append_page(&pDataBufferNode->data.backend.decodedPaged.data, pPage); @@ -68445,7 +69019,7 @@ static ma_result ma_resource_manager_data_buffer_node_acquire_critical_section(m if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { ma_resource_manager_inline_notification_uninit(pInitNotification); } else { - /* These will have been freed by the job thread, but with WAIT_INIT they will already have happend sinced the job has already been handled. */ + /* These will have been freed by the job thread, but with WAIT_INIT they will already have happened since the job has already been handled. */ ma_free(pFilePathCopy, &pResourceManager->config.allocationCallbacks); ma_free(pFilePathWCopy, &pResourceManager->config.allocationCallbacks); } @@ -68810,6 +69384,10 @@ static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_ma flags &= ~MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC; } + if (pConfig->isLooping) { + flags |= MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING; + } + async = (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) != 0; /* @@ -68822,7 +69400,7 @@ static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_ma These fences are always released at the "done" tag at the end of this function. They'll be acquired a second if loading asynchronously. This double acquisition system is just done to - simplify code maintanence. + simplify code maintenance. */ ma_resource_manager_pipeline_notifications_acquire_all_fences(¬ifications); { @@ -68867,7 +69445,7 @@ static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_ma /* The status of the data buffer needs to be set to MA_BUSY before posting the job so that the - worker thread is aware of it's busy state. If the LOAD_DATA_BUFFER job sees a status other + worker thread is aware of its busy state. If the LOAD_DATA_BUFFER job sees a status other than MA_BUSY, it'll assume an error and fall through to an early exit. */ ma_atomic_exchange_i32(&pDataBuffer->result, MA_BUSY); @@ -68886,7 +69464,7 @@ static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_ma job.data.resourceManager.loadDataBuffer.rangeEndInPCMFrames = pConfig->rangeEndInPCMFrames; job.data.resourceManager.loadDataBuffer.loopPointBegInPCMFrames = pConfig->loopPointBegInPCMFrames; job.data.resourceManager.loadDataBuffer.loopPointEndInPCMFrames = pConfig->loopPointEndInPCMFrames; - job.data.resourceManager.loadDataBuffer.isLooping = pConfig->isLooping; + job.data.resourceManager.loadDataBuffer.isLooping = (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING) != 0; /* If we need to wait for initialization to complete we can just process the job in place. */ if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { @@ -69107,22 +69685,29 @@ MA_API ma_result ma_resource_manager_data_buffer_read_pcm_frames(ma_resource_man isDecodedBufferBusy = (ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) == MA_BUSY); if (ma_resource_manager_data_buffer_get_available_frames(pDataBuffer, &availableFrames) == MA_SUCCESS) { - /* Don't try reading more than the available frame count. */ - if (frameCount > availableFrames) { - frameCount = availableFrames; + /* Don't try reading more than the available frame count if the data buffer node is still loading. */ + if (isDecodedBufferBusy) { + if (frameCount > availableFrames) { + frameCount = availableFrames; - /* - If there's no frames available we want to set the status to MA_AT_END. The logic below - will check if the node is busy, and if so, change it to MA_BUSY. The reason we do this - is because we don't want to call `ma_data_source_read_pcm_frames()` if the frame count - is 0 because that'll result in a situation where it's possible MA_AT_END won't get - returned. - */ - if (frameCount == 0) { - result = MA_AT_END; + /* + If there's no frames available we want to set the status to MA_AT_END. The logic below + will check if the node is busy, and if so, change it to MA_BUSY. The reason we do this + is because we don't want to call `ma_data_source_read_pcm_frames()` if the frame count + is 0 because that'll result in a situation where it's possible MA_AT_END won't get + returned. + */ + if (frameCount == 0) { + result = MA_AT_END; + } + } else { + isDecodedBufferBusy = MA_FALSE; /* We have enough frames available in the buffer to avoid a MA_BUSY status. */ } } else { - isDecodedBufferBusy = MA_FALSE; /* We have enough frames available in the buffer to avoid a MA_BUSY status. */ + /* + Getting here means the buffer has been fully loaded. We can just pass the frame count straight + into ma_data_source_read_pcm_frames() below and let ma_data_source handle it. + */ } } } @@ -69522,6 +70107,7 @@ MA_API ma_result ma_resource_manager_data_stream_init_ex(ma_resource_manager* pR ma_bool32 waitBeforeReturning = MA_FALSE; ma_resource_manager_inline_notification waitNotification; ma_resource_manager_pipeline_notifications notifications; + ma_uint32 flags; if (pDataStream == NULL) { if (pConfig != NULL && pConfig->pNotifications != NULL) { @@ -69552,13 +70138,18 @@ MA_API ma_result ma_resource_manager_data_stream_init_ex(ma_resource_manager* pR return result; } + flags = pConfig->flags; + if (pConfig->isLooping) { + flags |= MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING; + } + pDataStream->pResourceManager = pResourceManager; pDataStream->flags = pConfig->flags; pDataStream->result = MA_BUSY; ma_data_source_set_range_in_pcm_frames(pDataStream, pConfig->rangeBegInPCMFrames, pConfig->rangeEndInPCMFrames); ma_data_source_set_loop_point_in_pcm_frames(pDataStream, pConfig->loopPointBegInPCMFrames, pConfig->loopPointEndInPCMFrames); - ma_data_source_set_looping(pDataStream, pConfig->isLooping); + ma_data_source_set_looping(pDataStream, (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING) != 0); if (pResourceManager == NULL || (pConfig->pFilePath == NULL && pConfig->pFilePathW == NULL)) { ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); @@ -70180,6 +70771,9 @@ static ma_result ma_resource_manager_data_source_preinit(ma_resource_manager* pR } pDataSource->flags = pConfig->flags; + if (pConfig->isLooping) { + pDataSource->flags |= MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING; + } return MA_SUCCESS; } @@ -70738,9 +71332,10 @@ static ma_result ma_job_process__resource_manager__load_data_buffer(ma_job* pJob */ result = ma_resource_manager_data_buffer_result(pDataBuffer); if (result != MA_BUSY) { - goto done; /* <-- This will ensure the exucution pointer is incremented. */ + goto done; /* <-- This will ensure the execution pointer is incremented. */ } else { result = MA_SUCCESS; /* <-- Make sure this is reset. */ + (void)result; /* <-- This is to suppress a static analysis diagnostic about "result" not being used. But for safety when I do future maintenance I don't want to delete that assignment. */ } /* Try initializing the connector if we haven't already. */ @@ -71087,11 +71682,74 @@ static ma_result ma_job_process__resource_manager__seek_data_stream(ma_job* pJob #ifndef MA_NO_NODE_GRAPH + +static ma_stack* ma_stack_init(size_t sizeInBytes, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_stack* pStack; + + if (sizeInBytes == 0) { + return NULL; + } + + pStack = (ma_stack*)ma_malloc(sizeof(*pStack) - sizeof(pStack->_data) + sizeInBytes, pAllocationCallbacks); + if (pStack == NULL) { + return NULL; + } + + pStack->offset = 0; + pStack->sizeInBytes = sizeInBytes; + + return pStack; +} + +static void ma_stack_uninit(ma_stack* pStack, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pStack == NULL) { + return; + } + + ma_free(pStack, pAllocationCallbacks); +} + +static void* ma_stack_alloc(ma_stack* pStack, size_t sz) +{ + /* The size of the allocation is stored in the memory directly before the pointer. This needs to include padding to keep it aligned to ma_uintptr */ + void* p = (void*)((char*)pStack->_data + pStack->offset); + size_t* pSize = (size_t*)p; + + sz = (sz + (sizeof(ma_uintptr) - 1)) & ~(sizeof(ma_uintptr) - 1); /* Padding. */ + if (pStack->offset + sz + sizeof(size_t) > pStack->sizeInBytes) { + return NULL; /* Out of memory. */ + } + + pStack->offset += sz + sizeof(size_t); + + *pSize = sz; + return (void*)((char*)p + sizeof(size_t)); +} + +static void ma_stack_free(ma_stack* pStack, void* p) +{ + size_t* pSize; + + if (p == NULL) { + return; + } + + pSize = (size_t*)p - 1; + pStack->offset -= *pSize + sizeof(size_t); +} + + + /* 10ms @ 48K = 480. Must never exceed 65535. */ #ifndef MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS #define MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS 480 #endif +#ifndef MA_DEFAULT_PREMIX_STACK_SIZE_PER_CHANNEL +#define MA_DEFAULT_PREMIX_STACK_SIZE_PER_CHANNEL 524288 +#endif static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusIndex, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime); @@ -71131,8 +71789,8 @@ MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels) ma_node_graph_config config; MA_ZERO_OBJECT(&config); - config.channels = channels; - config.nodeCacheCapInFrames = MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS; + config.channels = channels; + config.processingSizeInFrames = 0; return config; } @@ -71219,11 +71877,7 @@ MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const m } MA_ZERO_OBJECT(pNodeGraph); - pNodeGraph->nodeCacheCapInFrames = pConfig->nodeCacheCapInFrames; - if (pNodeGraph->nodeCacheCapInFrames == 0) { - pNodeGraph->nodeCacheCapInFrames = MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS; - } - + pNodeGraph->processingSizeInFrames = pConfig->processingSizeInFrames; /* Base node so we can use the node graph as a node into another graph. */ baseConfig = ma_node_config_init(); @@ -71248,6 +71902,40 @@ MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const m return result; } + + /* Processing cache. */ + if (pConfig->processingSizeInFrames > 0) { + pNodeGraph->pProcessingCache = (float*)ma_malloc(pConfig->processingSizeInFrames * pConfig->channels * sizeof(float), pAllocationCallbacks); + if (pNodeGraph->pProcessingCache == NULL) { + ma_node_uninit(&pNodeGraph->endpoint, pAllocationCallbacks); + ma_node_uninit(&pNodeGraph->base, pAllocationCallbacks); + return MA_OUT_OF_MEMORY; + } + } + + + /* + We need a pre-mix stack. The size of this stack is configurable via the config. The default value depends on the channel count. + */ + { + size_t preMixStackSizeInBytes = pConfig->preMixStackSizeInBytes; + if (preMixStackSizeInBytes == 0) { + preMixStackSizeInBytes = pConfig->channels * MA_DEFAULT_PREMIX_STACK_SIZE_PER_CHANNEL; + } + + pNodeGraph->pPreMixStack = ma_stack_init(preMixStackSizeInBytes, pAllocationCallbacks); + if (pNodeGraph->pPreMixStack == NULL) { + ma_node_uninit(&pNodeGraph->endpoint, pAllocationCallbacks); + ma_node_uninit(&pNodeGraph->base, pAllocationCallbacks); + if (pNodeGraph->pProcessingCache != NULL) { + ma_free(pNodeGraph->pProcessingCache, pAllocationCallbacks); + } + + return MA_OUT_OF_MEMORY; + } + } + + return MA_SUCCESS; } @@ -71258,6 +71946,17 @@ MA_API void ma_node_graph_uninit(ma_node_graph* pNodeGraph, const ma_allocation_ } ma_node_uninit(&pNodeGraph->endpoint, pAllocationCallbacks); + ma_node_uninit(&pNodeGraph->base, pAllocationCallbacks); + + if (pNodeGraph->pProcessingCache != NULL) { + ma_free(pNodeGraph->pProcessingCache, pAllocationCallbacks); + pNodeGraph->pProcessingCache = NULL; + } + + if (pNodeGraph->pPreMixStack != NULL) { + ma_stack_uninit(pNodeGraph->pPreMixStack, pAllocationCallbacks); + pNodeGraph->pPreMixStack = NULL; + } } MA_API ma_node* ma_node_graph_get_endpoint(ma_node_graph* pNodeGraph) @@ -71290,27 +71989,72 @@ MA_API ma_result ma_node_graph_read_pcm_frames(ma_node_graph* pNodeGraph, void* totalFramesRead = 0; while (totalFramesRead < frameCount) { ma_uint32 framesJustRead; - ma_uint64 framesToRead = frameCount - totalFramesRead; + ma_uint64 framesToRead; + float* pRunningFramesOut; + framesToRead = frameCount - totalFramesRead; if (framesToRead > 0xFFFFFFFF) { framesToRead = 0xFFFFFFFF; } - ma_node_graph_set_is_reading(pNodeGraph, MA_TRUE); - { - result = ma_node_read_pcm_frames(&pNodeGraph->endpoint, 0, (float*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels), (ma_uint32)framesToRead, &framesJustRead, ma_node_get_time(&pNodeGraph->endpoint)); - } - ma_node_graph_set_is_reading(pNodeGraph, MA_FALSE); + pRunningFramesOut = (float*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels); - totalFramesRead += framesJustRead; + /* If there's anything in the cache, consume that first. */ + if (pNodeGraph->processingCacheFramesRemaining > 0) { + ma_uint32 framesToReadFromCache; - if (result != MA_SUCCESS) { - break; - } + framesToReadFromCache = (ma_uint32)framesToRead; + if (framesToReadFromCache > pNodeGraph->processingCacheFramesRemaining) { + framesToReadFromCache = pNodeGraph->processingCacheFramesRemaining; + } - /* Abort if we weren't able to read any frames or else we risk getting stuck in a loop. */ - if (framesJustRead == 0) { - break; + MA_COPY_MEMORY(pRunningFramesOut, pNodeGraph->pProcessingCache, framesToReadFromCache * channels * sizeof(float)); + MA_MOVE_MEMORY(pNodeGraph->pProcessingCache, pNodeGraph->pProcessingCache + (framesToReadFromCache * channels), (pNodeGraph->processingCacheFramesRemaining - framesToReadFromCache) * channels * sizeof(float)); + pNodeGraph->processingCacheFramesRemaining -= framesToReadFromCache; + + totalFramesRead += framesToReadFromCache; + continue; + } else { + /* + If processingSizeInFrames is non-zero, we need to make sure we always read in chunks of that size. If the frame count is less than + that, we need to read into the cache and then continue on. + */ + float* pReadDst = pRunningFramesOut; + + if (pNodeGraph->processingSizeInFrames > 0) { + if (framesToRead < pNodeGraph->processingSizeInFrames) { + pReadDst = pNodeGraph->pProcessingCache; /* We need to read into the cache because otherwise we'll overflow the output buffer. */ + } + + framesToRead = pNodeGraph->processingSizeInFrames; + } + + ma_node_graph_set_is_reading(pNodeGraph, MA_TRUE); + { + result = ma_node_read_pcm_frames(&pNodeGraph->endpoint, 0, pReadDst, (ma_uint32)framesToRead, &framesJustRead, ma_node_get_time(&pNodeGraph->endpoint)); + } + ma_node_graph_set_is_reading(pNodeGraph, MA_FALSE); + + /* + Do not increment the total frames read counter if we read into the cache. We use this to determine how many frames have + been written to the final output buffer. + */ + if (pReadDst == pNodeGraph->pProcessingCache) { + /* We read into the cache. */ + pNodeGraph->processingCacheFramesRemaining = framesJustRead; + } else { + /* We read straight into the output buffer. */ + totalFramesRead += framesJustRead; + } + + if (result != MA_SUCCESS) { + break; + } + + /* Abort if we weren't able to read any frames or else we risk getting stuck in a loop. */ + if (framesJustRead == 0) { + break; + } } } @@ -71511,7 +72255,7 @@ static void ma_node_input_bus_detach__no_output_bus_lock(ma_node_input_bus* pInp *not* using a lock when iterating over the list in the audio thread. We therefore need to craft this in a way such that the iteration on the audio thread doesn't break. - The the first thing to do is swap out the "next" pointer of the previous output bus with the + The first thing to do is swap out the "next" pointer of the previous output bus with the new "next" output bus. This is the operation that matters for iteration on the audio thread. After that, the previous pointer on the new "next" pointer needs to be updated, after which point the linked list will be in a good state. @@ -71604,7 +72348,7 @@ static void ma_node_input_bus_attach(ma_node_input_bus* pInputBus, ma_node_outpu /* Now we need to attach the output bus to the linked list. This involves updating two pointers on two different output buses so I'm going to go ahead and keep this simple and just use a lock. - There are ways to do this without a lock, but it's just too hard to maintain for it's value. + There are ways to do this without a lock, but it's just too hard to maintain for its value. Although we're locking here, it's important to remember that we're *not* locking when iterating and reading audio data since that'll be running on the audio thread. As a result we need to be @@ -71697,11 +72441,9 @@ static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_ ma_uint32 inputChannels; ma_bool32 doesOutputBufferHaveContent = MA_FALSE; - (void)pInputNode; /* Not currently used. */ - /* This will be called from the audio thread which means we can't be doing any locking. Basically, - this function will not perfom any locking, whereas attaching and detaching will, but crafted in + this function will not perform any locking, whereas attaching and detaching will, but crafted in such a way that we don't need to perform any locking here. The important thing to remember is to always iterate in a forward direction. @@ -71747,19 +72489,12 @@ static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_ if (pFramesOut != NULL) { /* Read. */ - float temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE / sizeof(float)]; - ma_uint32 tempCapInFrames = ma_countof(temp) / inputChannels; - while (framesProcessed < frameCount) { float* pRunningFramesOut; ma_uint32 framesToRead; - ma_uint32 framesJustRead; + ma_uint32 framesJustRead = 0; framesToRead = frameCount - framesProcessed; - if (framesToRead > tempCapInFrames) { - framesToRead = tempCapInFrames; - } - pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(pFramesOut, framesProcessed, inputChannels); if (doesOutputBufferHaveContent == MA_FALSE) { @@ -71767,11 +72502,32 @@ static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_ result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, pRunningFramesOut, framesToRead, &framesJustRead, globalTime + framesProcessed); } else { /* Slow path. Not the first attachment. Mixing required. */ - result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, temp, framesToRead, &framesJustRead, globalTime + framesProcessed); - if (result == MA_SUCCESS || result == MA_AT_END) { - if (isSilentOutput == MA_FALSE) { /* Don't mix if the node outputs silence. */ - ma_mix_pcm_frames_f32(pRunningFramesOut, temp, framesJustRead, inputChannels, /*volume*/1); + ma_uint32 preMixBufferCapInFrames = ((ma_node_base*)pInputNode)->cachedDataCapInFramesPerBus; + float* pPreMixBuffer = (float*)ma_stack_alloc(((ma_node_base*)pInputNode)->pNodeGraph->pPreMixStack, preMixBufferCapInFrames * inputChannels * sizeof(float)); + + if (pPreMixBuffer == NULL) { + /* + If you're hitting this assert it means you've got an unusually deep chain of nodes, you've got an excessively large processing + size, or you have a combination of both, and as a result have run out of stack space. You can increase this using the + preMixStackSizeInBytes variable in ma_node_graph_config. If you're using ma_engine, you can do it via the preMixStackSizeInBytes + variable in ma_engine_config. It defaults to 512KB per output channel. + */ + MA_ASSERT(MA_FALSE); + } else { + if (framesToRead > preMixBufferCapInFrames) { + framesToRead = preMixBufferCapInFrames; } + + result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, pPreMixBuffer, framesToRead, &framesJustRead, globalTime + framesProcessed); + if (result == MA_SUCCESS || result == MA_AT_END) { + if (isSilentOutput == MA_FALSE) { /* Don't mix if the node outputs silence. */ + ma_mix_pcm_frames_f32(pRunningFramesOut, pPreMixBuffer, framesJustRead, inputChannels, /*volume*/1); + } + } + + /* The pre-mix buffer is no longer required. */ + ma_stack_free(((ma_node_base*)pInputNode)->pNodeGraph->pPreMixStack, pPreMixBuffer); + pPreMixBuffer = NULL; } } @@ -71826,6 +72582,25 @@ MA_API ma_node_config ma_node_config_init(void) return config; } +static ma_uint16 ma_node_config_get_cache_size_in_frames(const ma_node_config* pConfig, const ma_node_graph* pNodeGraph) +{ + ma_uint32 cacheSizeInFrames; + + (void)pConfig; + + if (pNodeGraph->processingSizeInFrames > 0) { + cacheSizeInFrames = pNodeGraph->processingSizeInFrames; + } else { + cacheSizeInFrames = MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS; + } + + if (cacheSizeInFrames > 0xFFFF) { + cacheSizeInFrames = 0xFFFF; + } + + return (ma_uint16)cacheSizeInFrames; +} + static ma_result ma_node_detach_full(ma_node* pNode); @@ -71980,7 +72755,7 @@ static ma_result ma_node_get_heap_layout(ma_node_graph* pNodeGraph, const ma_nod /* Cached audio data. - We need to allocate memory for a caching both input and output data. We have an optimization + We need to allocate memory for caching both input and output data. We have an optimization where no caching is necessary for specific conditions: - The node has 0 inputs and 1 output. @@ -71999,14 +72774,18 @@ static ma_result ma_node_get_heap_layout(ma_node_graph* pNodeGraph, const ma_nod } else { /* Slow path. Cache needed. */ size_t cachedDataSizeInBytes = 0; + ma_uint32 cacheCapInFrames; ma_uint32 iBus; + /* The capacity of the cache is based on our callback processing size. */ + cacheCapInFrames = ma_node_config_get_cache_size_in_frames(pConfig, pNodeGraph); + for (iBus = 0; iBus < inputBusCount; iBus += 1) { - cachedDataSizeInBytes += pNodeGraph->nodeCacheCapInFrames * ma_get_bytes_per_frame(ma_format_f32, pConfig->pInputChannels[iBus]); + cachedDataSizeInBytes += cacheCapInFrames * ma_get_bytes_per_frame(ma_format_f32, pConfig->pInputChannels[iBus]); } for (iBus = 0; iBus < outputBusCount; iBus += 1) { - cachedDataSizeInBytes += pNodeGraph->nodeCacheCapInFrames * ma_get_bytes_per_frame(ma_format_f32, pConfig->pOutputChannels[iBus]); + cachedDataSizeInBytes += cacheCapInFrames * ma_get_bytes_per_frame(ma_format_f32, pConfig->pOutputChannels[iBus]); } pHeapLayout->cachedDataOffset = pHeapLayout->sizeInBytes; @@ -72092,13 +72871,12 @@ MA_API ma_result ma_node_init_preallocated(ma_node_graph* pNodeGraph, const ma_n if (heapLayout.cachedDataOffset != MA_SIZE_MAX) { pNodeBase->pCachedData = (float*)ma_offset_ptr(pHeap, heapLayout.cachedDataOffset); - pNodeBase->cachedDataCapInFramesPerBus = pNodeGraph->nodeCacheCapInFrames; + pNodeBase->cachedDataCapInFramesPerBus = ma_node_config_get_cache_size_in_frames(pConfig, pNodeGraph); } else { pNodeBase->pCachedData = NULL; } - /* We need to run an initialization step for each input and output bus. */ for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNodeBase); iInputBus += 1) { result = ma_node_input_bus_init(pConfig->pInputChannels[iInputBus], &pNodeBase->pInputBuses[iInputBus]); @@ -72272,7 +73050,7 @@ static ma_result ma_node_detach_full(ma_node* pNode) /* At this point all output buses will have been detached from the graph and we can be guaranteed - that none of it's input nodes will be getting processed by the graph. We can detach these + that none of its input nodes will be getting processed by the graph. We can detach these without needing to worry about the audio thread touching them. */ for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNode); iInputBus += 1) { @@ -72287,7 +73065,7 @@ static ma_result ma_node_detach_full(ma_node* pNode) linked list logic. We don't need to worry about the audio thread referencing these because the step above severed the connection to the graph. */ - for (pOutputBus = (ma_node_output_bus*)ma_atomic_load_ptr(&pInputBus->head.pNext); pOutputBus != NULL; pOutputBus = (ma_node_output_bus*)ma_atomic_load_ptr(&pOutputBus->pNext)) { + for (pOutputBus = (ma_node_output_bus*)ma_atomic_load_ptr(&pInputBus->head.pNext); pOutputBus != NULL; pOutputBus = (ma_node_output_bus*)ma_atomic_load_ptr(&pInputBus->head.pNext)) { ma_node_detach_output_bus(pOutputBus->pNode, pOutputBus->outputBusIndex); /* This won't do any waiting in practice and should be efficient. */ } } @@ -72309,7 +73087,7 @@ MA_API ma_result ma_node_detach_output_bus(ma_node* pNode, ma_uint32 outputBusIn return MA_INVALID_ARGS; /* Invalid output bus index. */ } - /* We need to lock the output bus because we need to inspect the input node and grab it's input bus. */ + /* We need to lock the output bus because we need to inspect the input node and grab its input bus. */ ma_node_output_bus_lock(&pNodeBase->pOutputBuses[outputBusIndex]); { pInputNodeBase = (ma_node_base*)pNodeBase->pOutputBuses[outputBusIndex].pInputNode; @@ -72475,7 +73253,7 @@ MA_API ma_node_state ma_node_get_state_by_time_range(const ma_node* pNode, ma_ui /* Getting here means the node is marked as started, but it may still not be truly started due to - it's start time not having been reached yet. Also, the stop time may have also been reached in + its start time not having been reached yet. Also, the stop time may have also been reached in which case it'll be considered stopped. */ if (ma_node_get_state_time(pNode, ma_node_state_started) > globalTimeBeg) { @@ -72486,7 +73264,7 @@ MA_API ma_node_state ma_node_get_state_by_time_range(const ma_node* pNode, ma_ui return ma_node_state_stopped; /* Stop time has been reached. */ } - /* Getting here means the node is marked as started and is within it's start/stop times. */ + /* Getting here means the node is marked as started and is within its start/stop times. */ return ma_node_state_started; } @@ -72648,12 +73426,12 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde frameCountOut = totalFramesRead; if (totalFramesRead > 0) { - ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Excplicit cast to silence the warning. */ + ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Explicit cast to silence the warning. */ } /* A passthrough should never have modified the input and output frame counts. If you're - triggering these assers you need to fix your processing callback. + triggering these asserts you need to fix your processing callback. */ MA_ASSERT(frameCountIn == totalFramesRead); MA_ASSERT(frameCountOut == totalFramesRead); @@ -72831,7 +73609,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde frames available right now. */ if (frameCountIn > 0 || (pNodeBase->vtable->flags & MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES) != 0) { - ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Excplicit cast to silence the warning. */ + ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Explicit cast to silence the warning. */ } else { frameCountOut = 0; /* No data was processed. */ } @@ -74068,7 +74846,7 @@ static ma_bool32 ma_engine_node_is_pitching_enabled(const ma_engine_node* pEngin { MA_ASSERT(pEngineNode != NULL); - /* Don't try to be clever by skiping resampling in the pitch=1 case or else you'll glitch when moving away from 1. */ + /* Don't try to be clever by skipping resampling in the pitch=1 case or else you'll glitch when moving away from 1. */ return !ma_atomic_load_explicit_32(&pEngineNode->isPitchDisabled, ma_atomic_memory_order_acquire); } @@ -74105,7 +74883,7 @@ static ma_result ma_engine_node_set_volume(ma_engine_node* pEngineNode, float vo /* If we're not smoothing we should bypass the volume gainer entirely. */ if (pEngineNode->volumeSmoothTimeInPCMFrames == 0) { - /* We should always have an active spatializer because it can be enabled and disabled dynamically. We can just use that for hodling our volume. */ + /* We should always have an active spatializer because it can be enabled and disabled dynamically. We can just use that for holding our volume. */ ma_spatializer_set_master_volume(&pEngineNode->spatializer, volume); } else { /* We're using volume smoothing, so apply the master volume to the gainer. */ @@ -74420,7 +75198,7 @@ static void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, const float ma_sound_set_at_end(pSound, MA_TRUE); /* This will be set to false in ma_sound_start(). */ } - pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesRead, ma_engine_get_channels(ma_sound_get_engine(pSound))); + pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesRead, ma_node_get_output_channels(pNode, 0)); frameCountIn = (ma_uint32)framesJustRead; frameCountOut = framesRemaining; @@ -74751,7 +75529,7 @@ MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* p /* - Spatialization comes next. We spatialize based ont he node's output channel count. It's up the caller to + Spatialization comes next. We spatialize based on the node's output channel count. It's up the caller to ensure channels counts link up correctly in the node graph. */ spatializerConfig = ma_engine_node_spatializer_config_init(&baseNodeConfig); @@ -74941,6 +75719,21 @@ static void ma_engine_data_callback_internal(ma_device* pDevice, void* pFramesOu ma_engine_read_pcm_frames(pEngine, pFramesOut, frameCount, NULL); } + +static ma_uint32 ma_device__get_processing_size_in_frames(ma_device* pDevice) +{ + /* + The processing size is the period size. The device can have a fixed sized processing size, or + it can be decided by the backend in which case it can be variable. + */ + if (pDevice->playback.intermediaryBufferCap > 0) { + /* Using a fixed sized processing callback. */ + return pDevice->playback.intermediaryBufferCap; + } else { + /* Not using a fixed sized processing callback. Need to estimate the processing size based on the backend. */ + return pDevice->playback.internalPeriodSizeInFrames; + } +} #endif MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine) @@ -75034,6 +75827,14 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng if (pEngine->pDevice != NULL) { engineConfig.channels = pEngine->pDevice->playback.channels; engineConfig.sampleRate = pEngine->pDevice->sampleRate; + + /* + The processing size used by the engine is determined by engineConfig.periodSizeInFrames. We want + to make this equal to what the device is using for it's period size. If we don't do that, it's + possible that the node graph will split it's processing into multiple passes which can introduce + glitching. + */ + engineConfig.periodSizeInFrames = ma_device__get_processing_size_in_frames(pEngine->pDevice); } } #endif @@ -75060,9 +75861,10 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng } - /* The engine is a node graph. This needs to be initialized after we have the device so we can can determine the channel count. */ + /* The engine is a node graph. This needs to be initialized after we have the device so we can determine the channel count. */ nodeGraphConfig = ma_node_graph_config_init(engineConfig.channels); - nodeGraphConfig.nodeCacheCapInFrames = (engineConfig.periodSizeInFrames > 0xFFFF) ? 0xFFFF : (ma_uint16)engineConfig.periodSizeInFrames; + nodeGraphConfig.processingSizeInFrames = engineConfig.periodSizeInFrames; + nodeGraphConfig.preMixStackSizeInBytes = engineConfig.preMixStackSizeInBytes; result = ma_node_graph_init(&nodeGraphConfig, &pEngine->allocationCallbacks, &pEngine->nodeGraph); if (result != MA_SUCCESS) { @@ -75142,8 +75944,8 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng ma_allocation_callbacks_init_copy(&resourceManagerConfig.allocationCallbacks, &pEngine->allocationCallbacks); resourceManagerConfig.pVFS = engineConfig.pResourceManagerVFS; - /* The Emscripten build cannot use threads. */ - #if defined(MA_EMSCRIPTEN) + /* The Emscripten build cannot use threads unless it's targeting pthreads. */ + #if defined(MA_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__) { resourceManagerConfig.jobThreadCount = 0; resourceManagerConfig.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING; @@ -75658,7 +76460,7 @@ MA_API ma_result ma_engine_play_sound_ex(ma_engine* pEngine, const char* pFilePa return MA_INVALID_ARGS; } - /* Attach to the endpoint node if nothing is specicied. */ + /* Attach to the endpoint node if nothing is specified. */ if (pNode == NULL) { pNode = ma_node_graph_get_endpoint(&pEngine->nodeGraph); nodeInputBusIndex = 0; @@ -75875,7 +76677,7 @@ static ma_result ma_sound_init_from_data_source_internal(ma_engine* pEngine, con ma_data_source_set_range_in_pcm_frames(ma_sound_get_data_source(pSound), pConfig->loopPointBegInPCMFrames, pConfig->loopPointEndInPCMFrames); } - ma_sound_set_looping(pSound, pConfig->isLooping); + ma_sound_set_looping(pSound, pConfig->isLooping || ((pConfig->flags & MA_SOUND_FLAG_LOOPING) != 0)); return MA_SUCCESS; } @@ -75899,6 +76701,9 @@ MA_API ma_result ma_sound_init_from_file_internal(ma_engine* pEngine, const ma_s it and can avoid accessing the sound from within the notification. */ flags = pConfig->flags | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT; + if (pConfig->isLooping) { + flags |= MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING; + } pSound->pResourceManagerDataSource = (ma_resource_manager_data_source*)ma_malloc(sizeof(*pSound->pResourceManagerDataSource), &pEngine->allocationCallbacks); if (pSound->pResourceManagerDataSource == NULL) { @@ -75927,7 +76732,7 @@ MA_API ma_result ma_sound_init_from_file_internal(ma_engine* pEngine, const ma_s resourceManagerDataSourceConfig.rangeEndInPCMFrames = pConfig->rangeEndInPCMFrames; resourceManagerDataSourceConfig.loopPointBegInPCMFrames = pConfig->loopPointBegInPCMFrames; resourceManagerDataSourceConfig.loopPointEndInPCMFrames = pConfig->loopPointEndInPCMFrames; - resourceManagerDataSourceConfig.isLooping = pConfig->isLooping; + resourceManagerDataSourceConfig.isLooping = (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING) != 0; result = ma_resource_manager_data_source_init_ex(pEngine->pResourceManager, &resourceManagerDataSourceConfig, pSound->pResourceManagerDataSource); if (result != MA_SUCCESS) { @@ -76079,7 +76884,7 @@ MA_API ma_result ma_sound_init_ex(ma_engine* pEngine, const ma_sound_config* pCo { /* Getting here means we're not loading from a file. We may be loading from an already-initialized - data source, or none at all. If we aren't specifying any data source, we'll be initializing the + data source, or none at all. If we aren't specifying any data source, we'll be initializing the equivalent to a group. ma_data_source_init_from_data_source_internal() will deal with this for us, so no special treatment required here. */ @@ -76799,6 +77604,27 @@ MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameInd return MA_SUCCESS; } +MA_API ma_result ma_sound_seek_to_second(ma_sound* pSound, float seekPointInSeconds) +{ + ma_uint64 frameIndex; + ma_uint32 sampleRate; + ma_result result; + + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_sound_get_data_format(pSound, NULL, NULL, &sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; + } + + /* We need PCM frames. We need to convert first */ + frameIndex = (ma_uint64)(seekPointInSeconds * sampleRate); + + return ma_sound_seek_to_pcm_frame(pSound, frameIndex); +} + MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { if (pSound == NULL) { @@ -77245,7 +78071,7 @@ code below please report the bug to the respective repository for the relevant p *************************************************************************************************************************************************************** **************************************************************************************************************************************************************/ #if !defined(MA_NO_WAV) && (!defined(MA_NO_DECODING) || !defined(MA_NO_ENCODING)) -#if !defined(MA_DR_WAV_IMPLEMENTATION) && !defined(MA_DR_WAV_IMPLEMENTATION) /* For backwards compatibility. Will be removed in version 0.11 for cleanliness. */ +#if !defined(MA_DR_WAV_IMPLEMENTATION) /* dr_wav_c begin */ #ifndef ma_dr_wav_c #define ma_dr_wav_c @@ -78567,7 +79393,6 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p } if (pWav->container == ma_dr_wav_container_riff || pWav->container == ma_dr_wav_container_rifx) { if (ma_dr_wav_bytes_to_u32_ex(chunkSizeBytes, pWav->container) < 36) { - return MA_FALSE; } } else if (pWav->container == ma_dr_wav_container_rf64) { if (ma_dr_wav_bytes_to_u32_le(chunkSizeBytes) != 0xFFFFFFFF) { @@ -78836,7 +79661,9 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p compressionFormat = MA_DR_WAVE_FORMAT_MULAW; } else if (ma_dr_wav_fourcc_equal(type, "ima4")) { compressionFormat = MA_DR_WAVE_FORMAT_DVI_ADPCM; - sampleSizeInBits = 4; + sampleSizeInBits = 4; + (void)compressionFormat; + (void)sampleSizeInBits; return MA_FALSE; } else { return MA_FALSE; @@ -78894,9 +79721,7 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p } } if (isProcessingMetadata) { - ma_uint64 metadataBytesRead; - metadataBytesRead = ma_dr_wav__metadata_process_chunk(&metadataParser, &header, ma_dr_wav_metadata_type_all_including_unknown); - MA_DR_WAV_ASSERT(metadataBytesRead <= header.sizeInBytes); + ma_dr_wav__metadata_process_chunk(&metadataParser, &header, ma_dr_wav_metadata_type_all_including_unknown); if (ma_dr_wav__seek_from_start(pWav->onSeek, cursor, pWav->pUserData) == MA_FALSE) { break; } @@ -80344,6 +81169,12 @@ MA_API ma_uint64 ma_dr_wav_write_pcm_frames(ma_dr_wav* pWav, ma_uint64 framesToW MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__msadpcm(ma_dr_wav* pWav, ma_uint64 framesToRead, ma_int16* pBufferOut) { ma_uint64 totalFramesRead = 0; + static ma_int32 adaptationTable[] = { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 + }; + static ma_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; + static ma_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; MA_DR_WAV_ASSERT(pWav != NULL); MA_DR_WAV_ASSERT(framesToRead > 0); while (pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { @@ -80362,6 +81193,9 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__msadpcm(ma_dr_wav* pWav, ma_ pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][0]; pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.cachedFrameCount = 2; + if (pWav->msadpcm.predictor[0] >= ma_dr_wav_countof(coeff1Table)) { + return totalFramesRead; + } } else { ma_uint8 header[14]; if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { @@ -80381,6 +81215,9 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__msadpcm(ma_dr_wav* pWav, ma_ pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[1][1]; pWav->msadpcm.cachedFrameCount = 2; + if (pWav->msadpcm.predictor[0] >= ma_dr_wav_countof(coeff1Table) || pWav->msadpcm.predictor[1] >= ma_dr_wav_countof(coeff2Table)) { + return totalFramesRead; + } } } while (framesToRead > 0 && pWav->msadpcm.cachedFrameCount > 0 && pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { @@ -80403,12 +81240,6 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__msadpcm(ma_dr_wav* pWav, ma_ if (pWav->msadpcm.bytesRemainingInBlock == 0) { continue; } else { - static ma_int32 adaptationTable[] = { - 230, 230, 230, 230, 307, 409, 512, 614, - 768, 614, 512, 409, 307, 230, 230, 230 - }; - static ma_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; - static ma_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; ma_uint8 nibbles; ma_int32 nibble0; ma_int32 nibble1; @@ -81659,7 +82490,7 @@ MA_API void ma_dr_wav_f32_to_s32(ma_int32* pOut, const float* pIn, size_t sample return; } for (i = 0; i < sampleCount; ++i) { - *pOut++ = (ma_int32)(2147483648.0 * pIn[i]); + *pOut++ = (ma_int32)(2147483648.0f * pIn[i]); } } MA_API void ma_dr_wav_f64_to_s32(ma_int32* pOut, const double* pIn, size_t sampleCount) @@ -82073,7 +82904,7 @@ MA_API ma_bool32 ma_dr_wav_fourcc_equal(const ma_uint8* a, const char* b) #endif /* MA_NO_WAV */ #if !defined(MA_NO_FLAC) && !defined(MA_NO_DECODING) -#if !defined(MA_DR_FLAC_IMPLEMENTATION) && !defined(MA_DR_FLAC_IMPLEMENTATION) /* For backwards compatibility. Will be removed in version 0.11 for cleanliness. */ +#if !defined(MA_DR_FLAC_IMPLEMENTATION) /* dr_flac_c begin */ #ifndef ma_dr_flac_c #define ma_dr_flac_c @@ -85105,6 +85936,7 @@ static ma_bool32 ma_dr_flac__read_subframe_header(ma_dr_flac_bs* bs, ma_dr_flac_ if ((header & 0x80) != 0) { return MA_FALSE; } + pSubframe->lpcOrder = 0; type = (header & 0x7E) >> 1; if (type == 0) { pSubframe->subframeType = MA_DR_FLAC_SUBFRAME_CONSTANT; @@ -85162,6 +85994,9 @@ static ma_bool32 ma_dr_flac__decode_subframe(ma_dr_flac_bs* bs, ma_dr_flac_frame } subframeBitsPerSample -= pSubframe->wastedBitsPerSample; pSubframe->pSamplesS32 = pDecodedSamplesOut; + if (frame->header.blockSizeInPCMFrames < pSubframe->lpcOrder) { + return MA_FALSE; + } switch (pSubframe->subframeType) { case MA_DR_FLAC_SUBFRAME_CONSTANT: @@ -89818,7 +90653,7 @@ MA_API ma_bool32 ma_dr_flac_next_cuesheet_track(ma_dr_flac_cuesheet_track_iterat #endif /* MA_NO_FLAC */ #if !defined(MA_NO_MP3) && !defined(MA_NO_DECODING) -#if !defined(MA_DR_MP3_IMPLEMENTATION) && !defined(MA_DR_MP3_IMPLEMENTATION) /* For backwards compatibility. Will be removed in version 0.11 for cleanliness. */ +#if !defined(MA_DR_MP3_IMPLEMENTATION) /* dr_mp3_c begin */ #ifndef ma_dr_mp3_c #define ma_dr_mp3_c @@ -89879,7 +90714,7 @@ MA_API const char* ma_dr_mp3_version_string(void) #define MA_DR_MP3_MIN(a, b) ((a) > (b) ? (b) : (a)) #define MA_DR_MP3_MAX(a, b) ((a) < (b) ? (b) : (a)) #if !defined(MA_DR_MP3_NO_SIMD) -#if !defined(MA_DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) || defined(_M_ARM64)) +#if !defined(MA_DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)) #define MA_DR_MP3_ONLY_SIMD #endif #if ((defined(_MSC_VER) && _MSC_VER >= 1400) && defined(_M_X64)) || ((defined(__i386) || defined(_M_IX86) || defined(__i386__) || defined(__x86_64__)) && ((defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE2__))) @@ -89952,7 +90787,7 @@ end: return g_have_simd - 1; #endif } -#elif defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64) +#elif defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) #include #define MA_DR_MP3_HAVE_SSE 0 #define MA_DR_MP3_HAVE_SIMD 1 @@ -89981,7 +90816,7 @@ static int ma_dr_mp3_have_simd(void) #else #define MA_DR_MP3_HAVE_SIMD 0 #endif -#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64) && !defined(__ARM_ARCH_6M__) +#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64) && !defined(_M_ARM64EC) && !defined(__ARM_ARCH_6M__) #define MA_DR_MP3_HAVE_ARMV6 1 static __inline__ __attribute__((always_inline)) ma_int32 ma_dr_mp3_clip_int16_arm(ma_int32 a) { @@ -91147,8 +91982,8 @@ static ma_int16 ma_dr_mp3d_scale_pcm(float sample) s32 -= (s32 < 0); s = (ma_int16)ma_dr_mp3_clip_int16_arm(s32); #else - if (sample >= 32766.5) return (ma_int16) 32767; - if (sample <= -32767.5) return (ma_int16)-32768; + if (sample >= 32766.5f) return (ma_int16) 32767; + if (sample <= -32767.5f) return (ma_int16)-32768; s = (ma_int16)(sample + .5f); s -= (s < 0); #endif @@ -91534,9 +92369,9 @@ MA_API void ma_dr_mp3dec_f32_to_s16(const float *in, ma_int16 *out, size_t num_s for(; i < num_samples; i++) { float sample = in[i] * 32768.0f; - if (sample >= 32766.5) + if (sample >= 32766.5f) out[i] = (ma_int16) 32767; - else if (sample <= -32767.5) + else if (sample <= -32767.5f) out[i] = (ma_int16)-32768; else { @@ -92614,7 +93449,7 @@ For more information, please refer to =============================================================================== ALTERNATIVE 2 - MIT No Attribution =============================================================================== -Copyright 2023 David Reid +Copyright 2025 David Reid Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From 59338c2c29cdb4ebd977c39a203bb7613e9f56e8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 2 Jun 2025 16:37:46 +0000 Subject: [PATCH 031/242] Update raylib_api.* by CI --- parser/output/raylib_api.json | 4 ++-- parser/output/raylib_api.lua | 4 ++-- parser/output/raylib_api.txt | 4 ++-- parser/output/raylib_api.xml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index e796478e4..e95a14eac 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -6010,11 +6010,11 @@ }, { "type": "Color", - "name": "topRight" + "name": "bottomRight" }, { "type": "Color", - "name": "bottomRight" + "name": "topRight" } ] }, diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index a2897a226..a7a2ff688 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -4976,8 +4976,8 @@ return { {type = "Rectangle", name = "rec"}, {type = "Color", name = "topLeft"}, {type = "Color", name = "bottomLeft"}, - {type = "Color", name = "topRight"}, - {type = "Color", name = "bottomRight"} + {type = "Color", name = "bottomRight"}, + {type = "Color", name = "topRight"} } }, { diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 4de3e267a..e9a8a1315 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -2356,8 +2356,8 @@ Function 239: DrawRectangleGradientEx() (5 input parameters) Param[1]: rec (type: Rectangle) Param[2]: topLeft (type: Color) Param[3]: bottomLeft (type: Color) - Param[4]: topRight (type: Color) - Param[5]: bottomRight (type: Color) + Param[4]: bottomRight (type: Color) + Param[5]: topRight (type: Color) Function 240: DrawRectangleLines() (5 input parameters) Name: DrawRectangleLines Return type: void diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index e79e55f4f..9f4cfaa86 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -1488,8 +1488,8 @@ - + From 3f228f4594fac91958aed645bb5b1dec67e54910 Mon Sep 17 00:00:00 2001 From: Hamza RAHAL Date: Mon, 2 Jun 2025 22:52:24 +0200 Subject: [PATCH 032/242] [examples] : adding new fancy clock --- examples/Makefile | 3 +- examples/Makefile.Web | 3 +- examples/README.md | 210 +++++++++++------------ examples/shapes/shapes_digital_clock.c | 191 +++++++++++++++++++++ examples/shapes/shapes_digital_clock.png | Bin 0 -> 3651 bytes 5 files changed, 300 insertions(+), 107 deletions(-) create mode 100644 examples/shapes/shapes_digital_clock.c create mode 100644 examples/shapes/shapes_digital_clock.png diff --git a/examples/Makefile b/examples/Makefile index 548e869c2..32a3a75ab 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -547,7 +547,8 @@ SHAPES = \ shapes/shapes_rectangle_advanced \ shapes/shapes_rectangle_scaling \ shapes/shapes_splines_drawing \ - shapes/shapes_top_down_lights + shapes/shapes_top_down_lights \ + shapes/shapes_digital_clock TEXTURES = \ textures/textures_background_scrolling \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 04fbe927a..35ae70a18 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -429,7 +429,8 @@ SHAPES = \ shapes/shapes_rectangle_advanced \ shapes/shapes_rectangle_scaling \ shapes/shapes_splines_drawing \ - shapes/shapes_top_down_lights + shapes/shapes_top_down_lights \ + shapes/shapes_digital_clock TEXTURES = \ textures/textures_background_scrolling \ diff --git a/examples/README.md b/examples/README.md index 9982c2001..7553ad113 100644 --- a/examples/README.md +++ b/examples/README.md @@ -85,39 +85,39 @@ Examples using raylib shapes drawing functionality, provided by raylib [shapes]( | 51 | [shapes_top_down_lights](shapes/shapes_top_down_lights.c) | shapes_top_down_lights | ⭐️⭐️⭐️⭐️ | 4.2 | 4.2 | [Jeffery Myers](https://github.com/JeffM2501) | | 52 | [shapes_rectangle_advanced](shapes/shapes_rectangle_advanced.c) | shapes_rectangle_advanced | ⭐️⭐️⭐️⭐️ | 5.5 | 5.5 | [Everton Jr.](https://github.com/evertonse) | | 53 | [shapes_splines_drawing](shapes/shapes_splines_drawing.c) | shapes_splines_drawing | ⭐️⭐️⭐️☆ | 5.0 | 5.0 | [Ray](https://github.com/raysan5) | - +| 54 | [shapes_digital_clock](shapes/shapes_digital_clock.c) | shapes_digital_clock | ⭐️⭐️☆☆ | 5.5 | 5.5 | [Hamza RAHAL](https://github.com/rhmz-rhl) | ### category: textures Examples using raylib textures functionality, including image/textures loading/generation and drawing, provided by raylib [textures](../src/textures.c) modul | ## | example | image | difficulty
level | version
created | last version
updated | original
developer | |----|----------|--------|:-------------------:|:------------------:|:-----------------------:|:----------------------| -| 54 | [textures_logo_raylib](textures/textures_logo_raylib.c) | textures_logo_raylib | ⭐️☆☆☆ | 1.0 | 1.0 | [Ray](https://github.com/raysan5) | -| 55 | [textures_srcrec_dstrec](textures/textures_srcrec_dstrec.c) | textures_srcrec_dstrec | ⭐️⭐️⭐️☆ | 1.3 | 1.3 | [Ray](https://github.com/raysan5) | -| 56 | [textures_image_drawing](textures/textures_image_drawing.c) | textures_image_drawing | ⭐️⭐️☆☆ | 1.4 | 1.4 | [Ray](https://github.com/raysan5) | -| 57 | [textures_image_generation](textures/textures_image_generation.c) | textures_image_generation | ⭐️⭐️☆☆ | 1.8 | 1.8 | [Wilhem Barbier](https://github.com/nounoursheureux) | -| 58 | [textures_image_loading](textures/textures_image_loading.c) | textures_image_loading | ⭐️☆☆☆ | 1.3 | 1.3 | [Ray](https://github.com/raysan5) | -| 59 | [textures_image_processing](textures/textures_image_processing.c) | textures_image_processing | ⭐️⭐️⭐️☆ | 1.4 | 3.5 | [Ray](https://github.com/raysan5) | -| 60 | [textures_image_text](textures/textures_image_text.c) | textures_image_text | ⭐️⭐️☆☆ | 1.8 | 4.0 | [Ray](https://github.com/raysan5) | -| 61 | [textures_to_image](textures/textures_to_image.c) | textures_to_image | ⭐️☆☆☆ | 1.3 | 4.0 | [Ray](https://github.com/raysan5) | -| 62 | [textures_raw_data](textures/textures_raw_data.c) | textures_raw_data | ⭐️⭐️⭐️☆ | 1.3 | 3.5 | [Ray](https://github.com/raysan5) | -| 63 | [textures_particles_blending](textures/textures_particles_blending.c) | textures_particles_blending | ⭐️☆☆☆ | 1.7 | 3.5 | [Ray](https://github.com/raysan5) | -| 64 | [textures_npatch_drawing](textures/textures_npatch_drawing.c) | textures_npatch_drawing | ⭐️⭐️⭐️☆ | 2.0 | 2.5 | [Jorge A. Gomes](https://github.com/overdev) | -| 65 | [textures_background_scrolling](textures/textures_background_scrolling.c) | textures_background_scrolling | ⭐️☆☆☆ | 2.0 | 2.5 | [Ray](https://github.com/raysan5) | -| 66 | [textures_sprite_anim](textures/textures_sprite_anim.c) | textures_sprite_anim | ⭐️⭐️☆☆ | 1.3 | 1.3 | [Ray](https://github.com/raysan5) | -| 67 | [textures_sprite_button](textures/textures_sprite_button.c) | textures_sprite_button | ⭐️⭐️☆☆ | 2.5 | 2.5 | [Ray](https://github.com/raysan5) | -| 68 | [textures_sprite_explosion](textures/textures_sprite_explosion.c) | textures_sprite_explosion | ⭐️⭐️☆☆ | 2.5 | 3.5 | [Ray](https://github.com/raysan5) | -| 69 | [textures_bunnymark](textures/textures_bunnymark.c) | textures_bunnymark | ⭐️⭐️⭐️☆ | 1.6 | 2.5 | [Ray](https://github.com/raysan5) | -| 70 | [textures_mouse_painting](textures/textures_mouse_painting.c) | textures_mouse_painting | ⭐️⭐️⭐️☆ | 3.0 | 3.0 | [Chris Dill](https://github.com/MysteriousSpace) | -| 71 | [textures_blend_modes](textures/textures_blend_modes.c) | textures_blend_modes | ⭐️☆☆☆ | 3.5 | 3.5 | [Karlo Licudine](https://github.com/accidentalrebel) | -| 72 | [textures_draw_tiled](textures/textures_draw_tiled.c) | textures_draw_tiled | ⭐️⭐️⭐️☆ | 3.0 | 4.2 | [Vlad Adrian](https://github.com/demizdor) | -| 73 | [textures_polygon](textures/textures_polygon.c) | textures_polygon | ⭐️☆☆☆ | 3.7 | 3.7 | [Chris Camacho](https://github.com/chriscamacho) | -| 74 | [textures_fog_of_war](textures/textures_fog_of_war.c) | textures_fog_of_war | ⭐️⭐️⭐️☆ | 4.2 | 4.2 | [Ray](https://github.com/raysan5) | -| 75 | [textures_gif_player](textures/textures_gif_player.c) | textures_gif_player | ⭐️⭐️⭐️☆ | 4.2 | 4.2 | [Ray](https://github.com/raysan5) | -| 76 | [textures_image_kernel](textures/textures_image_kernel.c) | textures_image_kernel | ⭐️⭐️⭐️⭐️ | 1.3 | 1.3 | [Karim Salem](https://github.com/kimo-s) | -| 77 | [textures_image_channel](textures/textures_image_channel.c) | textures_image_channel | ⭐️⭐️☆☆ | 5.1-dev | 5.1-dev | [Bruno Cabral](https://github.com/brccabral) | -| 78 | [textures_image_rotate](textures/textures_image_rotate.c) | textures_image_rotate | ⭐️⭐️☆☆ | 1.0 | 1.0 | [Ray](https://github.com/raysan5) | -| 79 | [textures_textured_curve](textures/textures_textured_curve.c) | textures_textured_curve | ⭐️⭐️⭐️☆ | 4.5 | 4.5 | [Jeffery Myers](https://github.com/JeffM2501) | +| 55 | [textures_logo_raylib](textures/textures_logo_raylib.c) | textures_logo_raylib | ⭐️☆☆☆ | 1.0 | 1.0 | [Ray](https://github.com/raysan5) | +| 56 | [textures_srcrec_dstrec](textures/textures_srcrec_dstrec.c) | textures_srcrec_dstrec | ⭐️⭐️⭐️☆ | 1.3 | 1.3 | [Ray](https://github.com/raysan5) | +| 57 | [textures_image_drawing](textures/textures_image_drawing.c) | textures_image_drawing | ⭐️⭐️☆☆ | 1.4 | 1.4 | [Ray](https://github.com/raysan5) | +| 58 | [textures_image_generation](textures/textures_image_generation.c) | textures_image_generation | ⭐️⭐️☆☆ | 1.8 | 1.8 | [Wilhem Barbier](https://github.com/nounoursheureux) | +| 59 | [textures_image_loading](textures/textures_image_loading.c) | textures_image_loading | ⭐️☆☆☆ | 1.3 | 1.3 | [Ray](https://github.com/raysan5) | +| 60 | [textures_image_processing](textures/textures_image_processing.c) | textures_image_processing | ⭐️⭐️⭐️☆ | 1.4 | 3.5 | [Ray](https://github.com/raysan5) | +| 61 | [textures_image_text](textures/textures_image_text.c) | textures_image_text | ⭐️⭐️☆☆ | 1.8 | 4.0 | [Ray](https://github.com/raysan5) | +| 62 | [textures_to_image](textures/textures_to_image.c) | textures_to_image | ⭐️☆☆☆ | 1.3 | 4.0 | [Ray](https://github.com/raysan5) | +| 63 | [textures_raw_data](textures/textures_raw_data.c) | textures_raw_data | ⭐️⭐️⭐️☆ | 1.3 | 3.5 | [Ray](https://github.com/raysan5) | +| 64 | [textures_particles_blending](textures/textures_particles_blending.c) | textures_particles_blending | ⭐️☆☆☆ | 1.7 | 3.5 | [Ray](https://github.com/raysan5) | +| 65 | [textures_npatch_drawing](textures/textures_npatch_drawing.c) | textures_npatch_drawing | ⭐️⭐️⭐️☆ | 2.0 | 2.5 | [Jorge A. Gomes](https://github.com/overdev) | +| 66 | [textures_background_scrolling](textures/textures_background_scrolling.c) | textures_background_scrolling | ⭐️☆☆☆ | 2.0 | 2.5 | [Ray](https://github.com/raysan5) | +| 67 | [textures_sprite_anim](textures/textures_sprite_anim.c) | textures_sprite_anim | ⭐️⭐️☆☆ | 1.3 | 1.3 | [Ray](https://github.com/raysan5) | +| 68 | [textures_sprite_button](textures/textures_sprite_button.c) | textures_sprite_button | ⭐️⭐️☆☆ | 2.5 | 2.5 | [Ray](https://github.com/raysan5) | +| 69 | [textures_sprite_explosion](textures/textures_sprite_explosion.c) | textures_sprite_explosion | ⭐️⭐️☆☆ | 2.5 | 3.5 | [Ray](https://github.com/raysan5) | +| 70 | [textures_bunnymark](textures/textures_bunnymark.c) | textures_bunnymark | ⭐️⭐️⭐️☆ | 1.6 | 2.5 | [Ray](https://github.com/raysan5) | +| 71 | [textures_mouse_painting](textures/textures_mouse_painting.c) | textures_mouse_painting | ⭐️⭐️⭐️☆ | 3.0 | 3.0 | [Chris Dill](https://github.com/MysteriousSpace) | +| 72 | [textures_blend_modes](textures/textures_blend_modes.c) | textures_blend_modes | ⭐️☆☆☆ | 3.5 | 3.5 | [Karlo Licudine](https://github.com/accidentalrebel) | +| 73 | [textures_draw_tiled](textures/textures_draw_tiled.c) | textures_draw_tiled | ⭐️⭐️⭐️☆ | 3.0 | 4.2 | [Vlad Adrian](https://github.com/demizdor) | +| 74 | [textures_polygon](textures/textures_polygon.c) | textures_polygon | ⭐️☆☆☆ | 3.7 | 3.7 | [Chris Camacho](https://github.com/chriscamacho) | +| 75 | [textures_fog_of_war](textures/textures_fog_of_war.c) | textures_fog_of_war | ⭐️⭐️⭐️☆ | 4.2 | 4.2 | [Ray](https://github.com/raysan5) | +| 76 | [textures_gif_player](textures/textures_gif_player.c) | textures_gif_player | ⭐️⭐️⭐️☆ | 4.2 | 4.2 | [Ray](https://github.com/raysan5) | +| 77 | [textures_image_kernel](textures/textures_image_kernel.c) | textures_image_kernel | ⭐️⭐️⭐️⭐️ | 1.3 | 1.3 | [Karim Salem](https://github.com/kimo-s) | +| 78 | [textures_image_channel](textures/textures_image_channel.c) | textures_image_channel | ⭐️⭐️☆☆ | 5.1-dev | 5.1-dev | [Bruno Cabral](https://github.com/brccabral) | +| 79 | [textures_image_rotate](textures/textures_image_rotate.c) | textures_image_rotate | ⭐️⭐️☆☆ | 1.0 | 1.0 | [Ray](https://github.com/raysan5) | +| 80 | [textures_textured_curve](textures/textures_textured_curve.c) | textures_textured_curve | ⭐️⭐️⭐️☆ | 4.5 | 4.5 | [Jeffery Myers](https://github.com/JeffM2501) | ### category: text @@ -125,18 +125,18 @@ Examples using raylib text functionality, including sprite fonts loading/generat | ## | example | image | difficulty
level | version
created | last version
updated | original
developer | |----|----------|--------|:-------------------:|:------------------:|:-----------------------:|:----------------------| -| 80 | [text_raylib_fonts](text/text_raylib_fonts.c) | text_raylib_fonts | ⭐️☆☆☆ | 1.7 | 3.7 | [Ray](https://github.com/raysan5) | -| 81 | [text_font_spritefont](text/text_font_spritefont.c) | text_font_spritefont | ⭐️☆☆☆ | 1.0 | 1.0 | [Ray](https://github.com/raysan5) | -| 82 | [text_font_filters](text/text_font_filters.c) | text_font_filters | ⭐️⭐️☆☆ | 1.3 | 4.2 | [Ray](https://github.com/raysan5) | -| 83 | [text_font_loading](text/text_font_loading.c) | text_font_loading | ⭐️☆☆☆ | 1.4 | 3.0 | [Ray](https://github.com/raysan5) | -| 84 | [text_font_sdf](text/text_font_sdf.c) | text_font_sdf | ⭐️⭐️⭐️☆ | 1.3 | 4.0 | [Ray](https://github.com/raysan5) | -| 85 | [text_format_text](text/text_format_text.c) | text_format_text | ⭐️☆☆☆ | 1.1 | 3.0 | [Ray](https://github.com/raysan5) | -| 86 | [text_input_box](text/text_input_box.c) | text_input_box | ⭐️⭐️☆☆ | 1.7 | 3.5 | [Ray](https://github.com/raysan5) | -| 87 | [text_writing_anim](text/text_writing_anim.c) | text_writing_anim | ⭐️⭐️☆☆ | 1.4 | 1.4 | [Ray](https://github.com/raysan5) | -| 88 | [text_rectangle_bounds](text/text_rectangle_bounds.c) | text_rectangle_bounds | ⭐️⭐️⭐️⭐️ | 2.5 | 4.0 | [Vlad Adrian](https://github.com/demizdor) | -| 89 | [text_unicode](text/text_unicode.c) | text_unicode | ⭐️⭐️⭐️⭐️ | 2.5 | 4.0 | [Vlad Adrian](https://github.com/demizdor) | -| 90 | [text_draw_3d](text/text_draw_3d.c) | text_draw_3d | ⭐️⭐️⭐️⭐️ | 3.5 | 4.0 | [Vlad Adrian](https://github.com/demizdor) | -| 91 | [text_codepoints_loading](text/text_codepoints_loading.c) | text_codepoints_loading | ⭐️⭐️⭐️☆ | 4.2 | 4.2 | [Ray](https://github.com/raysan5) | +| 81 | [text_raylib_fonts](text/text_raylib_fonts.c) | text_raylib_fonts | ⭐️☆☆☆ | 1.7 | 3.7 | [Ray](https://github.com/raysan5) | +| 82 | [text_font_spritefont](text/text_font_spritefont.c) | text_font_spritefont | ⭐️☆☆☆ | 1.0 | 1.0 | [Ray](https://github.com/raysan5) | +| 83 | [text_font_filters](text/text_font_filters.c) | text_font_filters | ⭐️⭐️☆☆ | 1.3 | 4.2 | [Ray](https://github.com/raysan5) | +| 84 | [text_font_loading](text/text_font_loading.c) | text_font_loading | ⭐️☆☆☆ | 1.4 | 3.0 | [Ray](https://github.com/raysan5) | +| 85 | [text_font_sdf](text/text_font_sdf.c) | text_font_sdf | ⭐️⭐️⭐️☆ | 1.3 | 4.0 | [Ray](https://github.com/raysan5) | +| 86 | [text_format_text](text/text_format_text.c) | text_format_text | ⭐️☆☆☆ | 1.1 | 3.0 | [Ray](https://github.com/raysan5) | +| 87 | [text_input_box](text/text_input_box.c) | text_input_box | ⭐️⭐️☆☆ | 1.7 | 3.5 | [Ray](https://github.com/raysan5) | +| 88 | [text_writing_anim](text/text_writing_anim.c) | text_writing_anim | ⭐️⭐️☆☆ | 1.4 | 1.4 | [Ray](https://github.com/raysan5) | +| 89 | [text_rectangle_bounds](text/text_rectangle_bounds.c) | text_rectangle_bounds | ⭐️⭐️⭐️⭐️ | 2.5 | 4.0 | [Vlad Adrian](https://github.com/demizdor) | +| 90 | [text_unicode](text/text_unicode.c) | text_unicode | ⭐️⭐️⭐️⭐️ | 2.5 | 4.0 | [Vlad Adrian](https://github.com/demizdor) | +| 91 | [text_draw_3d](text/text_draw_3d.c) | text_draw_3d | ⭐️⭐️⭐️⭐️ | 3.5 | 4.0 | [Vlad Adrian](https://github.com/demizdor) | +| 92 | [text_codepoints_loading](text/text_codepoints_loading.c) | text_codepoints_loading | ⭐️⭐️⭐️☆ | 4.2 | 4.2 | [Ray](https://github.com/raysan5) | ### category: models @@ -144,29 +144,29 @@ Examples using raylib models functionality, including models loading/generation | ## | example | image | difficulty
level | version
created | last version
updated | original
developer | |----|----------|--------|:-------------------:|:------------------:|:-----------------------:|:----------------------| -| 92 | [models_animation](models/models_animation.c) | models_animation | ⭐️⭐️☆☆ | 2.5 | 3.5 | [Culacant](https://github.com/culacant) | -| 93 | [models_billboard](models/models_billboard.c) | models_billboard | ⭐️⭐️⭐️☆ | 1.3 | 3.5 | [Ray](https://github.com/raysan5) | -| 94 | [models_box_collisions](models/models_box_collisions.c) | models_box_collisions | ⭐️☆☆☆ | 1.3 | 3.5 | [Ray](https://github.com/raysan5) | -| 95 | [models_cubicmap](models/models_cubicmap.c) | models_cubicmap | ⭐️⭐️☆☆ | 1.8 | 3.5 | [Ray](https://github.com/raysan5) | -| 96 | [models_first_person_maze](models/models_first_person_maze.c) | models_first_person_maze | ⭐️⭐️☆☆ | 2.5 | 3.5 | [Ray](https://github.com/raysan5) | -| 97 | [models_geometric_shapes](models/models_geometric_shapes.c) | models_geometric_shapes | ⭐️☆☆☆ | 1.0 | 3.5 | [Ray](https://github.com/raysan5) | -| 98 | [models_mesh_generation](models/models_mesh_generation.c) | models_mesh_generation | ⭐️⭐️☆☆ | 1.8 | 4.0 | [Ray](https://github.com/raysan5) | -| 99 | [models_mesh_picking](models/models_mesh_picking.c) | models_mesh_picking | ⭐️⭐️⭐️☆ | 1.7 | 4.0 | [Joel Davis](https://github.com/joeld42) | -| 100 | [models_loading](models/models_loading.c) | models_loading | ⭐️☆☆☆ | 2.0 | 4.2 | [Ray](https://github.com/raysan5) | -| 101 | [models_loading_gltf](models/models_loading_gltf.c) | models_loading_gltf | ⭐️☆☆☆ | 3.7 | 4.2 | [Ray](https://github.com/raysan5) | -| 102 | [models_loading_vox](models/models_loading_vox.c) | models_loading_vox | ⭐️☆☆☆ | 4.0 | 4.0 | [Johann Nadalutti](https://github.com/procfxgen) | -| 103 | [models_loading_m3d](models/models_loading_m3d.c) | models_loading_m3d | ⭐️⭐️☆☆ | 4.5 | 4.5 | [bzt](https://bztsrc.gitlab.io/model3d) | -| 104 | [models_orthographic_projection](models/models_orthographic_projection.c) | models_orthographic_projection | ⭐️☆☆☆ | 2.0 | 3.7 | [Max Danielsson](https://github.com/autious) | -| 105 | [models_point_rendering](models/models_point_rendering.c) | models_point_rendering | ⭐️⭐️⭐️☆ | 5.0 | 5.0 | [Reese Gallagher](https://github.com/satchelfrost) | -| 106 | [models_rlgl_solar_system](models/models_rlgl_solar_system.c) | models_rlgl_solar_system | ⭐️⭐️⭐️⭐️ | 2.5 | 4.0 | [Ray](https://github.com/raysan5) | -| 107 | [models_yaw_pitch_roll](models/models_yaw_pitch_roll.c) | models_yaw_pitch_roll | ⭐️⭐️☆☆ | 1.8 | 4.0 | [Berni](https://github.com/Berni8k) | -| 108 | [models_waving_cubes](models/models_waving_cubes.c) | models_waving_cubes | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [Codecat](https://github.com/codecat) | -| 109 | [models_heightmap](models/models_heightmap.c) | models_heightmap | ⭐️☆☆☆ | 1.8 | 3.5 | [Ray](https://github.com/raysan5) | -| 110 | [models_skybox](models/models_skybox.c) | models_skybox | ⭐️⭐️☆☆ | 1.8 | 4.0 | [Ray](https://github.com/raysan5) | -| 111 | [models_draw_cube_texture](models/models_draw_cube_texture.c) | models_draw_cube_texture | ⭐️⭐️☆☆ | 4.5 | 4.5 | [Ray](https://github.com/raysan5) | -| 112 | [models_gpu_skinning](models/models_gpu_skinning.c) | models_gpu_skinning | ⭐️⭐️⭐️☆ | 4.5 | 4.5 | [Daniel Holden](https://github.com/orangeduck) | -| 113 | [models_bone_socket](models/models_bone_socket.c) | models_bone_socket | ⭐️⭐️⭐️⭐️ | 4.5 | 4.5 | [iP](https://github.com/ipzaur) | -| 114 | [models_tesseract_view](models/models_tesseract_view.c) | models_tesseract_view | ⭐️⭐️☆☆ | 5.6-dev | 5.6-dev | [Timothy van der Valk](https://github.com/arceryz) | +| 93 | [models_animation](models/models_animation.c) | models_animation | ⭐️⭐️☆☆ | 2.5 | 3.5 | [Culacant](https://github.com/culacant) | +| 94 | [models_billboard](models/models_billboard.c) | models_billboard | ⭐️⭐️⭐️☆ | 1.3 | 3.5 | [Ray](https://github.com/raysan5) | +| 95 | [models_box_collisions](models/models_box_collisions.c) | models_box_collisions | ⭐️☆☆☆ | 1.3 | 3.5 | [Ray](https://github.com/raysan5) | +| 96 | [models_cubicmap](models/models_cubicmap.c) | models_cubicmap | ⭐️⭐️☆☆ | 1.8 | 3.5 | [Ray](https://github.com/raysan5) | +| 97 | [models_first_person_maze](models/models_first_person_maze.c) | models_first_person_maze | ⭐️⭐️☆☆ | 2.5 | 3.5 | [Ray](https://github.com/raysan5) | +| 98 | [models_geometric_shapes](models/models_geometric_shapes.c) | models_geometric_shapes | ⭐️☆☆☆ | 1.0 | 3.5 | [Ray](https://github.com/raysan5) | +| 99 | [models_mesh_generation](models/models_mesh_generation.c) | models_mesh_generation | ⭐️⭐️☆☆ | 1.8 | 4.0 | [Ray](https://github.com/raysan5) | +| 100 | [models_mesh_picking](models/models_mesh_picking.c) | models_mesh_picking | ⭐️⭐️⭐️☆ | 1.7 | 4.0 | [Joel Davis](https://github.com/joeld42) | +| 101 | [models_loading](models/models_loading.c) | models_loading | ⭐️☆☆☆ | 2.0 | 4.2 | [Ray](https://github.com/raysan5) | +| 102 | [models_loading_gltf](models/models_loading_gltf.c) | models_loading_gltf | ⭐️☆☆☆ | 3.7 | 4.2 | [Ray](https://github.com/raysan5) | +| 103 | [models_loading_vox](models/models_loading_vox.c) | models_loading_vox | ⭐️☆☆☆ | 4.0 | 4.0 | [Johann Nadalutti](https://github.com/procfxgen) | +| 104 | [models_loading_m3d](models/models_loading_m3d.c) | models_loading_m3d | ⭐️⭐️☆☆ | 4.5 | 4.5 | [bzt](https://bztsrc.gitlab.io/model3d) | +| 105 | [models_orthographic_projection](models/models_orthographic_projection.c) | models_orthographic_projection | ⭐️☆☆☆ | 2.0 | 3.7 | [Max Danielsson](https://github.com/autious) | +| 106 | [models_point_rendering](models/models_point_rendering.c) | models_point_rendering | ⭐️⭐️⭐️☆ | 5.0 | 5.0 | [Reese Gallagher](https://github.com/satchelfrost) | +| 107 | [models_rlgl_solar_system](models/models_rlgl_solar_system.c) | models_rlgl_solar_system | ⭐️⭐️⭐️⭐️ | 2.5 | 4.0 | [Ray](https://github.com/raysan5) | +| 108 | [models_yaw_pitch_roll](models/models_yaw_pitch_roll.c) | models_yaw_pitch_roll | ⭐️⭐️☆☆ | 1.8 | 4.0 | [Berni](https://github.com/Berni8k) | +| 109 | [models_waving_cubes](models/models_waving_cubes.c) | models_waving_cubes | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [Codecat](https://github.com/codecat) | +| 110 | [models_heightmap](models/models_heightmap.c) | models_heightmap | ⭐️☆☆☆ | 1.8 | 3.5 | [Ray](https://github.com/raysan5) | +| 111 | [models_skybox](models/models_skybox.c) | models_skybox | ⭐️⭐️☆☆ | 1.8 | 4.0 | [Ray](https://github.com/raysan5) | +| 112 | [models_draw_cube_texture](models/models_draw_cube_texture.c) | models_draw_cube_texture | ⭐️⭐️☆☆ | 4.5 | 4.5 | [Ray](https://github.com/raysan5) | +| 113 | [models_gpu_skinning](models/models_gpu_skinning.c) | models_gpu_skinning | ⭐️⭐️⭐️☆ | 4.5 | 4.5 | [Daniel Holden](https://github.com/orangeduck) | +| 114 | [models_bone_socket](models/models_bone_socket.c) | models_bone_socket | ⭐️⭐️⭐️⭐️ | 4.5 | 4.5 | [iP](https://github.com/ipzaur) | +| 115 | [models_tesseract_view](models/models_tesseract_view.c) | models_tesseract_view | ⭐️⭐️☆☆ | 5.6-dev | 5.6-dev | [Timothy van der Valk](https://github.com/arceryz) | ### category: shaders @@ -174,34 +174,34 @@ Examples using raylib shaders functionality, including shaders loading, paramete | ## | example | image | difficulty
level | version
created | last version
updated | original
developer | |----|----------|--------|:-------------------:|:------------------:|:-----------------------:|:----------------------| -| 115 | [shaders_basic_lighting](shaders/shaders_basic_lighting.c) | shaders_basic_lighting | ⭐️⭐️⭐️⭐️ | 3.0 | 4.2 | [Chris Camacho](https://github.com/chriscamacho) | -| 116 | [shaders_model_shader](shaders/shaders_model_shader.c) | shaders_model_shader | ⭐️⭐️☆☆ | 1.3 | 3.7 | [Ray](https://github.com/raysan5) | -| 117 | [shaders_shapes_textures](shaders/shaders_shapes_textures.c) | shaders_shapes_textures | ⭐️⭐️☆☆ | 1.7 | 3.7 | [Ray](https://github.com/raysan5) | -| 118 | [shaders_custom_uniform](shaders/shaders_custom_uniform.c) | shaders_custom_uniform | ⭐️⭐️☆☆ | 1.3 | 4.0 | [Ray](https://github.com/raysan5) | -| 119 | [shaders_postprocessing](shaders/shaders_postprocessing.c) | shaders_postprocessing | ⭐️⭐️⭐️☆ | 1.3 | 4.0 | [Ray](https://github.com/raysan5) | -| 120 | [shaders_palette_switch](shaders/shaders_palette_switch.c) | shaders_palette_switch | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [Marco Lizza](https://github.com/MarcoLizza) | -| 121 | [shaders_raymarching](shaders/shaders_raymarching.c) | shaders_raymarching | ⭐️⭐️⭐️⭐️ | 2.0 | 4.2 | [Ray](https://github.com/raysan5) | -| 122 | [shaders_texture_drawing](shaders/shaders_texture_drawing.c) | shaders_texture_drawing | ⭐️⭐️☆☆ | 2.0 | 3.7 | [Michał Ciesielski](https://github.com/ciessielski) | -| 123 | [shaders_texture_outline](shaders/shaders_texture_outline.c) | shaders_texture_outline | ⭐️⭐️⭐️☆ | 4.0 | 4.0 | [Samuel Skiff](https://github.com/GoldenThumbs) | -| 124 | [shaders_texture_waves](shaders/shaders_texture_waves.c) | shaders_texture_waves | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Anata](https://github.com/anatagawa) | -| 125 | [shaders_julia_set](shaders/shaders_julia_set.c) | shaders_julia_set | ⭐️⭐️⭐️☆ | 2.5 | 4.0 | [Josh Colclough](https://github.com/joshcol9232) | -| 126 | [shaders_eratosthenes](shaders/shaders_eratosthenes.c) | shaders_eratosthenes | ⭐️⭐️⭐️☆ | 2.5 | 4.0 | [ProfJski](https://github.com/ProfJski) | -| 127 | [shaders_fog](shaders/shaders_fog.c) | shaders_fog | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/chriscamacho) | -| 128 | [shaders_simple_mask](shaders/shaders_simple_mask.c) | shaders_simple_mask | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/chriscamacho) | -| 129 | [shaders_hot_reloading](shaders/shaders_hot_reloading.c) | shaders_hot_reloading | ⭐️⭐️⭐️☆ | 3.0 | 3.5 | [Ray](https://github.com/raysan5) | -| 130 | [shaders_mesh_instancing](shaders/shaders_mesh_instancing.c) | shaders_mesh_instancing | ⭐️⭐️⭐️⭐️ | 3.7 | 4.2 | [seanpringle](https://github.com/seanpringle) | -| 131 | [shaders_multi_sample2d](shaders/shaders_multi_sample2d.c) | shaders_multi_sample2d | ⭐️⭐️☆☆ | 3.5 | 3.5 | [Ray](https://github.com/raysan5) | -| 132 | [shaders_spotlight](shaders/shaders_spotlight.c) | shaders_spotlight | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/chriscamacho) | -| 133 | [shaders_deferred_render](shaders/shaders_deferred_render.c) | shaders_deferred_render | ⭐️⭐️⭐️⭐️ | 4.5 | 4.5 | [Justin Andreas Lacoste](https://github.com/27justin) | -| 134 | [shaders_hybrid_render](shaders/shaders_hybrid_render.c) | shaders_hybrid_render | ⭐️⭐️⭐️⭐️ | 4.2 | 4.2 | [Buğra Alptekin Sarı](https://github.com/BugraAlptekinSari) | -| 135 | [shaders_texture_tiling](shaders/shaders_texture_tiling.c) | shaders_texture_tiling | ⭐️⭐️☆☆ | 4.5 | 4.5 | [Luis Almeida](https://github.com/luis605) | -| 136 | [shaders_shadowmap](shaders/shaders_shadowmap.c) | shaders_shadowmap | ⭐️⭐️⭐️⭐️ | 5.0 | 5.0 | [TheManTheMythTheGameDev](https://github.com/TheManTheMythTheGameDev) | -| 137 | [shaders_vertex_displacement](shaders/shaders_vertex_displacement.c) | shaders_vertex_displacement | ⭐️⭐️⭐️☆ | 5.0 | 4.5 | [Alex ZH](https://github.com/ZzzhHe) | -| 138 | [shaders_write_depth](shaders/shaders_write_depth.c) | shaders_write_depth | ⭐️⭐️☆☆ | 4.2 | 4.2 | [Buğra Alptekin Sarı](https://github.com/BugraAlptekinSari) | -| 139 | [shaders_basic_pbr](shaders/shaders_basic_pbr.c) | shaders_basic_pbr | ⭐️⭐️⭐️⭐️ | 5.0 | 5.1-dev | [Afan OLOVCIC](https://github.com/_DevDad) | -| 140 | [shaders_lightmap](shaders/shaders_lightmap.c) | shaders_lightmap | ⭐️⭐️⭐️☆ | 4.5 | 4.5 | [Jussi Viitala](https://github.com/nullstare) | -| 141 | [shaders_rounded_rectangle](shaders/shaders_rounded_rectangle.c) | shaders_rounded_rectangle | ⭐️⭐️⭐️☆ | 5.5 | 5.5 | [Anstro Pleuton](https://github.com/anstropleuton) | -| 142 | [shaders_view_depth](shaders/shaders_view_depth.c) | shaders_view_depth | ⭐️⭐️⭐️☆ | 5.6-dev | 5.6-dev | [Luís Almeida](https://github.com/luis605) | +| 116 | [shaders_basic_lighting](shaders/shaders_basic_lighting.c) | shaders_basic_lighting | ⭐️⭐️⭐️⭐️ | 3.0 | 4.2 | [Chris Camacho](https://github.com/chriscamacho) | +| 117 | [shaders_model_shader](shaders/shaders_model_shader.c) | shaders_model_shader | ⭐️⭐️☆☆ | 1.3 | 3.7 | [Ray](https://github.com/raysan5) | +| 118 | [shaders_shapes_textures](shaders/shaders_shapes_textures.c) | shaders_shapes_textures | ⭐️⭐️☆☆ | 1.7 | 3.7 | [Ray](https://github.com/raysan5) | +| 119 | [shaders_custom_uniform](shaders/shaders_custom_uniform.c) | shaders_custom_uniform | ⭐️⭐️☆☆ | 1.3 | 4.0 | [Ray](https://github.com/raysan5) | +| 120 | [shaders_postprocessing](shaders/shaders_postprocessing.c) | shaders_postprocessing | ⭐️⭐️⭐️☆ | 1.3 | 4.0 | [Ray](https://github.com/raysan5) | +| 121 | [shaders_palette_switch](shaders/shaders_palette_switch.c) | shaders_palette_switch | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [Marco Lizza](https://github.com/MarcoLizza) | +| 122 | [shaders_raymarching](shaders/shaders_raymarching.c) | shaders_raymarching | ⭐️⭐️⭐️⭐️ | 2.0 | 4.2 | [Ray](https://github.com/raysan5) | +| 123 | [shaders_texture_drawing](shaders/shaders_texture_drawing.c) | shaders_texture_drawing | ⭐️⭐️☆☆ | 2.0 | 3.7 | [Michał Ciesielski](https://github.com/ciessielski) | +| 124 | [shaders_texture_outline](shaders/shaders_texture_outline.c) | shaders_texture_outline | ⭐️⭐️⭐️☆ | 4.0 | 4.0 | [Samuel Skiff](https://github.com/GoldenThumbs) | +| 125 | [shaders_texture_waves](shaders/shaders_texture_waves.c) | shaders_texture_waves | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Anata](https://github.com/anatagawa) | +| 126 | [shaders_julia_set](shaders/shaders_julia_set.c) | shaders_julia_set | ⭐️⭐️⭐️☆ | 2.5 | 4.0 | [Josh Colclough](https://github.com/joshcol9232) | +| 127 | [shaders_eratosthenes](shaders/shaders_eratosthenes.c) | shaders_eratosthenes | ⭐️⭐️⭐️☆ | 2.5 | 4.0 | [ProfJski](https://github.com/ProfJski) | +| 128 | [shaders_fog](shaders/shaders_fog.c) | shaders_fog | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/chriscamacho) | +| 129 | [shaders_simple_mask](shaders/shaders_simple_mask.c) | shaders_simple_mask | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/chriscamacho) | +| 130 | [shaders_hot_reloading](shaders/shaders_hot_reloading.c) | shaders_hot_reloading | ⭐️⭐️⭐️☆ | 3.0 | 3.5 | [Ray](https://github.com/raysan5) | +| 131 | [shaders_mesh_instancing](shaders/shaders_mesh_instancing.c) | shaders_mesh_instancing | ⭐️⭐️⭐️⭐️ | 3.7 | 4.2 | [seanpringle](https://github.com/seanpringle) | +| 132 | [shaders_multi_sample2d](shaders/shaders_multi_sample2d.c) | shaders_multi_sample2d | ⭐️⭐️☆☆ | 3.5 | 3.5 | [Ray](https://github.com/raysan5) | +| 133 | [shaders_spotlight](shaders/shaders_spotlight.c) | shaders_spotlight | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/chriscamacho) | +| 134 | [shaders_deferred_render](shaders/shaders_deferred_render.c) | shaders_deferred_render | ⭐️⭐️⭐️⭐️ | 4.5 | 4.5 | [Justin Andreas Lacoste](https://github.com/27justin) | +| 135 | [shaders_hybrid_render](shaders/shaders_hybrid_render.c) | shaders_hybrid_render | ⭐️⭐️⭐️⭐️ | 4.2 | 4.2 | [Buğra Alptekin Sarı](https://github.com/BugraAlptekinSari) | +| 136 | [shaders_texture_tiling](shaders/shaders_texture_tiling.c) | shaders_texture_tiling | ⭐️⭐️☆☆ | 4.5 | 4.5 | [Luis Almeida](https://github.com/luis605) | +| 137 | [shaders_shadowmap](shaders/shaders_shadowmap.c) | shaders_shadowmap | ⭐️⭐️⭐️⭐️ | 5.0 | 5.0 | [TheManTheMythTheGameDev](https://github.com/TheManTheMythTheGameDev) | +| 138 | [shaders_vertex_displacement](shaders/shaders_vertex_displacement.c) | shaders_vertex_displacement | ⭐️⭐️⭐️☆ | 5.0 | 4.5 | [Alex ZH](https://github.com/ZzzhHe) | +| 139 | [shaders_write_depth](shaders/shaders_write_depth.c) | shaders_write_depth | ⭐️⭐️☆☆ | 4.2 | 4.2 | [Buğra Alptekin Sarı](https://github.com/BugraAlptekinSari) | +| 140 | [shaders_basic_pbr](shaders/shaders_basic_pbr.c) | shaders_basic_pbr | ⭐️⭐️⭐️⭐️ | 5.0 | 5.1-dev | [Afan OLOVCIC](https://github.com/_DevDad) | +| 141 | [shaders_lightmap](shaders/shaders_lightmap.c) | shaders_lightmap | ⭐️⭐️⭐️☆ | 4.5 | 4.5 | [Jussi Viitala](https://github.com/nullstare) | +| 142 | [shaders_rounded_rectangle](shaders/shaders_rounded_rectangle.c) | shaders_rounded_rectangle | ⭐️⭐️⭐️☆ | 5.5 | 5.5 | [Anstro Pleuton](https://github.com/anstropleuton) | +| 143 | [shaders_view_depth](shaders/shaders_view_depth.c) | shaders_view_depth | ⭐️⭐️⭐️☆ | 5.6-dev | 5.6-dev | [Luís Almeida](https://github.com/luis605) | ### category: audio @@ -209,26 +209,26 @@ Examples using raylib audio functionality, including sound/music loading and pla | ## | example | image | difficulty
level | version
created | last version
updated | original
developer | |----|----------|--------|:-------------------:|:------------------:|:-----------------------:|:----------------------| -| 142 | [audio_module_playing](audio/audio_module_playing.c) | audio_module_playing | ⭐️☆☆☆ | 1.5 | 3.5 | [Ray](https://github.com/raysan5) | -| 143 | [audio_music_stream](audio/audio_music_stream.c) | audio_music_stream | ⭐️☆☆☆ | 1.3 | 4.2 | [Ray](https://github.com/raysan5) | -| 144 | [audio_raw_stream](audio/audio_raw_stream.c) | audio_raw_stream | ⭐️⭐️⭐️☆ | 1.6 | 4.2 | [Ray](https://github.com/raysan5) | -| 145 | [audio_sound_loading](audio/audio_sound_loading.c) | audio_sound_loading | ⭐️☆☆☆ | 1.1 | 3.5 | [Ray](https://github.com/raysan5) | -| 146 | [audio_mixed_processor](audio/audio_mixed_processor.c) | audio_mixed_processor | ⭐️⭐️⭐️⭐️ | 4.2 | 4.2 | [hkc](https://github.com/hatkidchan) | -| 147 | [audio_stream_effects](audio/audio_stream_effects.c) | audio_stream_effects | ⭐️⭐️⭐️⭐️ | 4.2 | 5.0 | [Ray](https://github.com/raysan5) | -| 148 | [audio_sound_multi](audio/audio_sound_multi.c) | audio_sound_multi | ⭐️⭐️☆☆ | 4.6 | 4.6 | [Jeffery Myers](https://github.com/JeffM2501) | -| 149 | [audio_sound_positioning](audio/audio_sound_positioning.c) | audio_sound_positioning | ⭐️⭐️☆☆ | 5.5 | 5.5 | [Le Juez Victor](https://github.com/Bigfoot71) | +| 144 | [audio_module_playing](audio/audio_module_playing.c) | audio_module_playing | ⭐️☆☆☆ | 1.5 | 3.5 | [Ray](https://github.com/raysan5) | +| 145 | [audio_music_stream](audio/audio_music_stream.c) | audio_music_stream | ⭐️☆☆☆ | 1.3 | 4.2 | [Ray](https://github.com/raysan5) | +| 146 | [audio_raw_stream](audio/audio_raw_stream.c) | audio_raw_stream | ⭐️⭐️⭐️☆ | 1.6 | 4.2 | [Ray](https://github.com/raysan5) | +| 147 | [audio_sound_loading](audio/audio_sound_loading.c) | audio_sound_loading | ⭐️☆☆☆ | 1.1 | 3.5 | [Ray](https://github.com/raysan5) | +| 148 | [audio_mixed_processor](audio/audio_mixed_processor.c) | audio_mixed_processor | ⭐️⭐️⭐️⭐️ | 4.2 | 4.2 | [hkc](https://github.com/hatkidchan) | +| 149 | [audio_stream_effects](audio/audio_stream_effects.c) | audio_stream_effects | ⭐️⭐️⭐️⭐️ | 4.2 | 5.0 | [Ray](https://github.com/raysan5) | +| 150 [audio_sound_multi](audio/audio_sound_multi.c) | audio_sound_multi | ⭐️⭐️☆☆ | 4.6 | 4.6 | [Jeffery Myers](https://github.com/JeffM2501) | +| 151 | [audio_sound_positioning](audio/audio_sound_positioning.c) | audio_sound_positioning | ⭐️⭐️☆☆ | 5.5 | 5.5 | [Le Juez Victor](https://github.com/Bigfoot71) | ### category: others -Examples showing raylib misc functionality that does not fit in other categories, like standalone modules usage or examples integrating external libraries. +Ex150amples showing raylib misc functionality that does not fit in other categories, like standalone modules usage or examples integrating external libraries. | ## | example | image | difficulty
level | version
created | last version
updated | original
developer | |----|----------|--------|:-------------------:|:------------------:|:-----------------------:|:----------------------| -| 150 | [rlgl_standalone](others/rlgl_standalone.c) | rlgl_standalone | ⭐️⭐️⭐️⭐️ | 1.6 | 4.0 | [Ray](https://github.com/raysan5) | -| 151 | [rlgl_compute_shader](others/rlgl_compute_shader.c) | rlgl_compute_shader | ⭐️⭐️⭐️⭐️ | 4.0 | 4.0 | [Teddy Astie](https://github.com/tsnake41) | -| 152 | [easings_testbed](others/easings_testbed.c) | easings_testbed | ⭐️⭐️⭐️☆ | 2.5 | 3.0 | [Juan Miguel López](https://github.com/flashback-fx) | -| 153 | [raylib_opengl_interop](others/raylib_opengl_interop.c) | raylib_opengl_interop | ⭐️⭐️⭐️⭐️ | 3.8 | 4.0 | [Stephan Soller](https://github.com/arkanis) | -| 154 | [embedded_files_loading](others/embedded_files_loading.c) | embedded_files_loading | ⭐️⭐️☆☆ | 3.0 | 3.5 | [Kristian Holmgren](https://github.com/defutura) | -| 155 | [raymath_vector_angle](others/raymath_vector_angle.c) | raymath_vector_angle | ⭐️⭐️☆☆ | 1.0 | 4.6 | [Ray](https://github.com/raysan5) | +| 152 | [rlgl_standalone](others/rlgl_standalone.c) | rlgl_standalone | ⭐️⭐️⭐️⭐️ | 1.6 | 4.0 | [Ray](https://github.com/raysan5) | +| 153 | [rlgl_compute_shader](others/rlgl_compute_shader.c) | rlgl_compute_shader | ⭐️⭐️⭐️⭐️ | 4.0 | 4.0 | [Teddy Astie](https://github.com/tsnake41) | +| 154 | [easings_testbed](others/easings_testbed.c) | easings_testbed | ⭐️⭐️⭐️☆ | 2.5 | 3.0 | [Juan Miguel López](https://github.com/flashback-fx) | +| 155 | [raylib_opengl_interop](others/raylib_opengl_interop.c) | raylib_opengl_interop | ⭐️⭐️⭐️⭐️ | 3.8 | 4.0 | [Stephan Soller](https://github.com/arkanis) | +| 156 | [embedded_files_loading](others/embedded_files_loading.c) | embedded_files_loading | ⭐️⭐️☆☆ | 3.0 | 3.5 | [Kristian Holmgren](https://github.com/defutura) | +| 157 | [raymath_vector_angle](others/raymath_vector_angle.c) | raymath_vector_angle | ⭐️⭐️☆☆ | 1.0 | 4.6 | [Ray](https://github.com/raysan5) | As always contributions are welcome, feel free to send new examples! Here is an [examples template](examples_template.c) to start with! diff --git a/examples/shapes/shapes_digital_clock.c b/examples/shapes/shapes_digital_clock.c new file mode 100644 index 000000000..12a8c8dfe --- /dev/null +++ b/examples/shapes/shapes_digital_clock.c @@ -0,0 +1,191 @@ +/******************************************************************************************* +* +* raylib [shapes] example - fancy clock using basic shapes +* +* Example complexity rating: [★★☆☆] 2/4 +* +* Example originally created with raylib 5.5, last time updated with raylib 5.5 +* +* Example contributed by Hamza RAHAL (@hmz-rhl) and reviewed by Ramon Santamaria (@raysan5) +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2025-2025 Hamza RAHAL (@hmz-rhl) +* +********************************************************************************************/ + +#include "raylib.h" +#include // needed for cos & sin functions +#include // needed to get machine time + +#define DIGIT_SIZE 30 + +typedef enum +{ + NORMAL_MODE = 0, + HANDS_FREE_MODE, +} ClockMode; + +typedef struct +{ + int value; + Vector2 origin; + float angle; + int length; + int thickness; + Color colour; +} Hand; + +typedef struct +{ + Hand second; + Hand minute; + Hand hour; + ClockMode cm; +} Clock; + + +void UpdateClock(Clock *clock) +{ + time_t rawtime; + struct tm * timeinfo; + + time(&rawtime); + timeinfo = localtime(&rawtime); + + // updating datas + clock->second.value = timeinfo->tm_sec; + clock->minute.value = timeinfo->tm_min; + clock->hour.value = timeinfo->tm_hour; + + clock->hour.angle = (timeinfo->tm_hour % 12)*180.0/6.0f; + clock->hour.angle += (timeinfo->tm_min % 60)*30/60.0f; + clock->hour.angle -= 90 ; + + clock->minute.angle = (timeinfo->tm_min % 60)*6.0f; + clock->minute.angle += (timeinfo->tm_sec % 60)*6/60.0f; + clock->minute.angle -= 90 ; + + clock->second.angle = (timeinfo->tm_sec % 60)*6.0f; + clock->second.angle -= 90 ; + +} + +void drawClock(Clock clock) +{ + if (clock.cm == HANDS_FREE_MODE) + { + DrawText(TextFormat("%i", clock.second.value), clock.second.origin.x + (clock.second.length - 10)*cos(clock.second.angle*(float)(M_PI/180)) - DIGIT_SIZE/2, clock.second.origin.y + clock.second.length*sin(clock.second.angle*(float)(M_PI/180)) - DIGIT_SIZE/2, DIGIT_SIZE, GRAY); + + DrawText(TextFormat("%i", clock.minute.value), clock.minute.origin.x + clock.minute.length*cos(clock.minute.angle*(float)(M_PI/180)) - DIGIT_SIZE/2, clock.minute.origin.y + clock.minute.length*sin(clock.minute.angle*(float)(M_PI/180)) - DIGIT_SIZE/2, DIGIT_SIZE, RED); + + DrawText(TextFormat("%i", clock.hour.value), clock.hour.origin.x + clock.hour.length*cos(clock.hour.angle*(float)(M_PI/180)) - DIGIT_SIZE/2, clock.hour.origin.y + clock.hour.length*sin(clock.hour.angle*(float)(M_PI/180)) - DIGIT_SIZE/2, DIGIT_SIZE, GOLD); + } + else + { + DrawRectanglePro( + (Rectangle){ clock.second.origin.x, + clock.second.origin.y, + clock.second.length, + clock.second.thickness, + } + , (Vector2){0, clock.second.thickness/2}, + clock.second.angle, + clock.second.colour + ); + + DrawRectanglePro( + (Rectangle){ clock.minute.origin.x, + clock.minute.origin.y, + clock.minute.length, + clock.minute.thickness, + } + , (Vector2){0, clock.minute.thickness/2}, + clock.minute.angle, + clock.minute.colour + ); + + DrawRectanglePro( + (Rectangle){ clock.hour.origin.x, + clock.hour.origin.y, + clock.hour.length, + clock.hour.thickness, + } + , (Vector2){0, clock.hour.thickness/2}, + clock.hour.angle, + clock.hour.colour + ); + } +} + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + Clock myClock = { + .cm = NORMAL_MODE, + + .second.origin = (Vector2){400, 225}, + .second.angle = 45, + .second.length = 140, + .second.thickness = 3, + .second.colour = BEIGE, + + .minute.origin = (Vector2){400, 225}, + .minute.angle = 10, + .minute.length = 130, + .minute.thickness = 7, + .minute.colour = DARKGRAY, + + .hour.origin = (Vector2){400, 225}, + .hour.angle = 0, + .hour.length = 100, + .hour.thickness = 7, + .hour.colour = BLACK, + }; + + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - digital clock"); + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + if (IsKeyPressed(KEY_SPACE)) + { + myClock.cm = (myClock.cm == HANDS_FREE_MODE) ? NORMAL_MODE : HANDS_FREE_MODE; + } + + UpdateClock(&myClock); + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + DrawCircle(400, 225, 5, BLACK); // center dot + drawClock(myClock); + + DrawText("press [SPACE] to switch clock mode", 350, 400, 10, GRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/shapes/shapes_digital_clock.png b/examples/shapes/shapes_digital_clock.png new file mode 100644 index 0000000000000000000000000000000000000000..22939253176be52b334d781d99a2e7c23a750ddd GIT binary patch literal 3651 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 A#sB~S literal 0 HcmV?d00001 From 5f497d068788722c6a4b02e6bcc6a35ea7d2e8a7 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 3 Jun 2025 20:42:27 +0200 Subject: [PATCH 033/242] REVIEWED: `shapes_digital_clock` example --- examples/shapes/shapes_digital_clock.c | 202 ++++++++++++------------- 1 file changed, 98 insertions(+), 104 deletions(-) diff --git a/examples/shapes/shapes_digital_clock.c b/examples/shapes/shapes_digital_clock.c index 12a8c8dfe..8aa887c3c 100644 --- a/examples/shapes/shapes_digital_clock.c +++ b/examples/shapes/shapes_digital_clock.c @@ -11,113 +11,46 @@ * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, * BSD-like license that allows static linking with closed source software * -* Copyright (c) 2025-2025 Hamza RAHAL (@hmz-rhl) +* Copyright (c) 2025 Hamza RAHAL (@hmz-rhl) * ********************************************************************************************/ #include "raylib.h" -#include // needed for cos & sin functions -#include // needed to get machine time + +#include // Required for: cosf(), sinf() +#include // Required for: time(), localtime() #define DIGIT_SIZE 30 -typedef enum -{ - NORMAL_MODE = 0, - HANDS_FREE_MODE, +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef enum { + MODE_NORMAL = 0, + MODE_HANDS_FREE, } ClockMode; -typedef struct -{ +typedef struct { int value; Vector2 origin; float angle; int length; int thickness; - Color colour; -} Hand; + Color color; +} ClockHand; -typedef struct -{ - Hand second; - Hand minute; - Hand hour; - ClockMode cm; +typedef struct { + ClockMode mode; + ClockHand second; + ClockHand minute; + ClockHand hour; } Clock; - -void UpdateClock(Clock *clock) -{ - time_t rawtime; - struct tm * timeinfo; - - time(&rawtime); - timeinfo = localtime(&rawtime); - - // updating datas - clock->second.value = timeinfo->tm_sec; - clock->minute.value = timeinfo->tm_min; - clock->hour.value = timeinfo->tm_hour; - - clock->hour.angle = (timeinfo->tm_hour % 12)*180.0/6.0f; - clock->hour.angle += (timeinfo->tm_min % 60)*30/60.0f; - clock->hour.angle -= 90 ; - - clock->minute.angle = (timeinfo->tm_min % 60)*6.0f; - clock->minute.angle += (timeinfo->tm_sec % 60)*6/60.0f; - clock->minute.angle -= 90 ; - - clock->second.angle = (timeinfo->tm_sec % 60)*6.0f; - clock->second.angle -= 90 ; - -} - -void drawClock(Clock clock) -{ - if (clock.cm == HANDS_FREE_MODE) - { - DrawText(TextFormat("%i", clock.second.value), clock.second.origin.x + (clock.second.length - 10)*cos(clock.second.angle*(float)(M_PI/180)) - DIGIT_SIZE/2, clock.second.origin.y + clock.second.length*sin(clock.second.angle*(float)(M_PI/180)) - DIGIT_SIZE/2, DIGIT_SIZE, GRAY); - - DrawText(TextFormat("%i", clock.minute.value), clock.minute.origin.x + clock.minute.length*cos(clock.minute.angle*(float)(M_PI/180)) - DIGIT_SIZE/2, clock.minute.origin.y + clock.minute.length*sin(clock.minute.angle*(float)(M_PI/180)) - DIGIT_SIZE/2, DIGIT_SIZE, RED); - - DrawText(TextFormat("%i", clock.hour.value), clock.hour.origin.x + clock.hour.length*cos(clock.hour.angle*(float)(M_PI/180)) - DIGIT_SIZE/2, clock.hour.origin.y + clock.hour.length*sin(clock.hour.angle*(float)(M_PI/180)) - DIGIT_SIZE/2, DIGIT_SIZE, GOLD); - } - else - { - DrawRectanglePro( - (Rectangle){ clock.second.origin.x, - clock.second.origin.y, - clock.second.length, - clock.second.thickness, - } - , (Vector2){0, clock.second.thickness/2}, - clock.second.angle, - clock.second.colour - ); - - DrawRectanglePro( - (Rectangle){ clock.minute.origin.x, - clock.minute.origin.y, - clock.minute.length, - clock.minute.thickness, - } - , (Vector2){0, clock.minute.thickness/2}, - clock.minute.angle, - clock.minute.colour - ); - - DrawRectanglePro( - (Rectangle){ clock.hour.origin.x, - clock.hour.origin.y, - clock.hour.length, - clock.hour.thickness, - } - , (Vector2){0, clock.hour.thickness/2}, - clock.hour.angle, - clock.hour.colour - ); - } -} +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +static void UpdateClock(Clock *clock); // Update clock time +static void DrawClock(Clock clock, Vector2 centerPos); // Draw clock at desired position //------------------------------------------------------------------------------------ // Program main entry point @@ -128,31 +61,29 @@ int main(void) //-------------------------------------------------------------------------------------- const int screenWidth = 800; const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - digital clock"); + // Initialize clock Clock myClock = { - .cm = NORMAL_MODE, + .mode = MODE_NORMAL, - .second.origin = (Vector2){400, 225}, .second.angle = 45, .second.length = 140, .second.thickness = 3, - .second.colour = BEIGE, + .second.color = BEIGE, - .minute.origin = (Vector2){400, 225}, .minute.angle = 10, .minute.length = 130, .minute.thickness = 7, - .minute.colour = DARKGRAY, + .minute.color = DARKGRAY, - .hour.origin = (Vector2){400, 225}, .hour.angle = 0, .hour.length = 100, .hour.thickness = 7, - .hour.colour = BLACK, + .hour.color = BLACK, }; - InitWindow(screenWidth, screenHeight, "raylib [shapes] example - digital clock"); - SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- @@ -160,23 +91,27 @@ int main(void) while (!WindowShouldClose()) // Detect window close button or ESC key { // Update + //---------------------------------------------------------------------------------- if (IsKeyPressed(KEY_SPACE)) { - myClock.cm = (myClock.cm == HANDS_FREE_MODE) ? NORMAL_MODE : HANDS_FREE_MODE; + if (myClock.mode == MODE_HANDS_FREE) myClock.mode = MODE_NORMAL; + else if (myClock.mode == MODE_NORMAL) myClock.mode = MODE_HANDS_FREE; } UpdateClock(&myClock); + //---------------------------------------------------------------------------------- // Draw //---------------------------------------------------------------------------------- BeginDrawing(); - ClearBackground(RAYWHITE); + ClearBackground(RAYWHITE); - DrawCircle(400, 225, 5, BLACK); // center dot - drawClock(myClock); + DrawCircle(400, 225, 5, BLACK); // Clock center dot + + DrawClock(myClock, (Vector2){ 400, 225 }); // Clock in selected mode - DrawText("press [SPACE] to switch clock mode", 350, 400, 10, GRAY); + DrawText("Press [SPACE] to switch clock mode", 10, 10, 20, DARKGRAY); EndDrawing(); //---------------------------------------------------------------------------------- @@ -188,4 +123,63 @@ int main(void) //-------------------------------------------------------------------------------------- return 0; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Update clock time +static void UpdateClock(Clock *clock) +{ + time_t rawtime; + struct tm * timeinfo; + + time(&rawtime); + timeinfo = localtime(&rawtime); + + // Updating time data + clock->second.value = timeinfo->tm_sec; + clock->minute.value = timeinfo->tm_min; + clock->hour.value = timeinfo->tm_hour; + + clock->hour.angle = (timeinfo->tm_hour%12)*180.0/6.0f; + clock->hour.angle += (timeinfo->tm_min%60)*30/60.0f; + clock->hour.angle -= 90; + + clock->minute.angle = (timeinfo->tm_min%60)*6.0f; + clock->minute.angle += (timeinfo->tm_sec%60)*6/60.0f; + clock->minute.angle -= 90; + + clock->second.angle = (timeinfo->tm_sec%60)*6.0f; + clock->second.angle -= 90; +} + +// Draw clock +static void DrawClock(Clock clock, Vector2 centerPosition) +{ + if (clock.mode == MODE_HANDS_FREE) + { + DrawCircleLinesV(centerPosition, clock.minute.length, LIGHTGRAY); + + DrawText(TextFormat("%i", clock.second.value), centerPosition.x + (clock.second.length - 10)*cosf(clock.second.angle*(float)(PI/180)) - DIGIT_SIZE/2, centerPosition.y + clock.second.length*sinf(clock.second.angle*(float)(PI/180)) - DIGIT_SIZE/2, DIGIT_SIZE, GRAY); + + DrawText(TextFormat("%i", clock.minute.value), clock.minute.origin.x + clock.minute.length*cosf(clock.minute.angle*(float)(PI/180)) - DIGIT_SIZE/2, centerPosition.y + clock.minute.length*sinf(clock.minute.angle*(float)(PI/180)) - DIGIT_SIZE/2, DIGIT_SIZE, RED); + + DrawText(TextFormat("%i", clock.hour.value), centerPosition.x + clock.hour.length*cosf(clock.hour.angle*(float)(PI/180)) - DIGIT_SIZE/2, centerPosition.y + clock.hour.length*sinf(clock.hour.angle*(float)(PI/180)) - DIGIT_SIZE/2, DIGIT_SIZE, GOLD); + } + else if (clock.mode == MODE_NORMAL) + { + // Draw hand seconds + DrawRectanglePro((Rectangle){ centerPosition.x, centerPosition.y, clock.second.length, clock.second.thickness }, + (Vector2){ 0.0f, clock.second.thickness/2.0f }, clock.second.angle, clock.second.color); + + // Draw hand minutes + DrawRectanglePro((Rectangle){ centerPosition.x, centerPosition.y, clock.minute.length, clock.minute.thickness }, + (Vector2){ 0.0f, clock.minute.thickness/2.0f }, clock.minute.angle, clock.minute.color); + + // Draw hand hours + DrawRectanglePro((Rectangle){ centerPosition.x, centerPosition.y, clock.hour.length, clock.hour.thickness }, + (Vector2){ 0.0f, clock.hour.thickness/2.0f }, clock.hour.angle, clock.hour.color); + } } \ No newline at end of file From 060bd787b1051e1974b8efd7ca048a9c320f8c63 Mon Sep 17 00:00:00 2001 From: katonar Date: Thu, 5 Jun 2025 15:50:07 +0200 Subject: [PATCH 034/242] Refactor: Replace DRM swap buffer implementation with asynchronous page-flipping and framebuffer caching The original implementation created/destroyed framebuffers (FBs) per-frame, leading to kernel overhead and screen tearing. This commit replaces it with a different approach using: - Asynchronous `drmModePageFlip()` with vblank sync - Framebuffer caching to reduce repeated FB creation/removal operations - Proper resource management through BO callbacks and buffer release synchronization - Added error handling for busy displays, cache overflows, and flip failures - Event-driven cleanup via page_flip_handler to prevent GPU/scanout conflicts Co-authored-by: rob-bits --- src/platforms/rcore_drm.c | 245 ++++++++++++++++++++++++++++++++++---- 1 file changed, 220 insertions(+), 25 deletions(-) diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 3ec3135e1..9aebe6cf0 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -71,6 +71,21 @@ #include "EGL/egl.h" // Native platform windowing system interface #include "EGL/eglext.h" // EGL extensions +#include // for drmHandleEvent poll +#include //for EBUSY, EAGAIN + +#define MAX_CACHED_BOS 3 + +typedef struct { + struct gbm_bo *bo; + uint32_t fb_id; // DRM framebuffer ID +} FramebufferCache; + +static FramebufferCache fbCache[MAX_CACHED_BOS]; +static volatile int fbCacheCount = 0; +static volatile bool pendingFlip = false; +static bool crtcSet = false; + #ifndef EGL_OPENGL_ES3_BIT #define EGL_OPENGL_ES3_BIT 0x40 #endif @@ -217,6 +232,7 @@ static const short linuxToRaylibMap[KEYMAP_SIZE] = { //---------------------------------------------------------------------------------- // Module Internal Functions Declaration //---------------------------------------------------------------------------------- +int InitSwapScreenBuffer(void); int InitPlatform(void); // Initialize platform (graphics, inputs and more) void ClosePlatform(void); // Close platform @@ -551,34 +567,207 @@ void DisableCursor(void) CORE.Input.Mouse.cursorHidden = true; } -// Swap back buffer with front buffer (screen drawing) -void SwapScreenBuffer(void) -{ - eglSwapBuffers(platform.device, platform.surface); +static void drm_fb_destroy_callback(struct gbm_bo *bo, void *data) { + uint32_t fb_id = (uintptr_t)data; + // Remove from cache + for (int i = 0; i < fbCacheCount; i++) { + if (fbCache[i].bo == bo) { + TRACELOG(LOG_INFO, "DRM: fb removed %u", (uintptr_t)fb_id); + drmModeRmFB(platform.fd, fbCache[i].fb_id); // Release DRM FB + // Shift remaining entries + for (int j = i; j < fbCacheCount - 1; j++) { + fbCache[j] = fbCache[j + 1]; + } + fbCacheCount--; + break; + } + } +} - if (!platform.gbmSurface || (-1 == platform.fd) || !platform.connector || !platform.crtc) TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap"); - - struct gbm_bo *bo = gbm_surface_lock_front_buffer(platform.gbmSurface); - if (!bo) TRACELOG(LOG_ERROR, "DISPLAY: Failed GBM to lock front buffer"); - - uint32_t fb = 0; - int result = drmModeAddFB(platform.fd, platform.connector->modes[platform.modeIndex].hdisplay, platform.connector->modes[platform.modeIndex].vdisplay, 24, 32, gbm_bo_get_stride(bo), gbm_bo_get_handle(bo).u32, &fb); - if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d", result); - - result = drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, fb, 0, 0, &platform.connector->connector_id, 1, &platform.connector->modes[platform.modeIndex]); - if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d", result); - - if (platform.prevFB) - { - result = drmModeRmFB(platform.fd, platform.prevFB); - if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeRmFB() failed with result: %d", result); +// Create or retrieve cached DRM FB for BO +static uint32_t get_or_create_fb_for_bo(struct gbm_bo *bo) { + // Try to find existing cache entry + for (int i = 0; i < fbCacheCount; i++) { + if (fbCache[i].bo == bo) { + return fbCache[i].fb_id; + } } - platform.prevFB = fb; + // Create new entry if cache not full + if (fbCacheCount >= MAX_CACHED_BOS) { + //FB cache full! + return 0; + } - if (platform.prevBO) gbm_surface_release_buffer(platform.gbmSurface, platform.prevBO); + uint32_t handle = gbm_bo_get_handle(bo).u32; + uint32_t stride = gbm_bo_get_stride(bo); + uint32_t width = gbm_bo_get_width(bo); + uint32_t height = gbm_bo_get_height(bo); + uint32_t fb_id; + if (drmModeAddFB(platform.fd, width, height, 24, 32, stride, handle, &fb_id)) { + //rmModeAddFB failed + return 0; + } + + // Store in cache + fbCache[fbCacheCount] = (FramebufferCache){ .bo = bo, .fb_id = fb_id }; + fbCacheCount++; + + // Set destroy callback to auto-cleanup + gbm_bo_set_user_data(bo, (void*)(uintptr_t)fb_id, drm_fb_destroy_callback); + + TRACELOG(LOG_INFO, "DRM: added new bo %u" , (uintptr_t)fb_id); + return fb_id; +} + +// Renders a blank frame to allocate initial buffers +void RenderBlankFrame() { + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + eglSwapBuffers(platform.device, platform.surface); + + // Ensure the buffer is processed + glFinish(); +} + +// Initialize with first buffer only +int InitSwapScreenBuffer() { + if (!platform.gbmSurface || platform.fd < 0) { + TRACELOG(LOG_ERROR, "DRM not initialized"); + return -1; + } + + // Render a blank frame to allocate buffers + RenderBlankFrame(); + + // Get first buffer + struct gbm_bo *bo = gbm_surface_lock_front_buffer(platform.gbmSurface); + if (!bo) { + TRACELOG(LOG_ERROR, "Failed to lock initial buffer"); + return -1; + } + + // Create FB for first buffer + uint32_t fb_id = get_or_create_fb_for_bo(bo); + if (!fb_id) { + gbm_surface_release_buffer(platform.gbmSurface, bo); + return -1; + } + + // Initial CRTC setup + if (drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, fb_id, + 0, 0, &platform.connector->connector_id, 1, + &platform.connector->modes[platform.modeIndex])) { + TRACELOG(LOG_ERROR, "Initial CRTC setup failed: %s", strerror(errno)); + gbm_surface_release_buffer(platform.gbmSurface, bo); + return -1; + } + + // Keep first buffer locked until flipped platform.prevBO = bo; + crtcSet = true; + return 0; +} + +// Static page flip handler +// this will be called once the drmModePageFlip() finished from the drmHandleEvent(platform.fd, &evctx); context +static void page_flip_handler(int fd, unsigned int frame, + unsigned int sec, unsigned int usec, + void *data) { + (void)fd; (void)frame; (void)sec; (void)usec; // Unused + pendingFlip = false; + struct gbm_bo *bo_to_release = (struct gbm_bo *)data; + //Buffers are released after the flip completes (via page_flip_handler), ensuring they're no longer in use. + // Prevents the GPU from writing to a buffer being scanned out + if (bo_to_release) { + gbm_surface_release_buffer(platform.gbmSurface, bo_to_release); + } +} + +// Swap implementation with proper caching +void SwapScreenBuffer() { + static int loopCnt = 0; + loopCnt++; + static int errCnt[5] = {0}; + if (!crtcSet || !platform.gbmSurface) return; + + //call this only, if pendingFlip is not set + eglSwapBuffers(platform.device, platform.surface); + + // Process pending events non-blocking + drmEventContext evctx = { + .version = DRM_EVENT_CONTEXT_VERSION, + .page_flip_handler = page_flip_handler + }; + + struct pollfd pfd = { .fd = platform.fd, .events = POLLIN }; + //polling for event for 0ms + while (poll(&pfd, 1, 0) > 0) { + drmHandleEvent(platform.fd, &evctx); + } + + // Skip if previous flip pending + if (pendingFlip) { + //Skip frame: flip pending + errCnt[0]++; + return; + } + + // Get new front buffer + struct gbm_bo *next_bo = gbm_surface_lock_front_buffer(platform.gbmSurface); + if (!next_bo) { + //Failed to lock front buffer + errCnt[1]++; + return; + } + + // Get FB ID (creates new one if needed) + uint32_t fb_id = get_or_create_fb_for_bo(next_bo); + if (!fb_id) { + gbm_surface_release_buffer(platform.gbmSurface, next_bo); + errCnt[2]++; + return; + } + + // Attempt page flip + /* rmModePageFlip() schedules a buffer-flip for the next vblank and then + * notifies us about it. It takes a CRTC-id, fb-id and an arbitrary + * data-pointer and then schedules the page-flip. This is fully asynchronous and + * When the page-flip happens, the DRM-fd will become readable and we can call + * drmHandleEvent(). This will read all vblank/page-flip events and call our + * modeset_page_flip_event() callback with the data-pointer that we passed to + * drmModePageFlip(). We simply call modeset_draw_dev() then so the next frame + * is rendered.. + * returns immediately. + */ + if (drmModePageFlip(platform.fd, platform.crtc->crtc_id, fb_id, + DRM_MODE_PAGE_FLIP_EVENT, platform.prevBO)) { + if (errno == EBUSY) { + //Display busy - skip flip + errCnt[3]++; + } else { + //Page flip failed + errCnt[4]++; + } + gbm_surface_release_buffer(platform.gbmSurface, next_bo); + return; + } + + // Success: update state + pendingFlip = true; + + platform.prevBO = next_bo; + //successful usage, do benchmarking + //in every 10 sec, at 60FPS 60*10 -> 600 + if(loopCnt >= 600) { + TRACELOG(LOG_INFO, "DRM err counters: %d, %d, %d, %d, %d, %d",errCnt[0],errCnt[1],errCnt[2],errCnt[3],errCnt[4], loopCnt); + //reinit the errors + for(int i=0;i<5;i++) { + errCnt[i] = 0; + } + loopCnt = 0; + } } //---------------------------------------------------------------------------------- @@ -910,7 +1099,8 @@ int InitPlatform(void) EGL_BLUE_SIZE, 8, // BLUE color bit depth (alternative: 5) EGL_ALPHA_SIZE, 8, // ALPHA bit depth (required for transparent framebuffer) //EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI) - EGL_DEPTH_SIZE, 24, // Depth buffer size (Required to use Depth testing!) + //ToDo: verify this. In 5.5 it is 16, in master it was 24 + EGL_DEPTH_SIZE, 16, // Depth buffer size (Required to use Depth testing!) //EGL_STENCIL_SIZE, 8, // Stencil buffer size EGL_SAMPLE_BUFFERS, sampleBuffer, // Activate MSAA EGL_SAMPLES, samples, // 4x Antialiasing if activated (Free on MALI GPUs) @@ -1083,9 +1273,14 @@ int InitPlatform(void) CORE.Storage.basePath = GetWorkingDirectory(); //---------------------------------------------------------------------------- - TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized successfully"); + if(InitSwapScreenBuffer() == 0) { + TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized successfully"); + return 0; + } else { + TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized failed"); + return -1; + } - return 0; } // Close platform From 533c12c386e7f663f8c4da47bf50dc2e44a64af4 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 7 Jun 2025 15:33:35 +0200 Subject: [PATCH 035/242] Small security tweaks --- src/platforms/rcore_drm.c | 2 +- src/platforms/rcore_web.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 3ec3135e1..619d05dcd 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -1374,7 +1374,7 @@ static void InitEvdevInput(void) if ((strncmp("event", entity->d_name, strlen("event")) == 0) || // Search for devices named "event*" (strncmp("mouse", entity->d_name, strlen("mouse")) == 0)) // Search for devices named "mouse*" { - sprintf(path, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name); + snprintf(path, MAX_FILEPATH_LENGTH, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name); ConfigureEvdevDevice(path); // Configure the device if appropriate } } diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index f83e3159e..62e4e94cc 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -1746,7 +1746,7 @@ static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadE if (gamepadEvent->connected && (gamepadEvent->index < MAX_GAMEPADS)) { CORE.Input.Gamepad.ready[gamepadEvent->index] = true; - sprintf(CORE.Input.Gamepad.name[gamepadEvent->index], "%s", gamepadEvent->id); + snprintf(CORE.Input.Gamepad.name[gamepadEvent->index], MAX_GAMEPAD_NAME_LENGTH, "%s", gamepadEvent->id); } else CORE.Input.Gamepad.ready[gamepadEvent->index] = false; From 8a3a8ee8e3d9f295a70df75b9c84f5cf86dd345d Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 7 Jun 2025 20:14:10 +0200 Subject: [PATCH 036/242] Update shapes_digital_clock.c --- examples/shapes/shapes_digital_clock.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/shapes/shapes_digital_clock.c b/examples/shapes/shapes_digital_clock.c index 8aa887c3c..ffa1ff5dc 100644 --- a/examples/shapes/shapes_digital_clock.c +++ b/examples/shapes/shapes_digital_clock.c @@ -61,7 +61,7 @@ int main(void) //-------------------------------------------------------------------------------------- const int screenWidth = 800; const int screenHeight = 450; - + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - digital clock"); // Initialize clock @@ -69,7 +69,7 @@ int main(void) .mode = MODE_NORMAL, .second.angle = 45, - .second.length = 140, + .second.length = 140, .second.thickness = 3, .second.color = BEIGE, @@ -104,11 +104,11 @@ int main(void) // Draw //---------------------------------------------------------------------------------- BeginDrawing(); - + ClearBackground(RAYWHITE); DrawCircle(400, 225, 5, BLACK); // Clock center dot - + DrawClock(myClock, (Vector2){ 400, 225 }); // Clock in selected mode DrawText("Press [SPACE] to switch clock mode", 10, 10, 20, DARKGRAY); @@ -138,7 +138,7 @@ static void UpdateClock(Clock *clock) time(&rawtime); timeinfo = localtime(&rawtime); - // Updating time data + // Updating time data clock->second.value = timeinfo->tm_sec; clock->minute.value = timeinfo->tm_min; clock->hour.value = timeinfo->tm_hour; @@ -150,7 +150,7 @@ static void UpdateClock(Clock *clock) clock->minute.angle = (timeinfo->tm_min%60)*6.0f; clock->minute.angle += (timeinfo->tm_sec%60)*6/60.0f; clock->minute.angle -= 90; - + clock->second.angle = (timeinfo->tm_sec%60)*6.0f; clock->second.angle -= 90; } @@ -161,7 +161,7 @@ static void DrawClock(Clock clock, Vector2 centerPosition) if (clock.mode == MODE_HANDS_FREE) { DrawCircleLinesV(centerPosition, clock.minute.length, LIGHTGRAY); - + DrawText(TextFormat("%i", clock.second.value), centerPosition.x + (clock.second.length - 10)*cosf(clock.second.angle*(float)(PI/180)) - DIGIT_SIZE/2, centerPosition.y + clock.second.length*sinf(clock.second.angle*(float)(PI/180)) - DIGIT_SIZE/2, DIGIT_SIZE, GRAY); DrawText(TextFormat("%i", clock.minute.value), clock.minute.origin.x + clock.minute.length*cosf(clock.minute.angle*(float)(PI/180)) - DIGIT_SIZE/2, centerPosition.y + clock.minute.length*sinf(clock.minute.angle*(float)(PI/180)) - DIGIT_SIZE/2, DIGIT_SIZE, RED); @@ -171,11 +171,11 @@ static void DrawClock(Clock clock, Vector2 centerPosition) else if (clock.mode == MODE_NORMAL) { // Draw hand seconds - DrawRectanglePro((Rectangle){ centerPosition.x, centerPosition.y, clock.second.length, clock.second.thickness }, + DrawRectanglePro((Rectangle){ centerPosition.x, centerPosition.y, clock.second.length, clock.second.thickness }, (Vector2){ 0.0f, clock.second.thickness/2.0f }, clock.second.angle, clock.second.color); // Draw hand minutes - DrawRectanglePro((Rectangle){ centerPosition.x, centerPosition.y, clock.minute.length, clock.minute.thickness }, + DrawRectanglePro((Rectangle){ centerPosition.x, centerPosition.y, clock.minute.length, clock.minute.thickness }, (Vector2){ 0.0f, clock.minute.thickness/2.0f }, clock.minute.angle, clock.minute.color); // Draw hand hours From 59bcf680aafb6f054e150f70c8c71deb44ad04b1 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 7 Jun 2025 20:14:24 +0200 Subject: [PATCH 037/242] Code gardening... --- src/platforms/rcore_desktop_glfw.c | 22 ++-- src/platforms/rcore_desktop_sdl.c | 4 +- src/platforms/rcore_drm.c | 2 +- src/platforms/rcore_web.c | 4 +- src/raudio.c | 10 +- src/rcore.c | 14 +-- src/rlgl.h | 2 +- src/rmodels.c | 187 ++++++++++++++--------------- src/rtext.c | 6 +- src/rtextures.c | 8 +- 10 files changed, 128 insertions(+), 131 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 83c13d34e..9bbd6559a 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -130,9 +130,9 @@ static void CursorEnterCallback(GLFWwindow *window, int enter); static void JoystickCallback(int jid, int event); // GLFW3 Joystick Connected/Disconnected Callback // Wrappers used by glfwInitAllocator -static void* AllocateWrapper(size_t size, void* user); // GLFW3 GLFWallocatefun, wrapps around RL_MALLOC macro -static void* ReallocateWrapper(void* block, size_t size, void* user); // GLFW3 GLFWreallocatefun, wrapps around RL_MALLOC macro -static void DeallocateWrapper(void* block, void* user); // GLFW3 GLFWdeallocatefun, wraps around RL_FREE macro +static void *AllocateWrapper(size_t size, void *user); // GLFW3 GLFWallocatefun, wrapps around RL_MALLOC macro +static void *ReallocateWrapper(void *block, size_t size, void *user); // GLFW3 GLFWreallocatefun, wrapps around RL_MALLOC macro +static void DeallocateWrapper(void *block, void *user); // GLFW3 GLFWdeallocatefun, wraps around RL_FREE macro //---------------------------------------------------------------------------------- // Module Functions Declaration @@ -576,7 +576,7 @@ void SetWindowIcons(Image *images, int count) else { int valid = 0; - GLFWimage *icons = RL_CALLOC(count, sizeof(GLFWimage)); + GLFWimage *icons = (GLFWimage *)RL_CALLOC(count, sizeof(GLFWimage)); for (int i = 0; i < count; i++) { @@ -1300,17 +1300,17 @@ static void SetDimensionsFromMonitor(GLFWmonitor *monitor) // We need to provide these because GLFWallocator expects function pointers with specific signatures. // Similar wrappers exist in utils.c but we cannot reuse them here due to declaration mismatch. // https://www.glfw.org/docs/latest/intro_guide.html#init_allocator -static void* AllocateWrapper(size_t size, void* user) +static void *AllocateWrapper(size_t size, void *user) { (void)user; return RL_MALLOC(size); } -static void* ReallocateWrapper(void* block, size_t size, void* user) +static void *ReallocateWrapper(void *block, size_t size, void *user) { (void)user; return RL_REALLOC(block, size); } -static void DeallocateWrapper(void* block, void* user) +static void DeallocateWrapper(void *block, void *user) { (void)user; RL_FREE(block); @@ -1327,6 +1327,7 @@ int InitPlatform(void) .reallocate = ReallocateWrapper, .user = NULL, // RL_*ALLOC macros are not capable of handling user-provided data }; + glfwInitAllocator(&allocator); #if defined(__APPLE__) @@ -1696,6 +1697,8 @@ int InitPlatform(void) // Retrieve gamepad names for (int i = 0; i < MAX_GAMEPADS; i++) { + // WARNING: If glfwGetJoystickName() is longer than MAX_GAMEPAD_NAME_LENGTH, + // we can get a not-NULL terminated string, so, we only copy up to (MAX_GAMEPAD_NAME_LENGTH - 1) if (glfwJoystickPresent(i)) strncpy(CORE.Input.Gamepad.name[i], glfwGetJoystickName(i), MAX_GAMEPAD_NAME_LENGTH - 1); } //---------------------------------------------------------------------------- @@ -1753,7 +1756,7 @@ static void ErrorCallback(int error, const char *description) static void WindowSizeCallback(GLFWwindow *window, int width, int height) { // WARNING: On window minimization, callback is called, - // but we don't want to change internal screen values, it breaks things + // but we don't want to change internal screen values, it breaks things if ((width == 0) || (height == 0)) return; // Reset viewport and projection matrix for new size @@ -1973,6 +1976,9 @@ static void JoystickCallback(int jid, int event) { if (event == GLFW_CONNECTED) { + // WARNING: If glfwGetJoystickName() is longer than MAX_GAMEPAD_NAME_LENGTH, + // we can get a not-NULL terminated string, so, we clean destination and only copy up to -1 + memset(CORE.Input.Gamepad.name[jid], 0, MAX_GAMEPAD_NAME_LENGTH); strncpy(CORE.Input.Gamepad.name[jid], glfwGetJoystickName(jid), MAX_GAMEPAD_NAME_LENGTH - 1); } else if (event == GLFW_DISCONNECTED) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index aacb2f800..6af1cb6c7 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -721,7 +721,7 @@ void SetWindowIcon(Image image) bmask = 0x001F, amask = 0; depth = 16, pitch = image.width*2; } break; - case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: { // WARNING: SDL2 could be using BGR but SDL3 RGB rmask = 0xFF0000, gmask = 0x00FF00; @@ -1697,8 +1697,8 @@ void PollInputEvents(void) CORE.Input.Gamepad.axisCount[jid] = SDL_JoystickNumAxes(SDL_GameControllerGetJoystick(platform.gamepad[jid])); CORE.Input.Gamepad.axisState[jid][GAMEPAD_AXIS_LEFT_TRIGGER] = -1.0f; CORE.Input.Gamepad.axisState[jid][GAMEPAD_AXIS_RIGHT_TRIGGER] = -1.0f; + memset(CORE.Input.Gamepad.name[jid], 0, MAX_GAMEPAD_NAME_LENGTH); strncpy(CORE.Input.Gamepad.name[jid], SDL_GameControllerNameForIndex(jid), MAX_GAMEPAD_NAME_LENGTH - 1); - CORE.Input.Gamepad.name[jid][MAX_GAMEPAD_NAME_LENGTH - 1] = '\0'; } else { diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 619d05dcd..06d3b5d5a 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -948,7 +948,7 @@ int InitPlatform(void) TRACELOG(LOG_TRACE, "DISPLAY: EGL configs available: %d", numConfigs); - EGLConfig *configs = RL_CALLOC(numConfigs, sizeof(*configs)); + EGLConfig *configs = (EGLConfig *)RL_CALLOC(numConfigs, sizeof(*configs)); if (!configs) { TRACELOG(LOG_WARNING, "DISPLAY: Failed to get memory for EGL configs"); diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index 62e4e94cc..046b79187 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -163,13 +163,13 @@ bool WindowShouldClose(void) // REF: https://emscripten.org/docs/porting/asyncify.html // WindowShouldClose() is not called on a web-ready raylib application if using emscripten_set_main_loop() - // and encapsulating one frame execution on a UpdateDrawFrame() function, + // and encapsulating one frame execution on a UpdateDrawFrame() function, // allowing the browser to manage execution asynchronously // Optionally we can manage the time we give-control-back-to-browser if required, // but it seems below line could generate stuttering on some browsers emscripten_sleep(12); - + return false; } diff --git a/src/raudio.c b/src/raudio.c index dd8f4a255..a9e47b0ec 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -1337,7 +1337,7 @@ Music LoadMusicStream(const char *fileName) #if defined(SUPPORT_FILEFORMAT_WAV) else if (IsFileExtension(fileName, ".wav")) { - drwav *ctxWav = RL_CALLOC(1, sizeof(drwav)); + drwav *ctxWav = (drwav *)RL_CALLOC(1, sizeof(drwav)); bool success = drwav_init_file(ctxWav, fileName, NULL); if (success) @@ -1387,7 +1387,7 @@ Music LoadMusicStream(const char *fileName) #if defined(SUPPORT_FILEFORMAT_MP3) else if (IsFileExtension(fileName, ".mp3")) { - drmp3 *ctxMp3 = RL_CALLOC(1, sizeof(drmp3)); + drmp3 *ctxMp3 = (drmp3 *)RL_CALLOC(1, sizeof(drmp3)); int result = drmp3_init_file(ctxMp3, fileName, NULL); if (result > 0) @@ -1478,7 +1478,7 @@ Music LoadMusicStream(const char *fileName) #if defined(SUPPORT_FILEFORMAT_MOD) else if (IsFileExtension(fileName, ".mod")) { - jar_mod_context_t *ctxMod = RL_CALLOC(1, sizeof(jar_mod_context_t)); + jar_mod_context_t *ctxMod = (jar_mod_context_t *)RL_CALLOC(1, sizeof(jar_mod_context_t)); jar_mod_init(ctxMod); int result = jar_mod_load_file(ctxMod, fileName); @@ -1529,7 +1529,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, #if defined(SUPPORT_FILEFORMAT_WAV) else if ((strcmp(fileType, ".wav") == 0) || (strcmp(fileType, ".WAV") == 0)) { - drwav *ctxWav = RL_CALLOC(1, sizeof(drwav)); + drwav *ctxWav = (drwav *)RL_CALLOC(1, sizeof(drwav)); bool success = drwav_init_memory(ctxWav, (const void *)data, dataSize, NULL); @@ -1580,7 +1580,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, #if defined(SUPPORT_FILEFORMAT_MP3) else if ((strcmp(fileType, ".mp3") == 0) || (strcmp(fileType, ".MP3") == 0)) { - drmp3 *ctxMp3 = RL_CALLOC(1, sizeof(drmp3)); + drmp3 *ctxMp3 = (drmp3 *)RL_CALLOC(1, sizeof(drmp3)); int success = drmp3_init_memory(ctxMp3, (const void*)data, dataSize, NULL); if (success) diff --git a/src/rcore.c b/src/rcore.c index f216a197a..f8b061a50 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2500,7 +2500,7 @@ unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDa #if defined(SUPPORT_COMPRESSION_API) // Compress data and generate a valid DEFLATE stream - struct sdefl *sdefl = RL_CALLOC(1, sizeof(struct sdefl)); // WARNING: Possible stack overflow, struct sdefl is almost 1MB + struct sdefl *sdefl = (struct sdefl *)RL_CALLOC(1, sizeof(struct sdefl)); // WARNING: Possible stack overflow, struct sdefl is almost 1MB int bounds = sdefl_bound(dataSize); compData = (unsigned char *)RL_CALLOC(bounds, 1); @@ -2580,7 +2580,7 @@ char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize) outputCount += 4; i += 3; } - + // Add required padding bytes for (int p = 0; p < padding; p++) encodedData[outputCount - p - 1] = '='; @@ -2602,10 +2602,10 @@ unsigned char *DecodeDataBase64(const char *text, int *outputSize) // every character in the corresponding ASCII position static const unsigned char base64DecodeTable[256] = { ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, ['F'] = 5, ['G'] = 6, ['H'] = 7, - ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15, + ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15, ['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, ['Y'] = 24, ['Z'] = 25, - ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31, ['g'] = 32, ['h'] = 33, - ['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, ['o'] = 40, ['p'] = 41, + ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31, ['g'] = 32, ['h'] = 33, + ['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, ['o'] = 40, ['p'] = 41, ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47, ['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51, ['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55, ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, ['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63 @@ -2747,7 +2747,7 @@ unsigned int *ComputeMD5(unsigned char *data, int dataSize) int newDataSize = ((((dataSize + 8)/64) + 1)*64) - 8; - unsigned char *msg = RL_CALLOC(newDataSize + 64, 1); // Initialize with '0' bits, allocating 64 extra bytes + unsigned char *msg = (unsigned char *)RL_CALLOC(newDataSize + 64, 1); // Initialize with '0' bits, allocating 64 extra bytes memcpy(msg, data, dataSize); msg[dataSize] = 128; // Write the '1' bit @@ -2837,7 +2837,7 @@ unsigned int *ComputeSHA1(unsigned char *data, int dataSize) int newDataSize = ((((dataSize + 8)/64) + 1)*64); - unsigned char *msg = RL_CALLOC(newDataSize, 1); // Initialize with '0' bits + unsigned char *msg = (unsigned char *)RL_CALLOC(newDataSize, 1); // Initialize with '0' bits memcpy(msg, data, dataSize); msg[dataSize] = 128; // Write the '1' bit diff --git a/src/rlgl.h b/src/rlgl.h index d595f4606..686260d3b 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -2425,7 +2425,7 @@ void rlLoadExtensions(void *loader) // Get supported extensions list GLint numExt = 0; - const char **extList = RL_MALLOC(512*sizeof(const char *)); // Allocate 512 strings pointers (2 KB) + const char **extList = (char **)RL_MALLOC(512*sizeof(const char *)); // Allocate 512 strings pointers (2 KB) const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string // NOTE: We have to duplicate string because glGetString() returns a const string diff --git a/src/rmodels.c b/src/rmodels.c index 5f3fd8993..84daabc15 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -2174,7 +2174,7 @@ Material *LoadMaterials(const char *fileName, int *materialCount) int result = tinyobj_parse_mtl_file(&mats, &count, fileName); if (result != TINYOBJ_SUCCESS) TRACELOG(LOG_WARNING, "MATERIAL: [%s] Failed to parse materials file", fileName); - materials = RL_MALLOC(count*sizeof(Material)); + materials = (Material *)RL_MALLOC(count*sizeof(Material)); ProcessMaterialsOBJ(materials, mats, count); tinyobj_materials_free(mats, count); @@ -3713,7 +3713,7 @@ void GenMeshTangents(Mesh *mesh) // Create a tangent perpendicular to the normal if (fabsf(normal.z) > 0.707f) tangent = (Vector3){ 1.0f, 0.0f, 0.0f }; else tangent = Vector3Normalize((Vector3){ -normal.y, normal.x, 0.0f }); - + mesh->tangents[i*4 + 0] = tangent.x; mesh->tangents[i*4 + 1] = tangent.y; mesh->tangents[i*4 + 2] = tangent.z; @@ -3724,7 +3724,7 @@ void GenMeshTangents(Mesh *mesh) // Gram-Schmidt orthogonalization to make tangent orthogonal to normal // T_prime = T - N * dot(N, T) Vector3 orthogonalized = Vector3Subtract(tangent, Vector3Scale(normal, Vector3DotProduct(normal, tangent))); - + // Handle cases where orthogonalized vector is too small if (Vector3Length(orthogonalized) < 0.0001f) { @@ -3742,7 +3742,7 @@ void GenMeshTangents(Mesh *mesh) mesh->tangents[i*4 + 0] = orthogonalized.x; mesh->tangents[i*4 + 1] = orthogonalized.y; mesh->tangents[i*4 + 2] = orthogonalized.z; - + // Calculate the handedness (w component) mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, orthogonalized), tan2[i]) < 0.0f)? -1.0f : 1.0f; } @@ -4642,13 +4642,13 @@ static Model LoadIQM(const char *fileName) //fileDataPtr += sizeof(IQMHeader); // Move file data pointer // Meshes data processing - imesh = RL_MALLOC(iqmHeader->num_meshes*sizeof(IQMMesh)); + imesh = (IQMMesh *)RL_MALLOC(iqmHeader->num_meshes*sizeof(IQMMesh)); //fseek(iqmFile, iqmHeader->ofs_meshes, SEEK_SET); //fread(imesh, sizeof(IQMMesh)*iqmHeader->num_meshes, 1, iqmFile); memcpy(imesh, fileDataPtr + iqmHeader->ofs_meshes, iqmHeader->num_meshes*sizeof(IQMMesh)); model.meshCount = iqmHeader->num_meshes; - model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh)); + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); model.materialCount = model.meshCount; model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); @@ -4676,24 +4676,24 @@ static Model LoadIQM(const char *fileName) model.meshes[i].vertexCount = imesh[i].num_vertexes; - model.meshes[i].vertices = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); // Default vertex positions - model.meshes[i].normals = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); // Default vertex normals - model.meshes[i].texcoords = RL_CALLOC(model.meshes[i].vertexCount*2, sizeof(float)); // Default vertex texcoords + model.meshes[i].vertices = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); // Default vertex positions + model.meshes[i].normals = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); // Default vertex normals + model.meshes[i].texcoords = (float *)RL_CALLOC(model.meshes[i].vertexCount*2, sizeof(float)); // Default vertex texcoords - model.meshes[i].boneIds = RL_CALLOC(model.meshes[i].vertexCount*4, sizeof(unsigned char)); // Up-to 4 bones supported! - model.meshes[i].boneWeights = RL_CALLOC(model.meshes[i].vertexCount*4, sizeof(float)); // Up-to 4 bones supported! + model.meshes[i].boneIds = (unsigned char *)RL_CALLOC(model.meshes[i].vertexCount*4, sizeof(unsigned char)); // Up-to 4 bones supported! + model.meshes[i].boneWeights = (float *)RL_CALLOC(model.meshes[i].vertexCount*4, sizeof(float)); // Up-to 4 bones supported! model.meshes[i].triangleCount = imesh[i].num_triangles; - model.meshes[i].indices = RL_CALLOC(model.meshes[i].triangleCount*3, sizeof(unsigned short)); + model.meshes[i].indices = (unsigned short *)RL_CALLOC(model.meshes[i].triangleCount*3, sizeof(unsigned short)); // Animated vertex data, what we actually process for rendering // NOTE: Animated vertex should be re-uploaded to GPU (if not using GPU skinning) - model.meshes[i].animVertices = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); - model.meshes[i].animNormals = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); + model.meshes[i].animVertices = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); + model.meshes[i].animNormals = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); } // Triangles data processing - tri = RL_MALLOC(iqmHeader->num_triangles*sizeof(IQMTriangle)); + tri = (IQMTriangle *)RL_MALLOC(iqmHeader->num_triangles*sizeof(IQMTriangle)); //fseek(iqmFile, iqmHeader->ofs_triangles, SEEK_SET); //fread(tri, sizeof(IQMTriangle), iqmHeader->num_triangles, iqmFile); memcpy(tri, fileDataPtr + iqmHeader->ofs_triangles, iqmHeader->num_triangles*sizeof(IQMTriangle)); @@ -4715,7 +4715,7 @@ static Model LoadIQM(const char *fileName) } // Vertex arrays data processing - va = RL_MALLOC(iqmHeader->num_vertexarrays*sizeof(IQMVertexArray)); + va = (IQMVertexArray *)RL_MALLOC(iqmHeader->num_vertexarrays*sizeof(IQMVertexArray)); //fseek(iqmFile, iqmHeader->ofs_vertexarrays, SEEK_SET); //fread(va, sizeof(IQMVertexArray), iqmHeader->num_vertexarrays, iqmFile); memcpy(va, fileDataPtr + iqmHeader->ofs_vertexarrays, iqmHeader->num_vertexarrays*sizeof(IQMVertexArray)); @@ -4726,7 +4726,7 @@ static Model LoadIQM(const char *fileName) { case IQM_POSITION: { - vertex = RL_MALLOC(iqmHeader->num_vertexes*3*sizeof(float)); + vertex = (float *)RL_MALLOC(iqmHeader->num_vertexes*3*sizeof(float)); //fseek(iqmFile, va[i].offset, SEEK_SET); //fread(vertex, iqmHeader->num_vertexes*3*sizeof(float), 1, iqmFile); memcpy(vertex, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*3*sizeof(float)); @@ -4744,7 +4744,7 @@ static Model LoadIQM(const char *fileName) } break; case IQM_NORMAL: { - normal = RL_MALLOC(iqmHeader->num_vertexes*3*sizeof(float)); + normal = (float *)RL_MALLOC(iqmHeader->num_vertexes*3*sizeof(float)); //fseek(iqmFile, va[i].offset, SEEK_SET); //fread(normal, iqmHeader->num_vertexes*3*sizeof(float), 1, iqmFile); memcpy(normal, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*3*sizeof(float)); @@ -4762,7 +4762,7 @@ static Model LoadIQM(const char *fileName) } break; case IQM_TEXCOORD: { - text = RL_MALLOC(iqmHeader->num_vertexes*2*sizeof(float)); + text = (float *)RL_MALLOC(iqmHeader->num_vertexes*2*sizeof(float)); //fseek(iqmFile, va[i].offset, SEEK_SET); //fread(text, iqmHeader->num_vertexes*2*sizeof(float), 1, iqmFile); memcpy(text, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*2*sizeof(float)); @@ -4779,7 +4779,7 @@ static Model LoadIQM(const char *fileName) } break; case IQM_BLENDINDEXES: { - blendi = RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(char)); + blendi = (char *)RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(char)); //fseek(iqmFile, va[i].offset, SEEK_SET); //fread(blendi, iqmHeader->num_vertexes*4*sizeof(char), 1, iqmFile); memcpy(blendi, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(char)); @@ -4796,7 +4796,7 @@ static Model LoadIQM(const char *fileName) } break; case IQM_BLENDWEIGHTS: { - blendw = RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(unsigned char)); + blendw = (unsigned char *)RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(unsigned char)); //fseek(iqmFile, va[i].offset, SEEK_SET); //fread(blendw, iqmHeader->num_vertexes*4*sizeof(unsigned char), 1, iqmFile); memcpy(blendw, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(unsigned char)); @@ -4813,14 +4813,14 @@ static Model LoadIQM(const char *fileName) } break; case IQM_COLOR: { - color = RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(unsigned char)); + color = (unsigned char *)RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(unsigned char)); //fseek(iqmFile, va[i].offset, SEEK_SET); //fread(blendw, iqmHeader->num_vertexes*4*sizeof(unsigned char), 1, iqmFile); memcpy(color, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(unsigned char)); for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) { - model.meshes[m].colors = RL_CALLOC(model.meshes[m].vertexCount*4, sizeof(unsigned char)); + model.meshes[m].colors = (unsigned char *)RL_CALLOC(model.meshes[m].vertexCount*4, sizeof(unsigned char)); int vCounter = 0; for (unsigned int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++) @@ -4834,14 +4834,14 @@ static Model LoadIQM(const char *fileName) } // Bones (joints) data processing - ijoint = RL_MALLOC(iqmHeader->num_joints*sizeof(IQMJoint)); + ijoint = (IQMJoint *)RL_MALLOC(iqmHeader->num_joints*sizeof(IQMJoint)); //fseek(iqmFile, iqmHeader->ofs_joints, SEEK_SET); //fread(ijoint, sizeof(IQMJoint), iqmHeader->num_joints, iqmFile); memcpy(ijoint, fileDataPtr + iqmHeader->ofs_joints, iqmHeader->num_joints*sizeof(IQMJoint)); model.boneCount = iqmHeader->num_joints; - model.bones = RL_MALLOC(iqmHeader->num_joints*sizeof(BoneInfo)); - model.bindPose = RL_MALLOC(iqmHeader->num_joints*sizeof(Transform)); + model.bones = (BoneInfo *)RL_MALLOC(iqmHeader->num_joints*sizeof(BoneInfo)); + model.bindPose = (Transform *)RL_MALLOC(iqmHeader->num_joints*sizeof(Transform)); for (unsigned int i = 0; i < iqmHeader->num_joints; i++) { @@ -4871,7 +4871,7 @@ static Model LoadIQM(const char *fileName) for (int i = 0; i < model.meshCount; i++) { model.meshes[i].boneCount = model.boneCount; - model.meshes[i].boneMatrices = RL_CALLOC(model.meshes[i].boneCount, sizeof(Matrix)); + model.meshes[i].boneMatrices = (Matrix *)RL_CALLOC(model.meshes[i].boneCount, sizeof(Matrix)); for (int j = 0; j < model.meshes[i].boneCount; j++) { @@ -4963,36 +4963,36 @@ static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, int *animCou } // Get bones data - IQMPose *poses = RL_MALLOC(iqmHeader->num_poses*sizeof(IQMPose)); + IQMPose *poses = (IQMPose *)RL_MALLOC(iqmHeader->num_poses*sizeof(IQMPose)); //fseek(iqmFile, iqmHeader->ofs_poses, SEEK_SET); //fread(poses, sizeof(IQMPose), iqmHeader->num_poses, iqmFile); memcpy(poses, fileDataPtr + iqmHeader->ofs_poses, iqmHeader->num_poses*sizeof(IQMPose)); // Get animations data *animCount = iqmHeader->num_anims; - IQMAnim *anim = RL_MALLOC(iqmHeader->num_anims*sizeof(IQMAnim)); + IQMAnim *anim = (IQMAnim *)RL_MALLOC(iqmHeader->num_anims*sizeof(IQMAnim)); //fseek(iqmFile, iqmHeader->ofs_anims, SEEK_SET); //fread(anim, sizeof(IQMAnim), iqmHeader->num_anims, iqmFile); memcpy(anim, fileDataPtr + iqmHeader->ofs_anims, iqmHeader->num_anims*sizeof(IQMAnim)); - ModelAnimation *animations = RL_MALLOC(iqmHeader->num_anims*sizeof(ModelAnimation)); + ModelAnimation *animations = (ModelAnimation *)RL_MALLOC(iqmHeader->num_anims*sizeof(ModelAnimation)); // frameposes - unsigned short *framedata = RL_MALLOC(iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short)); + unsigned short *framedata = (unsigned short *)RL_MALLOC(iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short)); //fseek(iqmFile, iqmHeader->ofs_frames, SEEK_SET); //fread(framedata, sizeof(unsigned short), iqmHeader->num_frames*iqmHeader->num_framechannels, iqmFile); memcpy(framedata, fileDataPtr + iqmHeader->ofs_frames, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short)); // joints - IQMJoint *joints = RL_MALLOC(iqmHeader->num_joints*sizeof(IQMJoint)); + IQMJoint *joints = (IQMJoint *)RL_MALLOC(iqmHeader->num_joints*sizeof(IQMJoint)); memcpy(joints, fileDataPtr + iqmHeader->ofs_joints, iqmHeader->num_joints*sizeof(IQMJoint)); for (unsigned int a = 0; a < iqmHeader->num_anims; a++) { animations[a].frameCount = anim[a].num_frames; animations[a].boneCount = iqmHeader->num_poses; - animations[a].bones = RL_MALLOC(iqmHeader->num_poses*sizeof(BoneInfo)); - animations[a].framePoses = RL_MALLOC(anim[a].num_frames*sizeof(Transform *)); + animations[a].bones = (BoneInfo *)RL_MALLOC(iqmHeader->num_poses*sizeof(BoneInfo)); + animations[a].framePoses = (Transform **)RL_MALLOC(anim[a].num_frames*sizeof(Transform *)); memcpy(animations[a].name, fileDataPtr + iqmHeader->ofs_text + anim[a].name, 32); // I don't like this 32 here TraceLog(LOG_INFO, "IQM Anim %s", animations[a].name); // animations[a].framerate = anim.framerate; // TODO: Use animation framerate data? @@ -5007,7 +5007,7 @@ static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, int *animCou animations[a].bones[j].parent = poses[j].parent; } - for (unsigned int j = 0; j < anim[a].num_frames; j++) animations[a].framePoses[j] = RL_MALLOC(iqmHeader->num_poses*sizeof(Transform)); + for (unsigned int j = 0; j < anim[a].num_frames; j++) animations[a].framePoses[j] = (Transform *)RL_MALLOC(iqmHeader->num_poses*sizeof(Transform)); int dcounter = anim[a].first_frame*iqmHeader->num_framechannels; @@ -5198,7 +5198,7 @@ static Image LoadImageFromCgltfImage(cgltf_image *cgltfImage, const char *texPat } else if ((cgltfImage->buffer_view != NULL) && (cgltfImage->buffer_view->buffer->data != NULL)) // Check if image is provided as data buffer { - unsigned char *data = RL_MALLOC(cgltfImage->buffer_view->size); + unsigned char *data = (unsigned char *)RL_MALLOC(cgltfImage->buffer_view->size); int offset = (int)cgltfImage->buffer_view->offset; int stride = (int)cgltfImage->buffer_view->stride? (int)cgltfImage->buffer_view->stride : 1; @@ -5231,16 +5231,12 @@ static Image LoadImageFromCgltfImage(cgltf_image *cgltfImage, const char *texPat static BoneInfo *LoadBoneInfoGLTF(cgltf_skin skin, int *boneCount) { *boneCount = (int)skin.joints_count; - BoneInfo *bones = RL_MALLOC(skin.joints_count*sizeof(BoneInfo)); + BoneInfo *bones = (BoneInfo *)RL_CALLOC(skin.joints_count, sizeof(BoneInfo)); for (unsigned int i = 0; i < skin.joints_count; i++) { cgltf_node node = *skin.joints[i]; - if (node.name != NULL) - { - strncpy(bones[i].name, node.name, sizeof(bones[i].name)); - bones[i].name[sizeof(bones[i].name) - 1] = '\0'; - } + if (node.name != NULL) strncpy(bones[i].name, node.name, sizeof(bones[i].name) - 1); // Find parent bone index int parentIndex = -1; @@ -5362,15 +5358,15 @@ static Model LoadGLTF(const char *fileName) // Load our model data: meshes and materials model.meshCount = primitivesCount; - model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh)); + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); // NOTE: We keep an extra slot for default material, in case some mesh requires it model.materialCount = (int)data->materials_count + 1; - model.materials = RL_CALLOC(model.materialCount, sizeof(Material)); + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); model.materials[0] = LoadMaterialDefault(); // Load default material (index: 0) // Load mesh-material indices, by default all meshes are mapped to material index: 0 - model.meshMaterial = RL_CALLOC(model.meshCount, sizeof(int)); + model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); // Load materials data //---------------------------------------------------------------------------------------------------- @@ -5540,7 +5536,7 @@ static Model LoadGLTF(const char *fileName) { // Init raylib mesh vertices to copy glTF attribute data model.meshes[meshIndex].vertexCount = (int)attribute->count; - model.meshes[meshIndex].vertices = RL_MALLOC(attribute->count*3*sizeof(float)); + model.meshes[meshIndex].vertices = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); // Load 3 components of float data type into mesh.vertices LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[meshIndex].vertices) @@ -5564,7 +5560,7 @@ static Model LoadGLTF(const char *fileName) if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_32f)) { // Init raylib mesh normals to copy glTF attribute data - model.meshes[meshIndex].normals = RL_MALLOC(attribute->count*3*sizeof(float)); + model.meshes[meshIndex].normals = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); // Load 3 components of float data type into mesh.normals LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[meshIndex].normals) @@ -5588,7 +5584,7 @@ static Model LoadGLTF(const char *fileName) if ((attribute->type == cgltf_type_vec4) && (attribute->component_type == cgltf_component_type_r_32f)) { // Init raylib mesh tangent to copy glTF attribute data - model.meshes[meshIndex].tangents = RL_MALLOC(attribute->count*4*sizeof(float)); + model.meshes[meshIndex].tangents = (float *)RL_MALLOC(attribute->count*4*sizeof(float)); // Load 4 components of float data type into mesh.tangents LOAD_ATTRIBUTE(attribute, 4, float, model.meshes[meshIndex].tangents) @@ -5674,10 +5670,10 @@ static Model LoadGLTF(const char *fileName) if (attribute->component_type == cgltf_component_type_r_8u) { // Init raylib mesh color to copy glTF attribute data - model.meshes[meshIndex].colors = RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char)); // Load data into a temp buffer to be converted to raylib data type - unsigned char *temp = RL_MALLOC(attribute->count*3*sizeof(unsigned char)); + unsigned char *temp = (unsigned char *)RL_MALLOC(attribute->count*3*sizeof(unsigned char)); LOAD_ATTRIBUTE(attribute, 3, unsigned char, temp); // Convert data to raylib color data type (4 bytes) @@ -5694,10 +5690,10 @@ static Model LoadGLTF(const char *fileName) else if (attribute->component_type == cgltf_component_type_r_16u) { // Init raylib mesh color to copy glTF attribute data - model.meshes[meshIndex].colors = RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char)); // Load data into a temp buffer to be converted to raylib data type - unsigned short *temp = RL_MALLOC(attribute->count*3*sizeof(unsigned short)); + unsigned short *temp = (unsigned short *)RL_MALLOC(attribute->count*3*sizeof(unsigned short)); LOAD_ATTRIBUTE(attribute, 3, unsigned short, temp); // Convert data to raylib color data type (4 bytes) @@ -5714,10 +5710,10 @@ static Model LoadGLTF(const char *fileName) else if (attribute->component_type == cgltf_component_type_r_32f) { // Init raylib mesh color to copy glTF attribute data - model.meshes[meshIndex].colors = RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char)); // Load data into a temp buffer to be converted to raylib data type - float *temp = RL_MALLOC(attribute->count*3*sizeof(float)); + float *temp = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); LOAD_ATTRIBUTE(attribute, 3, float, temp); // Convert data to raylib color data type (4 bytes) @@ -5738,7 +5734,7 @@ static Model LoadGLTF(const char *fileName) if (attribute->component_type == cgltf_component_type_r_8u) { // Init raylib mesh color to copy glTF attribute data - model.meshes[meshIndex].colors = RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char)); // Load 4 components of unsigned char data type into mesh.colors LOAD_ATTRIBUTE(attribute, 4, unsigned char, model.meshes[meshIndex].colors) @@ -5746,10 +5742,10 @@ static Model LoadGLTF(const char *fileName) else if (attribute->component_type == cgltf_component_type_r_16u) { // Init raylib mesh color to copy glTF attribute data - model.meshes[meshIndex].colors = RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char)); // Load data into a temp buffer to be converted to raylib data type - unsigned short *temp = RL_MALLOC(attribute->count*4*sizeof(unsigned short)); + unsigned short *temp = (unsigned short *)RL_MALLOC(attribute->count*4*sizeof(unsigned short)); LOAD_ATTRIBUTE(attribute, 4, unsigned short, temp); // Convert data to raylib color data type (4 bytes) @@ -5760,10 +5756,10 @@ static Model LoadGLTF(const char *fileName) else if (attribute->component_type == cgltf_component_type_r_32f) { // Init raylib mesh color to copy glTF attribute data - model.meshes[meshIndex].colors = RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char)); // Load data into a temp buffer to be converted to raylib data type - float *temp = RL_MALLOC(attribute->count*4*sizeof(float)); + float *temp = (float *)RL_MALLOC(attribute->count*4*sizeof(float)); LOAD_ATTRIBUTE(attribute, 4, float, temp); // Convert data to raylib color data type (4 bytes), we expect the color data normalized @@ -5789,7 +5785,7 @@ static Model LoadGLTF(const char *fileName) if (attribute->component_type == cgltf_component_type_r_16u) { // Init raylib mesh indices to copy glTF attribute data - model.meshes[meshIndex].indices = RL_MALLOC(attribute->count*sizeof(unsigned short)); + model.meshes[meshIndex].indices = (unsigned short *)RL_MALLOC(attribute->count*sizeof(unsigned short)); // Load unsigned short data type into mesh.indices LOAD_ATTRIBUTE(attribute, 1, unsigned short, model.meshes[meshIndex].indices) @@ -5797,14 +5793,14 @@ static Model LoadGLTF(const char *fileName) else if (attribute->component_type == cgltf_component_type_r_8u) { // Init raylib mesh indices to copy glTF attribute data - model.meshes[meshIndex].indices = RL_MALLOC(attribute->count*sizeof(unsigned short)); + model.meshes[meshIndex].indices = (unsigned short *)RL_MALLOC(attribute->count*sizeof(unsigned short)); LOAD_ATTRIBUTE_CAST(attribute, 1, unsigned char, model.meshes[meshIndex].indices, unsigned short) } else if (attribute->component_type == cgltf_component_type_r_32u) { // Init raylib mesh indices to copy glTF attribute data - model.meshes[meshIndex].indices = RL_MALLOC(attribute->count*sizeof(unsigned short)); + model.meshes[meshIndex].indices = (unsigned short *)RL_MALLOC(attribute->count*sizeof(unsigned short)); LOAD_ATTRIBUTE_CAST(attribute, 1, unsigned int, model.meshes[meshIndex].indices, unsigned short); TRACELOG(LOG_WARNING, "MODEL: [%s] Indices data converted from u32 to u16, possible loss of data", fileName); @@ -5848,7 +5844,7 @@ static Model LoadGLTF(const char *fileName) { cgltf_skin skin = data->skins[0]; model.bones = LoadBoneInfoGLTF(skin, &model.boneCount); - model.bindPose = RL_MALLOC(model.boneCount*sizeof(Transform)); + model.bindPose = (Transform *)RL_MALLOC(model.boneCount*sizeof(Transform)); for (int i = 0; i < model.boneCount; i++) { @@ -5905,7 +5901,7 @@ static Model LoadGLTF(const char *fileName) if (attribute->component_type == cgltf_component_type_r_8u) { // Init raylib mesh boneIds to copy glTF attribute data - model.meshes[meshIndex].boneIds = RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char)); + model.meshes[meshIndex].boneIds = (unsigned char *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char)); // Load attribute: vec4, u8 (unsigned char) LOAD_ATTRIBUTE(attribute, 4, unsigned char, model.meshes[meshIndex].boneIds) @@ -5913,10 +5909,10 @@ static Model LoadGLTF(const char *fileName) else if (attribute->component_type == cgltf_component_type_r_16u) { // Init raylib mesh boneIds to copy glTF attribute data - model.meshes[meshIndex].boneIds = RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char)); + model.meshes[meshIndex].boneIds = (unsigned char *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char)); // Load data into a temp buffer to be converted to raylib data type - unsigned short *temp = RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned short)); + unsigned short *temp = (unsigned short *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned short)); LOAD_ATTRIBUTE(attribute, 4, unsigned short, temp); // Convert data to raylib color data type (4 bytes) @@ -5948,10 +5944,10 @@ static Model LoadGLTF(const char *fileName) if (attribute->component_type == cgltf_component_type_r_8u) { // Init raylib mesh bone weight to copy glTF attribute data - model.meshes[meshIndex].boneWeights = RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float)); + model.meshes[meshIndex].boneWeights = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float)); // Load data into a temp buffer to be converted to raylib data type - unsigned char *temp = RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + unsigned char *temp = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char)); LOAD_ATTRIBUTE(attribute, 4, unsigned char, temp); // Convert data to raylib bone weight data type (4 bytes) @@ -5962,10 +5958,10 @@ static Model LoadGLTF(const char *fileName) else if (attribute->component_type == cgltf_component_type_r_16u) { // Init raylib mesh bone weight to copy glTF attribute data - model.meshes[meshIndex].boneWeights = RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float)); + model.meshes[meshIndex].boneWeights = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float)); // Load data into a temp buffer to be converted to raylib data type - unsigned short *temp = RL_MALLOC(attribute->count*4*sizeof(unsigned short)); + unsigned short *temp = (unsigned short *)RL_MALLOC(attribute->count*4*sizeof(unsigned short)); LOAD_ATTRIBUTE(attribute, 4, unsigned short, temp); // Convert data to raylib bone weight data type @@ -5976,7 +5972,7 @@ static Model LoadGLTF(const char *fileName) else if (attribute->component_type == cgltf_component_type_r_32f) { // Init raylib mesh bone weight to copy glTF attribute data - model.meshes[meshIndex].boneWeights = RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float)); + model.meshes[meshIndex].boneWeights = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float)); // Load 4 components of float data type into mesh.boneWeights // for cgltf_attribute_type_weights we have: @@ -6007,8 +6003,8 @@ static Model LoadGLTF(const char *fileName) if (parentBoneId >= 0) { - model.meshes[meshIndex].boneIds = RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char)); - model.meshes[meshIndex].boneWeights = RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float)); + model.meshes[meshIndex].boneIds = (unsigned char *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char)); + model.meshes[meshIndex].boneWeights = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float)); for (int vertexIndex = 0; vertexIndex < model.meshes[meshIndex].vertexCount*4; vertexIndex += 4) { @@ -6019,9 +6015,9 @@ static Model LoadGLTF(const char *fileName) } // Animated vertex data - model.meshes[meshIndex].animVertices = RL_CALLOC(model.meshes[meshIndex].vertexCount*3, sizeof(float)); + model.meshes[meshIndex].animVertices = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*3, sizeof(float)); memcpy(model.meshes[meshIndex].animVertices, model.meshes[meshIndex].vertices, model.meshes[meshIndex].vertexCount*3*sizeof(float)); - model.meshes[meshIndex].animNormals = RL_CALLOC(model.meshes[meshIndex].vertexCount*3, sizeof(float)); + model.meshes[meshIndex].animNormals = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*3, sizeof(float)); if (model.meshes[meshIndex].normals != NULL) { memcpy(model.meshes[meshIndex].animNormals, model.meshes[meshIndex].normals, model.meshes[meshIndex].vertexCount*3*sizeof(float)); @@ -6029,7 +6025,7 @@ static Model LoadGLTF(const char *fileName) // Bone Transform Matrices model.meshes[meshIndex].boneCount = model.boneCount; - model.meshes[meshIndex].boneMatrices = RL_CALLOC(model.meshes[meshIndex].boneCount, sizeof(Matrix)); + model.meshes[meshIndex].boneMatrices = (Matrix *)RL_CALLOC(model.meshes[meshIndex].boneCount, sizeof(Matrix)); for (int j = 0; j < model.meshes[meshIndex].boneCount; j++) { @@ -6219,7 +6215,7 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCo { cgltf_skin skin = data->skins[0]; *animCount = (int)data->animations_count; - animations = RL_MALLOC(data->animations_count*sizeof(ModelAnimation)); + animations = (ModelAnimation *)RL_CALLOC(data->animations_count, sizeof(ModelAnimation)); for (unsigned int i = 0; i < data->animations_count; i++) { @@ -6234,7 +6230,7 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCo cgltf_interpolation_type interpolationType; }; - struct Channels *boneChannels = RL_CALLOC(animations[i].boneCount, sizeof(struct Channels)); + struct Channels *boneChannels = (struct Channels *)RL_CALLOC(animations[i].boneCount, sizeof(struct Channels)); float animDuration = 0.0f; for (unsigned int j = 0; j < animData.channels_count; j++) @@ -6292,18 +6288,14 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCo animDuration = (t > animDuration)? t : animDuration; } - if (animData.name != NULL) - { - strncpy(animations[i].name, animData.name, sizeof(animations[i].name)); - animations[i].name[sizeof(animations[i].name) - 1] = '\0'; - } + if (animData.name != NULL) strncpy(animations[i].name, animData.name, sizeof(animations[i].name) - 1); animations[i].frameCount = (int)(animDuration*1000.0f/GLTF_ANIMDELAY) + 1; - animations[i].framePoses = RL_MALLOC(animations[i].frameCount*sizeof(Transform *)); + animations[i].framePoses = (Transform **)RL_MALLOC(animations[i].frameCount*sizeof(Transform *)); for (int j = 0; j < animations[i].frameCount; j++) { - animations[i].framePoses[j] = RL_MALLOC(animations[i].boneCount*sizeof(Transform)); + animations[i].framePoses[j] = (Transform *)RL_MALLOC(animations[i].boneCount*sizeof(Transform)); float time = ((float) j*GLTF_ANIMDELAY)/1000.0f; for (int k = 0; k < animations[i].boneCount; k++) @@ -6453,7 +6445,7 @@ static Model LoadVOX(const char *fileName) // Copy colors size = pmesh->vertexCount*sizeof(Color); - pmesh->colors = RL_MALLOC(size); + pmesh->colors = (unsigned char *)RL_MALLOC(size); memcpy(pmesh->colors, pcolors, size); // First material index @@ -6589,7 +6581,7 @@ static Model LoadM3D(const char *fileName) // If no map is provided, or we have colors defined, we allocate storage for vertex colors // M3D specs only consider vertex colors if no material is provided, however raylib uses both and mixes the colors - if ((mi == M3D_UNDEF) || vcolor) model.meshes[k].colors = RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(unsigned char)); + if ((mi == M3D_UNDEF) || vcolor) model.meshes[k].colors = (unsigned char *)RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(unsigned char)); // If no map is provided and we allocated vertex colors, set them to white if ((mi == M3D_UNDEF) && (model.meshes[k].colors != NULL)) @@ -6756,13 +6748,13 @@ static Model LoadM3D(const char *fileName) if (m3d->numbone) { model.boneCount = m3d->numbone + 1; - model.bones = RL_CALLOC(model.boneCount, sizeof(BoneInfo)); - model.bindPose = RL_CALLOC(model.boneCount, sizeof(Transform)); + model.bones = (BoneInfo *)RL_CALLOC(model.boneCount, sizeof(BoneInfo)); + model.bindPose = (Transform *)RL_CALLOC(model.boneCount, sizeof(Transform)); for (i = 0; i < (int)m3d->numbone; i++) { model.bones[i].parent = m3d->bone[i].parent; - strncpy(model.bones[i].name, m3d->bone[i].name, sizeof(model.bones[i].name)); + strncpy(model.bones[i].name, m3d->bone[i].name, sizeof(model.bones[i].name) - 1); model.bindPose[i].translation.x = m3d->vertex[m3d->bone[i].pos].x*m3d->scale; model.bindPose[i].translation.y = m3d->vertex[m3d->bone[i].pos].y*m3d->scale; model.bindPose[i].translation.z = m3d->vertex[m3d->bone[i].pos].z*m3d->scale; @@ -6808,7 +6800,7 @@ static Model LoadM3D(const char *fileName) memcpy(model.meshes[i].animNormals, model.meshes[i].normals, model.meshes[i].vertexCount*3*sizeof(float)); model.meshes[i].boneCount = model.boneCount; - model.meshes[i].boneMatrices = RL_CALLOC(model.meshes[i].boneCount, sizeof(Matrix)); + model.meshes[i].boneMatrices = (Matrix *)RL_CALLOC(model.meshes[i].boneCount, sizeof(Matrix)); for (j = 0; j < model.meshes[i].boneCount; j++) { model.meshes[i].boneMatrices[j] = MatrixIdentity(); @@ -6858,24 +6850,23 @@ static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, int *animCou return NULL; } - animations = RL_MALLOC(m3d->numaction*sizeof(ModelAnimation)); + animations = (ModelAnimation *)RL_CALLOC(m3d->numaction, sizeof(ModelAnimation)); *animCount = m3d->numaction; for (unsigned int a = 0; a < m3d->numaction; a++) { animations[a].frameCount = m3d->action[a].durationmsec/M3D_ANIMDELAY; animations[a].boneCount = m3d->numbone + 1; - animations[a].bones = RL_MALLOC((m3d->numbone + 1)*sizeof(BoneInfo)); - animations[a].framePoses = RL_MALLOC(animations[a].frameCount*sizeof(Transform *)); - strncpy(animations[a].name, m3d->action[a].name, sizeof(animations[a].name)); - animations[a].name[sizeof(animations[a].name) - 1] = '\0'; + animations[a].bones = (BoneInfo *)RL_MALLOC((m3d->numbone + 1)*sizeof(BoneInfo)); + animations[a].framePoses = (Transform **)RL_MALLOC(animations[a].frameCount*sizeof(Transform *)); + strncpy(animations[a].name, m3d->action[a].name, sizeof(animations[a].name) - 1); TRACELOG(LOG_INFO, "MODEL: [%s] animation #%i: %i msec, %i frames", fileName, a, m3d->action[a].durationmsec, animations[a].frameCount); for (i = 0; i < (int)m3d->numbone; i++) { animations[a].bones[i].parent = m3d->bone[i].parent; - strncpy(animations[a].bones[i].name, m3d->bone[i].name, sizeof(animations[a].bones[i].name)); + strncpy(animations[a].bones[i].name, m3d->bone[i].name, sizeof(animations[a].bones[i].name) - 1); } // A special, never transformed "no bone" bone, used for boneless vertices @@ -6886,7 +6877,7 @@ static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, int *animCou // regular intervals, so let the M3D SDK do the heavy lifting and calculate interpolated bones for (i = 0; i < animations[a].frameCount; i++) { - animations[a].framePoses[i] = RL_MALLOC((m3d->numbone + 1)*sizeof(Transform)); + animations[a].framePoses[i] = (Transform *)RL_MALLOC((m3d->numbone + 1)*sizeof(Transform)); m3db_t *pose = m3d_pose(m3d, a, i*M3D_ANIMDELAY); diff --git a/src/rtext.c b/src/rtext.c index ee498b119..d7818d2db 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -162,7 +162,7 @@ extern void LoadFontDefault(void) #define BIT_CHECK(a,b) ((a) & (1u << (b))) // check to see if we have allready allocated the font for an image, and if we don't need to upload, then just return - if (defaultFont.glyphs != NULL && !isGpuReady) + if (defaultFont.glyphs != NULL && !isGpuReady) return; // NOTE: Using UTF-8 encoding table for Unicode U+0000..U+00FF Basic Latin + Latin-1 Supplement @@ -260,11 +260,11 @@ extern void LoadFontDefault(void) counter++; } - + if (isGpuReady) { defaultFont.texture = LoadTextureFromImage(imFont); - + // we have already loaded the font glyph data an image, and the GPU is ready, we are done // if we don't do this, we will leak memory by reallocating the glyphs and rects if (defaultFont.glyphs != NULL) diff --git a/src/rtextures.c b/src/rtextures.c index 7a5a5e57e..ac06d6d80 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -2102,8 +2102,8 @@ void ImageBlurGaussian(Image *image, int blurSize) Color *pixels = LoadImageColors(*image); // Loop switches between pixelsCopy1 and pixelsCopy2 - Vector4 *pixelsCopy1 = RL_MALLOC((image->height)*(image->width)*sizeof(Vector4)); - Vector4 *pixelsCopy2 = RL_MALLOC((image->height)*(image->width)*sizeof(Vector4)); + Vector4 *pixelsCopy1 = (Vector4 *)RL_MALLOC((image->height)*(image->width)*sizeof(Vector4)); + Vector4 *pixelsCopy2 = (Vector4 *)RL_MALLOC((image->height)*(image->width)*sizeof(Vector4)); for (int i = 0; i < (image->height*image->width); i++) { @@ -2251,8 +2251,8 @@ void ImageKernelConvolution(Image *image, const float *kernel, int kernelSize) Color *pixels = LoadImageColors(*image); - Vector4 *imageCopy2 = RL_MALLOC((image->height)*(image->width)*sizeof(Vector4)); - Vector4 *temp = RL_MALLOC(kernelSize*sizeof(Vector4)); + Vector4 *imageCopy2 = (Vector4 *)RL_MALLOC((image->height)*(image->width)*sizeof(Vector4)); + Vector4 *temp = (Vector4 *)RL_MALLOC(kernelSize*sizeof(Vector4)); for (int i = 0; i < kernelSize; i++) { From 3e336e4470f7975af67f716d4d809441883d7eef Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 11 Jun 2025 19:52:35 +0200 Subject: [PATCH 038/242] Reviewed warning --- src/rlgl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rlgl.h b/src/rlgl.h index 686260d3b..c39d096fb 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -2425,7 +2425,7 @@ void rlLoadExtensions(void *loader) // Get supported extensions list GLint numExt = 0; - const char **extList = (char **)RL_MALLOC(512*sizeof(const char *)); // Allocate 512 strings pointers (2 KB) + const char **extList = (const char **)RL_MALLOC(512*sizeof(const char *)); // Allocate 512 strings pointers (2 KB) const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string // NOTE: We have to duplicate string because glGetString() returns a const string From 96c898852cfefe23a9f8e9658700ee9e0c2bb2eb Mon Sep 17 00:00:00 2001 From: M374LX Date: Thu, 12 Jun 2025 19:23:12 -0300 Subject: [PATCH 039/242] Update RGFW --- src/external/RGFW.h | 609 ++++++++++++++++++++++++++++++-------------- 1 file changed, 422 insertions(+), 187 deletions(-) diff --git a/src/external/RGFW.h b/src/external/RGFW.h index 1582fbfad..8bd9b5709 100644 --- a/src/external/RGFW.h +++ b/src/external/RGFW.h @@ -101,7 +101,7 @@ int main() { RGFW_window_swapBuffers(win); - glClearColor(1, 1, 1, 1); + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); } @@ -370,13 +370,13 @@ int main() { #if !defined(RGFW_NO_API) && (!defined(RGFW_BUFFER) || defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) #define RGFW_EGL #define RGFW_OPENGL - #define RGFW_UNIX #include #endif + #define RGFW_UNIX #include #endif -#if !defined(RGFW_NO_X11) && !defined(RGFW_NO_X11) && (defined(__unix__) || defined(RGFW_MACOS_X11) || defined(RGFW_X11)) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) +#if !defined(RGFW_NO_X11) && (defined(__unix__) || defined(RGFW_MACOS_X11) || defined(RGFW_X11)) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) #define RGFW_MACOS_X11 #define RGFW_X11 #define RGFW_UNIX @@ -945,6 +945,8 @@ RGFWDEF void RGFW_window_restore(RGFW_window* win); /*!< restore the window from RGFWDEF void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating); /*!< make the window a floating window */ RGFWDEF void RGFW_window_setOpacity(RGFW_window* win, u8 opacity); /*!< sets the opacity of a window */ +RGFWDEF RGFW_bool RGFW_window_opengl_isSoftware(RGFW_window* win); + /*! if the window should have a border or not (borderless) based on bool value of `border` */ RGFWDEF void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border); RGFWDEF RGFW_bool RGFW_window_borderless(RGFW_window* win); @@ -1278,7 +1280,7 @@ typedef void (*RGFW_proc)(void); /* function pointer equivalent of void* */ /*! native API functions */ #if defined(RGFW_OPENGL) || defined(RGFW_EGL) /*!< create an opengl context for the RGFW window, run by createWindow by default (unless the RGFW_windowNoInitAPI is included) */ -RGFWDEF void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software); +RGFWDEF void RGFW_window_initOpenGL(RGFW_window* win); /*!< called by `RGFW_window_close` by default (unless the RGFW_windowNoInitAPI is set) */ RGFWDEF void RGFW_window_freeOpenGL(RGFW_window* win); @@ -1443,12 +1445,13 @@ typedef RGFW_ENUM(u8, RGFW_key) { RGFW_period = '.', RGFW_comma = ',', RGFW_slash = '/', - RGFW_bracket = '{', - RGFW_closeBracket = '}', - RGFW_semicolon = ';', + RGFW_bracket = '[', + RGFW_closeBracket = ']', + RGFW_semicolon = ';', RGFW_apostrophe = '\'', RGFW_backSlash = '\\', RGFW_return = '\n', + RGFW_enter = RGFW_return, RGFW_delete = '\177', /* 127 */ @@ -1501,10 +1504,18 @@ typedef RGFW_ENUM(u8, RGFW_key) { RGFW_KP_Period, RGFW_KP_Return, RGFW_scrollLock, - RGFW_keyLast = 256 /* padding for alignment ~(175 by default) */ + RGFW_printScreen, + RGFW_pause, + RGFW_keyLast = 256 /* padding for alignment ~(175 by default) */ }; + +/*! converts api keycode to the RGFW unmapped/physical key */ RGFWDEF u32 RGFW_apiKeyToRGFW(u32 keycode); +/*! converts RGFW keycode to the unmapped/physical api key */ +RGFWDEF u32 RGFW_rgfwToApiKey(u32 keycode); +/*! converts RGFW keycode to the mapped keychar */ +RGFWDEF u8 RGFW_rgfwToKeyChar(u32 keycode); typedef RGFW_ENUM(u8, RGFW_mouseIcons) { RGFW_mouseNormal = 0, @@ -1541,8 +1552,10 @@ RGFW_bool RGFW_usingWayland(void) { return RGFW_useWaylandBool; } #if !defined(RGFW_NO_X11) && defined(RGFW_WAYLAND) #define RGFW_GOTO_WAYLAND(fallback) if (RGFW_useWaylandBool && fallback == 0) goto wayland +#define RGFW_WAYLAND_LABEL wayland:; #else #define RGFW_GOTO_WAYLAND(fallback) +#define RGFW_WAYLAND_LABEL #endif char* RGFW_clipboard_data; @@ -1626,11 +1639,6 @@ u64 RGFW_getTimeNS(void) { RGFW_IMPLEMENTATION starts with generic RGFW defines This is the start of keycode data - - Why not use macros instead of the numbers itself? - Windows -> Not all scancodes keys are macros - Linux -> Only symcodes are values, (XK_0 - XK_1, XK_a - XK_z) are larger than 0xFF00, I can't find any way to work with them without making the array an unreasonable size - MacOS -> windows and linux already don't have keycodes as macros, so there's no point */ @@ -1650,6 +1658,8 @@ This is the start of keycode data #define RGFW_MAP RGFW_keycodes #endif +u32 RGFW_apiKeycodes[RGFW_keyLast] = { 0 }; + u8 RGFW_keycodes [RGFW_OS_BASED_VALUE(256, 512, 128, 256)] = { #if defined(__cplusplus) || defined(RGFW_C89) 0 @@ -1748,7 +1758,7 @@ void RGFW_init_keys(void) { RGFW_MAP [RGFW_OS_BASED_VALUE(75, 0x043, 102, DOM_VK_F9)] = RGFW_F9 RGFW_NEXT RGFW_MAP [RGFW_OS_BASED_VALUE(76, 0x044, 110, DOM_VK_F10)] = RGFW_F10 RGFW_NEXT RGFW_MAP [RGFW_OS_BASED_VALUE(95, 0x057, 104, DOM_VK_F11)] = RGFW_F11 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(96, 0x058, 112, DOM_VK_F12)] = RGFW_F12 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(96, 0x058, 111, DOM_VK_F12)] = RGFW_F12 RGFW_NEXT RGFW_MAP [RGFW_OS_BASED_VALUE(111, 0x148, 126, DOM_VK_UP)] = RGFW_up RGFW_NEXT RGFW_MAP [RGFW_OS_BASED_VALUE(116, 0x150, 125, DOM_VK_DOWN)] = RGFW_down RGFW_NEXT RGFW_MAP [RGFW_OS_BASED_VALUE(113, 0x14B, 123, DOM_VK_LEFT)] = RGFW_left RGFW_NEXT @@ -1760,6 +1770,8 @@ void RGFW_init_keys(void) { RGFW_MAP [RGFW_OS_BASED_VALUE(9, 0x001, 53, DOM_VK_ESCAPE)] = RGFW_escape RGFW_NEXT RGFW_MAP [RGFW_OS_BASED_VALUE(110, 0x147, 116, DOM_VK_HOME)] = RGFW_home RGFW_NEXT RGFW_MAP [RGFW_OS_BASED_VALUE(78, 0x046, 107, DOM_VK_SCROLL_LOCK)] = RGFW_scrollLock RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(107, 0x137, 105, DOM_VK_PRINTSCREEN)] = RGFW_printScreen RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(128, 0x045, 113, DOM_VK_PAUSE)] = RGFW_pause RGFW_NEXT #if defined(__cplusplus) || defined(RGFW_C89) } #else @@ -1782,6 +1794,25 @@ u32 RGFW_apiKeyToRGFW(u32 keycode) { return RGFW_keycodes[keycode]; } + +u32 RGFW_rgfwToApiKey(u32 keycode) { + if (RGFW_apiKeycodes[RGFW_backtick] != RGFW_OS_BASED_VALUE(49, 0x029, 50, DOM_VK_BACK_QUOTE)) { + for (u32 i = 0; i < RGFW_keyLast; i++) { + for (u32 y = 0; y < sizeof(RGFW_keycodes); y++) { + if (RGFW_keycodes[y] == i) { + RGFW_apiKeycodes[i] = y; + break; + } + } + } + } + + /* make sure the key isn't out of bounds */ + if (keycode > sizeof(RGFW_apiKeycodes) / sizeof(u32)) + return 0; + + return RGFW_apiKeycodes[keycode]; +} #endif /* RGFW_CUSTOM_BACKEND */ typedef struct { @@ -1947,7 +1978,7 @@ typedef struct RGFW_globalStruct { #ifdef RGFW_WAYLAND struct wl_display* wl_display; #endif - #if defined(RGFW_X11) || defined(RGFW_WINDOWS) + #if defined(RGFW_X11) || defined(RGFW_WINDOWS) || defined(RGFW_WAYLAND) RGFW_mouse* hiddenMouse; #endif RGFW_event events[RGFW_MAX_EVENTS]; @@ -1964,7 +1995,7 @@ RGFW_globalStruct _RGFW; void RGFW_eventQueuePush(RGFW_event event) { if (_RGFW.eventLen >= RGFW_MAX_EVENTS) return; _RGFW.events[_RGFW.eventLen] = event; - _RGFW.eventLen++; + _RGFW.eventLen++; } RGFW_event* RGFW_eventQueuePop(RGFW_window* win) { @@ -1973,18 +2004,19 @@ RGFW_event* RGFW_eventQueuePop(RGFW_window* win) { ev = (RGFW_event*)&_RGFW.events[_RGFW.eventIndex]; - _RGFW.eventLen--; - if (_RGFW.eventLen && _RGFW.eventIndex < (_RGFW.eventLen - 1)) + _RGFW.eventLen--; + if (_RGFW.eventLen >= 0 && _RGFW.eventIndex < _RGFW.eventLen) { _RGFW.eventIndex++; - else if (_RGFW.eventLen == 0) - _RGFW.eventIndex = 0; + } else if (_RGFW.eventLen == 0) { + _RGFW.eventIndex = 0; + } if (ev->_win != win && ev->_win != NULL) { - RGFW_eventQueuePush(*ev); - return NULL; + RGFW_eventQueuePush(*ev); + return NULL; } - ev->droppedFilesCount = win->event.droppedFilesCount; + ev->droppedFilesCount = win->event.droppedFilesCount; ev->droppedFiles = win->event.droppedFiles; return ev; } @@ -2010,7 +2042,7 @@ RGFW_event* RGFW_window_checkEventCore(RGFW_window* win) { if (ev != NULL) { if (ev->type == RGFW_quit) RGFW_window_setShouldClose(win, RGFW_TRUE); win->event = *ev; - } + } else return NULL; return &win->event; @@ -2095,6 +2127,10 @@ void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags flags) { win->_flags = flags | (win->_flags & RGFW_INTERNAL_FLAGS); } +RGFW_bool RGFW_window_opengl_isSoftware(RGFW_window* win) { + return RGFW_BOOL(win->_flags |= RGFW_windowOpenglSoftware); +} + RGFW_bool RGFW_window_isInFocus(RGFW_window* win) { #ifdef RGFW_WASM return RGFW_TRUE; @@ -2411,6 +2447,28 @@ RGFW_bool RGFW_window_borderless(RGFW_window* win) { RGFW_bool RGFW_window_isFullscreen(RGFW_window* win){ return RGFW_BOOL(win->_flags & RGFW_windowFullscreen); } RGFW_bool RGFW_window_allowsDND(RGFW_window* win) { return RGFW_BOOL(win->_flags & RGFW_windowAllowDND); } +void RGFW_window_focusLost(RGFW_window* win) { + /* standard routines for when a window looses focus */ + _RGFW.root->_flags &= ~(u32)RGFW_windowFocus; + if ((win->_flags & RGFW_windowFullscreen)) + RGFW_window_minimize(win); + + for (size_t key = 0; key < RGFW_keyLast; key++) { + if (RGFW_isPressed(NULL, (u8)key) == RGFW_FALSE) continue; + RGFW_keyboard[key].current = RGFW_FALSE; + u8 keyChar = RGFW_rgfwToKeyChar((u32)key); + RGFW_keyCallback(win, (u8)key, keyChar, win->event.keyMod, RGFW_FALSE); + RGFW_eventQueuePushEx(e.type = RGFW_keyReleased; + e.key = (u8)key; + e.keyChar = keyChar; + e.repeat = RGFW_FALSE; + e.keyMod = win->event.keyMod; + e._win = win); + } + + RGFW_resetKey(); +} + #ifndef RGFW_WINDOWS void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow) { RGFW_setBit(&win->_flags, RGFW_windowAllowDND, allow); @@ -2425,7 +2483,7 @@ void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow) { struct timespec; #endif -#if defined(RGFW_X11) || defined(RGFW_WINDOWS) +#if defined(RGFW_WAYLAND) || defined(RGFW_X11) || defined(RGFW_WINDOWS) void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show) { RGFW_window_showMouseFlags(win, show); if (show == 0) @@ -2587,9 +2645,8 @@ RGFW_bool RGFW_extensionSupported(const char* extension, size_t len) { MacOS and Windows do this using a structure called a "pixel format" X11 calls it a "Visual" This function returns the attributes for the format we want */ -i32* RGFW_initFormatAttribs(u32 useSoftware); -i32* RGFW_initFormatAttribs(u32 useSoftware) { - RGFW_UNUSED(useSoftware); +i32* RGFW_initFormatAttribs(void); +i32* RGFW_initFormatAttribs(void) { static i32 attribs[] = { #if defined(RGFW_X11) || defined(RGFW_WINDOWS) RGFW_GL_RENDER_TYPE, @@ -2670,7 +2727,7 @@ i32* RGFW_initFormatAttribs(u32 useSoftware) { #endif #ifdef RGFW_MACOS - if (useSoftware) { + if (_RGFW.root->_flags & RGFW_windowOpenglSoftware) { RGFW_GL_ADD_ATTRIB(70, kCGLRendererGenericFloatID); } else { attribs[index] = RGFW_GL_RENDER_TYPE; @@ -2747,8 +2804,7 @@ i32* RGFW_initFormatAttribs(u32 useSoftware) { #endif -void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { - RGFW_UNUSED(software); +void RGFW_window_initOpenGL(RGFW_window* win) { #if defined(RGFW_LINK_EGL) eglInitializeSource = (PFNEGLINITIALIZEPROC) eglGetProcAddress("eglInitialize"); eglGetConfigsSource = (PFNEGLGETCONFIGSPROC) eglGetProcAddress("eglGetConfigs"); @@ -2791,10 +2847,15 @@ void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { #elif defined(RGFW_WAYLAND) if (RGFW_useWaylandBool) win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.wl_display); - else + else + #endif + #ifdef RGFW_X11 win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.display); - #else - win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.display); + #else + {} + #endif + #if !defined(RGFW_WAYLAND) && !defined(RGFW_WINDOWS) && !defined(RGFW_X11) + win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.display); #endif EGLint major, minor; @@ -2852,8 +2913,13 @@ void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { if (RGFW_useWaylandBool) win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.eglWindow, NULL); else - win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.window, NULL); - #else + #endif + #ifdef RGFW_X11 + win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.window, NULL); + #else + {} + #endif + #if !defined(RGFW_X11) && !defined(RGFW_WAYLAND) && !defined(RGFW_MACOS) win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.window, NULL); #endif @@ -3000,7 +3066,7 @@ VkResult RGFW_window_createVKSurface(RGFW_window* win, VkInstance instance, VkSu return vkCreateXlibSurfaceKHR(instance, &x11, NULL, surface); #endif #if defined(RGFW_WAYLAND) -wayland: +RGFW_WAYLAND_LABEL VkWaylandSurfaceCreateInfoKHR wayland = { VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, 0, 0, (struct wl_display*) win->src.wl_display, (struct wl_surface*) win->src.surface }; return vkCreateWaylandSurfaceKHR(instance, &wayland, NULL, surface); #elif defined(RGFW_WINDOWS) @@ -3029,7 +3095,7 @@ RGFW_bool RGFW_getVKPresentationSupport(VkInstance instance, VkPhysicalDevice ph return out; #endif #if defined(RGFW_WAYLAND) -wayland: +RGFW_WAYLAND_LABEL RGFW_bool wlout = vkGetPhysicalDeviceWaylandPresentationSupportKHR(physicalDevice, queueFamilyIndex, _RGFW.wl_display); return wlout; #elif defined(RGFW_WINDOWS) @@ -3362,16 +3428,17 @@ void pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial, uin RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(serial); RGFW_ASSERT(RGFW_mouse_win != NULL); - u32 b = (button - 0x110) + 1; + u32 b = (button - 0x110); /* flip right and middle button codes */ - if (b == 2) b = 3; - else if (b == 3) b = 2; + if (b == 1) b = 2; + else if (b == 2) b = 1; RGFW_mouseButtons[b].prev = RGFW_mouseButtons[b].current; RGFW_mouseButtons[b].current = RGFW_BOOL(state); - RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonPressed + RGFW_BOOL(state); + RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonReleased - RGFW_BOOL(state); + e.point = RGFW_mouse_win->event.point; e.button = (u8)b; e._win = RGFW_mouse_win); RGFW_mouseButtonCallback(RGFW_mouse_win, (u8)b, 0, RGFW_BOOL(state)); @@ -3380,9 +3447,10 @@ void pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_ RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(axis); RGFW_ASSERT(RGFW_mouse_win != NULL); - double scroll = wl_fixed_to_double(value); + double scroll = - wl_fixed_to_double(value); RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonPressed; + e.point = RGFW_mouse_win->event.point; e.button = RGFW_mouseScrollUp + (scroll < 0); e.scroll = scroll; e._win = RGFW_mouse_win); @@ -3410,11 +3478,10 @@ void keyboard_enter (void *data, struct wl_keyboard *keyboard, uint32_t serial, RGFW_key_win = (RGFW_window*)wl_surface_get_user_data(surface); RGFW_key_win->_flags |= RGFW_windowFocus; - RGFW_eventQueuePushEx(e.type = RGFW_focusIn, e._win = RGFW_key_win); + RGFW_eventQueuePushEx(e.type = RGFW_focusIn; e._win = RGFW_key_win); RGFW_focusCallback(RGFW_key_win, RGFW_TRUE); - RGFW_resetKey(); - if ((win->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(win, RGFW_AREA(win->r.w, win->r.h)); + if ((RGFW_key_win->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(RGFW_key_win, RGFW_AREA(RGFW_key_win->r.w, RGFW_key_win->r.h)); } void keyboard_leave (void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); @@ -3424,8 +3491,8 @@ void keyboard_leave (void *data, struct wl_keyboard *keyboard, uint32_t serial, RGFW_key_win = NULL; RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e._win = win); - win->_flags &= ~(u32)RGFW_windowFocus; RGFW_focusCallback(win, RGFW_FALSE); + RGFW_window_focusLost(win); } void keyboard_key (void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); @@ -3456,9 +3523,9 @@ int, int))&RGFW_doNothing}; void seat_capabilities (void *data, struct wl_seat *seat, uint32_t capabilities) { RGFW_UNUSED(data); - struct wl_pointer_listener pointer_listener = (struct wl_pointer_listener){&pointer_enter, &pointer_leave, &pointer_motion, &pointer_button, &pointer_axis, (void (*)(void *, struct wl_pointer *))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t, uint32_t))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t, int32_t))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t, int32_t))&RGFW_doNothing}; + static struct wl_pointer_listener pointer_listener = {&pointer_enter, &pointer_leave, &pointer_motion, &pointer_button, &pointer_axis, (void (*)(void *, struct wl_pointer *))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t, uint32_t))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t, int32_t))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t, int32_t))&RGFW_doNothing, (void (*)(void*, struct wl_pointer*, uint32_t, uint32_t))&RGFW_doNothing}; - if (capabilities & WL_SEAT_CAPABILITY_POINTER) { + if (capabilities & WL_SEAT_CAPABILITY_POINTER) { struct wl_pointer *pointer = wl_seat_get_pointer (seat); wl_pointer_add_listener (pointer, &pointer_listener, NULL); } @@ -3620,16 +3687,16 @@ Atom RGFW_XUTF8_STRING = 0; Atom wm_delete_window = 0, RGFW_XCLIPBOARD = 0; -#if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) +#if defined(RGFW_X11) && !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) typedef XcursorImage* (*PFN_XcursorImageCreate)(int, int); typedef void (*PFN_XcursorImageDestroy)(XcursorImage*); typedef Cursor(*PFN_XcursorImageLoadCursor)(Display*, const XcursorImage*); #endif -#ifdef RGFW_OPENGL +#if defined(RGFW_OPENGL) && defined(RGFW_X11) typedef GLXContext(*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); #endif -#if !defined(RGFW_NO_X11_XI_PRELOAD) +#if !defined(RGFW_NO_X11_XI_PRELOAD) && defined(RGFW_X11) typedef int (* PFN_XISelectEvents)(Display*,Window,XIEventMask*,int); PFN_XISelectEvents XISelectEventsSRC = NULL; #define XISelectEvents XISelectEventsSRC @@ -3637,7 +3704,7 @@ Atom wm_delete_window = 0, RGFW_XCLIPBOARD = 0; void* X11Xihandle = NULL; #endif -#if !defined(RGFW_NO_X11_EXT_PRELOAD) +#if !defined(RGFW_NO_X11_EXT_PRELOAD) && defined(RGFW_X11) typedef void (* PFN_XSyncIntToValue)(XSyncValue*, int); PFN_XSyncIntToValue XSyncIntToValueSRC = NULL; #define XSyncIntToValue XSyncIntToValueSRC @@ -3660,7 +3727,7 @@ Atom wm_delete_window = 0, RGFW_XCLIPBOARD = 0; void* X11XEXThandle = NULL; #endif -#if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) +#if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) && defined(RGFW_X11) PFN_XcursorImageLoadCursor XcursorImageLoadCursorSRC = NULL; PFN_XcursorImageCreate XcursorImageCreateSRC = NULL; PFN_XcursorImageDestroy XcursorImageDestroySRC = NULL; @@ -3672,10 +3739,10 @@ Atom wm_delete_window = 0, RGFW_XCLIPBOARD = 0; void* X11Cursorhandle = NULL; #endif +#ifdef RGFW_X11 const char* RGFW_instName = NULL; -void RGFW_setXInstName(const char* name) { - RGFW_instName = name; -} +void RGFW_setXInstName(const char* name) { RGFW_instName = name; } +#endif #if defined(RGFW_OPENGL) && !defined(RGFW_EGL) RGFW_bool RGFW_extensionSupportedPlatform(const char * extension, size_t len) { @@ -3706,7 +3773,7 @@ void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area) { ); #endif #ifdef RGFW_WAYLAND - wayland: {} + RGFW_WAYLAND_LABEL {} u32 size = (u32)(win->r.w * win->r.h * 4); int fd = create_shm_file(size); if (fd < 0) { @@ -3750,7 +3817,7 @@ void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area) { #endif #else #ifdef RGFW_WAYLAND - wayland:{} + RGFW_WAYLAND_LABEL{} #endif RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); @@ -3786,7 +3853,7 @@ void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL RGFW_UNUSED(win); RGFW_UNUSED(border); #endif } @@ -3806,7 +3873,7 @@ RGFW_GOTO_WAYLAND(0); XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL RGFW_UNUSED(win); #endif } @@ -3829,7 +3896,7 @@ RGFW_GOTO_WAYLAND(0); RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (i32)(r.w / 2), win->r.y + (i32)(r.h / 2))); #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL RGFW_UNUSED(win); RGFW_UNUSED(r); #endif } @@ -3840,11 +3907,10 @@ RGFW_GOTO_WAYLAND(0); if (ptr != NULL) memcpy(&name##SRC, &ptr, sizeof(PFN_##name)); \ } - - -void RGFW_window_getVisual(RGFW_window* win, RGFW_bool software) { +#ifdef RGFW_X11 +void RGFW_window_getVisual(RGFW_window* win) { #if defined(RGFW_OPENGL) && !defined(RGFW_EGL) - i32* visual_attribs = RGFW_initFormatAttribs(software); + i32* visual_attribs = RGFW_initFormatAttribs(); i32 fbcount; GLXFBConfig* fbc = glXChooseFBConfig(win->src.display, DefaultScreen(win->src.display), visual_attribs, &fbcount); @@ -3893,11 +3959,16 @@ void RGFW_window_getVisual(RGFW_window* win, RGFW_bool software) { if (best_samples < RGFW_GL_HINTS[RGFW_glSamples]) RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, RGFW_DEBUG_CTX(win, 0), "Failed to load matching sampiling"); + int configCaveat; + if (glXGetFBConfigAttrib(win->src.display, win->src.bestFbc, GLX_CONFIG_CAVEAT, &configCaveat) == Success && + configCaveat == GLX_SLOW_CONFIG) { + win->_flags |= RGFW_windowOpenglSoftware; + } + XFree(fbc); win->src.visual = *vi; XFree(vi); #else - RGFW_UNUSED(software); win->src.visual.visual = DefaultVisual(win->src.display, DefaultScreen(win->src.display)); win->src.visual.depth = DefaultDepth(win->src.display, DefaultScreen(win->src.display)); if (win->_flags & RGFW_windowTransparent) { @@ -3907,10 +3978,9 @@ void RGFW_window_getVisual(RGFW_window* win, RGFW_bool software) { } #endif } - +#endif #ifndef RGFW_EGL -void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { - RGFW_UNUSED(software); +void RGFW_window_initOpenGL(RGFW_window* win) { #ifdef RGFW_OPENGL i32 context_attribs[7] = { 0, 0, 0, 0, 0, 0, 0 }; context_attribs[0] = GLX_CONTEXT_PROFILE_MASK_ARB; @@ -3948,11 +4018,11 @@ void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { win->src.ctx = glXCreateContext(win->src.display, &win->src.visual, ctx, True); } } - - glXMakeCurrent(win->src.display, (Drawable) win->src.window, (GLXContext) win->src.ctx); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context initalized"); + + glXMakeCurrent(win->src.display, (Drawable) win->src.window, (GLXContext) win->src.ctx); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context initalized"); #else - RGFW_UNUSED(win); RGFW_UNUSED(software); + RGFW_UNUSED(win); #endif } @@ -4064,7 +4134,7 @@ i32 RGFW_init(void) { } #endif #ifdef RGFW_WAYLAND -wayland: +RGFW_WAYLAND_LABEL _RGFW.wl_display = wl_display_connect(NULL); #endif _RGFW.windowCount = 0; @@ -4084,7 +4154,7 @@ RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowF i64 event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | FocusChangeMask | LeaveWindowMask | EnterWindowMask | ExposureMask; /*!< X11 events accepted */ win->src.display = XOpenDisplay(NULL); - RGFW_window_getVisual(win, RGFW_BOOL(flags & RGFW_windowOpenglSoftware)); + RGFW_window_getVisual(win); /* make X window attrubutes */ XSetWindowAttributes swa; @@ -4164,7 +4234,7 @@ RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowF #endif if ((flags & RGFW_windowNoInitAPI) == 0) { - RGFW_window_initOpenGL(win, RGFW_BOOL(flags & RGFW_windowOpenglSoftware)); + RGFW_window_initOpenGL(win); RGFW_window_initBuffer(win); } @@ -4178,7 +4248,7 @@ RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowF return win; /*return newly created window */ #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningWayland, RGFW_DEBUG_CTX(win, 0), "RGFW Wayland support is experimental"); win->src.wl_display = _RGFW.wl_display; @@ -4194,8 +4264,9 @@ RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowF #ifdef RGFW_X11 + win->src.display = _RGFW.display; win->src.window = _RGFW.helperWindow; - XMapWindow(win->src.display, win->src.window); + XMapWindow(_RGFW.display, win->src.window); XFlush(win->src.display); if (wm_delete_window == 0) { wm_delete_window = XInternAtom(win->src.display, "WM_DELETE_WINDOW", False); @@ -4259,7 +4330,7 @@ RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowF while (wl_display_dispatch(win->src.wl_display) != -1 && !RGFW_wl_configured) { } if ((flags & RGFW_windowNoInitAPI) == 0) { - RGFW_window_initOpenGL(win, RGFW_BOOL(flags & RGFW_windowOpenglSoftware)); + RGFW_window_initOpenGL(win); RGFW_window_initBuffer(win); } struct wl_callback* callback = wl_surface_frame(win->src.surface); @@ -4271,7 +4342,8 @@ RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowF if (flags & RGFW_windowScaleToMonitor) RGFW_window_scaleToMonitor(win); #endif - + + RGFW_window_setName(win, name); RGFW_window_setMouseDefault(win); RGFW_window_setFlags(win, flags); return win; /* return newly created window */ @@ -4287,25 +4359,31 @@ RGFW_area RGFW_getScreenSize(void) { return RGFW_AREA(scrn->width, scrn->height); #endif #ifdef RGFW_WAYLAND - wayland: return RGFW_AREA(_RGFW.root->r.w, _RGFW.root->r.h); /* TODO */ + RGFW_WAYLAND_LABEL return RGFW_AREA(_RGFW.root->r.w, _RGFW.root->r.h); /* TODO */ #endif } RGFW_point RGFW_getGlobalMousePoint(void) { RGFW_init(); - RGFW_point RGFWMouse; - + RGFW_point RGFWMouse = RGFW_POINT(0, 0); + RGFW_GOTO_WAYLAND(1); +#ifdef RGFW_X11 i32 x, y; u32 z; Window window1, window2; XQueryPointer(_RGFW.display, XDefaultRootWindow(_RGFW.display), &window1, &window2, &RGFWMouse.x, &RGFWMouse.y, &x, &y, &z); - return RGFWMouse; +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + return RGFWMouse; +#endif } RGFWDEF void RGFW_XHandleClipboardSelection(XEvent* event); -void RGFW_XHandleClipboardSelection(XEvent* event) { - RGFW_LOAD_ATOM(ATOM_PAIR); +void RGFW_XHandleClipboardSelection(XEvent* event) { RGFW_UNUSED(event); +#ifdef RGFW_X11 + RGFW_LOAD_ATOM(ATOM_PAIR); RGFW_LOAD_ATOM(MULTIPLE); RGFW_LOAD_ATOM(TARGETS); RGFW_LOAD_ATOM(SAVE_TARGETS); @@ -4365,6 +4443,7 @@ void RGFW_XHandleClipboardSelection(XEvent* event) { reply.xselection.time = request->time; XSendEvent(_RGFW.display, request->requestor, False, 0, &reply); +#endif } char* RGFW_strtok(char* str, const char* delimStr); @@ -4419,6 +4498,31 @@ char* RGFW_strtok(char* str, const char* delimStr) { i32 RGFW_XHandleClipboardSelectionHelper(void); + +u8 RGFW_rgfwToKeyChar(u32 key) { + u32 keycode = RGFW_rgfwToApiKey(key); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + Window root = DefaultRootWindow(_RGFW.display); + Window ret_root, ret_child; + int root_x, root_y, win_x, win_y; + unsigned int mask; + XQueryPointer(_RGFW.display, root, &ret_root, &ret_child, &root_x, &root_y, &win_x, &win_y, &mask); + KeySym sym = (KeySym)XkbKeycodeToKeysym(_RGFW.display, (KeyCode)keycode, 0, (KeyCode)mask & ShiftMask ? 1 : 0); + + if ((mask & LockMask) && sym >= XK_a && sym <= XK_z) + sym = (mask & ShiftMask) ? sym + 32 : sym - 32; + if ((u8)sym != (u32)sym) + sym = 0; + + return (u8)sym; +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL RGFW_UNUSED(keycode); + return (u8)key; +#endif +} + RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { RGFW_XHandleClipboardSelectionHelper(); @@ -4479,21 +4583,14 @@ RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { /* set event key data */ win->event.key = (u8)RGFW_apiKeyToRGFW(E.xkey.keycode); - KeySym sym = (KeySym)XkbKeycodeToKeysym(win->src.display, (KeyCode)E.xkey.keycode, 0, (KeyCode)E.xkey.state & ShiftMask ? 1 : 0); - - if ((E.xkey.state & LockMask) && sym >= XK_a && sym <= XK_z) - sym = (E.xkey.state & ShiftMask) ? sym + 32 : sym - 32; - if ((u8)sym != (u32)sym) - sym = 0; - - win->event.keyChar = (u8)sym; + win->event.keyChar = (u8)RGFW_rgfwToKeyChar(win->event.key); RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; /* get keystate data */ win->event.type = (E.type == KeyPress) ? RGFW_keyPressed : RGFW_keyReleased; - XKeyboardState keystate; + XKeyboardState keystate; XGetKeyboardControl(win->src.display, &keystate); RGFW_keyboard[win->event.key].current = (E.type == KeyPress); @@ -4813,12 +4910,13 @@ RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { XFree(data); if (version >= 2) { - XEvent new_reply = { ClientMessage }; - new_reply.xclient.format = 32; - new_reply.xclient.message_type = XdndFinished; + XEvent new_reply = { ClientMessage }; + new_reply.xclient.window = source; + new_reply.xclient.message_type = XdndFinished; + new_reply.xclient.format = 32; new_reply.xclient.data.l[1] = (long int)result; new_reply.xclient.data.l[2] = (long int)XdndActionCopy; - XSendEvent(win->src.display, source, False, NoEventMask, &new_reply); + XSendEvent(win->src.display, source, False, NoEventMask, &new_reply); XFlush(win->src.display); } break; @@ -4832,16 +4930,12 @@ RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { RGFW_focusCallback(win, 1); - RGFW_resetKey(); if ((win->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(win, RGFW_AREA(win->r.w, win->r.h)); break; case FocusOut: - if ((win->_flags & RGFW_windowFullscreen)) - RGFW_window_minimize(win); - - win->_flags &= ~(u32)RGFW_windowFocus; - win->event.type = RGFW_focusOut; + win->event.type = RGFW_focusOut; RGFW_focusCallback(win, 0); + RGFW_window_focusLost(win); break; case PropertyNotify: RGFW_window_checkMode(win); break; case EnterNotify: { @@ -4887,7 +4981,7 @@ RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { else return NULL; #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL if ((win->_flags & RGFW_windowHide) == 0) wl_display_roundtrip(win->src.wl_display); return NULL; @@ -4903,7 +4997,7 @@ void RGFW_window_move(RGFW_window* win, RGFW_point v) { XMoveWindow(win->src.display, win->src.window, v.x, v.y); #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL RGFW_ASSERT(win != NULL); if (win->src.compositor) { @@ -4936,7 +5030,7 @@ void RGFW_window_resize(RGFW_window* win, RGFW_area a) { } #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL if (win->src.compositor) { xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->r.w, win->r.h); #ifdef RGFW_OPENGL @@ -4948,10 +5042,11 @@ void RGFW_window_resize(RGFW_window* win, RGFW_area a) { void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(0); if (a.w == 0 && a.h == 0) return; - +#ifdef RGFW_X11 XSizeHints hints; long flags; @@ -4963,11 +5058,17 @@ void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { hints.min_aspect.y = hints.max_aspect.y = (i32)a.h; XSetWMNormalHints(win->src.display, win->src.window, &hints); + return; +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL +#endif } void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { RGFW_ASSERT(win != NULL); - + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 long flags; XSizeHints hints; RGFW_MEMSET(&hints, 0, sizeof(XSizeHints)); @@ -4980,11 +5081,17 @@ void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { hints.min_height = (i32)a.h; XSetWMNormalHints(win->src.display, win->src.window, &hints); + return; +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL RGFW_UNUSED(a); +#endif } void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { RGFW_ASSERT(win != NULL); - + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 long flags; XSizeHints hints; RGFW_MEMSET(&hints, 0, sizeof(XSizeHints)); @@ -4997,8 +5104,13 @@ void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { hints.max_height = (i32)a.h; XSetWMNormalHints(win->src.display, win->src.window, &hints); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL RGFW_UNUSED(a); +#endif } +#ifdef RGFW_X11 void RGFW_toggleXMaximized(RGFW_window* win, RGFW_bool maximized); void RGFW_toggleXMaximized(RGFW_window* win, RGFW_bool maximized) { RGFW_ASSERT(win != NULL); @@ -5019,33 +5131,54 @@ void RGFW_toggleXMaximized(RGFW_window* win, RGFW_bool maximized) { XSendEvent(win->src.display, DefaultRootWindow(win->src.display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); } +#endif void RGFW_window_maximize(RGFW_window* win) { win->_oldRect = win->r; - RGFW_toggleXMaximized(win, 1); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + RGFW_toggleXMaximized(win, 1); + return; +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + return; +#endif } void RGFW_window_focus(RGFW_window* win) { RGFW_ASSERT(win); - + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 XWindowAttributes attr; XGetWindowAttributes(win->src.display, win->src.window, &attr); if (attr.map_state != IsViewable) return; XSetInputFocus(win->src.display, win->src.window, RevertToPointerRoot, CurrentTime); XFlush(win->src.display); +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL; +#endif } void RGFW_window_raise(RGFW_window* win) { RGFW_ASSERT(win); - XRaiseWindow(win->src.display, win->src.window); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + XRaiseWindow(win->src.display, win->src.window); XMapRaised(win->src.display, win->src.window); +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL; +#endif } +#ifdef RGFW_X11 void RGFW_window_setXAtom(RGFW_window* win, Atom netAtom, RGFW_bool fullscreen); void RGFW_window_setXAtom(RGFW_window* win, Atom netAtom, RGFW_bool fullscreen) { RGFW_ASSERT(win != NULL); - RGFW_LOAD_ATOM(_NET_WM_STATE); + RGFW_LOAD_ATOM(_NET_WM_STATE); XEvent xev = {0}; xev.xclient.type = ClientMessage; @@ -5060,61 +5193,93 @@ void RGFW_window_setXAtom(RGFW_window* win, Atom netAtom, RGFW_bool fullscreen) XSendEvent(win->src.display, DefaultRootWindow(win->src.display), False, SubstructureNotifyMask | SubstructureRedirectMask, &xev); } +#endif void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { RGFW_ASSERT(win != NULL); - if (fullscreen) { + RGFW_GOTO_WAYLAND(0); + if (fullscreen) { win->_flags |= RGFW_windowFullscreen; win->_oldRect = win->r; } else win->_flags &= ~(u32)RGFW_windowFullscreen; - +#ifdef RGFW_X11 RGFW_LOAD_ATOM(_NET_WM_STATE_FULLSCREEN); RGFW_window_setXAtom(win, _NET_WM_STATE_FULLSCREEN, fullscreen); XRaiseWindow(win->src.display, win->src.window); XMapRaised(win->src.display, win->src.window); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL; +#endif } void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { RGFW_ASSERT(win != NULL); - + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 RGFW_LOAD_ATOM(_NET_WM_STATE_ABOVE); RGFW_window_setXAtom(win, _NET_WM_STATE_ABOVE, floating); +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL RGFW_UNUSED(floating); +#endif } void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 const u32 value = (u32) (0xffffffffu * (double) opacity); RGFW_LOAD_ATOM(NET_WM_WINDOW_OPACITY); XChangeProperty(win->src.display, win->src.window, NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) &value, 1); +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL RGFW_UNUSED(opacity); +#endif } void RGFW_window_minimize(RGFW_window* win) { RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(0); if (RGFW_window_isMaximized(win)) return; win->_oldRect = win->r; - XIconifyWindow(win->src.display, win->src.window, DefaultScreen(win->src.display)); +#ifdef RGFW_X11 + XIconifyWindow(win->src.display, win->src.window, DefaultScreen(win->src.display)); XFlush(win->src.display); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL; +#endif } void RGFW_window_restore(RGFW_window* win) { RGFW_ASSERT(win != NULL); - RGFW_toggleXMaximized(win, 0); - + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + RGFW_toggleXMaximized(win, 0); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL +#endif win->r = win->_oldRect; RGFW_window_move(win, RGFW_POINT(win->r.x, win->r.y)); RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h)); - - RGFW_window_show(win); - XFlush(win->src.display); + + RGFW_window_show(win); +#ifdef RGFW_X11 + XFlush(win->src.display); +#endif } RGFW_bool RGFW_window_isFloating(RGFW_window* win) { - RGFW_LOAD_ATOM(_NET_WM_STATE); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + RGFW_LOAD_ATOM(_NET_WM_STATE); RGFW_LOAD_ATOM(_NET_WM_STATE_ABOVE); Atom actual_type; @@ -5135,7 +5300,10 @@ RGFW_bool RGFW_window_isFloating(RGFW_window* win) { if (prop_return) XFree(prop_return); - +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL RGFW_UNUSED(win); +#endif return RGFW_FALSE; } @@ -5157,7 +5325,7 @@ void RGFW_window_setName(RGFW_window* win, const char* name) { ); #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL if (win->src.compositor) xdg_toplevel_set_title(win->src.xdg_toplevel, name); #endif @@ -5166,7 +5334,9 @@ void RGFW_window_setName(RGFW_window* win, const char* name) { #ifndef RGFW_NO_PASSTHROUGH void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { RGFW_ASSERT(win != NULL); - if (passthrough) { + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + if (passthrough) { Region region = XCreateRegion(); XShapeCombineRegion(win->src.display, win->src.window, ShapeInput, 0, 0, region, ShapeSet); XDestroyRegion(region); @@ -5175,6 +5345,10 @@ void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { } XShapeCombineMask(win->src.display, win->src.window, ShapeInput, 0, 0, None, ShapeSet); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL RGFW_UNUSED(passthrough); +#endif } #endif /* RGFW_NO_PASSTHROUGH */ @@ -5243,7 +5417,7 @@ RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* icon, RGFW_area a, i32 cha return RGFW_BOOL(res); #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL RGFW_UNUSED(icon); RGFW_UNUSED(a); RGFW_UNUSED(channels); RGFW_UNUSED(type); return RGFW_FALSE; #endif } @@ -5284,7 +5458,7 @@ RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { #endif #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL RGFW_UNUSED(icon); RGFW_UNUSED(a); RGFW_UNUSED(channels); return NULL; /* TODO */ #endif @@ -5297,7 +5471,7 @@ RGFW_GOTO_WAYLAND(0); XDefineCursor(win->src.display, win->src.window, (Cursor)mouse); #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL RGFW_UNUSED(win); RGFW_UNUSED(mouse); #endif } @@ -5309,7 +5483,7 @@ RGFW_GOTO_WAYLAND(0); XFreeCursor(_RGFW.display, (Cursor)mouse); #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL RGFW_UNUSED(mouse); #endif } @@ -5333,7 +5507,7 @@ RGFW_GOTO_WAYLAND(1); XWarpPointer(win->src.display, None, win->src.window, 0, 0, 0, 0, (int) p.x - win->r.x, (int) p.y - win->r.y); #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL RGFW_UNUSED(win); RGFW_UNUSED(p); #endif } @@ -5360,7 +5534,7 @@ RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { return RGFW_TRUE; #endif #ifdef RGFW_WAYLAND - wayland: { } + RGFW_WAYLAND_LABEL { } static const char* iconStrings[16] = { "left_ptr", "left_ptr", "text", "cross", "pointer", "e-resize", "n-resize", "nw-resize", "ne-resize", "all-resize", "not-allowed" }; struct wl_cursor* wlcursor = wl_cursor_theme_get_cursor(RGFW_wl_cursor_theme, iconStrings[mouse]); @@ -5380,7 +5554,7 @@ void RGFW_window_hide(RGFW_window* win) { XUnmapWindow(win->src.display, win->src.window); #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL wl_surface_attach(win->src.surface, NULL, 0, 0); wl_surface_commit(win->src.surface); win->_flags |= RGFW_windowHide; @@ -5395,7 +5569,7 @@ void RGFW_window_show(RGFW_window* win) { XMapWindow(win->src.display, win->src.window); #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL /* wl_surface_attach(win->src.surface, win->rc., 0, 0); */ wl_surface_commit(win->src.surface); #endif @@ -5448,11 +5622,12 @@ RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property); size = (RGFW_ssize_t)sizeN; - return size; -#endif -#if defined(RGFW_WAYLAND) -wayland: return 0; -#endif + return size; + #endif + #if defined(RGFW_WAYLAND) + RGFW_WAYLAND_LABEL RGFW_UNUSED(str); RGFW_UNUSED(strCapacity); + return 0; + #endif } i32 RGFW_XHandleClipboardSelectionHelper(void) { @@ -5508,23 +5683,32 @@ void RGFW_writeClipboard(const char* text, u32 textLen) { _RGFW.clipboard_len = textLen; #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL RGFW_UNUSED(text); RGFW_UNUSED(textLen); #endif } RGFW_bool RGFW_window_isHidden(RGFW_window* win) { RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 XWindowAttributes windowAttributes; XGetWindowAttributes(win->src.display, win->src.window, &windowAttributes); return (windowAttributes.map_state == IsUnmapped && !RGFW_window_isMinimized(win)); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + return RGFW_FALSE; +#endif } RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { RGFW_ASSERT(win != NULL); - RGFW_LOAD_ATOM(WM_STATE); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + RGFW_LOAD_ATOM(WM_STATE); Atom actual_type; i32 actual_format; @@ -5545,12 +5729,19 @@ RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { XWindowAttributes windowAttributes; XGetWindowAttributes(win->src.display, win->src.window, &windowAttributes); - return windowAttributes.map_state != IsViewable; + return windowAttributes.map_state != IsViewable; +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + return RGFW_FALSE; +#endif } RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { RGFW_ASSERT(win != NULL); - RGFW_LOAD_ATOM(_NET_WM_STATE); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + RGFW_LOAD_ATOM(_NET_WM_STATE); RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_VERT); RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ); @@ -5581,7 +5772,10 @@ RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { if (prop_data != NULL) XFree(prop_data); - +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL; +#endif return RGFW_FALSE; } @@ -5594,6 +5788,7 @@ u32 RGFW_XCalculateRefreshRate(XRRModeInfo mi) { #endif +#ifdef RGFW_X11 static float XGetSystemContentDPI(Display* display, i32 screen) { float dpi = 96.0f; @@ -5617,11 +5812,15 @@ static float XGetSystemContentDPI(Display* display, i32 screen) { return dpi; } +#endif RGFW_monitor RGFW_XCreateMonitor(i32 screen); RGFW_monitor RGFW_XCreateMonitor(i32 screen) { RGFW_monitor monitor; RGFW_init(); + + RGFW_GOTO_WAYLAND(1); +#ifdef RGFW_X11 Display* display = _RGFW.display; if (screen == -1) screen = DefaultScreen(display); @@ -5694,7 +5893,13 @@ RGFW_monitor RGFW_XCreateMonitor(i32 screen) { #endif RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); - return monitor; + return monitor; +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL RGFW_UNUSED(screen); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); + return monitor; +#endif } RGFW_monitor* RGFW_getMonitors(size_t* len) { @@ -5716,7 +5921,8 @@ RGFW_monitor* RGFW_getMonitors(size_t* len) { return monitors; #endif #ifdef RGFW_WAYLAND - wayland: return monitors; /* TODO WAYLAND */ + RGFW_WAYLAND_LABEL RGFW_UNUSED(len); + return monitors; /* TODO WAYLAND */ #endif } @@ -5726,7 +5932,7 @@ RGFW_monitor RGFW_getPrimaryMonitor(void) { return RGFW_XCreateMonitor(-1); #endif #ifdef RGFW_WAYLAND - wayland: return (RGFW_monitor){ 0 }; /* TODO WAYLAND */ + RGFW_WAYLAND_LABEL return (RGFW_monitor){ 0 }; /* TODO WAYLAND */ #endif } @@ -5781,7 +5987,7 @@ RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW #endif #endif #ifdef RGFW_WAYLAND -wayland: +RGFW_WAYLAND_LABEL RGFW_UNUSED(mon); RGFW_UNUSED(mode); RGFW_UNUSED(request); #endif return RGFW_FALSE; } @@ -5807,7 +6013,7 @@ RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { } #endif #ifdef RGFW_WAYLAND -wayland: +RGFW_WAYLAND_LABEL #endif return mon; @@ -5836,7 +6042,7 @@ void RGFW_window_swapBuffers_software(RGFW_window* win) { return; #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL #if !defined(RGFW_BUFFER_BGR) && !defined(RGFW_OSMESA) RGFW_RGB_to_BGR(win, win->src.buffer); #else @@ -5853,7 +6059,7 @@ void RGFW_window_swapBuffers_software(RGFW_window* win) { #endif #else #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL #endif RGFW_UNUSED(win); #endif @@ -5912,6 +6118,8 @@ void RGFW_deinit(void) { } RGFW_freeMouse(_RGFW.hiddenMouse); + + XDestroyWindow(_RGFW.display, (Drawable) _RGFW.helperWindow); /*!< close the window */ XCloseDisplay(_RGFW.display); /*!< kill connection to the x server */ #if !defined(RGFW_NO_X11_CURSOR_PRELOAD) && !defined(RGFW_NO_X11_CURSOR) @@ -5954,9 +6162,8 @@ void RGFW_window_close(RGFW_window* win) { RGFW_ASSERT(win != NULL); if ((win->_flags & RGFW_windowNoInitAPI) == 0) RGFW_window_freeOpenGL(win); - #ifdef RGFW_X11 RGFW_GOTO_WAYLAND(0); - + #ifdef RGFW_X11 /* ungrab pointer if it was grabbed */ if (win->_flags & RGFW_HOLD_MOUSE) XUngrabPointer(win->src.display, CurrentTime); @@ -5988,19 +6195,17 @@ void RGFW_window_close(RGFW_window* win) { #endif #ifdef RGFW_WAYLAND - wayland: + RGFW_WAYLAND_LABEL - #ifdef RGFW_X11 - XDestroyWindow(win->src.display, (Drawable) win->src.window); - #endif RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a window was freed"); - _RGFW.windowCount--; - if (_RGFW.windowCount == 0) RGFW_deinit(); - xdg_toplevel_destroy(win->src.xdg_toplevel); - xdg_surface_destroy(win->src.xdg_surface); + xdg_toplevel_destroy(win->src.xdg_toplevel); + xdg_surface_destroy(win->src.xdg_surface); wl_surface_destroy(win->src.surface); + _RGFW.windowCount--; + if (_RGFW.windowCount == 0) RGFW_deinit(); + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) wl_buffer_destroy(win->src.wl_buffer); if ((win->_flags & RGFW_BUFFER_ALLOC)) @@ -6297,6 +6502,7 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) RGFW_eventQueuePushEx(e.type = (RGFW_eventType)((u8)RGFW_focusOut - inFocus); e._win = win); RGFW_focusCallback(win, inFocus); + RGFW_window_focusLost(win); if ((win->_flags & RGFW_windowFullscreen) == 0) return DefWindowProcW(hWnd, message, wParam, lParam); @@ -6561,7 +6767,7 @@ void RGFW_win32_loadOpenGLFuncs(HWND dummyWin) { } #ifndef RGFW_EGL -void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { +void RGFW_window_initOpenGL(RGFW_window* win) { #ifdef RGFW_OPENGL PIXELFORMATDESCRIPTOR pfd; pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); @@ -6577,13 +6783,13 @@ void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { if (RGFW_GL_HINTS[RGFW_glStereo]) pfd.dwFlags |= PFD_STEREO; /* try to create the pixel format we want for opengl and then try to create an opengl context for the specified version */ - if (software) + if (win->_flags & RGFW_windowOpenglSoftware) pfd.dwFlags |= PFD_GENERIC_FORMAT | PFD_GENERIC_ACCELERATED; /* get pixel format, default to a basic pixel format */ int pixel_format = ChoosePixelFormat(win->src.hdc, &pfd); if (wglChoosePixelFormatARB != NULL) { - i32* pixel_format_attribs = (i32*)RGFW_initFormatAttribs(software); + i32* pixel_format_attribs = (i32*)RGFW_initFormatAttribs(); int new_pixel_format; UINT num_formats; @@ -6597,6 +6803,10 @@ void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { if (!DescribePixelFormat(win->src.hdc, pixel_format, sizeof(suggested), &suggested) || !SetPixelFormat(win->src.hdc, pixel_format, &pfd)) RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to set the WGL pixel format"); + + if (!(pfd.dwFlags & PFD_GENERIC_ACCELERATED)) { + win->_flags |= RGFW_windowOpenglSoftware; + } if (wglCreateContextAttribsARB != NULL) { /* create opengl/WGL context for the specified version */ @@ -6631,7 +6841,7 @@ void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { wglShareLists(_RGFW.root->src.ctx, win->src.ctx); RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context initalized"); #else - RGFW_UNUSED(win); RGFW_UNUSED(software); + RGFW_UNUSED(win); #endif } @@ -6769,7 +6979,7 @@ RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowF win->src.hdc = GetDC(win->src.window); if ((flags & RGFW_windowNoInitAPI) == 0) { - RGFW_window_initOpenGL(win, RGFW_BOOL(flags & RGFW_windowOpenglSoftware)); + RGFW_window_initOpenGL(win); RGFW_window_initBuffer(win); } @@ -7043,12 +7253,33 @@ void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { MsgWaitForMultipleObjects(0, NULL, FALSE, (DWORD)waitMS, QS_ALLINPUT); } +u8 RGFW_rgfwToKeyChar(u32 rgfw_keycode) { + UINT vsc = RGFW_rgfwToApiKey(rgfw_keycode); // Should return a Windows VK_* code + BYTE keyboardState[256] = {0}; + + if (!GetKeyboardState(keyboardState)) + return (u8)rgfw_keycode; + + UINT vk = MapVirtualKeyW(vsc, MAPVK_VSC_TO_VK); + HKL layout = GetKeyboardLayout(0); + + wchar_t charBuffer[2] = {0}; + int result = ToUnicodeEx(vk, vsc, keyboardState, charBuffer, 1, 0, layout); + + if (result <= 0) + return (u8)rgfw_keycode; + + return (u8)charBuffer[0]; +} + RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { if (win == NULL || ((win->_flags & RGFW_windowFreeOnClose) && (win->_flags & RGFW_EVENT_QUIT))) return NULL; RGFW_event* ev = RGFW_window_checkEventCore(win); - if (ev) return ev; + if (ev) { + return ev; + } - static HDROP drop; + static HDROP drop; if (win->event.type == RGFW_DNDInit) { if (win->event.droppedFilesCount) { u32 i; @@ -8619,7 +8850,6 @@ void RGFW__osxWindowBecameKey(id self, SEL sel) { RGFW_focusCallback(win, RGFW_TRUE); - RGFW_resetKey(); if ((win->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(win, RGFW_AREA(win->r.w, win->r.h)); } @@ -8629,8 +8859,8 @@ void RGFW__osxWindowResignKey(id self, SEL sel) { object_getInstanceVariable(self, "RGFW_window", (void**)&win); if (win == NULL) return; - win->_flags &= ~(u32)RGFW_windowFocus; - RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e._win = win); + RGFW_window_focusLost(win); + RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e._win = win); RGFW_focusCallback(win, RGFW_FALSE); } @@ -8739,14 +8969,15 @@ id RGFW__osx_generateViewClass(const char* subclass, RGFW_window* win) { } #ifndef RGFW_EGL -void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { +void RGFW_window_initOpenGL(RGFW_window* win) { #ifdef RGFW_OPENGL - void* attrs = RGFW_initFormatAttribs(software); + void* attrs = RGFW_initFormatAttribs(); void* format = NSOpenGLPixelFormat_initWithAttributes((uint32_t*)attrs); if (format == NULL) { RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to load pixel format for OpenGL"); - void* subAttrs = RGFW_initFormatAttribs(1); + win->_flags |= RGFW_windowOpenglSoftware; + void* subAttrs = RGFW_initFormatAttribs(); format = NSOpenGLPixelFormat_initWithAttributes((uint32_t*)subAttrs); if (format == NULL) @@ -8773,7 +9004,7 @@ void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { objc_msgSend_void(win->src.ctx, sel_registerName("makeCurrentContext")); RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context initalized"); #else - RGFW_UNUSED(win); RGFW_UNUSED(software); + RGFW_UNUSED(win); #endif } @@ -8856,7 +9087,7 @@ RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowF objc_msgSend_void_id((id)win->src.window, sel_registerName("setTitle:"), str); if ((flags & RGFW_windowNoInitAPI) == 0) { - RGFW_window_initOpenGL(win, RGFW_BOOL(flags & RGFW_windowOpenglSoftware)); + RGFW_window_initOpenGL(win); RGFW_window_initBuffer(win); } @@ -9067,7 +9298,6 @@ void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { (NSApp, eventFunc, ULONG_MAX, date, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); - if (e) { ((void (*)(id, SEL, id, bool))objc_msgSend) (NSApp, sel_registerName("postEvent:atStart:"), e, 1); @@ -9076,6 +9306,10 @@ void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { objc_msgSend_bool_void(eventPool, sel_registerName("drain")); } +u8 RGFW_rgfwToKeyChar(u32 rgfw_keycode) { + return (u8)rgfw_keycode; /* TODO */ +} + RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { if (win == NULL || ((win->_flags & RGFW_windowFreeOnClose) && (win->_flags & RGFW_EVENT_QUIT))) return NULL; @@ -9160,8 +9394,6 @@ RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { case NSEventTypeKeyUp: { u32 key = (u16) objc_msgSend_uint(e, sel_registerName("keyCode")); - - u32 mappedKey = (u32)*(((char*)(const char*) NSString_to_char(objc_msgSend_id(e, sel_registerName("charactersIgnoringModifiers"))))); if (((u8)mappedKey) == 239) mappedKey = 0; @@ -9919,7 +10151,6 @@ EM_BOOL Emscripten_on_focusin(int eventType, const EmscriptenFocusEvent* E, void _RGFW.root->_flags |= RGFW_windowFocus; RGFW_focusCallback(_RGFW.root, 1); - RGFW_resetKey(); if ((_RGFW.root->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(_RGFW.root, RGFW_AREA(_RGFW.root->r.w, _RGFW.root->r.h)); return EM_TRUE; } @@ -9928,8 +10159,8 @@ EM_BOOL Emscripten_on_focusout(int eventType, const EmscriptenFocusEvent* E, voi RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(E); RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e._win = _RGFW.root); - _RGFW.root->_flags &= ~(u32)RGFW_windowFocus; - RGFW_focusCallback(_RGFW.root, 0); + RGFW_window_focusLost(_RGFW.root); + RGFW_focusCallback(_RGFW.root, 0); return EM_TRUE; } @@ -10292,7 +10523,7 @@ void EMSCRIPTEN_KEEPALIVE RGFW_writeFile(const char *path, const char *data, siz fclose(file); } -void RGFW_window_initOpenGL(RGFW_window* win, RGFW_bool software) { +void RGFW_window_initOpenGL(RGFW_window* win) { #if defined(RGFW_OPENGL) && !defined(RGFW_WEBGPU) && !defined(RGFW_OSMESA) && !defined(RGFW_BUFFER) EmscriptenWebGLContextAttributes attrs; attrs.alpha = RGFW_GL_HINTS[RGFW_glDepth]; @@ -10355,7 +10586,7 @@ i32 RGFW_init(void) { RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { RGFW_window_basic_init(win, rect, flags); - RGFW_window_initOpenGL(win, 0); + RGFW_window_initOpenGL(win); #if defined(RGFW_WEBGPU) win->src.ctx = wgpuCreateInstance(NULL); @@ -10465,6 +10696,10 @@ RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowF return win; } +u8 RGFW_rgfwToKeyChar(u32 rgfw_keycode) { + return (u8)rgfw_keycode; /* TODO */ +} + RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { if (win == NULL || ((win->_flags & RGFW_windowFreeOnClose) && (win->_flags & RGFW_EVENT_QUIT))) return NULL; RGFW_event* ev = RGFW_window_checkEventCore(win); From 106bcf460ae65242d680f29dd1196e71c5308498 Mon Sep 17 00:00:00 2001 From: Marcos Grzesiak Date: Thu, 12 Jun 2025 23:41:57 -0400 Subject: [PATCH 040/242] add uiua bindings to the list --- BINDINGS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BINDINGS.md b/BINDINGS.md index 3468b7e89..4088a13ce 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -93,6 +93,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [raylib-apl](https://github.com/Brian-ED/raylib-apl) | **5.0** | [Dyalog APL](https://www.dyalog.com/) | MIT | | [raylib-jai](https://github.com/ahmedqarmout2/raylib-jai) | **5.5** | [Jai](https://github.com/BSVino/JaiPrimer/blob/master/JaiPrimer.md) | MIT | | [fnl-raylib](https://github.com/0riginaln0/fnl-raylib) | **5.5** | [Fennel](https://fennel-lang.org/) | MIT | +| [Rayua](https://github.com/uiua-lang/rayua) | **5.5** | [Uiua](https://www.uiua.org/) | **???** | ### Utility Wrapers @@ -103,6 +104,7 @@ These are utility wrappers for specific languages, they are not required to use | [claylib](https://github.com/defun-games/claylib) | 4.5 | [Common Lisp](https://common-lisp.net) | Zlib | | [rayed-bqn](https://github.com/Brian-ED/rayed-bqn) | **5.0** | [BQN](https://mlochbaum.github.io/BQN) | MIT | | [DOOR](https://github.com/RealDoigt/DOOR) | 4.0 | [D](https://dlang.org) | MIT | +| [Iris](https://github.com/Marcos-cat/iris) | **5.5** | [Uiua](https://www.uiua.org/) | MIT | ### Older or Unmaintained Language Bindings From 43bad2612bccdf6e74b0d45f0d3de0c5255a673e Mon Sep 17 00:00:00 2001 From: danil <61111955+danilwhale@users.noreply.github.com> Date: Tue, 17 Jun 2025 17:15:09 +0300 Subject: [PATCH 041/242] docs: add Raylib-cs.BleedingEdge to the bindings --- BINDINGS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BINDINGS.md b/BINDINGS.md index 4088a13ce..986c27abc 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -14,6 +14,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [Raylib-CsLo](https://github.com/NotNotTech/Raylib-CsLo) | 4.2 | [C#](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)) | MPL-2.0 | | [Raylib-CSharp-Vinculum](https://github.com/ZeroElectric/Raylib-CSharp-Vinculum) | **5.0** | [C#](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)) | MPL-2.0 | | [Raylib-CSharp](https://github.com/MrScautHD/Raylib-CSharp) | **5.5** | [C#](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)) | MIT | +| [Raylib-cs.BleedingEdge](https://github.com/danilwhale/Raylib-cs.BleedingEdge) | **5.6-dev** | [C#](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)) | Zlib | | [cl-raylib](https://github.com/longlene/cl-raylib) | 4.0 | [Common Lisp](https://common-lisp.net) | MIT | | [claylib/wrap](https://github.com/defun-games/claylib) | 4.5 | [Common Lisp](https://common-lisp.net) | Zlib | | [claw-raylib](https://github.com/bohonghuang/claw-raylib) | **auto** | [Common Lisp](https://common-lisp.net) | Apache-2.0 | From 518ad8b018ab29879bbad65a010fcac6796bff99 Mon Sep 17 00:00:00 2001 From: mlorenc Date: Fri, 20 Jun 2025 01:15:14 +0200 Subject: [PATCH 042/242] Fix ScanDirectoryFilesRecursively Fixes a regression since 5.5, where `ScanDirectoryFilesRecursively` no longer does the "recursively" part due to `path` being `static`. The issue was once already fixed in https://github.com/raysan5/raylib/commit/5530a3ceb88962066affa5db8e13b00b64444b37 but recently made it back it in. --- src/rcore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rcore.c b/src/rcore.c index f8b061a50..a15e117b3 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -3757,7 +3757,7 @@ static void ScanDirectoryFiles(const char *basePath, FilePathList *files, const // Scan all files and directories recursively from a base path static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *files, const char *filter) { - static char path[MAX_FILEPATH_LENGTH] = { 0 }; + char path[MAX_FILEPATH_LENGTH] = { 0 }; memset(path, 0, MAX_FILEPATH_LENGTH); struct dirent *dp = NULL; From 1abac023bdc433370b8db00d770eaf152a6cdeae Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 20 Jun 2025 23:34:41 +0200 Subject: [PATCH 043/242] Update rcore.c --- src/rcore.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rcore.c b/src/rcore.c index a15e117b3..e97ef357c 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -3757,6 +3757,7 @@ static void ScanDirectoryFiles(const char *basePath, FilePathList *files, const // Scan all files and directories recursively from a base path static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *files, const char *filter) { + // WARNING: Path can not be static or it will be reused between recursive function calls! char path[MAX_FILEPATH_LENGTH] = { 0 }; memset(path, 0, MAX_FILEPATH_LENGTH); From b6773760887ed63f8856e3a29a5fe26391a33fc2 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 22 Jun 2025 23:52:34 +0200 Subject: [PATCH 044/242] Delete shader in case compilation fails --- src/rlgl.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/rlgl.h b/src/rlgl.h index c39d096fb..2562ad45d 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4178,6 +4178,9 @@ unsigned int rlCompileShader(const char *shaderCode, int type) RL_FREE(log); } + // Unload object allocated by glCreateShader(), + // despite failing in the compilation process + glDeleteShader(shader); shader = 0; } else From eee9dd8c944331dd804752383692076ab4d2790e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agnis=20Aldi=C5=86=C5=A1=20=22NeZv=C4=93rs?= Date: Mon, 23 Jun 2025 13:03:35 +0300 Subject: [PATCH 045/242] Example: core_3d_fps_controller Quake like camera animations and strafe jump movement --- examples/Makefile | 1 + examples/Makefile.Web | 4 + examples/core/core_3d_fps_controller.c | 360 +++++++++++++++++++++++++ examples/core/resources/huh_jump.wav | Bin 0 -> 19050 bytes 4 files changed, 365 insertions(+) create mode 100644 examples/core/core_3d_fps_controller.c create mode 100644 examples/core/resources/huh_jump.wav diff --git a/examples/Makefile b/examples/Makefile index 32a3a75ab..edd2eb399 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -500,6 +500,7 @@ CORE = \ core/core_3d_camera_free \ core/core_3d_camera_mode \ core/core_3d_camera_split_screen \ + core/core_3d_fps_controller \ core/core_3d_picking \ core/core_automation_events \ core/core_basic_screen_manager \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 35ae70a18..5ba550eb0 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -382,6 +382,7 @@ CORE = \ core/core_3d_camera_free \ core/core_3d_camera_mode \ core/core_3d_camera_split_screen \ + core/core_3d_fps_controller \ core/core_3d_picking \ core/core_automation_events \ core/core_basic_screen_manager \ @@ -587,6 +588,9 @@ core/core_3d_camera_mode: core/core_3d_camera_mode.c core/core_3d_camera_split_screen: core/core_3d_camera_split_screen.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) +core/core_3d_fps_controller: core/core_3d_fps_controller.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + core/core_3d_picking: core/core_3d_picking.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) diff --git a/examples/core/core_3d_fps_controller.c b/examples/core/core_3d_fps_controller.c new file mode 100644 index 000000000..56d8c4543 --- /dev/null +++ b/examples/core/core_3d_fps_controller.c @@ -0,0 +1,360 @@ +/******************************************************************************************* +* +* raylib [core] example - Input Gestures for Web +* +* Example complexity rating: [★★★☆] 3/4 +* +* Example originally created with raylib 5.5 +* +* Example contributed by Agnis Aldins (@nezvers) and reviewed by Ramon Santamaria (@raysan5) +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2025-2025 Agnis Aldins (@nezvers) +* +********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" +#include "rcamera.h" + +//#define PLATFORM_WEB + +#if defined(PLATFORM_WEB) +#include +#endif + +#if defined(PLATFORM_DESKTOP) +#define GLSL_VERSION 330 +#else // PLATFORM_ANDROID, PLATFORM_WEB +#define GLSL_VERSION 100 +#endif + + +/* Movement constants */ +#define GRAVITY 32.f +#define MAX_SPEED 20.f +#define CROUCH_SPEED 5.f +#define JUMP_FORCE 12.f +#define MAX_ACCEL 150.f +/* Grounded drag */ +#define FRICTION 0.86f +/* Increasing air drag, increases strafing speed */ +#define AIR_DRAG 0.98f +/* Responsiveness for turning movement direction to looked direction */ +#define CONTROL 15.f +#define CROUCH_HEIGHT 0.f +#define STAND_HEIGHT 1.f +#define BOTTOM_HEIGHT 0.5f + +#define NORMALIZE_INPUT 0 + +typedef struct { + Vector3 position; + Vector3 velocity; + Vector3 dir; + bool isGrounded; + Sound soundJump; +}Body; + +const int screenWidth = 1280; +const int screenHeight = 720; +Vector2 sensitivity = { 0.001f, 0.001f }; + +Body player; +Camera camera; +Vector2 lookRotation = { 0 }; +float headTimer; +float walkLerp; +float headLerp; +Vector2 lean; + +void UpdateDrawFrame(void); // Update and Draw one frame + +void DrawLevel(); + +void UpdateCameraAngle(Camera* camera, Vector2* rot, float headTimer, float walkLerp, Vector2 lean); + +void UpdateBody(Body* body, float rot, char side, char forward, bool jumpPressed, bool crouchHold); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + InitWindow(screenWidth, screenHeight, "Raylib Quake-like controller"); + InitAudioDevice(); + + player = (Body){ Vector3Zero(), Vector3Zero(), Vector3Zero(), false, LoadSound("resources/huh_jump.wav")}; + camera = (Camera){ 0 }; + camera.fovy = 60.f; // Camera field-of-view Y + camera.projection = CAMERA_PERSPECTIVE; // Camera projection type + + lookRotation = Vector2Zero(); + headTimer = 0.f; + walkLerp = 0.f; + headLerp = STAND_HEIGHT; + lean = Vector2Zero(); + + camera.position = (Vector3){ + player.position.x, + player.position.y + (BOTTOM_HEIGHT + headLerp), + player.position.z, + }; + UpdateCameraAngle(&camera, &lookRotation, headTimer, walkLerp, lean); + + DisableCursor(); // Limit cursor to relative movement inside the window + +#if defined(PLATFORM_WEB) + emscripten_set_main_loop(UpdateDrawFrame, 0, 1); +#else + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + UpdateDrawFrame(); + } +#endif + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadSound(player.soundJump); + CloseAudioDevice(); + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} + +void UpdateDrawFrame(void) +{ + // Update + //---------------------------------------------------------------------------------- + + Vector2 mouse_delta = GetMouseDelta(); + lookRotation.x -= mouse_delta.x * sensitivity.x; + lookRotation.y += mouse_delta.y * sensitivity.y; + + char sideway = (IsKeyDown(KEY_D) - IsKeyDown(KEY_A)); + char forward = (IsKeyDown(KEY_W) - IsKeyDown(KEY_S)); + bool crouching = IsKeyDown(KEY_LEFT_CONTROL); + UpdateBody(&player, lookRotation.x, sideway, forward, IsKeyPressed(KEY_SPACE), crouching); + + float delta = GetFrameTime(); + headLerp = Lerp(headLerp, (crouching ? CROUCH_HEIGHT : STAND_HEIGHT), 20.f * delta); + camera.position = (Vector3){ + player.position.x, + player.position.y + (BOTTOM_HEIGHT + headLerp), + player.position.z, + }; + + if (player.isGrounded && (forward != 0 || sideway != 0)) { + headTimer += delta * 3.f; + walkLerp = Lerp(walkLerp, 1.f, 10.f * delta); + camera.fovy = Lerp(camera.fovy, 55.f, 5.f * delta); + } + else { + walkLerp = Lerp(walkLerp, 0.f, 10.f * delta); + camera.fovy = Lerp(camera.fovy, 60.f, 5.f * delta); + } + + lean.x = Lerp(lean.x, sideway * 0.02f, 10.f * delta); + lean.y = Lerp(lean.y, forward * 0.015f, 10.f * delta); + + UpdateCameraAngle(&camera, &lookRotation, headTimer, walkLerp, lean); + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + BeginMode3D(camera); + + DrawLevel(); + + EndMode3D(); + + // Draw info box + DrawRectangle(5, 5, 330, 100, Fade(SKYBLUE, 0.5f)); + DrawRectangleLines(5, 5, 330, 100, BLUE); + + DrawText("Camera controls:", 15, 15, 10, BLACK); + DrawText("- Move keys: W, A, S, D, Space, Left-Ctrl", 15, 30, 10, BLACK); + DrawText("- Look around: arrow keys or mouse", 15, 45, 10, BLACK); + DrawText(TextFormat("- Velocity Len: (%06.3f)", Vector2Length((Vector2) { player.velocity.x, player.velocity.z })), 15, 60, 10, BLACK); + + + EndDrawing(); + //---------------------------------------------------------------------------------- +} + +void UpdateBody(Body* body, float rot, char side, char forward, bool jumpPressed, bool crouchHold) +{ + Vector2 input = (Vector2){ (float)side, (float)-forward }; +#if defined(NORMALIZE_INPUT) + // Slow down diagonal movement + if (side != 0 & forward != 0) + { + input = Vector2Normalize(input); + } +#endif + + float delta = GetFrameTime(); + + if (!body->isGrounded) + { + body->velocity.y -= GRAVITY * delta; + } + if (body->isGrounded && jumpPressed) + { + body->velocity.y = JUMP_FORCE; + body->isGrounded = false; + SetSoundPitch(body->soundJump, 1.f + (GetRandomValue(-100, 100) * 0.001)); + PlaySound(body->soundJump); + } + + Vector3 front_vec = (Vector3){ sin(rot), 0.f, cos(rot) }; + Vector3 right_vec = (Vector3){ cos(-rot), 0.f, sin(-rot) }; + + Vector3 desired_dir = (Vector3){ + input.x * right_vec.x + input.y * front_vec.x, + 0.f, + input.x * right_vec.z + input.y * front_vec.z, + }; + + body->dir = Vector3Lerp(body->dir, desired_dir, CONTROL * delta); + + float decel = body->isGrounded ? FRICTION : AIR_DRAG; + Vector3 hvel = (Vector3){ + body->velocity.x * decel, + 0.f, + body->velocity.z * decel + }; + + float hvel_length = Vector3Length(hvel); // a.k.a. magnitude + if (hvel_length < MAX_SPEED * 0.01f) { + hvel = (Vector3){ 0 }; + } + + /* This is what creates strafing */ + float speed = Vector3DotProduct(hvel, body->dir); + + /* + Whenever the amount of acceleration to add is clamped by the maximum acceleration constant, + a Player can make the speed faster by bringing the direction closer to horizontal velocity angle + More info here: https://youtu.be/v3zT3Z5apaM?t=165 + */ + float max_speed = crouchHold ? CROUCH_SPEED : MAX_SPEED; + float accel = Clamp(max_speed - speed, 0.f, MAX_ACCEL * delta); + hvel.x += body->dir.x * accel; + hvel.z += body->dir.z * accel; + + body->velocity.x = hvel.x; + body->velocity.z = hvel.z; + + body->position.x += body->velocity.x * delta; + body->position.y += body->velocity.y * delta; + body->position.z += body->velocity.z * delta; + + /* Fancy collision system against "THE FLOOR" */ + if (body->position.y <= 0.f) + { + body->position.y = 0.f; + body->velocity.y = 0.f; + body->isGrounded = true; // <= enables jumping + } +} + +void UpdateCameraAngle(Camera* camera, Vector2* rot, float headTimer, float walkLerp, Vector2 lean) +{ + const Vector3 up = (Vector3){ 0.f, 1.f, 0.f }; + const Vector3 targetOffset = (Vector3){ 0.f, 0.f, -1.f }; + + /* Left & Right */ + Vector3 yaw = Vector3RotateByAxisAngle(targetOffset, up, rot->x); + + // Clamp view up + float maxAngleUp = Vector3Angle(up, yaw); + maxAngleUp -= 0.001f; // avoid numerical errors + if ( -(rot->y) > maxAngleUp) { rot->y = -maxAngleUp; } + + // Clamp view down + float maxAngleDown = Vector3Angle(Vector3Negate(up), yaw); + maxAngleDown *= -1.0f; // downwards angle is negative + maxAngleDown += 0.001f; // avoid numerical errors + if ( -(rot->y) < maxAngleDown) { rot->y = -maxAngleDown; } + + /* Up & Down */ + Vector3 right = Vector3Normalize(Vector3CrossProduct(yaw, up)); + + // Rotate view vector around right axis + Vector3 pitch = Vector3RotateByAxisAngle(yaw, right, -rot->y - lean.y); + + // Head animation + // Rotate up direction around forward axis + float _sin = sin(headTimer * PI); + float _cos = cos(headTimer * PI); + const float stepRotation = 0.01f; + camera->up = Vector3RotateByAxisAngle(up, pitch, _sin * stepRotation + lean.x); + + /* BOB */ + const float bobSide = 0.1f; + const float bobUp = 0.15f; + Vector3 bobbing = Vector3Scale(right, _sin * bobSide); + bobbing.y = fabsf(_cos * bobUp); + camera->position = Vector3Add(camera->position, Vector3Scale(bobbing, walkLerp)); + + camera->target = Vector3Add(camera->position, pitch); +} + + +void DrawLevel() +{ + const int floorExtent = 25; + const float tileSize = 5.f; + const Color tileColor1 = (Color){ 150, 200, 200, 255 }; + // Floor tiles + for (int y = -floorExtent; y < floorExtent; y++) + { + for (int x = -floorExtent; x < floorExtent; x++) + { + if ((y & 1) && (x & 1)) + { + DrawPlane((Vector3) { x * tileSize, 0.f, y * tileSize}, + (Vector2) {tileSize, tileSize}, tileColor1); + } + else if(!(y & 1) && !(x & 1)) + { + DrawPlane((Vector3) { x * tileSize, 0.f, y * tileSize}, + (Vector2) {tileSize, tileSize}, LIGHTGRAY); + } + } + } + + const Vector3 towerSize = (Vector3){ 16.f, 32.f, 16.f }; + const Color towerColor = (Color){ 150, 200, 200, 255 }; + + Vector3 towerPos = (Vector3){ 16.f, 16.f, 16.f }; + DrawCubeV(towerPos, towerSize, towerColor); + DrawCubeWiresV(towerPos, towerSize, DARKBLUE); + + towerPos.x *= -1; + DrawCubeV(towerPos, towerSize, towerColor); + DrawCubeWiresV(towerPos, towerSize, DARKBLUE); + + towerPos.z *= -1; + DrawCubeV(towerPos, towerSize, towerColor); + DrawCubeWiresV(towerPos, towerSize, DARKBLUE); + + towerPos.x *= -1; + DrawCubeV(towerPos, towerSize, towerColor); + DrawCubeWiresV(towerPos, towerSize, DARKBLUE); +} + diff --git a/examples/core/resources/huh_jump.wav b/examples/core/resources/huh_jump.wav new file mode 100644 index 0000000000000000000000000000000000000000..9ca8a2e683ef87d19f594be54d04a40dd0bade10 GIT binary patch literal 19050 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 Date: Mon, 23 Jun 2025 13:15:14 +0300 Subject: [PATCH 046/242] change game's resolution --- examples/core/core_3d_fps_controller.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/core/core_3d_fps_controller.c b/examples/core/core_3d_fps_controller.c index 56d8c4543..ff348daef 100644 --- a/examples/core/core_3d_fps_controller.c +++ b/examples/core/core_3d_fps_controller.c @@ -58,8 +58,8 @@ typedef struct { Sound soundJump; }Body; -const int screenWidth = 1280; -const int screenHeight = 720; +const int screenWidth = 800; +const int screenHeight = 450; Vector2 sensitivity = { 0.001f, 0.001f }; Body player; From 4233544670c9a2ef9a30e4dcbe747395efea7c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agnis=20Aldi=C5=86=C5=A1=20=22NeZv=C4=93rs?= Date: Mon, 23 Jun 2025 13:32:08 +0300 Subject: [PATCH 047/242] Update core_3d_fps_controller.c --- examples/core/core_3d_fps_controller.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/core/core_3d_fps_controller.c b/examples/core/core_3d_fps_controller.c index ff348daef..d4936388b 100644 --- a/examples/core/core_3d_fps_controller.c +++ b/examples/core/core_3d_fps_controller.c @@ -356,5 +356,7 @@ void DrawLevel() towerPos.x *= -1; DrawCubeV(towerPos, towerSize, towerColor); DrawCubeWiresV(towerPos, towerSize, DARKBLUE); -} + // Red sun + DrawSphere((Vector3) { 300.f, 300.f, 0.f }, 100.f, (Color) { 255, 0, 0, 255 }); +} From a2cf878190569f2d37c90ff475594d90205eafb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agnis=20Aldi=C5=86=C5=A1=20=22NeZv=C4=93rs?= Date: Mon, 23 Jun 2025 13:43:38 +0300 Subject: [PATCH 048/242] cleanup variable shadowing --- examples/core/core_3d_fps_controller.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/core/core_3d_fps_controller.c b/examples/core/core_3d_fps_controller.c index d4936388b..3a21ac2dd 100644 --- a/examples/core/core_3d_fps_controller.c +++ b/examples/core/core_3d_fps_controller.c @@ -74,7 +74,7 @@ void UpdateDrawFrame(void); // Update and Draw one frame void DrawLevel(); -void UpdateCameraAngle(Camera* camera, Vector2* rot, float headTimer, float walkLerp, Vector2 lean); +void UpdateCameraAngle(); void UpdateBody(Body* body, float rot, char side, char forward, bool jumpPressed, bool crouchHold); @@ -167,7 +167,7 @@ void UpdateDrawFrame(void) lean.x = Lerp(lean.x, sideway * 0.02f, 10.f * delta); lean.y = Lerp(lean.y, forward * 0.015f, 10.f * delta); - UpdateCameraAngle(&camera, &lookRotation, headTimer, walkLerp, lean); + UpdateCameraAngle(); // Draw //---------------------------------------------------------------------------------- @@ -272,46 +272,46 @@ void UpdateBody(Body* body, float rot, char side, char forward, bool jumpPressed } } -void UpdateCameraAngle(Camera* camera, Vector2* rot, float headTimer, float walkLerp, Vector2 lean) +void UpdateCameraAngle() { const Vector3 up = (Vector3){ 0.f, 1.f, 0.f }; const Vector3 targetOffset = (Vector3){ 0.f, 0.f, -1.f }; /* Left & Right */ - Vector3 yaw = Vector3RotateByAxisAngle(targetOffset, up, rot->x); + Vector3 yaw = Vector3RotateByAxisAngle(targetOffset, up, lookRotation.x); // Clamp view up float maxAngleUp = Vector3Angle(up, yaw); maxAngleUp -= 0.001f; // avoid numerical errors - if ( -(rot->y) > maxAngleUp) { rot->y = -maxAngleUp; } + if ( -(lookRotation.y) > maxAngleUp) { lookRotation.y = -maxAngleUp; } // Clamp view down float maxAngleDown = Vector3Angle(Vector3Negate(up), yaw); maxAngleDown *= -1.0f; // downwards angle is negative maxAngleDown += 0.001f; // avoid numerical errors - if ( -(rot->y) < maxAngleDown) { rot->y = -maxAngleDown; } + if ( -(lookRotation.y) < maxAngleDown) { lookRotation.y = -maxAngleDown; } /* Up & Down */ Vector3 right = Vector3Normalize(Vector3CrossProduct(yaw, up)); // Rotate view vector around right axis - Vector3 pitch = Vector3RotateByAxisAngle(yaw, right, -rot->y - lean.y); + Vector3 pitch = Vector3RotateByAxisAngle(yaw, right, -lookRotation.y - lean.y); // Head animation // Rotate up direction around forward axis float _sin = sin(headTimer * PI); float _cos = cos(headTimer * PI); const float stepRotation = 0.01f; - camera->up = Vector3RotateByAxisAngle(up, pitch, _sin * stepRotation + lean.x); + camera.up = Vector3RotateByAxisAngle(up, pitch, _sin * stepRotation + lean.x); /* BOB */ const float bobSide = 0.1f; const float bobUp = 0.15f; Vector3 bobbing = Vector3Scale(right, _sin * bobSide); bobbing.y = fabsf(_cos * bobUp); - camera->position = Vector3Add(camera->position, Vector3Scale(bobbing, walkLerp)); + camera.position = Vector3Add(camera.position, Vector3Scale(bobbing, walkLerp)); - camera->target = Vector3Add(camera->position, pitch); + camera.target = Vector3Add(camera.position, pitch); } From fca2317640dc32e231316e33a0967cee70382c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agnis=20Aldi=C5=86=C5=A1=20=22NeZv=C4=93rs?= Date: Mon, 23 Jun 2025 13:44:50 +0300 Subject: [PATCH 049/242] remove //#define PLATFORM_WEB --- examples/core/core_3d_fps_controller.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/core/core_3d_fps_controller.c b/examples/core/core_3d_fps_controller.c index 3a21ac2dd..ddb25ba8a 100644 --- a/examples/core/core_3d_fps_controller.c +++ b/examples/core/core_3d_fps_controller.c @@ -19,8 +19,6 @@ #include "raymath.h" #include "rcamera.h" -//#define PLATFORM_WEB - #if defined(PLATFORM_WEB) #include #endif From fa9653d179b56a1c663f0e34a7167c2f6f3c299d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agnis=20Aldi=C5=86=C5=A1=20=22NeZv=C4=93rs?= Date: Mon, 23 Jun 2025 13:50:24 +0300 Subject: [PATCH 050/242] Fix function call --- examples/core/core_3d_fps_controller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/core/core_3d_fps_controller.c b/examples/core/core_3d_fps_controller.c index ddb25ba8a..c4ef367e9 100644 --- a/examples/core/core_3d_fps_controller.c +++ b/examples/core/core_3d_fps_controller.c @@ -102,7 +102,7 @@ int main(void) player.position.y + (BOTTOM_HEIGHT + headLerp), player.position.z, }; - UpdateCameraAngle(&camera, &lookRotation, headTimer, walkLerp, lean); + UpdateCameraAngle(); DisableCursor(); // Limit cursor to relative movement inside the window From f6b7168ed4c2c70d15c01ed9f3710e8c976b7452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agnis=20Aldi=C5=86=C5=A1=20=22NeZv=C4=93rs?= Date: Mon, 23 Jun 2025 16:06:46 +0300 Subject: [PATCH 051/242] Add preview image --- examples/core/core_3d_fps_controller.png | Bin 0 -> 16483 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/core/core_3d_fps_controller.png diff --git a/examples/core/core_3d_fps_controller.png b/examples/core/core_3d_fps_controller.png new file mode 100644 index 0000000000000000000000000000000000000000..65a908d529e5eeb82682af2230caf35d1877b4b2 GIT binary patch literal 16483 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 literal 0 HcmV?d00001 From ed022e89e5aabd91c80288df0c671efb4c7e9492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agnis=20Aldi=C5=86=C5=A1=20=22NeZv=C4=93rs?= Date: Mon, 23 Jun 2025 16:22:14 +0300 Subject: [PATCH 052/242] fix preview crop --- examples/core/core_3d_fps_controller.png | Bin 16483 -> 9083 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/core/core_3d_fps_controller.png b/examples/core/core_3d_fps_controller.png index 65a908d529e5eeb82682af2230caf35d1877b4b2..d4f83482ffbc77da8353983100d6590071719d23 100644 GIT binary patch literal 9083 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 V2;Vf*W?*1o@O1TaS?83{1OW3r=#BsY literal 16483 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 From 6266d0f419cc4bfbc1912ead8a691a2855440d56 Mon Sep 17 00:00:00 2001 From: Diego Sanz <35377545+ElDigoXD@users.noreply.github.com> Date: Tue, 24 Jun 2025 13:48:20 +0200 Subject: [PATCH 053/242] Fix typo on config.h --- src/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.h b/src/config.h index dc6cc5893..ef01013b3 100644 --- a/src/config.h +++ b/src/config.h @@ -189,7 +189,7 @@ //------------------------------------------------------------------------------------ // Module: rtextures - Configuration Flags //------------------------------------------------------------------------------------ -// Selecte desired fileformats to be supported for image data loading +// Selected desired fileformats to be supported for image data loading #define SUPPORT_FILEFORMAT_PNG 1 //#define SUPPORT_FILEFORMAT_BMP 1 //#define SUPPORT_FILEFORMAT_TGA 1 From 44f670899ced285793166616cac9af4a1137d2d4 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 24 Jun 2025 20:11:35 +0200 Subject: [PATCH 054/242] REVIEWED: Avoid `rtext` dependency on `rcore_desktop_sdl` #4959 --- src/platforms/rcore_desktop_sdl.c | 53 ++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 6af1cb6c7..60753caf4 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -424,6 +424,8 @@ void ClosePlatform(void); // Close platform static KeyboardKey ConvertScancodeToKey(SDL_Scancode sdlScancode); // Help convert SDL scancodes to raylib key +static int GetCodepointNextSDL(const char *text, int *codepointSize); // Get next codepoint in a byte sequence and bytes processed + //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- @@ -1601,13 +1603,18 @@ void PollInputEvents(void) { // NOTE: event.text.text data comes an UTF-8 text sequence but we register codepoints (int) - int codepointSize = 0; - // Check if there is space available in the queue if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE) { // Add character (codepoint) to the queue - CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = GetCodepointNext(event.text.text, &codepointSize); + #if defined(PLATFORM_DESKTOP_SDL3) + unsigned int textLen = strlen(event.text.text); + unsigned int codepoint = (unsigned int)SDL_StepUTF8(&event.text.text, textLen); + #else + int codepointSize = 0; + codepoint = GetCodepointNextSDL(event.text.text, &codepointSize); + #endif + CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = codepoint; CORE.Input.Keyboard.charPressedQueueCount++; } } break; @@ -2093,4 +2100,42 @@ static KeyboardKey ConvertScancodeToKey(SDL_Scancode sdlScancode) return KEY_NULL; // No equivalent key in Raylib } -// EOF + +// Get next codepoint in a byte sequence and bytes processed +static int GetCodepointNextSDL(const char *text, int *codepointSize) +{ + const char *ptr = text; + int codepoint = 0x3f; // Codepoint (defaults to '?') + *codepointSize = 1; + + // Get current codepoint and bytes processed + if (0xf0 == (0xf8 & ptr[0])) + { + // 4 byte UTF-8 codepoint + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } // 10xxxxxx checks + codepoint = ((0x07 & ptr[0]) << 18) | ((0x3f & ptr[1]) << 12) | ((0x3f & ptr[2]) << 6) | (0x3f & ptr[3]); + *codepointSize = 4; + } + else if (0xe0 == (0xf0 & ptr[0])) + { + // 3 byte UTF-8 codepoint */ + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } // 10xxxxxx checks + codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]); + *codepointSize = 3; + } + else if (0xc0 == (0xe0 & ptr[0])) + { + // 2 byte UTF-8 codepoint + if ((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } // 10xxxxxx checks + codepoint = ((0x1f & ptr[0]) << 6) | (0x3f & ptr[1]); + *codepointSize = 2; + } + else if (0x00 == (0x80 & ptr[0])) + { + // 1 byte UTF-8 codepoint + codepoint = ptr[0]; + *codepointSize = 1; + } + + return codepoint; +} From d659037fbe73c262d45add495a446d9e226b3563 Mon Sep 17 00:00:00 2001 From: Maicon Date: Fri, 27 Jun 2025 08:35:55 +0100 Subject: [PATCH 055/242] Update emsdk version for zig build to fix the issue with the EM_BOOL --- build.zig.zon | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 3bf82afe5..571550860 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -12,8 +12,8 @@ .lazy = true, }, .emsdk = .{ - .url = "git+https://github.com/emscripten-core/emsdk#3.1.50", - .hash = "N-V-__8AALRTBQDo_pUJ8IQ-XiIyYwDKQVwnr7-7o5kvPDGE", + .url = "git+https://github.com/emscripten-core/emsdk#4.0.9", + .hash = "N-V-__8AAJl1DwBezhYo_VE6f53mPVm00R-Fk28NPW7P14EQ", .lazy = true, }, }, @@ -23,6 +23,6 @@ "build.zig.zon", "src", "examples", - "LICENSE", + "LICENSE", }, } From 6e9c3acaa4e321d70940783dc9bbfb65974e6d20 Mon Sep 17 00:00:00 2001 From: Maicon Date: Sun, 29 Jun 2025 09:04:58 +0100 Subject: [PATCH 056/242] Add run examples using zig and emscripten for web --- build.zig | 244 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 178 insertions(+), 66 deletions(-) diff --git a/build.zig b/build.zig index e445bf576..754078534 100644 --- a/build.zig +++ b/build.zig @@ -4,6 +4,9 @@ const builtin = @import("builtin"); /// Minimum supported version of Zig const min_ver = "0.13.0"; +const emccOutputDir = "zig-out" ++ std.fs.path.sep_str ++ "htmlout" ++ std.fs.path.sep_str; +const emccOutputFile = "index.html"; + comptime { const order = std.SemanticVersion.order; const parse = std.SemanticVersion.parse; @@ -45,6 +48,26 @@ fn emSdkSetupStep(b: *std.Build, emsdk: *std.Build.Dependency) !?*std.Build.Step } } +// Adaoted from Not-Nik/raylib-zig +fn emscriptenRunStep(b: *std.Build, emsdk: *std.Build.Dependency, examplePath: []const u8) !*std.Build.Step.Run { + const dot_emsc_path = emsdk.path("upstream/emscripten/cache/sysroot/include").getPath(b); + // If compiling on windows , use emrun.bat. + const emrunExe = switch (builtin.os.tag) { + .windows => "emrun.bat", + else => "emrun", + }; + var emrun_run_arg = try b.allocator.alloc(u8, dot_emsc_path.len + emrunExe.len + 1); + defer b.allocator.free(emrun_run_arg); + + if (b.sysroot == null) { + emrun_run_arg = try std.fmt.bufPrint(emrun_run_arg, "{s}", .{emrunExe}); + } else { + emrun_run_arg = try std.fmt.bufPrint(emrun_run_arg, "{s}" ++ std.fs.path.sep_str ++ "{s}", .{ dot_emsc_path, emrunExe }); + } + const run_cmd = b.addSystemCommand(&.{ emrun_run_arg, examplePath }); + return run_cmd; +} + /// A list of all flags from `src/config.h` that one may override const config_h_flags = outer: { // Set this value higher if compile errors happen as `src/config.h` gets larger @@ -85,6 +108,9 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. "-D_GNU_SOURCE", "-DGL_SILENCE_DEPRECATION=199309L", "-fno-sanitize=undefined", // https://github.com/raysan5/raylib/issues/3674 + // This is off by default but some linux distributions have it on by default + // No Stack Protector is set to prevent the issues when running the examples for emscripten + "-fno-stack-protector", }); if (options.shared) { @@ -511,12 +537,9 @@ fn addExamples( optimize: std.builtin.OptimizeMode, raylib: *std.Build.Step.Compile, ) !*std.Build.Step { - if (target.result.os.tag == .emscripten) { - return &b.addFail("Emscripten building via Zig unsupported").step; - } - const all = b.step(module, "All " ++ module ++ " examples"); const module_subpath = b.pathJoin(&.{ "examples", module }); + const module_resources = b.pathJoin(&.{ module_subpath, "resources@resources" }); var dir = try std.fs.cwd().openDir(b.pathFromRoot(module_subpath), .{ .iterate = true }); defer if (comptime builtin.zig_version.minor >= 12) dir.close(); @@ -530,71 +553,160 @@ fn addExamples( // zig's mingw headers do not include pthread.h if (std.mem.eql(u8, "core_loading_thread", name) and target.result.os.tag == .windows) continue; - const exe = b.addExecutable(.{ - .name = name, - .target = target, - .optimize = optimize, - }); - exe.addCSourceFile(.{ .file = b.path(path), .flags = &.{} }); - exe.linkLibC(); + if (target.result.os.tag == .emscripten) { + const exe_lib = b.addStaticLibrary(.{ + .name = name, + .target = target, + .optimize = optimize, + }); + exe_lib.addCSourceFile(.{ + .file = b.path(path), + .flags = &.{ + "-fno-stack-protector", + }, + }); + exe_lib.linkLibC(); + exe_lib.rdynamic = true; - // special examples that test using these external dependencies directly - // alongside raylib - if (std.mem.eql(u8, name, "rlgl_standalone")) { - exe.addIncludePath(b.path("src")); - exe.addIncludePath(b.path("src/external/glfw/include")); - if (!hasCSource(raylib.root_module, "rglfw.c")) { - exe.addCSourceFile(.{ .file = b.path("src/rglfw.c"), .flags = &.{} }); + exe_lib.root_module.addCMacro("PLATFORM_WEB", ""); + exe_lib.shared_memory = false; + exe_lib.root_module.single_threaded = false; + + if (std.mem.eql(u8, name, "raylib_opengl_interop")) { + exe_lib.addIncludePath(b.path("src/external")); } + + exe_lib.linkLibrary(raylib); + + // Include emscripten for cross compilation + if (b.lazyDependency("emsdk", .{})) |emsdk_dep| { + if (try emSdkSetupStep(b, emsdk_dep)) |emSdkStep| { + exe_lib.step.dependOn(&emSdkStep.step); + } + + exe_lib.addIncludePath(emsdk_dep.path("upstream/emscripten/cache/sysroot/include")); + // addAssets(b, exe_lib); + // Create the output directory because emcc can't do it. + + const emccOutputDirExample = b.pathJoin(&.{ emccOutputDir, name, std.fs.path.sep_str }); + const mkdir_command = switch (builtin.os.tag) { + .windows => b.addSystemCommand(&.{ "cmd.exe", "/c", "if", "not", "exist", emccOutputDirExample, "mkdir", emccOutputDirExample }), + else => b.addSystemCommand(&.{ "mkdir", "-p", emccOutputDirExample }), + }; + + const emcc_exe = switch (builtin.os.tag) { // TODO bundle emcc as a build dependency + .windows => "emcc.bat", + else => "emcc", + }; + + const emcc_exe_path = b.pathJoin(&.{ emsdk_dep.path("upstream/emscripten").getPath(b), emcc_exe }); + const emcc_command = b.addSystemCommand(&[_][]const u8{emcc_exe_path}); + emcc_command.step.dependOn(&mkdir_command.step); + const emccOutputDirExampleWithFile = b.pathJoin(&.{ emccOutputDir, name, std.fs.path.sep_str, emccOutputFile }); + emcc_command.addArgs(&[_][]const u8{ + "-o", + emccOutputDirExampleWithFile, + "-sFULL-ES3=1", + "-sUSE_GLFW=3", + "-sSTACK_OVERFLOW_CHECK=1", + "-sASYNCIFY", + "-O0", + "--emrun", + "--preload-file", + module_resources, + "--shell-file", + b.path("src/shell.html").getPath(b), + }); + + const link_items: []const *std.Build.Step.Compile = &.{ + raylib, + exe_lib, + }; + for (link_items) |item| { + emcc_command.addFileArg(item.getEmittedBin()); + emcc_command.step.dependOn(&item.step); + } + + const run_step = emscriptenRunStep(b, emsdk_dep, emccOutputDirExampleWithFile) catch |err| { + // do some stuff, maybe log an error + std.debug.print("EmscriptenRunStep error: {}\n", .{err}); + continue; + }; + run_step.step.dependOn(&emcc_command.step); + run_step.addArg("--no_browser"); + const run_option = b.step(name, name); + + run_option.dependOn(&run_step.step); + + all.dependOn(&emcc_command.step); + } + } else { + const exe = b.addExecutable(.{ + .name = name, + .target = target, + .optimize = optimize, + }); + exe.addCSourceFile(.{ .file = b.path(path), .flags = &.{} }); + exe.linkLibC(); + + // special examples that test using these external dependencies directly + // alongside raylib + if (std.mem.eql(u8, name, "rlgl_standalone")) { + exe.addIncludePath(b.path("src")); + exe.addIncludePath(b.path("src/external/glfw/include")); + if (!hasCSource(raylib.root_module, "rglfw.c")) { + exe.addCSourceFile(.{ .file = b.path("src/rglfw.c"), .flags = &.{} }); + } + } + if (std.mem.eql(u8, name, "raylib_opengl_interop")) { + exe.addIncludePath(b.path("src/external")); + } + + exe.linkLibrary(raylib); + + switch (target.result.os.tag) { + .windows => { + exe.linkSystemLibrary("winmm"); + exe.linkSystemLibrary("gdi32"); + exe.linkSystemLibrary("opengl32"); + + exe.root_module.addCMacro("PLATFORM_DESKTOP", ""); + }, + .linux => { + exe.linkSystemLibrary("GL"); + exe.linkSystemLibrary("rt"); + exe.linkSystemLibrary("dl"); + exe.linkSystemLibrary("m"); + exe.linkSystemLibrary("X11"); + + exe.root_module.addCMacro("PLATFORM_DESKTOP", ""); + }, + .macos => { + exe.linkFramework("Foundation"); + exe.linkFramework("Cocoa"); + exe.linkFramework("OpenGL"); + exe.linkFramework("CoreAudio"); + exe.linkFramework("CoreVideo"); + exe.linkFramework("IOKit"); + + exe.root_module.addCMacro("PLATFORM_DESKTOP", ""); + }, + else => { + @panic("Unsupported OS"); + }, + } + + const install_cmd = b.addInstallArtifact(exe, .{}); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.cwd = b.path(module_subpath); + run_cmd.step.dependOn(&install_cmd.step); + + const run_step = b.step(name, name); + run_step.dependOn(&run_cmd.step); + + all.dependOn(&install_cmd.step); } - if (std.mem.eql(u8, name, "raylib_opengl_interop")) { - exe.addIncludePath(b.path("src/external")); - } - - exe.linkLibrary(raylib); - - switch (target.result.os.tag) { - .windows => { - exe.linkSystemLibrary("winmm"); - exe.linkSystemLibrary("gdi32"); - exe.linkSystemLibrary("opengl32"); - - exe.root_module.addCMacro("PLATFORM_DESKTOP", ""); - }, - .linux => { - exe.linkSystemLibrary("GL"); - exe.linkSystemLibrary("rt"); - exe.linkSystemLibrary("dl"); - exe.linkSystemLibrary("m"); - exe.linkSystemLibrary("X11"); - - exe.root_module.addCMacro("PLATFORM_DESKTOP", ""); - }, - .macos => { - exe.linkFramework("Foundation"); - exe.linkFramework("Cocoa"); - exe.linkFramework("OpenGL"); - exe.linkFramework("CoreAudio"); - exe.linkFramework("CoreVideo"); - exe.linkFramework("IOKit"); - - exe.root_module.addCMacro("PLATFORM_DESKTOP", ""); - }, - else => { - @panic("Unsupported OS"); - }, - } - - const install_cmd = b.addInstallArtifact(exe, .{}); - - const run_cmd = b.addRunArtifact(exe); - run_cmd.cwd = b.path(module_subpath); - run_cmd.step.dependOn(&install_cmd.step); - - const run_step = b.step(name, name); - run_step.dependOn(&run_cmd.step); - - all.dependOn(&install_cmd.step); } return all; } From 8f50436dc924cd8696d0fea1bf21ec8de521dfa3 Mon Sep 17 00:00:00 2001 From: Maicon Date: Sun, 29 Jun 2025 09:11:41 +0100 Subject: [PATCH 057/242] Fix comments --- build.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build.zig b/build.zig index 754078534..214de2970 100644 --- a/build.zig +++ b/build.zig @@ -585,9 +585,8 @@ fn addExamples( } exe_lib.addIncludePath(emsdk_dep.path("upstream/emscripten/cache/sysroot/include")); - // addAssets(b, exe_lib); - // Create the output directory because emcc can't do it. + // Create the output directory because emcc can't do it. const emccOutputDirExample = b.pathJoin(&.{ emccOutputDir, name, std.fs.path.sep_str }); const mkdir_command = switch (builtin.os.tag) { .windows => b.addSystemCommand(&.{ "cmd.exe", "/c", "if", "not", "exist", emccOutputDirExample, "mkdir", emccOutputDirExample }), @@ -628,7 +627,6 @@ fn addExamples( } const run_step = emscriptenRunStep(b, emsdk_dep, emccOutputDirExampleWithFile) catch |err| { - // do some stuff, maybe log an error std.debug.print("EmscriptenRunStep error: {}\n", .{err}); continue; }; From 1db006b0825f7e0bb2db59f2aa683211857f8388 Mon Sep 17 00:00:00 2001 From: Colin Woodbury Date: Mon, 30 Jun 2025 05:40:17 +0900 Subject: [PATCH 058/242] docs: mention another Common Lisp binding --- BINDINGS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/BINDINGS.md b/BINDINGS.md index 986c27abc..719a902a0 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -18,6 +18,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [cl-raylib](https://github.com/longlene/cl-raylib) | 4.0 | [Common Lisp](https://common-lisp.net) | MIT | | [claylib/wrap](https://github.com/defun-games/claylib) | 4.5 | [Common Lisp](https://common-lisp.net) | Zlib | | [claw-raylib](https://github.com/bohonghuang/claw-raylib) | **auto** | [Common Lisp](https://common-lisp.net) | Apache-2.0 | +| [raylib](https://github.com/fosskers/raylib) | 5.5 | [Common Lisp](https://common-lisp.net) | MPL-2.0 | | [chez-raylib](https://github.com/Yunoinsky/chez-raylib) | **auto** | [Chez Scheme](https://cisco.github.io/ChezScheme) | GPLv3 | | [CLIPSraylib](https://github.com/mrryanjohnston/CLIPSraylib) | **auto** | [CLIPS](https://www.clipsrules.net/) | MIT | | [raylib-cr](https://github.com/sol-vin/raylib-cr) | 4.6-dev (5e1a81) | [Crystal](https://crystal-lang.org) | Apache-2.0 | @@ -50,7 +51,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [raylib-luajit-generated](https://github.com/james2doyle/raylib-luajit-generated) | 5.5 | [Lua](http://www.lua.org) | MIT | | [raylib-matte](https://github.com/jcorks/raylib-matte) | 4.6-dev | [Matte](https://github.com/jcorks/matte) | **???** | | [Raylib.nelua](https://github.com/AuzFox/Raylib.nelua) | **5.5** | [nelua](https://nelua.io) | Zlib | -| [raylib-bindings](https://github.com/vaiorabbit/raylib-bindings) | 5.6-dev | [Ruby](https://www.ruby-lang.org/en) | Zlib | +| [raylib-bindings](https://github.com/vaiorabbit/raylib-bindings) | 5.6-dev | [Ruby](https://www.ruby-lang.org/en) | Zlib | | [naylib](https://github.com/planetis-m/naylib) | **5.6-dev** | [Nim](https://nim-lang.org) | MIT | | [node-raylib](https://github.com/RobLoach/node-raylib) | 4.5 | [Node.js](https://nodejs.org/en) | Zlib | | [raylib-odin](https://github.com/odin-lang/Odin/tree/master/vendor/raylib) | **5.5** | [Odin](https://odin-lang.org) | BSD-3Clause | From 0cae8890b85395285df3767de4baf1925148b8ea Mon Sep 17 00:00:00 2001 From: Maicon Date: Mon, 30 Jun 2025 09:54:20 +0100 Subject: [PATCH 059/242] Remove -fno-stack-protector as it is not needed and add requestFullscreen on exported methods --- build.zig | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/build.zig b/build.zig index 214de2970..4026a23eb 100644 --- a/build.zig +++ b/build.zig @@ -108,9 +108,6 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. "-D_GNU_SOURCE", "-DGL_SILENCE_DEPRECATION=199309L", "-fno-sanitize=undefined", // https://github.com/raysan5/raylib/issues/3674 - // This is off by default but some linux distributions have it on by default - // No Stack Protector is set to prevent the issues when running the examples for emscripten - "-fno-stack-protector", }); if (options.shared) { @@ -365,7 +362,6 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. setDesktopPlatform(raylib, options.platform); }, .emscripten => { - // Include emscripten for cross compilation if (b.lazyDependency("emsdk", .{})) |dep| { if (try emSdkSetupStep(b, dep)) |emSdkStep| { raylib.step.dependOn(&emSdkStep.step); @@ -561,19 +557,17 @@ fn addExamples( }); exe_lib.addCSourceFile(.{ .file = b.path(path), - .flags = &.{ - "-fno-stack-protector", - }, + .flags = &.{}, }); exe_lib.linkLibC(); - exe_lib.rdynamic = true; - - exe_lib.root_module.addCMacro("PLATFORM_WEB", ""); - exe_lib.shared_memory = false; - exe_lib.root_module.single_threaded = false; + if (std.mem.eql(u8, name, "rlgl_standalone")) { + //TODO: Make rlgl_standalone example work + continue; + } if (std.mem.eql(u8, name, "raylib_opengl_interop")) { - exe_lib.addIncludePath(b.path("src/external")); + //TODO: Make raylib_opengl_interop example work + continue; } exe_lib.linkLibrary(raylib); @@ -593,7 +587,7 @@ fn addExamples( else => b.addSystemCommand(&.{ "mkdir", "-p", emccOutputDirExample }), }; - const emcc_exe = switch (builtin.os.tag) { // TODO bundle emcc as a build dependency + const emcc_exe = switch (builtin.os.tag) { .windows => "emcc.bat", else => "emcc", }; @@ -608,6 +602,7 @@ fn addExamples( "-sFULL-ES3=1", "-sUSE_GLFW=3", "-sSTACK_OVERFLOW_CHECK=1", + "-sEXPORTED_RUNTIME_METHODS=['requestFullscreen']", "-sASYNCIFY", "-O0", "--emrun", @@ -626,10 +621,7 @@ fn addExamples( emcc_command.step.dependOn(&item.step); } - const run_step = emscriptenRunStep(b, emsdk_dep, emccOutputDirExampleWithFile) catch |err| { - std.debug.print("EmscriptenRunStep error: {}\n", .{err}); - continue; - }; + const run_step = try emscriptenRunStep(b, emsdk_dep, emccOutputDirExampleWithFile); run_step.step.dependOn(&emcc_command.step); run_step.addArg("--no_browser"); const run_option = b.step(name, name); From eef1bac3e249212e2d83da48c71a4ca73cf535b3 Mon Sep 17 00:00:00 2001 From: Maicon Santana Date: Mon, 30 Jun 2025 19:38:34 +0100 Subject: [PATCH 060/242] fix misspelling --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 4026a23eb..14e9af21e 100644 --- a/build.zig +++ b/build.zig @@ -48,7 +48,7 @@ fn emSdkSetupStep(b: *std.Build, emsdk: *std.Build.Dependency) !?*std.Build.Step } } -// Adaoted from Not-Nik/raylib-zig +// Adapted from Not-Nik/raylib-zig fn emscriptenRunStep(b: *std.Build, emsdk: *std.Build.Dependency, examplePath: []const u8) !*std.Build.Step.Run { const dot_emsc_path = emsdk.path("upstream/emscripten/cache/sysroot/include").getPath(b); // If compiling on windows , use emrun.bat. From bee524e5e6443a8abe6ae76a943db6109a26171f Mon Sep 17 00:00:00 2001 From: sir-irk Date: Tue, 1 Jul 2025 13:23:05 -0500 Subject: [PATCH 061/242] fixing offset for processing tangents for gltf loading --- src/rmodels.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index 84daabc15..ac6f4c594 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -5577,7 +5577,7 @@ static Model LoadGLTF(const char *fileName) } else TRACELOG(LOG_WARNING, "MODEL: [%s] Normal attribute data format not supported, use vec3 float", fileName); } - else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_tangent) // TANGENT, vec3, float + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_tangent) // TANGENT, vec4, float, w is tangent basis sign { cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; @@ -5593,10 +5593,10 @@ static Model LoadGLTF(const char *fileName) float *tangents = model.meshes[meshIndex].tangents; for (unsigned int k = 0; k < attribute->count; k++) { - Vector3 tt = Vector3Transform((Vector3){ tangents[3*k], tangents[3*k+1], tangents[3*k+2] }, worldMatrix); - tangents[3*k] = tt.x; - tangents[3*k+1] = tt.y; - tangents[3*k+2] = tt.z; + Vector3 tt = Vector3Transform((Vector3){ tangents[4*k], tangents[4*k+1], tangents[4*k+2] }, worldMatrix); + tangents[4*k] = tt.x; + tangents[4*k+1] = tt.y; + tangents[4*k+2] = tt.z; } } else TRACELOG(LOG_WARNING, "MODEL: [%s] Tangent attribute data format not supported, use vec4 float", fileName); From f86295732a5a15156917d2b92696826bae72ac6d Mon Sep 17 00:00:00 2001 From: sir-irk Date: Tue, 1 Jul 2025 15:18:11 -0500 Subject: [PATCH 062/242] fixing shader tangents to be vec4 --- examples/shaders/resources/shaders/glsl100/pbr.vs | 6 +++--- examples/shaders/resources/shaders/glsl120/pbr.vs | 8 ++++---- examples/shaders/resources/shaders/glsl330/pbr.vs | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/shaders/resources/shaders/glsl100/pbr.vs b/examples/shaders/resources/shaders/glsl100/pbr.vs index a55c0ea4b..079ccdc70 100644 --- a/examples/shaders/resources/shaders/glsl100/pbr.vs +++ b/examples/shaders/resources/shaders/glsl100/pbr.vs @@ -4,7 +4,7 @@ attribute vec3 vertexPosition; attribute vec2 vertexTexCoord; attribute vec3 vertexNormal; -attribute vec3 vertexTangent; +attribute vec4 vertexTangent; attribute vec4 vertexColor; // Input uniform values @@ -52,7 +52,7 @@ mat3 transpose(mat3 m) void main() { // Compute binormal from vertex normal and tangent - vec3 vertexBinormal = cross(vertexNormal, vertexTangent); + vec3 vertexBinormal = cross(vertexNormal, vertexTangent.xyz) * vertexTangent.w; // Compute fragment normal based on normal transformations mat3 normalMatrix = transpose(inverse(mat3(matModel))); @@ -62,7 +62,7 @@ void main() fragTexCoord = vertexTexCoord*2.0; fragNormal = normalize(normalMatrix*vertexNormal); - vec3 fragTangent = normalize(normalMatrix*vertexTangent); + vec3 fragTangent = normalize(normalMatrix*vertexTangent.xyz) * vertexTangent.w; fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal)*fragNormal); vec3 fragBinormal = normalize(normalMatrix*vertexBinormal); fragBinormal = cross(fragNormal, fragTangent); diff --git a/examples/shaders/resources/shaders/glsl120/pbr.vs b/examples/shaders/resources/shaders/glsl120/pbr.vs index d3cc66488..4b68ef673 100644 --- a/examples/shaders/resources/shaders/glsl120/pbr.vs +++ b/examples/shaders/resources/shaders/glsl120/pbr.vs @@ -4,7 +4,7 @@ attribute vec3 vertexPosition; attribute vec2 vertexTexCoord; attribute vec3 vertexNormal; -attribute vec3 vertexTangent; +attribute vec4 vertexTangent; attribute vec4 vertexColor; // Input uniform values @@ -52,7 +52,7 @@ mat3 transpose(mat3 m) void main() { // Compute binormal from vertex normal and tangent - vec3 vertexBinormal = cross(vertexNormal, vertexTangent); + vec3 vertexBinormal = cross(vertexNormal, vertexTangent.xyz) * vertexTangent.w; // Compute fragment normal based on normal transformations mat3 normalMatrix = transpose(inverse(mat3(matModel))); @@ -62,7 +62,7 @@ void main() fragTexCoord = vertexTexCoord*2.0; fragNormal = normalize(normalMatrix*vertexNormal); - vec3 fragTangent = normalize(normalMatrix*vertexTangent); + vec3 fragTangent = normalize(normalMatrix*vertexTangent.xyz) * vertexTangent.w; fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal)*fragNormal); vec3 fragBinormal = normalize(normalMatrix*vertexBinormal); fragBinormal = cross(fragNormal, fragTangent); @@ -71,4 +71,4 @@ void main() // Calculate final vertex position gl_Position = mvp*vec4(vertexPosition, 1.0); -} \ No newline at end of file +} diff --git a/examples/shaders/resources/shaders/glsl330/pbr.vs b/examples/shaders/resources/shaders/glsl330/pbr.vs index 6f262313c..a9e65e96c 100644 --- a/examples/shaders/resources/shaders/glsl330/pbr.vs +++ b/examples/shaders/resources/shaders/glsl330/pbr.vs @@ -4,7 +4,7 @@ in vec3 vertexPosition; in vec2 vertexTexCoord; in vec3 vertexNormal; -in vec3 vertexTangent; +in vec4 vertexTangent; in vec4 vertexColor; // Input uniform values @@ -26,7 +26,7 @@ const float normalOffset = 0.1; void main() { // Compute binormal from vertex normal and tangent - vec3 vertexBinormal = cross(vertexNormal, vertexTangent); + vec3 vertexBinormal = cross(vertexNormal, vertexTangent.xyz) * vertexTangent.w; // Compute fragment normal based on normal transformations mat3 normalMatrix = transpose(inverse(mat3(matModel))); @@ -36,7 +36,7 @@ void main() fragTexCoord = vertexTexCoord*2.0; fragNormal = normalize(normalMatrix*vertexNormal); - vec3 fragTangent = normalize(normalMatrix*vertexTangent); + vec3 fragTangent = normalize(normalMatrix*vertexTangent.xyz) * vertexTangent.w; fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal)*fragNormal); vec3 fragBinormal = normalize(normalMatrix*vertexBinormal); fragBinormal = cross(fragNormal, fragTangent); @@ -45,4 +45,4 @@ void main() // Calculate final vertex position gl_Position = mvp*vec4(vertexPosition, 1.0); -} \ No newline at end of file +} From ed509193d9d311648f5b6e4010588a1186118f19 Mon Sep 17 00:00:00 2001 From: sir-irk Date: Tue, 1 Jul 2025 15:30:50 -0500 Subject: [PATCH 063/242] remving w multiply on the tangent itself --- examples/shaders/resources/shaders/glsl100/pbr.vs | 2 +- examples/shaders/resources/shaders/glsl120/pbr.vs | 2 +- examples/shaders/resources/shaders/glsl330/pbr.vs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/shaders/resources/shaders/glsl100/pbr.vs b/examples/shaders/resources/shaders/glsl100/pbr.vs index 079ccdc70..baf003842 100644 --- a/examples/shaders/resources/shaders/glsl100/pbr.vs +++ b/examples/shaders/resources/shaders/glsl100/pbr.vs @@ -62,7 +62,7 @@ void main() fragTexCoord = vertexTexCoord*2.0; fragNormal = normalize(normalMatrix*vertexNormal); - vec3 fragTangent = normalize(normalMatrix*vertexTangent.xyz) * vertexTangent.w; + vec3 fragTangent = normalize(normalMatrix*vertexTangent.xyz); fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal)*fragNormal); vec3 fragBinormal = normalize(normalMatrix*vertexBinormal); fragBinormal = cross(fragNormal, fragTangent); diff --git a/examples/shaders/resources/shaders/glsl120/pbr.vs b/examples/shaders/resources/shaders/glsl120/pbr.vs index 4b68ef673..e9750a60a 100644 --- a/examples/shaders/resources/shaders/glsl120/pbr.vs +++ b/examples/shaders/resources/shaders/glsl120/pbr.vs @@ -62,7 +62,7 @@ void main() fragTexCoord = vertexTexCoord*2.0; fragNormal = normalize(normalMatrix*vertexNormal); - vec3 fragTangent = normalize(normalMatrix*vertexTangent.xyz) * vertexTangent.w; + vec3 fragTangent = normalize(normalMatrix*vertexTangent.xyz); fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal)*fragNormal); vec3 fragBinormal = normalize(normalMatrix*vertexBinormal); fragBinormal = cross(fragNormal, fragTangent); diff --git a/examples/shaders/resources/shaders/glsl330/pbr.vs b/examples/shaders/resources/shaders/glsl330/pbr.vs index a9e65e96c..8aabb6baf 100644 --- a/examples/shaders/resources/shaders/glsl330/pbr.vs +++ b/examples/shaders/resources/shaders/glsl330/pbr.vs @@ -36,7 +36,7 @@ void main() fragTexCoord = vertexTexCoord*2.0; fragNormal = normalize(normalMatrix*vertexNormal); - vec3 fragTangent = normalize(normalMatrix*vertexTangent.xyz) * vertexTangent.w; + vec3 fragTangent = normalize(normalMatrix*vertexTangent.xyz); fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal)*fragNormal); vec3 fragBinormal = normalize(normalMatrix*vertexBinormal); fragBinormal = cross(fragNormal, fragTangent); From f1600a0c7e981c04aad18371d757cf799f28dd0e Mon Sep 17 00:00:00 2001 From: Maicon Date: Fri, 4 Jul 2025 10:24:32 +0100 Subject: [PATCH 064/242] Fix issue on zig build emscripten run if the user has not installed emsdk --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 14e9af21e..501d7a229 100644 --- a/build.zig +++ b/build.zig @@ -60,7 +60,7 @@ fn emscriptenRunStep(b: *std.Build, emsdk: *std.Build.Dependency, examplePath: [ defer b.allocator.free(emrun_run_arg); if (b.sysroot == null) { - emrun_run_arg = try std.fmt.bufPrint(emrun_run_arg, "{s}", .{emrunExe}); + emrun_run_arg = try std.fmt.bufPrint(emrun_run_arg, "{s}" ++ std.fs.path.sep_str ++ "{s}", .{ emsdk.path("upstream/emscripten").getPath(b), emrunExe }); } else { emrun_run_arg = try std.fmt.bufPrint(emrun_run_arg, "{s}" ++ std.fs.path.sep_str ++ "{s}", .{ dot_emsc_path, emrunExe }); } From 910f4083e180df6d3d49584131665f0e9e61049b Mon Sep 17 00:00:00 2001 From: EmilSylveon Date: Sat, 5 Jul 2025 00:53:01 +0300 Subject: [PATCH 065/242] update dr_libs --- src/external/dr_flac.h | 462 +++++++++++---------- src/external/dr_mp3.h | 899 ++++++++++++++++++++++++++++++++--------- src/external/dr_wav.h | 492 +++++++++++++++------- 3 files changed, 1282 insertions(+), 571 deletions(-) diff --git a/src/external/dr_flac.h b/src/external/dr_flac.h index 14324cf37..497fcddd5 100644 --- a/src/external/dr_flac.h +++ b/src/external/dr_flac.h @@ -1,121 +1,12 @@ /* FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_flac - v0.12.42 - 2023-11-02 +dr_flac - v0.13.0 - TBD David Reid - mackron@gmail.com GitHub: https://github.com/mackron/dr_libs */ -/* -RELEASE NOTES - v0.12.0 -======================= -Version 0.12.0 has breaking API changes including changes to the existing API and the removal of deprecated APIs. - - -Improved Client-Defined Memory Allocation ------------------------------------------ -The main change with this release is the addition of a more flexible way of implementing custom memory allocation routines. The -existing system of DRFLAC_MALLOC, DRFLAC_REALLOC and DRFLAC_FREE are still in place and will be used by default when no custom -allocation callbacks are specified. - -To use the new system, you pass in a pointer to a drflac_allocation_callbacks object to drflac_open() and family, like this: - - void* my_malloc(size_t sz, void* pUserData) - { - return malloc(sz); - } - void* my_realloc(void* p, size_t sz, void* pUserData) - { - return realloc(p, sz); - } - void my_free(void* p, void* pUserData) - { - free(p); - } - - ... - - drflac_allocation_callbacks allocationCallbacks; - allocationCallbacks.pUserData = &myData; - allocationCallbacks.onMalloc = my_malloc; - allocationCallbacks.onRealloc = my_realloc; - allocationCallbacks.onFree = my_free; - drflac* pFlac = drflac_open_file("my_file.flac", &allocationCallbacks); - -The advantage of this new system is that it allows you to specify user data which will be passed in to the allocation routines. - -Passing in null for the allocation callbacks object will cause dr_flac to use defaults which is the same as DRFLAC_MALLOC, -DRFLAC_REALLOC and DRFLAC_FREE and the equivalent of how it worked in previous versions. - -Every API that opens a drflac object now takes this extra parameter. These include the following: - - drflac_open() - drflac_open_relaxed() - drflac_open_with_metadata() - drflac_open_with_metadata_relaxed() - drflac_open_file() - drflac_open_file_with_metadata() - drflac_open_memory() - drflac_open_memory_with_metadata() - drflac_open_and_read_pcm_frames_s32() - drflac_open_and_read_pcm_frames_s16() - drflac_open_and_read_pcm_frames_f32() - drflac_open_file_and_read_pcm_frames_s32() - drflac_open_file_and_read_pcm_frames_s16() - drflac_open_file_and_read_pcm_frames_f32() - drflac_open_memory_and_read_pcm_frames_s32() - drflac_open_memory_and_read_pcm_frames_s16() - drflac_open_memory_and_read_pcm_frames_f32() - - - -Optimizations -------------- -Seeking performance has been greatly improved. A new binary search based seeking algorithm has been introduced which significantly -improves performance over the brute force method which was used when no seek table was present. Seek table based seeking also takes -advantage of the new binary search seeking system to further improve performance there as well. Note that this depends on CRC which -means it will be disabled when DR_FLAC_NO_CRC is used. - -The SSE4.1 pipeline has been cleaned up and optimized. You should see some improvements with decoding speed of 24-bit files in -particular. 16-bit streams should also see some improvement. - -drflac_read_pcm_frames_s16() has been optimized. Previously this sat on top of drflac_read_pcm_frames_s32() and performed it's s32 -to s16 conversion in a second pass. This is now all done in a single pass. This includes SSE2 and ARM NEON optimized paths. - -A minor optimization has been implemented for drflac_read_pcm_frames_s32(). This will now use an SSE2 optimized pipeline for stereo -channel reconstruction which is the last part of the decoding process. - -The ARM build has seen a few improvements. The CLZ (count leading zeroes) and REV (byte swap) instructions are now used when -compiling with GCC and Clang which is achieved using inline assembly. The CLZ instruction requires ARM architecture version 5 at -compile time and the REV instruction requires ARM architecture version 6. - -An ARM NEON optimized pipeline has been implemented. To enable this you'll need to add -mfpu=neon to the command line when compiling. - - -Removed APIs ------------- -The following APIs were deprecated in version 0.11.0 and have been completely removed in version 0.12.0: - - drflac_read_s32() -> drflac_read_pcm_frames_s32() - drflac_read_s16() -> drflac_read_pcm_frames_s16() - drflac_read_f32() -> drflac_read_pcm_frames_f32() - drflac_seek_to_sample() -> drflac_seek_to_pcm_frame() - drflac_open_and_decode_s32() -> drflac_open_and_read_pcm_frames_s32() - drflac_open_and_decode_s16() -> drflac_open_and_read_pcm_frames_s16() - drflac_open_and_decode_f32() -> drflac_open_and_read_pcm_frames_f32() - drflac_open_and_decode_file_s32() -> drflac_open_file_and_read_pcm_frames_s32() - drflac_open_and_decode_file_s16() -> drflac_open_file_and_read_pcm_frames_s16() - drflac_open_and_decode_file_f32() -> drflac_open_file_and_read_pcm_frames_f32() - drflac_open_and_decode_memory_s32() -> drflac_open_memory_and_read_pcm_frames_s32() - drflac_open_and_decode_memory_s16() -> drflac_open_memory_and_read_pcm_frames_s16() - drflac_open_and_decode_memory_f32() -> drflac_open_memroy_and_read_pcm_frames_f32() - -Prior versions of dr_flac operated on a per-sample basis whereas now it operates on PCM frames. The removed APIs all relate -to the old per-sample APIs. You now need to use the "pcm_frame" versions. -*/ - - /* Introduction ============ @@ -179,7 +70,7 @@ reports metadata to the application through the use of a callback, and every met The main opening APIs (`drflac_open()`, etc.) will fail if the header is not present. The presents a problem in certain scenarios such as broadcast style streams or internet radio where the header may not be present because the user has started playback mid-stream. To handle this, use the relaxed APIs: - + `drflac_open_relaxed()` `drflac_open_with_metadata_relaxed()` @@ -234,8 +125,8 @@ extern "C" { #define DRFLAC_XSTRINGIFY(x) DRFLAC_STRINGIFY(x) #define DRFLAC_VERSION_MAJOR 0 -#define DRFLAC_VERSION_MINOR 12 -#define DRFLAC_VERSION_REVISION 42 +#define DRFLAC_VERSION_MINOR 13 +#define DRFLAC_VERSION_REVISION 0 #define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) #include /* For size_t. */ @@ -348,11 +239,11 @@ but also more memory. In my testing there is diminishing returns after about 4KB #define DRFLAC_64BIT #endif -#if defined(__x86_64__) || defined(_M_X64) +#if defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC)) #define DRFLAC_X64 #elif defined(__i386) || defined(_M_IX86) #define DRFLAC_X86 -#elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64) +#elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) #define DRFLAC_ARM #endif /* End Architecture Detection */ @@ -406,8 +297,9 @@ typedef enum typedef enum { - drflac_seek_origin_start, - drflac_seek_origin_current + DRFLAC_SEEK_SET, + DRFLAC_SEEK_CUR, + DRFLAC_SEEK_END } drflac_seek_origin; /* The order of members in this structure is important because we map this directly to the raw data within the SEEKTABLE metadata block. */ @@ -547,7 +439,7 @@ offset (in) The number of bytes to move, relative to the origin. Will never be negative. origin (in) - The origin of the seek - the current position or the start of the stream. + The origin of the seek - the current position, the start of the stream, or the end of the stream. Return Value @@ -557,14 +449,32 @@ Whether or not the seek was successful. Remarks ------- -The offset will never be negative. Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which will be -either drflac_seek_origin_start or drflac_seek_origin_current. +Seeking relative to the start and the current position must always be supported. If seeking from the end of the stream is not supported, return DRFLAC_FALSE. When seeking to a PCM frame using drflac_seek_to_pcm_frame(), dr_flac may call this with an offset beyond the end of the FLAC stream. This needs to be detected and handled by returning DRFLAC_FALSE. */ typedef drflac_bool32 (* drflac_seek_proc)(void* pUserData, int offset, drflac_seek_origin origin); +/* +Callback for when the current position in the stream needs to be retrieved. + + +Parameters +---------- +pUserData (in) + The user data that was passed to drflac_open() and family. + +pCursor (out) + A pointer to a variable to receive the current position in the stream. + + +Return Value +------------ +Whether or not the operation was successful. +*/ +typedef drflac_bool32 (* drflac_tell_proc)(void* pUserData, drflac_int64* pCursor); + /* Callback for when a metadata block is read. @@ -603,6 +513,9 @@ typedef struct /* The function to call when the current read position needs to be moved. */ drflac_seek_proc onSeek; + /* The function to call when the current read position needs to be retrieved. */ + drflac_tell_proc onTell; + /* The user data to pass around to onRead and onSeek. */ void* pUserData; @@ -828,7 +741,7 @@ drflac_open_memory() drflac_open_with_metadata() drflac_close() */ -DRFLAC_API drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* Opens a FLAC stream with relaxed validation of the header block. @@ -869,7 +782,7 @@ force your `onRead` callback to return 0, which dr_flac will use as an indicator Use `drflac_open_with_metadata_relaxed()` if you need access to metadata. */ -DRFLAC_API drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* Opens a FLAC decoder and notifies the caller of the metadata chunks (album art, etc.). @@ -926,7 +839,7 @@ drflac_open_memory_with_metadata() drflac_open() drflac_close() */ -DRFLAC_API drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* The same as drflac_open_with_metadata(), except attempts to open the stream even when a header block is not present. @@ -936,7 +849,7 @@ See Also drflac_open_with_metadata() drflac_open_relaxed() */ -DRFLAC_API drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* Closes the given FLAC decoder. @@ -1234,13 +1147,13 @@ read samples into a dynamically sized buffer on the heap until no samples are le Do not call this function on a broadcast type of stream (like internet radio streams and whatnot). */ -DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Same as drflac_open_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. */ -DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Same as drflac_open_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. */ -DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); #ifndef DR_FLAC_NO_STDIO /* Same as drflac_open_and_read_pcm_frames_s32() except opens the decoder from a file. */ @@ -2960,25 +2873,25 @@ static drflac_bool32 drflac__seek_to_byte(drflac_bs* bs, drflac_uint64 offsetFro */ if (offsetFromStart > 0x7FFFFFFF) { drflac_uint64 bytesRemaining = offsetFromStart; - if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, drflac_seek_origin_start)) { + if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, DRFLAC_SEEK_SET)) { return DRFLAC_FALSE; } bytesRemaining -= 0x7FFFFFFF; while (bytesRemaining > 0x7FFFFFFF) { - if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, drflac_seek_origin_current)) { + if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; } bytesRemaining -= 0x7FFFFFFF; } if (bytesRemaining > 0) { - if (!bs->onSeek(bs->pUserData, (int)bytesRemaining, drflac_seek_origin_current)) { + if (!bs->onSeek(bs->pUserData, (int)bytesRemaining, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; } } } else { - if (!bs->onSeek(bs->pUserData, (int)offsetFromStart, drflac_seek_origin_start)) { + if (!bs->onSeek(bs->pUserData, (int)offsetFromStart, DRFLAC_SEEK_SET)) { return DRFLAC_FALSE; } } @@ -5393,6 +5306,12 @@ static drflac_bool32 drflac__read_subframe_header(drflac_bs* bs, drflac_subframe return DRFLAC_FALSE; } + /* + Default to 0 for the LPC order. It's important that we always set this to 0 for non LPC + and FIXED subframes because we'll be using it in a generic validation check later. + */ + pSubframe->lpcOrder = 0; + type = (header & 0x7E) >> 1; if (type == 0) { pSubframe->subframeType = DRFLAC_SUBFRAME_CONSTANT; @@ -5465,6 +5384,18 @@ static drflac_bool32 drflac__decode_subframe(drflac_bs* bs, drflac_frame* frame, pSubframe->pSamplesS32 = pDecodedSamplesOut; + /* + pDecodedSamplesOut will be pointing to a buffer that was allocated with enough memory to store + maxBlockSizeInPCMFrames samples (as specified in the FLAC header). We need to guard against an + overflow here. At a higher level we are checking maxBlockSizeInPCMFrames from the header, but + here we need to do an additional check to ensure this frame's block size fully encompasses any + warmup samples which is determined by the LPC order. For non LPC and FIXED subframes, the LPC + order will be have been set to 0 in drflac__read_subframe_header(). + */ + if (frame->header.blockSizeInPCMFrames < pSubframe->lpcOrder) { + return DRFLAC_FALSE; + } + switch (pSubframe->subframeType) { case DRFLAC_SUBFRAME_CONSTANT: @@ -6312,6 +6243,7 @@ typedef struct { drflac_read_proc onRead; drflac_seek_proc onSeek; + drflac_tell_proc onTell; drflac_meta_proc onMeta; drflac_container container; void* pUserData; @@ -6479,7 +6411,7 @@ static void drflac__free_from_callbacks(void* p, const drflac_allocation_callbac } -static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_uint64* pFirstFramePos, drflac_uint64* pSeektablePos, drflac_uint32* pSeekpointCount, drflac_allocation_callbacks* pAllocationCallbacks) +static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_uint64* pFirstFramePos, drflac_uint64* pSeektablePos, drflac_uint32* pSeekpointCount, drflac_allocation_callbacks* pAllocationCallbacks) { /* We want to keep track of the byte position in the stream of the seektable. At the time of calling this function we know that @@ -6489,6 +6421,8 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d drflac_uint64 seektablePos = 0; drflac_uint32 seektableSize = 0; + (void)onTell; + for (;;) { drflac_metadata metadata; drflac_uint8 isLastBlock = 0; @@ -6702,10 +6636,10 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d /* Skip to the index point count */ pRunningData += 35; - + indexCount = pRunningData[0]; pRunningData += 1; - + bufferSize += indexCount * sizeof(drflac_cuesheet_track_index); /* Quick validation check. */ @@ -6840,7 +6774,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d metadata.data.padding.unused = 0; /* Padding doesn't have anything meaningful in it, so just skip over it, but make sure the caller is aware of it by firing the callback. */ - if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { + if (!onSeek(pUserData, blockSize, DRFLAC_SEEK_CUR)) { isLastBlock = DRFLAC_TRUE; /* An error occurred while seeking. Attempt to recover by treating this as the last block which will in turn terminate the loop. */ } else { onMeta(pUserDataMD, &metadata); @@ -6852,7 +6786,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d { /* Invalid chunk. Just skip over this one. */ if (onMeta) { - if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { + if (!onSeek(pUserData, blockSize, DRFLAC_SEEK_CUR)) { isLastBlock = DRFLAC_TRUE; /* An error occurred while seeking. Attempt to recover by treating this as the last block which will in turn terminate the loop. */ } } @@ -6886,7 +6820,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d /* If we're not handling metadata, just skip over the block. If we are, it will have been handled earlier in the switch statement above. */ if (onMeta == NULL && blockSize > 0) { - if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { + if (!onSeek(pUserData, blockSize, DRFLAC_SEEK_CUR)) { isLastBlock = DRFLAC_TRUE; } } @@ -7220,6 +7154,7 @@ typedef struct { drflac_read_proc onRead; /* The original onRead callback from drflac_open() and family. */ drflac_seek_proc onSeek; /* The original onSeek callback from drflac_open() and family. */ + drflac_tell_proc onTell; /* The original onTell callback from drflac_open() and family. */ void* pUserData; /* The user data passed on onRead and onSeek. This is the user data that was passed on drflac_open() and family. */ drflac_uint64 currentBytePos; /* The position of the byte we are sitting on in the physical byte stream. Used for efficient seeking. */ drflac_uint64 firstBytePos; /* The position of the first byte in the physical bitstream. Points to the start of the "OggS" identifier of the FLAC bos page. */ @@ -7241,32 +7176,32 @@ static size_t drflac_oggbs__read_physical(drflac_oggbs* oggbs, void* bufferOut, static drflac_bool32 drflac_oggbs__seek_physical(drflac_oggbs* oggbs, drflac_uint64 offset, drflac_seek_origin origin) { - if (origin == drflac_seek_origin_start) { + if (origin == DRFLAC_SEEK_SET) { if (offset <= 0x7FFFFFFF) { - if (!oggbs->onSeek(oggbs->pUserData, (int)offset, drflac_seek_origin_start)) { + if (!oggbs->onSeek(oggbs->pUserData, (int)offset, DRFLAC_SEEK_SET)) { return DRFLAC_FALSE; } oggbs->currentBytePos = offset; return DRFLAC_TRUE; } else { - if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, drflac_seek_origin_start)) { + if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, DRFLAC_SEEK_SET)) { return DRFLAC_FALSE; } oggbs->currentBytePos = offset; - return drflac_oggbs__seek_physical(oggbs, offset - 0x7FFFFFFF, drflac_seek_origin_current); + return drflac_oggbs__seek_physical(oggbs, offset - 0x7FFFFFFF, DRFLAC_SEEK_CUR); } } else { while (offset > 0x7FFFFFFF) { - if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, drflac_seek_origin_current)) { + if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; } oggbs->currentBytePos += 0x7FFFFFFF; offset -= 0x7FFFFFFF; } - if (!oggbs->onSeek(oggbs->pUserData, (int)offset, drflac_seek_origin_current)) { /* <-- Safe cast thanks to the loop above. */ + if (!oggbs->onSeek(oggbs->pUserData, (int)offset, DRFLAC_SEEK_CUR)) { /* <-- Safe cast thanks to the loop above. */ return DRFLAC_FALSE; } oggbs->currentBytePos += offset; @@ -7298,7 +7233,7 @@ static drflac_bool32 drflac_oggbs__goto_next_page(drflac_oggbs* oggbs, drflac_og if (header.serialNumber != oggbs->serialNumber) { /* It's not a FLAC page. Skip it. */ - if (pageBodySize > 0 && !drflac_oggbs__seek_physical(oggbs, pageBodySize, drflac_seek_origin_current)) { + if (pageBodySize > 0 && !drflac_oggbs__seek_physical(oggbs, pageBodySize, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; } continue; @@ -7384,7 +7319,7 @@ static drflac_bool32 drflac_oggbs__seek_to_next_packet(drflac_oggbs* oggbs) At this point we will have found either the packet or the end of the page. If were at the end of the page we'll want to load the next page and keep searching for the end of the packet. */ - drflac_oggbs__seek_physical(oggbs, bytesToEndOfPacketOrPage, drflac_seek_origin_current); + drflac_oggbs__seek_physical(oggbs, bytesToEndOfPacketOrPage, DRFLAC_SEEK_CUR); oggbs->bytesRemainingInPage -= bytesToEndOfPacketOrPage; if (atEndOfPage) { @@ -7462,8 +7397,8 @@ static drflac_bool32 drflac__on_seek_ogg(void* pUserData, int offset, drflac_see DRFLAC_ASSERT(offset >= 0); /* <-- Never seek backwards. */ /* Seeking is always forward which makes things a lot simpler. */ - if (origin == drflac_seek_origin_start) { - if (!drflac_oggbs__seek_physical(oggbs, (int)oggbs->firstBytePos, drflac_seek_origin_start)) { + if (origin == DRFLAC_SEEK_SET) { + if (!drflac_oggbs__seek_physical(oggbs, (int)oggbs->firstBytePos, DRFLAC_SEEK_SET)) { return DRFLAC_FALSE; } @@ -7471,38 +7406,50 @@ static drflac_bool32 drflac__on_seek_ogg(void* pUserData, int offset, drflac_see return DRFLAC_FALSE; } - return drflac__on_seek_ogg(pUserData, offset, drflac_seek_origin_current); - } + return drflac__on_seek_ogg(pUserData, offset, DRFLAC_SEEK_CUR); + } else if (origin == DRFLAC_SEEK_CUR) { + while (bytesSeeked < offset) { + int bytesRemainingToSeek = offset - bytesSeeked; + DRFLAC_ASSERT(bytesRemainingToSeek >= 0); - DRFLAC_ASSERT(origin == drflac_seek_origin_current); + if (oggbs->bytesRemainingInPage >= (size_t)bytesRemainingToSeek) { + bytesSeeked += bytesRemainingToSeek; + (void)bytesSeeked; /* <-- Silence a dead store warning emitted by Clang Static Analyzer. */ + oggbs->bytesRemainingInPage -= bytesRemainingToSeek; + break; + } - while (bytesSeeked < offset) { - int bytesRemainingToSeek = offset - bytesSeeked; - DRFLAC_ASSERT(bytesRemainingToSeek >= 0); + /* If we get here it means some of the requested data is contained in the next pages. */ + if (oggbs->bytesRemainingInPage > 0) { + bytesSeeked += (int)oggbs->bytesRemainingInPage; + oggbs->bytesRemainingInPage = 0; + } - if (oggbs->bytesRemainingInPage >= (size_t)bytesRemainingToSeek) { - bytesSeeked += bytesRemainingToSeek; - (void)bytesSeeked; /* <-- Silence a dead store warning emitted by Clang Static Analyzer. */ - oggbs->bytesRemainingInPage -= bytesRemainingToSeek; - break; - } - - /* If we get here it means some of the requested data is contained in the next pages. */ - if (oggbs->bytesRemainingInPage > 0) { - bytesSeeked += (int)oggbs->bytesRemainingInPage; - oggbs->bytesRemainingInPage = 0; - } - - DRFLAC_ASSERT(bytesRemainingToSeek > 0); - if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_fail_on_crc_mismatch)) { - /* Failed to go to the next page. We either hit the end of the stream or had a CRC mismatch. */ - return DRFLAC_FALSE; + DRFLAC_ASSERT(bytesRemainingToSeek > 0); + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_fail_on_crc_mismatch)) { + /* Failed to go to the next page. We either hit the end of the stream or had a CRC mismatch. */ + return DRFLAC_FALSE; + } } + } else if (origin == DRFLAC_SEEK_END) { + /* Seeking to the end is not supported. */ + return DRFLAC_FALSE; } return DRFLAC_TRUE; } +static drflac_bool32 drflac__on_tell_ogg(void* pUserData, drflac_int64* pCursor) +{ + /* + Not implemented for Ogg containers because we don't currently track the byte position of the logical bitstream. To support this, we'll need + to track the position in drflac__on_read_ogg and drflac__on_seek_ogg. + */ + (void)pUserData; + (void)pCursor; + return DRFLAC_FALSE; +} + static drflac_bool32 drflac_ogg__seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex) { @@ -7525,7 +7472,7 @@ static drflac_bool32 drflac_ogg__seek_to_pcm_frame(drflac* pFlac, drflac_uint64 runningGranulePosition = 0; for (;;) { if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { - drflac_oggbs__seek_physical(oggbs, originalBytePos, drflac_seek_origin_start); + drflac_oggbs__seek_physical(oggbs, originalBytePos, DRFLAC_SEEK_SET); return DRFLAC_FALSE; /* Never did find that sample... */ } @@ -7559,7 +7506,7 @@ static drflac_bool32 drflac_ogg__seek_to_pcm_frame(drflac* pFlac, drflac_uint64 a new frame. This property means that after we've seeked to the page we can immediately start looping over frames until we find the one containing the target sample. */ - if (!drflac_oggbs__seek_physical(oggbs, runningFrameBytePos, drflac_seek_origin_start)) { + if (!drflac_oggbs__seek_physical(oggbs, runningFrameBytePos, DRFLAC_SEEK_SET)) { return DRFLAC_FALSE; } if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { @@ -7726,7 +7673,7 @@ static drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_r The next 2 bytes are the non-audio packets, not including this one. We don't care about this because we're going to be handling it in a generic way based on the serial number and packet types. */ - if (!onSeek(pUserData, 2, drflac_seek_origin_current)) { + if (!onSeek(pUserData, 2, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; } @@ -7783,18 +7730,18 @@ static drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_r } } else { /* Not a FLAC header. Skip it. */ - if (!onSeek(pUserData, bytesRemainingInPage, drflac_seek_origin_current)) { + if (!onSeek(pUserData, bytesRemainingInPage, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; } } } else { /* Not a FLAC header. Seek past the entire page and move on to the next. */ - if (!onSeek(pUserData, bytesRemainingInPage, drflac_seek_origin_current)) { + if (!onSeek(pUserData, bytesRemainingInPage, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; } } } else { - if (!onSeek(pUserData, pageBodySize, drflac_seek_origin_current)) { + if (!onSeek(pUserData, pageBodySize, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; } } @@ -7819,18 +7766,19 @@ static drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_r } #endif -static drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD) +static drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD) { drflac_bool32 relaxed; drflac_uint8 id[4]; - if (pInit == NULL || onRead == NULL || onSeek == NULL) { + if (pInit == NULL || onRead == NULL || onSeek == NULL) { /* <-- onTell is optional. */ return DRFLAC_FALSE; } DRFLAC_ZERO_MEMORY(pInit, sizeof(*pInit)); pInit->onRead = onRead; pInit->onSeek = onSeek; + pInit->onTell = onTell; pInit->onMeta = onMeta; pInit->container = container; pInit->pUserData = pUserData; @@ -7838,6 +7786,7 @@ static drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_p pInit->bs.onRead = onRead; pInit->bs.onSeek = onSeek; + pInit->bs.onTell = onTell; pInit->bs.pUserData = pUserData; drflac__reset_cache(&pInit->bs); @@ -7870,7 +7819,7 @@ static drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_p headerSize += 10; } - if (!onSeek(pUserData, headerSize, drflac_seek_origin_current)) { + if (!onSeek(pUserData, headerSize, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; /* Failed to seek past the tag. */ } pInit->runningFilePos += headerSize; @@ -7922,7 +7871,7 @@ static void drflac__init_from_info(drflac* pFlac, const drflac_init_info* pInit) } -static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD, const drflac_allocation_callbacks* pAllocationCallbacks) +static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac_init_info init; drflac_uint32 allocationSize; @@ -7940,7 +7889,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac /* CPU support first. */ drflac__init_cpu_caps(); - if (!drflac__init_private(&init, onRead, onSeek, onMeta, container, pUserData, pUserDataMD)) { + if (!drflac__init_private(&init, onRead, onSeek, onTell, onMeta, container, pUserData, pUserDataMD)) { return NULL; } @@ -7996,6 +7945,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac DRFLAC_ZERO_MEMORY(pOggbs, sizeof(*pOggbs)); pOggbs->onRead = onRead; pOggbs->onSeek = onSeek; + pOggbs->onTell = onTell; pOggbs->pUserData = pUserData; pOggbs->currentBytePos = init.oggFirstBytePos; pOggbs->firstBytePos = init.oggFirstBytePos; @@ -8016,17 +7966,19 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac if (init.hasMetadataBlocks) { drflac_read_proc onReadOverride = onRead; drflac_seek_proc onSeekOverride = onSeek; + drflac_tell_proc onTellOverride = onTell; void* pUserDataOverride = pUserData; #ifndef DR_FLAC_NO_OGG if (init.container == drflac_container_ogg) { onReadOverride = drflac__on_read_ogg; onSeekOverride = drflac__on_seek_ogg; + onTellOverride = drflac__on_tell_ogg; pUserDataOverride = (void*)pOggbs; } #endif - if (!drflac__read_and_decode_metadata(onReadOverride, onSeekOverride, onMeta, pUserDataOverride, pUserDataMD, &firstFramePos, &seektablePos, &seekpointCount, &allocationCallbacks)) { + if (!drflac__read_and_decode_metadata(onReadOverride, onSeekOverride, onTellOverride, onMeta, pUserDataOverride, pUserDataMD, &firstFramePos, &seektablePos, &seekpointCount, &allocationCallbacks)) { #ifndef DR_FLAC_NO_OGG drflac__free_from_callbacks(pOggbs, &allocationCallbacks); #endif @@ -8061,6 +8013,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac /* The Ogg bistream needs to be layered on top of the original bitstream. */ pFlac->bs.onRead = drflac__on_read_ogg; pFlac->bs.onSeek = drflac__on_seek_ogg; + pFlac->bs.onTell = drflac__on_tell_ogg; pFlac->bs.pUserData = (void*)pInternalOggbs; pFlac->_oggbs = (void*)pInternalOggbs; } @@ -8087,7 +8040,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac DRFLAC_ASSERT(pFlac->bs.onRead != NULL); /* Seek to the seektable, then just read directly into our seektable buffer. */ - if (pFlac->bs.onSeek(pFlac->bs.pUserData, (int)seektablePos, drflac_seek_origin_start)) { + if (pFlac->bs.onSeek(pFlac->bs.pUserData, (int)seektablePos, DRFLAC_SEEK_SET)) { drflac_uint32 iSeekpoint; for (iSeekpoint = 0; iSeekpoint < seekpointCount; iSeekpoint += 1) { @@ -8105,7 +8058,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac } /* We need to seek back to where we were. If this fails it's a critical error. */ - if (!pFlac->bs.onSeek(pFlac->bs.pUserData, (int)pFlac->firstFLACFramePosInBytes, drflac_seek_origin_start)) { + if (!pFlac->bs.onSeek(pFlac->bs.pUserData, (int)pFlac->firstFLACFramePosInBytes, DRFLAC_SEEK_SET)) { drflac__free_from_callbacks(pFlac, &allocationCallbacks); return NULL; } @@ -8276,7 +8229,7 @@ static drflac_result drflac_result_from_errno(int e) #ifdef ENOSYS case ENOSYS: return DRFLAC_NOT_IMPLEMENTED; #endif - #ifdef ENOTEMPTY + #if defined(ENOTEMPTY) && ENOTEMPTY != EEXIST /* In AIX, ENOTEMPTY and EEXIST use the same value. */ case ENOTEMPTY: return DRFLAC_DIRECTORY_NOT_EMPTY; #endif #ifdef ELOOP @@ -8727,11 +8680,41 @@ static size_t drflac__on_read_stdio(void* pUserData, void* bufferOut, size_t byt static drflac_bool32 drflac__on_seek_stdio(void* pUserData, int offset, drflac_seek_origin origin) { - DRFLAC_ASSERT(offset >= 0); /* <-- Never seek backwards. */ + int whence = SEEK_SET; + if (origin == DRFLAC_SEEK_CUR) { + whence = SEEK_CUR; + } else if (origin == DRFLAC_SEEK_END) { + whence = SEEK_END; + } - return fseek((FILE*)pUserData, offset, (origin == drflac_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; + return fseek((FILE*)pUserData, offset, whence) == 0; } +static drflac_bool32 drflac__on_tell_stdio(void* pUserData, drflac_int64* pCursor) +{ + FILE* pFileStdio = (FILE*)pUserData; + drflac_int64 result; + + /* These were all validated at a higher level. */ + DRFLAC_ASSERT(pFileStdio != NULL); + DRFLAC_ASSERT(pCursor != NULL); + +#if defined(_WIN32) + #if defined(_MSC_VER) && _MSC_VER > 1200 + result = _ftelli64(pFileStdio); + #else + result = ftell(pFileStdio); + #endif +#else + result = ftell(pFileStdio); +#endif + + *pCursor = result; + + return DRFLAC_TRUE; +} + + DRFLAC_API drflac* drflac_open_file(const char* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks) { @@ -8742,7 +8725,7 @@ DRFLAC_API drflac* drflac_open_file(const char* pFileName, const drflac_allocati return NULL; } - pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, drflac__on_tell_stdio, (void*)pFile, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return NULL; @@ -8761,7 +8744,7 @@ DRFLAC_API drflac* drflac_open_file_w(const wchar_t* pFileName, const drflac_all return NULL; } - pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, drflac__on_tell_stdio, (void*)pFile, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return NULL; @@ -8780,7 +8763,7 @@ DRFLAC_API drflac* drflac_open_file_with_metadata(const char* pFileName, drflac_ return NULL; } - pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); + pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, drflac__on_tell_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return pFlac; @@ -8799,7 +8782,7 @@ DRFLAC_API drflac* drflac_open_file_with_metadata_w(const wchar_t* pFileName, dr return NULL; } - pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); + pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, drflac__on_tell_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return pFlac; @@ -8834,28 +8817,45 @@ static size_t drflac__on_read_memory(void* pUserData, void* bufferOut, size_t by static drflac_bool32 drflac__on_seek_memory(void* pUserData, int offset, drflac_seek_origin origin) { drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; + drflac_int64 newCursor; DRFLAC_ASSERT(memoryStream != NULL); - DRFLAC_ASSERT(offset >= 0); /* <-- Never seek backwards. */ - if (offset > (drflac_int64)memoryStream->dataSize) { + newCursor = memoryStream->currentReadPos; + + if (origin == DRFLAC_SEEK_SET) { + newCursor = 0; + } else if (origin == DRFLAC_SEEK_CUR) { + newCursor = (drflac_int64)memoryStream->currentReadPos; + } else if (origin == DRFLAC_SEEK_END) { + newCursor = (drflac_int64)memoryStream->dataSize; + } else { + DRFLAC_ASSERT(!"Invalid seek origin"); return DRFLAC_FALSE; } - if (origin == drflac_seek_origin_current) { - if (memoryStream->currentReadPos + offset <= memoryStream->dataSize) { - memoryStream->currentReadPos += offset; - } else { - return DRFLAC_FALSE; /* Trying to seek too far forward. */ - } - } else { - if ((drflac_uint32)offset <= memoryStream->dataSize) { - memoryStream->currentReadPos = offset; - } else { - return DRFLAC_FALSE; /* Trying to seek too far forward. */ - } + newCursor += offset; + + if (newCursor < 0) { + return DRFLAC_FALSE; /* Trying to seek prior to the start of the buffer. */ + } + if ((size_t)newCursor > memoryStream->dataSize) { + return DRFLAC_FALSE; /* Trying to seek beyond the end of the buffer. */ } + memoryStream->currentReadPos = (size_t)newCursor; + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__on_tell_memory(void* pUserData, drflac_int64* pCursor) +{ + drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; + + DRFLAC_ASSERT(memoryStream != NULL); + DRFLAC_ASSERT(pCursor != NULL); + + *pCursor = (drflac_int64)memoryStream->currentReadPos; return DRFLAC_TRUE; } @@ -8867,7 +8867,7 @@ DRFLAC_API drflac* drflac_open_memory(const void* pData, size_t dataSize, const memoryStream.data = (const drflac_uint8*)pData; memoryStream.dataSize = dataSize; memoryStream.currentReadPos = 0; - pFlac = drflac_open(drflac__on_read_memory, drflac__on_seek_memory, &memoryStream, pAllocationCallbacks); + pFlac = drflac_open(drflac__on_read_memory, drflac__on_seek_memory, drflac__on_tell_memory, &memoryStream, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -8898,7 +8898,7 @@ DRFLAC_API drflac* drflac_open_memory_with_metadata(const void* pData, size_t da memoryStream.data = (const drflac_uint8*)pData; memoryStream.dataSize = dataSize; memoryStream.currentReadPos = 0; - pFlac = drflac_open_with_metadata_private(drflac__on_read_memory, drflac__on_seek_memory, onMeta, drflac_container_unknown, &memoryStream, pUserData, pAllocationCallbacks); + pFlac = drflac_open_with_metadata_private(drflac__on_read_memory, drflac__on_seek_memory, drflac__on_tell_memory, onMeta, drflac_container_unknown, &memoryStream, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -8923,22 +8923,22 @@ DRFLAC_API drflac* drflac_open_memory_with_metadata(const void* pData, size_t da -DRFLAC_API drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +DRFLAC_API drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { - return drflac_open_with_metadata_private(onRead, onSeek, NULL, drflac_container_unknown, pUserData, pUserData, pAllocationCallbacks); + return drflac_open_with_metadata_private(onRead, onSeek, onTell, NULL, drflac_container_unknown, pUserData, pUserData, pAllocationCallbacks); } -DRFLAC_API drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +DRFLAC_API drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { - return drflac_open_with_metadata_private(onRead, onSeek, NULL, container, pUserData, pUserData, pAllocationCallbacks); + return drflac_open_with_metadata_private(onRead, onSeek, onTell, NULL, container, pUserData, pUserData, pAllocationCallbacks); } -DRFLAC_API drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +DRFLAC_API drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { - return drflac_open_with_metadata_private(onRead, onSeek, onMeta, drflac_container_unknown, pUserData, pUserData, pAllocationCallbacks); + return drflac_open_with_metadata_private(onRead, onSeek, onTell, onMeta, drflac_container_unknown, pUserData, pUserData, pAllocationCallbacks); } -DRFLAC_API drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +DRFLAC_API drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { - return drflac_open_with_metadata_private(onRead, onSeek, onMeta, container, pUserData, pUserData, pAllocationCallbacks); + return drflac_open_with_metadata_private(onRead, onSeek, onTell, onMeta, container, pUserData, pUserData, pAllocationCallbacks); } DRFLAC_API void drflac_close(drflac* pFlac) @@ -11770,7 +11770,7 @@ DRFLAC_DEFINE_FULL_READ_AND_CLOSE(s32, drflac_int32) DRFLAC_DEFINE_FULL_READ_AND_CLOSE(s16, drflac_int16) DRFLAC_DEFINE_FULL_READ_AND_CLOSE(f32, float) -DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) +DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; @@ -11784,7 +11784,7 @@ DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc on *totalPCMFrameCountOut = 0; } - pFlac = drflac_open(onRead, onSeek, pUserData, pAllocationCallbacks); + pFlac = drflac_open(onRead, onSeek, onTell, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -11792,7 +11792,7 @@ DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc on return drflac__full_read_and_close_s32(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); } -DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) +DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; @@ -11806,7 +11806,7 @@ DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc on *totalPCMFrameCountOut = 0; } - pFlac = drflac_open(onRead, onSeek, pUserData, pAllocationCallbacks); + pFlac = drflac_open(onRead, onSeek, onTell, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -11814,7 +11814,7 @@ DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc on return drflac__full_read_and_close_s16(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); } -DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) +DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; @@ -11828,7 +11828,7 @@ DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, d *totalPCMFrameCountOut = 0; } - pFlac = drflac_open(onRead, onSeek, pUserData, pAllocationCallbacks); + pFlac = drflac_open(onRead, onSeek, onTell, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -12077,6 +12077,26 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat /* REVISION HISTORY ================ +v0.13.0 - TBD + - API CHANGE: Seek origin enums have been renamed to match the naming convention used by other dr_libs libraries: + - drflac_seek_origin_start -> DRFLAC_SEEK_SET + - drflac_seek_origin_current -> DRFLAC_SEEK_CUR + - DRFLAC_SEEK_END (new) + - API CHANGE: A new seek origin has been added to allow seeking from the end of the file. If you implement your own `onSeek` callback, you should now detect and handle `DRFLAC_SEEK_END`. If seeking to the end is not supported, return `DRFLAC_FALSE`. If you only use `*_open_file()` or `*_open_memory()`, you need not change anything. + - API CHANGE: An `onTell` callback has been added to the following functions: + - drflac_open() + - drflac_open_relaxed() + - drflac_open_with_metadata() + - drflac_open_with_metadata_relaxed() + - drflac_open_and_read_pcm_frames_s32() + - drflac_open_and_read_pcm_frames_s16() + - drflac_open_and_read_pcm_frames_f32() + - Fix compilation for AIX OS. + +v0.12.43 - 2024-12-17 + - Fix a possible buffer overflow during decoding. + - Improve detection of ARM64EC + v0.12.42 - 2023-11-02 - Fix build for ARMv6-M. - Fix a compilation warning with GCC. diff --git a/src/external/dr_mp3.h b/src/external/dr_mp3.h index e1a66d99c..cc033ce87 100644 --- a/src/external/dr_mp3.h +++ b/src/external/dr_mp3.h @@ -1,6 +1,6 @@ /* MP3 audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_mp3 - v0.6.39 - 2024-02-27 +dr_mp3 - v0.7.0 - TBD David Reid - mackron@gmail.com @@ -10,30 +10,7 @@ Based on minimp3 (https://github.com/lieff/minimp3) which is where the real work */ /* -RELEASE NOTES - VERSION 0.6 -=========================== -Version 0.6 includes breaking changes with the configuration of decoders. The ability to customize the number of output channels and the sample rate has been -removed. You must now use the channel count and sample rate reported by the MP3 stream itself, and all channel and sample rate conversion must be done -yourself. - - -Changes to Initialization -------------------------- -Previously, `drmp3_init()`, etc. took a pointer to a `drmp3_config` object that allowed you to customize the output channels and sample rate. This has been -removed. If you need the old behaviour you will need to convert the data yourself or just not upgrade. The following APIs have changed. - - `drmp3_init()` - `drmp3_init_memory()` - `drmp3_init_file()` - - -Miscellaneous Changes ---------------------- -Support for loading a file from a `wchar_t` string has been added via the `drmp3_init_file_w()` API. -*/ - -/* -Introducation +Introduction ============= dr_mp3 is a single file library. To use it, do something like the following in one .c file. @@ -94,8 +71,8 @@ extern "C" { #define DRMP3_XSTRINGIFY(x) DRMP3_STRINGIFY(x) #define DRMP3_VERSION_MAJOR 0 -#define DRMP3_VERSION_MINOR 6 -#define DRMP3_VERSION_REVISION 39 +#define DRMP3_VERSION_MINOR 7 +#define DRMP3_VERSION_REVISION 0 #define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION) #include /* For size_t. */ @@ -124,7 +101,7 @@ typedef unsigned int drmp3_uint32; #pragma GCC diagnostic pop #endif #endif -#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) || defined(__powerpc64__) typedef drmp3_uint64 drmp3_uintptr; #else typedef drmp3_uint32 drmp3_uintptr; @@ -133,6 +110,9 @@ typedef drmp3_uint8 drmp3_bool8; typedef drmp3_uint32 drmp3_bool32; #define DRMP3_TRUE 1 #define DRMP3_FALSE 0 + +/* Weird shifting syntax is for VC6 compatibility. */ +#define DRMP3_UINT64_MAX (((drmp3_uint64)0xFFFFFFFF << 32) | (drmp3_uint64)0xFFFFFFFF) /* End Sized Types */ /* Decorations */ @@ -154,7 +134,7 @@ typedef drmp3_uint32 drmp3_bool32; #endif #endif - #if defined(DR_MP3_IMPLEMENTATION) || defined(DRMP3_IMPLEMENTATION) + #if defined(DR_MP3_IMPLEMENTATION) #define DRMP3_API DRMP3_DLL_EXPORT #else #define DRMP3_API DRMP3_DLL_IMPORT @@ -279,7 +259,7 @@ Low Level Push API */ typedef struct { - int frame_bytes, channels, hz, layer, bitrate_kbps; + int frame_bytes, channels, sample_rate, layer, bitrate_kbps; } drmp3dec_frame_info; typedef struct @@ -306,8 +286,9 @@ Main API (Pull API) */ typedef enum { - drmp3_seek_origin_start, - drmp3_seek_origin_current + DRMP3_SEEK_SET, + DRMP3_SEEK_CUR, + DRMP3_SEEK_END } drmp3_seek_origin; typedef struct @@ -318,10 +299,27 @@ typedef struct drmp3_uint16 pcmFramesToDiscard; /* The number of leading samples to read and discard. These are discarded after mp3FramesToDiscard. */ } drmp3_seek_point; +typedef enum +{ + DRMP3_METADATA_TYPE_ID3V1, + DRMP3_METADATA_TYPE_ID3V2, + DRMP3_METADATA_TYPE_APE, + DRMP3_METADATA_TYPE_XING, + DRMP3_METADATA_TYPE_VBRI +} drmp3_metadata_type; + +typedef struct +{ + drmp3_metadata_type type; + const void* pRawData; /* A pointer to the raw data. */ + size_t rawDataSize; +} drmp3_metadata; + + /* Callback for when data is read. Return value is the number of bytes actually read. -pUserData [in] The user data that was passed to drmp3_init(), drmp3_open() and family. +pUserData [in] The user data that was passed to drmp3_init(), and family. pBufferOut [out] The output buffer. bytesToRead [in] The number of bytes to read. @@ -335,17 +333,33 @@ typedef size_t (* drmp3_read_proc)(void* pUserData, void* pBufferOut, size_t byt /* Callback for when data needs to be seeked. -pUserData [in] The user data that was passed to drmp3_init(), drmp3_open() and family. -offset [in] The number of bytes to move, relative to the origin. Will never be negative. -origin [in] The origin of the seek - the current position or the start of the stream. +pUserData [in] The user data that was passed to drmp3_init(), and family. +offset [in] The number of bytes to move, relative to the origin. Can be negative. +origin [in] The origin of the seek. Returns whether or not the seek was successful. - -Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which -will be either drmp3_seek_origin_start or drmp3_seek_origin_current. */ typedef drmp3_bool32 (* drmp3_seek_proc)(void* pUserData, int offset, drmp3_seek_origin origin); +/* +Callback for retrieving the current cursor position. + +pUserData [in] The user data that was passed to drmp3_init(), and family. +pCursor [out] The cursor position in bytes from the start of the stream. + +Returns whether or not the cursor position was successfully retrieved. +*/ +typedef drmp3_bool32 (* drmp3_tell_proc)(void* pUserData, drmp3_int64* pCursor); + + +/* +Callback for when metadata is read. + +Only the raw data is provided. The client is responsible for parsing the contents of the data themsevles. +*/ +typedef void (* drmp3_meta_proc)(void* pUserData, const drmp3_metadata* pMetadata); + + typedef struct { drmp3_uint32 channels; @@ -359,22 +373,31 @@ typedef struct drmp3_uint32 sampleRate; drmp3_read_proc onRead; drmp3_seek_proc onSeek; + drmp3_meta_proc onMeta; void* pUserData; + void* pUserDataMeta; drmp3_allocation_callbacks allocationCallbacks; drmp3_uint32 mp3FrameChannels; /* The number of channels in the currently loaded MP3 frame. Internal use only. */ drmp3_uint32 mp3FrameSampleRate; /* The sample rate of the currently loaded MP3 frame. Internal use only. */ drmp3_uint32 pcmFramesConsumedInMP3Frame; drmp3_uint32 pcmFramesRemainingInMP3Frame; drmp3_uint8 pcmFrames[sizeof(float)*DRMP3_MAX_SAMPLES_PER_FRAME]; /* <-- Multipled by sizeof(float) to ensure there's enough room for DR_MP3_FLOAT_OUTPUT. */ - drmp3_uint64 currentPCMFrame; /* The current PCM frame, globally, based on the output sample rate. Mainly used for seeking. */ + drmp3_uint64 currentPCMFrame; /* The current PCM frame, globally. */ drmp3_uint64 streamCursor; /* The current byte the decoder is sitting on in the raw stream. */ + drmp3_uint64 streamLength; /* The length of the stream in bytes. dr_mp3 will not read beyond this. If a ID3v1 or APE tag is present, this will be set to the first byte of the tag. */ + drmp3_uint64 streamStartOffset; /* The offset of the start of the MP3 data. This is used for skipping ID3v2 and VBR tags. */ drmp3_seek_point* pSeekPoints; /* NULL by default. Set with drmp3_bind_seek_table(). Memory is owned by the client. dr_mp3 will never attempt to free this pointer. */ drmp3_uint32 seekPointCount; /* The number of items in pSeekPoints. When set to 0 assumes to no seek table. Defaults to zero. */ + drmp3_uint32 delayInPCMFrames; + drmp3_uint32 paddingInPCMFrames; + drmp3_uint64 totalPCMFrameCount; /* Set to DRMP3_UINT64_MAX if the length is unknown. Includes delay and padding. */ + drmp3_bool32 isVBR; + drmp3_bool32 isCBR; size_t dataSize; size_t dataCapacity; size_t dataConsumed; drmp3_uint8* pData; - drmp3_bool32 atEnd : 1; + drmp3_bool32 atEnd; struct { const drmp3_uint8* pData; @@ -388,6 +411,7 @@ Initializes an MP3 decoder. onRead [in] The function to call when data needs to be read from the client. onSeek [in] The function to call when the read position of the client data needs to move. +onTell [in] The function to call when the read position of the client data needs to be retrieved. pUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. Returns true if successful; false otherwise. @@ -396,7 +420,7 @@ Close the loader with drmp3_uninit(). See also: drmp3_init_file(), drmp3_init_memory(), drmp3_uninit() */ -DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, drmp3_meta_proc onMeta, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks); /* Initializes an MP3 decoder from a block of memory. @@ -406,6 +430,7 @@ the lifetime of the drmp3 object. The buffer should contain the contents of the entire MP3 file. */ +DRMP3_API drmp3_bool32 drmp3_init_memory_with_metadata(drmp3* pMP3, const void* pData, size_t dataSize, drmp3_meta_proc onMeta, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks); DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_allocation_callbacks* pAllocationCallbacks); #ifndef DR_MP3_NO_STDIO @@ -416,6 +441,9 @@ This holds the internal FILE object until drmp3_uninit() is called. Keep this in objects because the operating system may restrict the number of file handles an application can have open at any given time. */ +DRMP3_API drmp3_bool32 drmp3_init_file_with_metadata(drmp3* pMP3, const char* pFilePath, drmp3_meta_proc onMeta, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_bool32 drmp3_init_file_with_metadata_w(drmp3* pMP3, const wchar_t* pFilePath, drmp3_meta_proc onMeta, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks); + DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks); DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks); #endif @@ -495,8 +523,8 @@ On output pConfig will receive the channel count and sample rate of the stream. Free the returned pointer with drmp3_free(). */ -DRMP3_API float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); -DRMP3_API drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); DRMP3_API float* drmp3_open_memory_and_read_pcm_frames_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); DRMP3_API drmp3_int16* drmp3_open_memory_and_read_pcm_frames_s16(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); @@ -529,7 +557,7 @@ DRMP3_API void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocation ************************************************************************************************************************************************************ ************************************************************************************************************************************************************/ -#if defined(DR_MP3_IMPLEMENTATION) || defined(DRMP3_IMPLEMENTATION) +#if defined(DR_MP3_IMPLEMENTATION) #ifndef dr_mp3_c #define dr_mp3_c @@ -604,7 +632,7 @@ DRMP3_API const char* drmp3_version_string(void) #if !defined(DR_MP3_NO_SIMD) -#if !defined(DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) || defined(_M_ARM64)) +#if !defined(DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)) /* x64 always have SSE2, arm64 always have neon, no need for generic code */ #define DR_MP3_ONLY_SIMD #endif @@ -680,7 +708,7 @@ end: return g_have_simd - 1; #endif } -#elif defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64) +#elif defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) #include #define DRMP3_HAVE_SSE 0 #define DRMP3_HAVE_SIMD 1 @@ -713,7 +741,7 @@ static int drmp3_have_simd(void) #endif -#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64) && !defined(__ARM_ARCH_6M__) +#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64) && !defined(_M_ARM64EC) && !defined(__ARM_ARCH_6M__) #define DRMP3_HAVE_ARMV6 1 static __inline__ __attribute__((always_inline)) drmp3_int32 drmp3_clip_int16_arm(drmp3_int32 a) { @@ -2296,7 +2324,7 @@ DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int m DRMP3_COPY_MEMORY(dec->header, hdr, DRMP3_HDR_SIZE); info->frame_bytes = i + frame_size; info->channels = DRMP3_HDR_IS_MONO(hdr) ? 1 : 2; - info->hz = drmp3_hdr_sample_rate_hz(hdr); + info->sample_rate = drmp3_hdr_sample_rate_hz(hdr); info->layer = 4 - DRMP3_HDR_GET_LAYER(hdr); info->bitrate_kbps = drmp3_hdr_bitrate_kbps(hdr); @@ -2589,22 +2617,56 @@ static drmp3_allocation_callbacks drmp3_copy_allocation_callbacks_or_defaults(co static size_t drmp3__on_read(drmp3* pMP3, void* pBufferOut, size_t bytesToRead) { - size_t bytesRead = pMP3->onRead(pMP3->pUserData, pBufferOut, bytesToRead); + size_t bytesRead; + + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->onRead != NULL); + + /* + Don't try reading 0 bytes from the callback. This can happen when the stream is clamped against + ID3v1 or APE tags at the end of the stream. + */ + if (bytesToRead == 0) { + return 0; + } + + bytesRead = pMP3->onRead(pMP3->pUserData, pBufferOut, bytesToRead); pMP3->streamCursor += bytesRead; + return bytesRead; } +static size_t drmp3__on_read_clamped(drmp3* pMP3, void* pBufferOut, size_t bytesToRead) +{ + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->onRead != NULL); + + if (pMP3->streamLength == DRMP3_UINT64_MAX) { + return drmp3__on_read(pMP3, pBufferOut, bytesToRead); + } else { + drmp3_uint64 bytesRemaining; + + bytesRemaining = (pMP3->streamLength - pMP3->streamCursor); + if (bytesToRead > bytesRemaining) { + bytesToRead = (size_t)bytesRemaining; + } + + return drmp3__on_read(pMP3, pBufferOut, bytesToRead); + } +} + static drmp3_bool32 drmp3__on_seek(drmp3* pMP3, int offset, drmp3_seek_origin origin) { DRMP3_ASSERT(offset >= 0); + DRMP3_ASSERT(origin == DRMP3_SEEK_SET || origin == DRMP3_SEEK_CUR); if (!pMP3->onSeek(pMP3->pUserData, offset, origin)) { return DRMP3_FALSE; } - if (origin == drmp3_seek_origin_start) { + if (origin == DRMP3_SEEK_SET) { pMP3->streamCursor = (drmp3_uint64)offset; - } else { + } else{ pMP3->streamCursor += offset; } @@ -2617,21 +2679,20 @@ static drmp3_bool32 drmp3__on_seek_64(drmp3* pMP3, drmp3_uint64 offset, drmp3_se return drmp3__on_seek(pMP3, (int)offset, origin); } - /* Getting here "offset" is too large for a 32-bit integer. We just keep seeking forward until we hit the offset. */ - if (!drmp3__on_seek(pMP3, 0x7FFFFFFF, drmp3_seek_origin_start)) { + if (!drmp3__on_seek(pMP3, 0x7FFFFFFF, DRMP3_SEEK_SET)) { return DRMP3_FALSE; } offset -= 0x7FFFFFFF; while (offset > 0) { if (offset <= 0x7FFFFFFF) { - if (!drmp3__on_seek(pMP3, (int)offset, drmp3_seek_origin_current)) { + if (!drmp3__on_seek(pMP3, (int)offset, DRMP3_SEEK_CUR)) { return DRMP3_FALSE; } offset = 0; } else { - if (!drmp3__on_seek(pMP3, 0x7FFFFFFF, drmp3_seek_origin_current)) { + if (!drmp3__on_seek(pMP3, 0x7FFFFFFF, DRMP3_SEEK_CUR)) { return DRMP3_FALSE; } offset -= 0x7FFFFFFF; @@ -2641,8 +2702,22 @@ static drmp3_bool32 drmp3__on_seek_64(drmp3* pMP3, drmp3_uint64 offset, drmp3_se return DRMP3_TRUE; } +static void drmp3__on_meta(drmp3* pMP3, drmp3_metadata_type type, const void* pRawData, size_t rawDataSize) +{ + if (pMP3->onMeta) { + drmp3_metadata metadata; -static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) + DRMP3_ZERO_OBJECT(&metadata); + metadata.type = type; + metadata.pRawData = pRawData; + metadata.rawDataSize = rawDataSize; + + pMP3->onMeta(pMP3->pUserDataMeta, &metadata); + } +} + + +static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3dec_frame_info* pMP3FrameInfo, const drmp3_uint8** ppMP3FrameData) { drmp3_uint32 pcmFramesRead = 0; @@ -2682,7 +2757,7 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sa pMP3->dataCapacity = newDataCap; } - bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); + bytesRead = drmp3__on_read_clamped(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); if (bytesRead == 0) { if (pMP3->dataSize == 0) { pMP3->atEnd = DRMP3_TRUE; @@ -2709,10 +2784,8 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sa pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData + pMP3->dataConsumed, (int)pMP3->dataSize, pPCMFrames, &info); /* <-- Safe size_t -> int conversion thanks to the check above. */ /* Consume the data. */ - if (info.frame_bytes > 0) { - pMP3->dataConsumed += (size_t)info.frame_bytes; - pMP3->dataSize -= (size_t)info.frame_bytes; - } + pMP3->dataConsumed += (size_t)info.frame_bytes; + pMP3->dataSize -= (size_t)info.frame_bytes; /* pcmFramesRead will be equal to 0 if decoding failed. If it is zero and info.frame_bytes > 0 then we have successfully decoded the frame. */ if (pcmFramesRead > 0) { @@ -2720,7 +2793,16 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sa pMP3->pcmFramesConsumedInMP3Frame = 0; pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; pMP3->mp3FrameChannels = info.channels; - pMP3->mp3FrameSampleRate = info.hz; + pMP3->mp3FrameSampleRate = info.sample_rate; + + if (pMP3FrameInfo != NULL) { + *pMP3FrameInfo = info; + } + + if (ppMP3FrameData != NULL) { + *ppMP3FrameData = pMP3->pData + pMP3->dataConsumed - (size_t)info.frame_bytes; + } + break; } else if (info.frame_bytes == 0) { /* Need more data. minimp3 recommends doing data submission in 16K chunks. */ @@ -2747,7 +2829,7 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sa } /* Fill in a chunk. */ - bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); + bytesRead = drmp3__on_read_clamped(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); if (bytesRead == 0) { pMP3->atEnd = DRMP3_TRUE; return 0; /* Error reading more data. */ @@ -2760,7 +2842,7 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sa return pcmFramesRead; } -static drmp3_uint32 drmp3_decode_next_frame_ex__memory(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) +static drmp3_uint32 drmp3_decode_next_frame_ex__memory(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3dec_frame_info* pMP3FrameInfo, const drmp3_uint8** ppMP3FrameData) { drmp3_uint32 pcmFramesRead = 0; drmp3dec_frame_info info; @@ -2779,11 +2861,21 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__memory(drmp3* pMP3, drmp3d_sampl pMP3->pcmFramesConsumedInMP3Frame = 0; pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; pMP3->mp3FrameChannels = info.channels; - pMP3->mp3FrameSampleRate = info.hz; + pMP3->mp3FrameSampleRate = info.sample_rate; + + if (pMP3FrameInfo != NULL) { + *pMP3FrameInfo = info; + } + + if (ppMP3FrameData != NULL) { + *ppMP3FrameData = pMP3->memory.pData + pMP3->memory.currentReadPos; + } + break; } else if (info.frame_bytes > 0) { /* No frames were read, but it looks like we skipped past one. Read the next MP3 frame. */ pMP3->memory.currentReadPos += (size_t)info.frame_bytes; + pMP3->streamCursor += (size_t)info.frame_bytes; } else { /* Nothing at all was read. Abort. */ break; @@ -2792,23 +2884,24 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__memory(drmp3* pMP3, drmp3d_sampl /* Consume the data. */ pMP3->memory.currentReadPos += (size_t)info.frame_bytes; + pMP3->streamCursor += (size_t)info.frame_bytes; return pcmFramesRead; } -static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) +static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3dec_frame_info* pMP3FrameInfo, const drmp3_uint8** ppMP3FrameData) { if (pMP3->memory.pData != NULL && pMP3->memory.dataSize > 0) { - return drmp3_decode_next_frame_ex__memory(pMP3, pPCMFrames); + return drmp3_decode_next_frame_ex__memory(pMP3, pPCMFrames, pMP3FrameInfo, ppMP3FrameData); } else { - return drmp3_decode_next_frame_ex__callbacks(pMP3, pPCMFrames); + return drmp3_decode_next_frame_ex__callbacks(pMP3, pPCMFrames, pMP3FrameInfo, ppMP3FrameData); } } static drmp3_uint32 drmp3_decode_next_frame(drmp3* pMP3) { DRMP3_ASSERT(pMP3 != NULL); - return drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames); + return drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames, NULL, NULL); } #if 0 @@ -2818,7 +2911,7 @@ static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3) DRMP3_ASSERT(pMP3 != NULL); - pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, NULL); + pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, NULL, NULL, NULL); if (pcmFrameCount == 0) { return 0; } @@ -2832,8 +2925,13 @@ static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3) } #endif -static drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks) +static drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, drmp3_meta_proc onMeta, void* pUserData, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks) { + drmp3dec_frame_info firstFrameInfo; + const drmp3_uint8* pFirstFrameData; + drmp3_uint32 firstFramePCMFrameCount; + drmp3_uint32 detectedMP3FrameCount = 0xFFFFFFFF; + DRMP3_ASSERT(pMP3 != NULL); DRMP3_ASSERT(onRead != NULL); @@ -2842,17 +2940,326 @@ static drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drm pMP3->onRead = onRead; pMP3->onSeek = onSeek; + pMP3->onMeta = onMeta; pMP3->pUserData = pUserData; + pMP3->pUserDataMeta = pUserDataMeta; pMP3->allocationCallbacks = drmp3_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); if (pMP3->allocationCallbacks.onFree == NULL || (pMP3->allocationCallbacks.onMalloc == NULL && pMP3->allocationCallbacks.onRealloc == NULL)) { return DRMP3_FALSE; /* Invalid allocation callbacks. */ } - /* Decode the first frame to confirm that it is indeed a valid MP3 stream. */ - if (drmp3_decode_next_frame(pMP3) == 0) { + pMP3->streamCursor = 0; + pMP3->streamLength = DRMP3_UINT64_MAX; + pMP3->streamStartOffset = 0; + pMP3->delayInPCMFrames = 0; + pMP3->paddingInPCMFrames = 0; + pMP3->totalPCMFrameCount = DRMP3_UINT64_MAX; + + /* We'll first check for any ID3v1 or APE tags. */ + #if 1 + if (onSeek != NULL && onTell != NULL) { + if (onSeek(pUserData, 0, DRMP3_SEEK_END)) { + drmp3_int64 streamLen; + int streamEndOffset = 0; + + /* First get the length of the stream. We need this so we can ensure the stream is big enough to store the tags. */ + if (onTell(pUserData, &streamLen)) { + /* ID3v1 */ + if (streamLen > 128) { + char id3[3]; + if (onSeek(pUserData, streamEndOffset - 128, DRMP3_SEEK_END)) { + if (onRead(pUserData, id3, 3) == 3 && id3[0] == 'T' && id3[1] == 'A' && id3[2] == 'G') { + /* We have an ID3v1 tag. */ + streamEndOffset -= 128; + streamLen -= 128; + + /* Fire a metadata callback for the TAG data. */ + if (onMeta != NULL) { + drmp3_uint8 tag[128]; + tag[0] = 'T'; tag[1] = 'A'; tag[2] = 'G'; + + if (onRead(pUserData, tag + 3, 125) == 125) { + drmp3__on_meta(pMP3, DRMP3_METADATA_TYPE_ID3V1, tag, 128); + } + } + } else { + /* No ID3v1 tag. */ + } + } else { + /* Failed to seek to the ID3v1 tag. */ + } + } else { + /* Stream too short. No ID3v1 tag. */ + } + + /* APE */ + if (streamLen > 32) { + char ape[32]; /* The footer. */ + if (onSeek(pUserData, streamEndOffset - 32, DRMP3_SEEK_END)) { + if (onRead(pUserData, ape, 32) == 32 && ape[0] == 'A' && ape[1] == 'P' && ape[2] == 'E' && ape[3] == 'T' && ape[4] == 'A' && ape[5] == 'G' && ape[6] == 'E' && ape[7] == 'X') { + /* We have an APE tag. */ + drmp3_uint32 tagSize = + ((drmp3_uint32)ape[24] << 0) | + ((drmp3_uint32)ape[25] << 8) | + ((drmp3_uint32)ape[26] << 16) | + ((drmp3_uint32)ape[27] << 24); + + streamEndOffset -= 32 + tagSize; + streamLen -= 32 + tagSize; + + /* Fire a metadata callback for the APE data. Must include both the main content and footer. */ + if (onMeta != NULL) { + /* We first need to seek to the start of the APE tag. */ + if (onSeek(pUserData, streamEndOffset, DRMP3_SEEK_END)) { + size_t apeTagSize = (size_t)tagSize + 32; + drmp3_uint8* pTagData = (drmp3_uint8*)drmp3_malloc(apeTagSize, pAllocationCallbacks); + if (pTagData != NULL) { + if (onRead(pUserData, pTagData, apeTagSize) == apeTagSize) { + drmp3__on_meta(pMP3, DRMP3_METADATA_TYPE_APE, pTagData, apeTagSize); + } + + drmp3_free(pTagData, pAllocationCallbacks); + } + } + } + } + } + } else { + /* Stream too short. No APE tag. */ + } + + /* Seek back to the start. */ + if (!onSeek(pUserData, 0, DRMP3_SEEK_SET)) { + return DRMP3_FALSE; /* Failed to seek back to the start. */ + } + + pMP3->streamLength = (drmp3_uint64)streamLen; + + if (pMP3->memory.pData != NULL) { + pMP3->memory.dataSize = (size_t)pMP3->streamLength; + } + } else { + /* Failed to get the length of the stream. ID3v1 and APE tags cannot be skipped. */ + if (!onSeek(pUserData, 0, DRMP3_SEEK_SET)) { + return DRMP3_FALSE; /* Failed to seek back to the start. */ + } + } + } else { + /* Failed to seek to the end. Cannot skip ID3v1 or APE tags. */ + } + } else { + /* No onSeek or onTell callback. Cannot skip ID3v1 or APE tags. */ + } + #endif + + + /* ID3v2 tags */ + #if 1 + { + char header[10]; + if (onRead(pUserData, header, 10) == 10) { + if (header[0] == 'I' && header[1] == 'D' && header[2] == '3') { + drmp3_uint32 tagSize = + (((drmp3_uint32)header[6] & 0x7F) << 21) | + (((drmp3_uint32)header[7] & 0x7F) << 14) | + (((drmp3_uint32)header[8] & 0x7F) << 7) | + (((drmp3_uint32)header[9] & 0x7F) << 0); + + /* Account for the footer. */ + if (header[5] & 0x10) { + tagSize += 10; + } + + /* Read the tag content and fire a metadata callback. */ + if (onMeta != NULL) { + size_t tagSizeWithHeader = 10 + tagSize; + drmp3_uint8* pTagData = (drmp3_uint8*)drmp3_malloc(tagSizeWithHeader, pAllocationCallbacks); + if (pTagData != NULL) { + DRMP3_COPY_MEMORY(pTagData, header, 10); + + if (onRead(pUserData, pTagData + 10, tagSize) == tagSize) { + drmp3__on_meta(pMP3, DRMP3_METADATA_TYPE_ID3V2, pTagData, tagSizeWithHeader); + } + + drmp3_free(pTagData, pAllocationCallbacks); + } + } else { + /* Don't have a metadata callback, so just skip the tag. */ + if (onSeek != NULL) { + if (!onSeek(pUserData, tagSize, DRMP3_SEEK_CUR)) { + return DRMP3_FALSE; /* Failed to seek past the ID3v2 tag. */ + } + } else { + /* Don't have a seek callback. Read and discard. */ + char discard[1024]; + + while (tagSize > 0) { + size_t bytesToRead = tagSize; + if (bytesToRead > sizeof(discard)) { + bytesToRead = sizeof(discard); + } + + if (onRead(pUserData, discard, bytesToRead) != bytesToRead) { + return DRMP3_FALSE; /* Failed to read data. */ + } + + tagSize -= (drmp3_uint32)bytesToRead; + } + } + } + + pMP3->streamStartOffset += 10 + tagSize; /* +10 for the header. */ + pMP3->streamCursor = pMP3->streamStartOffset; + } else { + /* Not an ID3v2 tag. Seek back to the start. */ + if (onSeek != NULL) { + if (!onSeek(pUserData, 0, DRMP3_SEEK_SET)) { + return DRMP3_FALSE; /* Failed to seek back to the start. */ + } + } else { + /* Don't have a seek callback to move backwards. We'll just fall through and let the decoding process re-sync. The ideal solution here would be to read into the cache. */ + + /* + TODO: Copy the header into the cache. Will need to allocate space. See drmp3_decode_next_frame_ex__callbacks. There is not need + to handle the memory case because that will always have a seek implementation and will never hit this code path. + */ + } + } + } else { + /* Failed to read the header. We can return false here. If we couldn't read 10 bytes there's no way we'll have a valid MP3 stream. */ + return DRMP3_FALSE; + } + } + #endif + + /* + Decode the first frame to confirm that it is indeed a valid MP3 stream. Note that it's possible the first frame + is actually a Xing/LAME/VBRI header. If this is the case we need to skip over it. + */ + firstFramePCMFrameCount = drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames, &firstFrameInfo, &pFirstFrameData); + if (firstFramePCMFrameCount > 0) { + DRMP3_ASSERT(pFirstFrameData != NULL); + + /* + It might be a header. If so, we need to clear out the cached PCM frames in order to trigger a reload of fresh + data when decoding starts. We can assume all validation has already been performed to check if this is a valid + MP3 frame and that there is more than 0 bytes making up the frame. + + We're going to be basing this parsing code off the minimp3_ex implementation. + */ + #if 1 + DRMP3_ASSERT(firstFrameInfo.frame_bytes > 0); + { + drmp3_bs bs; + drmp3_L3_gr_info grInfo[4]; + const drmp3_uint8* pTagData = pFirstFrameData; + + drmp3_bs_init(&bs, pFirstFrameData + DRMP3_HDR_SIZE, firstFrameInfo.frame_bytes - DRMP3_HDR_SIZE); + + if (DRMP3_HDR_IS_CRC(pFirstFrameData)) { + drmp3_bs_get_bits(&bs, 16); /* CRC. */ + } + + if (drmp3_L3_read_side_info(&bs, grInfo, pFirstFrameData) >= 0) { + drmp3_bool32 isXing = DRMP3_FALSE; + drmp3_bool32 isInfo = DRMP3_FALSE; + const drmp3_uint8* pTagDataBeg; + + pTagDataBeg = pFirstFrameData + DRMP3_HDR_SIZE + (bs.pos/8); + pTagData = pTagDataBeg; + + /* Check for both "Xing" and "Info" identifiers. */ + isXing = (pTagData[0] == 'X' && pTagData[1] == 'i' && pTagData[2] == 'n' && pTagData[3] == 'g'); + isInfo = (pTagData[0] == 'I' && pTagData[1] == 'n' && pTagData[2] == 'f' && pTagData[3] == 'o'); + + if (isXing || isInfo) { + drmp3_uint32 bytes = 0; + drmp3_uint32 flags = pTagData[7]; + + pTagData += 8; /* Skip past the ID and flags. */ + + if (flags & 0x01) { /* FRAMES flag. */ + detectedMP3FrameCount = (drmp3_uint32)pTagData[0] << 24 | (drmp3_uint32)pTagData[1] << 16 | (drmp3_uint32)pTagData[2] << 8 | (drmp3_uint32)pTagData[3]; + pTagData += 4; + } + + if (flags & 0x02) { /* BYTES flag. */ + bytes = (drmp3_uint32)pTagData[0] << 24 | (drmp3_uint32)pTagData[1] << 16 | (drmp3_uint32)pTagData[2] << 8 | (drmp3_uint32)pTagData[3]; + (void)bytes; /* <-- Just to silence a warning about `bytes` being assigned but unused. Want to leave this here in case I want to make use of it later. */ + pTagData += 4; + } + + if (flags & 0x04) { /* TOC flag. */ + /* TODO: Extract and bind seek points. */ + pTagData += 100; + } + + if (flags & 0x08) { /* SCALE flag. */ + pTagData += 4; + } + + /* At this point we're done with the Xing/Info header. Now we can look at the LAME data. */ + if (pTagData[0]) { + pTagData += 21; + + if (pTagData - pFirstFrameData + 14 < firstFrameInfo.frame_bytes) { + int delayInPCMFrames; + int paddingInPCMFrames; + + delayInPCMFrames = (( (drmp3_uint32)pTagData[0] << 4) | ((drmp3_uint32)pTagData[1] >> 4)) + (528 + 1); + paddingInPCMFrames = ((((drmp3_uint32)pTagData[1] & 0xF) << 8) | ((drmp3_uint32)pTagData[2] )) - (528 + 1); + if (paddingInPCMFrames < 0) { + paddingInPCMFrames = 0; /* Padding cannot be negative. Probably a malformed file. Ignore. */ + } + + pMP3->delayInPCMFrames = (drmp3_uint32)delayInPCMFrames; + pMP3->paddingInPCMFrames = (drmp3_uint32)paddingInPCMFrames; + } + } + + /* + My understanding is that if the "Xing" header is present we can consider this to be a VBR stream and if the "Info" header is + present it's a CBR stream. If this is not the case let me know! I'm just tracking this for the time being in case I want to + look at doing some CBR optimizations later on, such as faster seeking. + */ + if (isXing) { + pMP3->isVBR = DRMP3_TRUE; + } else if (isInfo) { + pMP3->isCBR = DRMP3_TRUE; + } + + /* Post the raw data of the tag to the metadata callback. */ + if (onMeta != NULL) { + drmp3_metadata_type metadataType = isXing ? DRMP3_METADATA_TYPE_XING : DRMP3_METADATA_TYPE_VBRI; + size_t tagDataSize; + + tagDataSize = (size_t)firstFrameInfo.frame_bytes; + tagDataSize -= (size_t)(pTagDataBeg - pFirstFrameData); + + drmp3__on_meta(pMP3, metadataType, pTagDataBeg, tagDataSize); + } + + /* Since this was identified as a tag, we don't want to treat it as audio. We need to clear out the PCM cache. */ + pMP3->pcmFramesRemainingInMP3Frame = 0; + + /* The start offset needs to be moved to the end of this frame so it's not included in any audio processing after seeking. */ + pMP3->streamStartOffset += (drmp3_uint32)(firstFrameInfo.frame_bytes); + pMP3->streamCursor = pMP3->streamStartOffset; + } + } else { + /* Failed to read the side info. */ + } + } + #endif + } else { + /* Not a valid MP3 stream. */ drmp3__free_from_callbacks(pMP3->pData, &pMP3->allocationCallbacks); /* The call above may have allocated memory. Need to make sure it's freed before aborting. */ - return DRMP3_FALSE; /* Not a valid MP3 stream. */ + return DRMP3_FALSE; + } + + if (detectedMP3FrameCount != 0xFFFFFFFF) { + pMP3->totalPCMFrameCount = detectedMP3FrameCount * firstFramePCMFrameCount; } pMP3->channels = pMP3->mp3FrameChannels; @@ -2861,14 +3268,14 @@ static drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drm return DRMP3_TRUE; } -DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, drmp3_meta_proc onMeta, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pMP3 == NULL || onRead == NULL) { return DRMP3_FALSE; } DRMP3_ZERO_OBJECT(pMP3); - return drmp3_init_internal(pMP3, onRead, onSeek, pUserData, pAllocationCallbacks); + return drmp3_init_internal(pMP3, onRead, onSeek, onTell, onMeta, pUserData, pUserData, pAllocationCallbacks); } @@ -2896,35 +3303,52 @@ static size_t drmp3__on_read_memory(void* pUserData, void* pBufferOut, size_t by static drmp3_bool32 drmp3__on_seek_memory(void* pUserData, int byteOffset, drmp3_seek_origin origin) { drmp3* pMP3 = (drmp3*)pUserData; + drmp3_int64 newCursor; DRMP3_ASSERT(pMP3 != NULL); - if (origin == drmp3_seek_origin_current) { - if (byteOffset > 0) { - if (pMP3->memory.currentReadPos + byteOffset > pMP3->memory.dataSize) { - byteOffset = (int)(pMP3->memory.dataSize - pMP3->memory.currentReadPos); /* Trying to seek too far forward. */ - } - } else { - if (pMP3->memory.currentReadPos < (size_t)-byteOffset) { - byteOffset = -(int)pMP3->memory.currentReadPos; /* Trying to seek too far backwards. */ - } - } + newCursor = pMP3->memory.currentReadPos; - /* This will never underflow thanks to the clamps above. */ - pMP3->memory.currentReadPos += byteOffset; + if (origin == DRMP3_SEEK_SET) { + newCursor = 0; + } else if (origin == DRMP3_SEEK_CUR) { + newCursor = (drmp3_int64)pMP3->memory.currentReadPos; + } else if (origin == DRMP3_SEEK_END) { + newCursor = (drmp3_int64)pMP3->memory.dataSize; } else { - if ((drmp3_uint32)byteOffset <= pMP3->memory.dataSize) { - pMP3->memory.currentReadPos = byteOffset; - } else { - pMP3->memory.currentReadPos = pMP3->memory.dataSize; /* Trying to seek too far forward. */ - } + DRMP3_ASSERT(!"Invalid seek origin"); + return DRMP3_FALSE; } + newCursor += byteOffset; + + if (newCursor < 0) { + return DRMP3_FALSE; /* Trying to seek prior to the start of the buffer. */ + } + if ((size_t)newCursor > pMP3->memory.dataSize) { + return DRMP3_FALSE; /* Trying to seek beyond the end of the buffer. */ + } + + pMP3->memory.currentReadPos = (size_t)newCursor; + return DRMP3_TRUE; } -DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_allocation_callbacks* pAllocationCallbacks) +static drmp3_bool32 drmp3__on_tell_memory(void* pUserData, drmp3_int64* pCursor) { + drmp3* pMP3 = (drmp3*)pUserData; + + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pCursor != NULL); + + *pCursor = (drmp3_int64)pMP3->memory.currentReadPos; + return DRMP3_TRUE; +} + +DRMP3_API drmp3_bool32 drmp3_init_memory_with_metadata(drmp3* pMP3, const void* pData, size_t dataSize, drmp3_meta_proc onMeta, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + drmp3_bool32 result; + if (pMP3 == NULL) { return DRMP3_FALSE; } @@ -2939,7 +3363,26 @@ DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t pMP3->memory.dataSize = dataSize; pMP3->memory.currentReadPos = 0; - return drmp3_init_internal(pMP3, drmp3__on_read_memory, drmp3__on_seek_memory, pMP3, pAllocationCallbacks); + result = drmp3_init_internal(pMP3, drmp3__on_read_memory, drmp3__on_seek_memory, drmp3__on_tell_memory, onMeta, pMP3, pUserDataMeta, pAllocationCallbacks); + if (result == DRMP3_FALSE) { + return DRMP3_FALSE; + } + + /* Adjust the length of the memory stream to account for ID3v1 and APE tags. */ + if (pMP3->streamLength <= (drmp3_uint64)DRMP3_SIZE_MAX) { + pMP3->memory.dataSize = (size_t)pMP3->streamLength; /* Safe cast. */ + } + + if (pMP3->streamStartOffset > (drmp3_uint64)DRMP3_SIZE_MAX) { + return DRMP3_FALSE; /* Tags too big. */ + } + + return DRMP3_TRUE; +} + +DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + return drmp3_init_memory_with_metadata(pMP3, pData, dataSize, NULL, NULL, pAllocationCallbacks); } @@ -3069,7 +3512,7 @@ static drmp3_result drmp3_result_from_errno(int e) #ifdef ENOSYS case ENOSYS: return DRMP3_NOT_IMPLEMENTED; #endif - #ifdef ENOTEMPTY + #if defined(ENOTEMPTY) && ENOTEMPTY != EEXIST /* In AIX, ENOTEMPTY and EEXIST use the same value. */ case ENOTEMPTY: return DRMP3_DIRECTORY_NOT_EMPTY; #endif #ifdef ELOOP @@ -3519,19 +3962,56 @@ static size_t drmp3__on_read_stdio(void* pUserData, void* pBufferOut, size_t byt static drmp3_bool32 drmp3__on_seek_stdio(void* pUserData, int offset, drmp3_seek_origin origin) { - return fseek((FILE*)pUserData, offset, (origin == drmp3_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; + int whence = SEEK_SET; + if (origin == DRMP3_SEEK_CUR) { + whence = SEEK_CUR; + } else if (origin == DRMP3_SEEK_END) { + whence = SEEK_END; + } + + return fseek((FILE*)pUserData, offset, whence) == 0; } -DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks) +static drmp3_bool32 drmp3__on_tell_stdio(void* pUserData, drmp3_int64* pCursor) +{ + FILE* pFileStdio = (FILE*)pUserData; + drmp3_int64 result; + + /* These were all validated at a higher level. */ + DRMP3_ASSERT(pFileStdio != NULL); + DRMP3_ASSERT(pCursor != NULL); + +#if defined(_WIN32) + #if defined(_MSC_VER) && _MSC_VER > 1200 + result = _ftelli64(pFileStdio); + #else + result = ftell(pFileStdio); + #endif +#else + result = ftell(pFileStdio); +#endif + + *pCursor = result; + + return DRMP3_TRUE; +} + +DRMP3_API drmp3_bool32 drmp3_init_file_with_metadata(drmp3* pMP3, const char* pFilePath, drmp3_meta_proc onMeta, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3_bool32 result; FILE* pFile; + if (pMP3 == NULL) { + return DRMP3_FALSE; + } + + DRMP3_ZERO_OBJECT(pMP3); + if (drmp3_fopen(&pFile, pFilePath, "rb") != DRMP3_SUCCESS) { return DRMP3_FALSE; } - result = drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + result = drmp3_init_internal(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, drmp3__on_tell_stdio, onMeta, (void*)pFile, pUserDataMeta, pAllocationCallbacks); if (result != DRMP3_TRUE) { fclose(pFile); return result; @@ -3540,16 +4020,22 @@ DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const return DRMP3_TRUE; } -DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API drmp3_bool32 drmp3_init_file_with_metadata_w(drmp3* pMP3, const wchar_t* pFilePath, drmp3_meta_proc onMeta, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3_bool32 result; FILE* pFile; + if (pMP3 == NULL) { + return DRMP3_FALSE; + } + + DRMP3_ZERO_OBJECT(pMP3); + if (drmp3_wfopen(&pFile, pFilePath, L"rb", pAllocationCallbacks) != DRMP3_SUCCESS) { return DRMP3_FALSE; } - result = drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + result = drmp3_init_internal(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, drmp3__on_tell_stdio, onMeta, (void*)pFile, pUserDataMeta, pAllocationCallbacks); if (result != DRMP3_TRUE) { fclose(pFile); return result; @@ -3557,6 +4043,16 @@ DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, return DRMP3_TRUE; } + +DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + return drmp3_init_file_with_metadata(pMP3, pFilePath, NULL, NULL, pAllocationCallbacks); +} + +DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + return drmp3_init_file_with_metadata_w(pMP3, pFilePath, NULL, NULL, pAllocationCallbacks); +} #endif DRMP3_API void drmp3_uninit(drmp3* pMP3) @@ -3564,7 +4060,7 @@ DRMP3_API void drmp3_uninit(drmp3* pMP3) if (pMP3 == NULL) { return; } - + #ifndef DR_MP3_NO_STDIO if (pMP3->onRead == drmp3__on_read_stdio) { FILE* pFile = (FILE*)pMP3->pUserData; @@ -3644,19 +4140,48 @@ static drmp3_uint64 drmp3_read_pcm_frames_raw(drmp3* pMP3, drmp3_uint64 framesTo DRMP3_ASSERT(pMP3->onRead != NULL); while (framesToRead > 0) { - drmp3_uint32 framesToConsume = (drmp3_uint32)DRMP3_MIN(pMP3->pcmFramesRemainingInMP3Frame, framesToRead); + drmp3_uint32 framesToConsume; + + /* Skip frames if necessary. */ + if (pMP3->currentPCMFrame < pMP3->delayInPCMFrames) { + drmp3_uint32 framesToSkip = (drmp3_uint32)DRMP3_MIN(pMP3->pcmFramesRemainingInMP3Frame, pMP3->delayInPCMFrames - pMP3->currentPCMFrame); + + pMP3->currentPCMFrame += framesToSkip; + pMP3->pcmFramesConsumedInMP3Frame += framesToSkip; + pMP3->pcmFramesRemainingInMP3Frame -= framesToSkip; + } + + framesToConsume = (drmp3_uint32)DRMP3_MIN(pMP3->pcmFramesRemainingInMP3Frame, framesToRead); + + /* Clamp the number of frames to read to the padding. */ + if (pMP3->totalPCMFrameCount != DRMP3_UINT64_MAX && pMP3->totalPCMFrameCount > pMP3->paddingInPCMFrames) { + if (pMP3->currentPCMFrame < (pMP3->totalPCMFrameCount - pMP3->paddingInPCMFrames)) { + drmp3_uint64 framesRemainigToPadding = (pMP3->totalPCMFrameCount - pMP3->paddingInPCMFrames) - pMP3->currentPCMFrame; + if (framesToConsume > framesRemainigToPadding) { + framesToConsume = (drmp3_uint32)framesRemainigToPadding; + } + } else { + /* We're into the padding. Abort. */ + break; + } + } + if (pBufferOut != NULL) { - #if defined(DR_MP3_FLOAT_OUTPUT) - /* f32 */ - float* pFramesOutF32 = (float*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(float) * totalFramesRead * pMP3->channels); - float* pFramesInF32 = (float*)DRMP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(float) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); - DRMP3_COPY_MEMORY(pFramesOutF32, pFramesInF32, sizeof(float) * framesToConsume * pMP3->channels); - #else - /* s16 */ - drmp3_int16* pFramesOutS16 = (drmp3_int16*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(drmp3_int16) * totalFramesRead * pMP3->channels); - drmp3_int16* pFramesInS16 = (drmp3_int16*)DRMP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(drmp3_int16) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); - DRMP3_COPY_MEMORY(pFramesOutS16, pFramesInS16, sizeof(drmp3_int16) * framesToConsume * pMP3->channels); - #endif + #if defined(DR_MP3_FLOAT_OUTPUT) + { + /* f32 */ + float* pFramesOutF32 = (float*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(float) * totalFramesRead * pMP3->channels); + float* pFramesInF32 = (float*)DRMP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(float) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); + DRMP3_COPY_MEMORY(pFramesOutF32, pFramesInF32, sizeof(float) * framesToConsume * pMP3->channels); + } + #else + { + /* s16 */ + drmp3_int16* pFramesOutS16 = (drmp3_int16*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(drmp3_int16) * totalFramesRead * pMP3->channels); + drmp3_int16* pFramesInS16 = (drmp3_int16*)DRMP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(drmp3_int16) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); + DRMP3_COPY_MEMORY(pFramesOutS16, pFramesInS16, sizeof(drmp3_int16) * framesToConsume * pMP3->channels); + } + #endif } pMP3->currentPCMFrame += framesToConsume; @@ -3669,12 +4194,14 @@ static drmp3_uint64 drmp3_read_pcm_frames_raw(drmp3* pMP3, drmp3_uint64 framesTo break; } + /* If the cursor is already at the padding we need to abort. */ + if (pMP3->totalPCMFrameCount != DRMP3_UINT64_MAX && pMP3->totalPCMFrameCount > pMP3->paddingInPCMFrames && pMP3->currentPCMFrame >= (pMP3->totalPCMFrameCount - pMP3->paddingInPCMFrames)) { + break; + } + DRMP3_ASSERT(pMP3->pcmFramesRemainingInMP3Frame == 0); - /* - At this point we have exhausted our in-memory buffer so we need to re-fill. Note that the sample rate may have changed - at this point which means we'll also need to update our sample rate conversion pipeline. - */ + /* At this point we have exhausted our in-memory buffer so we need to re-fill. */ if (drmp3_decode_next_frame(pMP3) == 0) { break; } @@ -3776,7 +4303,7 @@ static drmp3_bool32 drmp3_seek_to_start_of_stream(drmp3* pMP3) DRMP3_ASSERT(pMP3->onSeek != NULL); /* Seek to the start of the stream to begin with. */ - if (!drmp3__on_seek(pMP3, 0, drmp3_seek_origin_start)) { + if (!drmp3__on_seek_64(pMP3, pMP3->streamStartOffset, DRMP3_SEEK_SET)) { return DRMP3_FALSE; } @@ -3876,7 +4403,7 @@ static drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint6 } /* First thing to do is seek to the first byte of the relevant MP3 frame. */ - if (!drmp3__on_seek_64(pMP3, seekPoint.seekPosInBytes, drmp3_seek_origin_start)) { + if (!drmp3__on_seek_64(pMP3, seekPoint.seekPosInBytes, DRMP3_SEEK_SET)) { return DRMP3_FALSE; /* Failed to seek. */ } @@ -3895,7 +4422,7 @@ static drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint6 } /* We first need to decode the next frame. */ - pcmFramesRead = drmp3_decode_next_frame_ex(pMP3, pPCMFrames); + pcmFramesRead = drmp3_decode_next_frame_ex(pMP3, pPCMFrames, NULL, NULL); if (pcmFramesRead == 0) { return DRMP3_FALSE; } @@ -3952,7 +4479,7 @@ DRMP3_API drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint /* We'll need to seek back to where we were, so grab the PCM frame we're currently sitting on so we can restore later. */ currentPCMFrame = pMP3->currentPCMFrame; - + if (!drmp3_seek_to_start_of_stream(pMP3)) { return DRMP3_FALSE; } @@ -3963,7 +4490,7 @@ DRMP3_API drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint for (;;) { drmp3_uint32 pcmFramesInCurrentMP3Frame; - pcmFramesInCurrentMP3Frame = drmp3_decode_next_frame_ex(pMP3, NULL); + pcmFramesInCurrentMP3Frame = drmp3_decode_next_frame_ex(pMP3, NULL, NULL, NULL); if (pcmFramesInCurrentMP3Frame == 0) { break; } @@ -3994,11 +4521,35 @@ DRMP3_API drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint DRMP3_API drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3) { drmp3_uint64 totalPCMFrameCount; - if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, NULL, &totalPCMFrameCount)) { + + if (pMP3 == NULL) { return 0; } - return totalPCMFrameCount; + if (pMP3->totalPCMFrameCount != DRMP3_UINT64_MAX) { + totalPCMFrameCount = pMP3->totalPCMFrameCount; + + if (totalPCMFrameCount >= pMP3->delayInPCMFrames) { + totalPCMFrameCount -= pMP3->delayInPCMFrames; + } else { + /* The delay is greater than the frame count reported by the Xing/Info tag. Assume it's invalid and ignore. */ + } + + if (totalPCMFrameCount >= pMP3->paddingInPCMFrames) { + totalPCMFrameCount -= pMP3->paddingInPCMFrames; + } else { + /* The padding is greater than the frame count reported by the Xing/Info tag. Assume it's invalid and ignore. */ + } + + return totalPCMFrameCount; + } else { + /* Unknown frame count. Need to calculate it. */ + if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, NULL, &totalPCMFrameCount)) { + return 0; + } + + return totalPCMFrameCount; + } } DRMP3_API drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3) @@ -4050,7 +4601,7 @@ DRMP3_API drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pS /* We'll need to seek back to the current sample after calculating the seekpoints so we need to go ahead and grab the current location at the top. */ currentPCMFrame = pMP3->currentPCMFrame; - + /* We never do more than the total number of MP3 frames and we limit it to 32-bits. */ if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, &totalMP3FrameCount, &totalPCMFrameCount)) { return DRMP3_FALSE; @@ -4101,7 +4652,7 @@ DRMP3_API drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pS mp3FrameInfo[iMP3Frame].pcmFrameIndex = runningPCMFrameCount; /* We need to get information about this frame so we can know how many samples it contained. */ - pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL); + pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, NULL, NULL); if (pcmFramesInCurrentMP3FrameIn == 0) { return DRMP3_FALSE; /* This should never happen. */ } @@ -4145,7 +4696,7 @@ DRMP3_API drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pS Go to the next MP3 frame. This shouldn't ever fail, but just in case it does we just set the seek point and break. If it happens, it should only ever do it for the last seek point. */ - pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL); + pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, NULL, NULL); if (pcmFramesInCurrentMP3FrameIn == 0) { pSeekPoints[iSeekPoint].seekPosInBytes = mp3FrameInfo[0].bytePos; pSeekPoints[iSeekPoint].pcmFrameIndex = nextTargetPCMFrame; @@ -4327,20 +4878,20 @@ static drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pC } -DRMP3_API float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pAllocationCallbacks)) { + if (!drmp3_init(&mp3, onRead, onSeek, onTell, NULL, pUserData, pAllocationCallbacks)) { return NULL; } return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); } -DRMP3_API drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pAllocationCallbacks)) { + if (!drmp3_init(&mp3, onRead, onSeek, onTell, NULL, pUserData, pAllocationCallbacks)) { return NULL; } @@ -4427,74 +4978,26 @@ DIFFERENCES BETWEEN minimp3 AND dr_mp3 using minimp3 in conjunction with stb_vorbis. dr_mp3 addresses this. */ -/* -RELEASE NOTES - v0.5.0 -======================= -Version 0.5.0 has breaking API changes. - -Improved Client-Defined Memory Allocation ------------------------------------------ -The main change with this release is the addition of a more flexible way of implementing custom memory allocation routines. The -existing system of DRMP3_MALLOC, DRMP3_REALLOC and DRMP3_FREE are still in place and will be used by default when no custom -allocation callbacks are specified. - -To use the new system, you pass in a pointer to a drmp3_allocation_callbacks object to drmp3_init() and family, like this: - - void* my_malloc(size_t sz, void* pUserData) - { - return malloc(sz); - } - void* my_realloc(void* p, size_t sz, void* pUserData) - { - return realloc(p, sz); - } - void my_free(void* p, void* pUserData) - { - free(p); - } - - ... - - drmp3_allocation_callbacks allocationCallbacks; - allocationCallbacks.pUserData = &myData; - allocationCallbacks.onMalloc = my_malloc; - allocationCallbacks.onRealloc = my_realloc; - allocationCallbacks.onFree = my_free; - drmp3_init_file(&mp3, "my_file.mp3", NULL, &allocationCallbacks); - -The advantage of this new system is that it allows you to specify user data which will be passed in to the allocation routines. - -Passing in null for the allocation callbacks object will cause dr_mp3 to use defaults which is the same as DRMP3_MALLOC, -DRMP3_REALLOC and DRMP3_FREE and the equivalent of how it worked in previous versions. - -Every API that opens a drmp3 object now takes this extra parameter. These include the following: - - drmp3_init() - drmp3_init_file() - drmp3_init_memory() - drmp3_open_and_read_pcm_frames_f32() - drmp3_open_and_read_pcm_frames_s16() - drmp3_open_memory_and_read_pcm_frames_f32() - drmp3_open_memory_and_read_pcm_frames_s16() - drmp3_open_file_and_read_pcm_frames_f32() - drmp3_open_file_and_read_pcm_frames_s16() - -Renamed APIs ------------- -The following APIs have been renamed for consistency with other dr_* libraries and to make it clear that they return PCM frame -counts rather than sample counts. - - drmp3_open_and_read_f32() -> drmp3_open_and_read_pcm_frames_f32() - drmp3_open_and_read_s16() -> drmp3_open_and_read_pcm_frames_s16() - drmp3_open_memory_and_read_f32() -> drmp3_open_memory_and_read_pcm_frames_f32() - drmp3_open_memory_and_read_s16() -> drmp3_open_memory_and_read_pcm_frames_s16() - drmp3_open_file_and_read_f32() -> drmp3_open_file_and_read_pcm_frames_f32() - drmp3_open_file_and_read_s16() -> drmp3_open_file_and_read_pcm_frames_s16() -*/ - /* REVISION HISTORY ================ +v0.7.0 - TBD + - The old `DRMP3_IMPLEMENTATION` has been removed. Use `DR_MP3_IMPLEMENTATION` instead. The reason for this change is that in the future everything will eventually be using the underscored naming convention in the future, so `drmp3` will become `dr_mp3`. + - API CHANGE: Seek origins have been renamed to match the naming convention used by dr_wav and my other libraries. + - drmp3_seek_origin_start -> DRMP3_SEEK_SET + - drmp3_seek_origin_current -> DRMP3_SEEK_CUR + - DRMP3_SEEK_END (new) + - API CHANGE: Add DRMP3_SEEK_END as a seek origin for the seek callback. This is required for detection of ID3v1 and APE tags. + - API CHANGE: Add onTell callback to `drmp3_init()`. This is needed in order to track the location of ID3v1 and APE tags. + - API CHANGE: Add onMeta callback to `drmp3_init()`. This is used for reporting tag data back to the caller. Currently this only reports the raw tag data which means applications need to parse the data themselves. + - API CHANGE: Rename `drmp3dec_frame_info.hz` to `drmp3dec_frame_info.sample_rate`. + - Add detection of ID3v2, ID3v1, APE and Xing/VBRI tags. This should fix errors with some files where the decoder was reading tags as audio data. + - Delay and padding samples from LAME tags are now handled. + - Fix compilation for AIX OS. + +v0.6.40 - 2024-12-17 + - Improve detection of ARM64EC + v0.6.39 - 2024-02-27 - Fix a Wdouble-promotion warning. diff --git a/src/external/dr_wav.h b/src/external/dr_wav.h index a8207ab90..7a7e02246 100644 --- a/src/external/dr_wav.h +++ b/src/external/dr_wav.h @@ -1,6 +1,6 @@ /* WAV audio loader and writer. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_wav - v0.13.16 - 2024-02-27 +dr_wav - v0.14.0 - TBD David Reid - mackron@gmail.com @@ -146,8 +146,8 @@ extern "C" { #define DRWAV_XSTRINGIFY(x) DRWAV_STRINGIFY(x) #define DRWAV_VERSION_MAJOR 0 -#define DRWAV_VERSION_MINOR 13 -#define DRWAV_VERSION_REVISION 16 +#define DRWAV_VERSION_MINOR 14 +#define DRWAV_VERSION_REVISION 0 #define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION) #include /* For size_t. */ @@ -176,7 +176,7 @@ typedef unsigned int drwav_uint32; #pragma GCC diagnostic pop #endif #endif -#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) || defined(__powerpc64__) typedef drwav_uint64 drwav_uintptr; #else typedef drwav_uint32 drwav_uintptr; @@ -305,8 +305,9 @@ typedef struct typedef enum { - drwav_seek_origin_start, - drwav_seek_origin_current + DRWAV_SEEK_SET, + DRWAV_SEEK_CUR, + DRWAV_SEEK_END } drwav_seek_origin; typedef enum @@ -415,11 +416,21 @@ origin [in] The origin of the seek - the current position or the start of the Returns whether or not the seek was successful. -Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which will be either drwav_seek_origin_start or -drwav_seek_origin_current. +Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which will be either DRWAV_SEEK_SET or +DRWAV_SEEK_CUR. */ typedef drwav_bool32 (* drwav_seek_proc)(void* pUserData, int offset, drwav_seek_origin origin); +/* +Callback for when the current position in the stream needs to be retrieved. + +pUserData [in] The user data that was passed to drwav_init() and family. +pCursor [out] A pointer to a variable to receive the current position in the stream. + +Returns whether or not the operation was successful. +*/ +typedef drwav_bool32 (* drwav_tell_proc)(void* pUserData, drwav_int64* pCursor); + /* Callback for when drwav_init_ex() finds a chunk. @@ -514,6 +525,11 @@ typedef enum drwav_metadata_type_list_info_genre = 1 << 15, drwav_metadata_type_list_info_album = 1 << 16, drwav_metadata_type_list_info_tracknumber = 1 << 17, + drwav_metadata_type_list_info_location = 1 << 18, + drwav_metadata_type_list_info_organization = 1 << 19, + drwav_metadata_type_list_info_keywords = 1 << 20, + drwav_metadata_type_list_info_medium = 1 << 21, + drwav_metadata_type_list_info_description = 1 << 22, /* Other type constants for convenience. */ drwav_metadata_type_list_all_info_strings = drwav_metadata_type_list_info_software @@ -524,7 +540,12 @@ typedef enum | drwav_metadata_type_list_info_date | drwav_metadata_type_list_info_genre | drwav_metadata_type_list_info_album - | drwav_metadata_type_list_info_tracknumber, + | drwav_metadata_type_list_info_tracknumber + | drwav_metadata_type_list_info_location + | drwav_metadata_type_list_info_organization + | drwav_metadata_type_list_info_keywords + | drwav_metadata_type_list_info_medium + | drwav_metadata_type_list_info_description, drwav_metadata_type_list_all_adtl = drwav_metadata_type_list_label | drwav_metadata_type_list_note @@ -555,11 +576,11 @@ typedef struct /* See drwav_smpl_loop_type. */ drwav_uint32 type; - /* The byte offset of the first sample to be played in the loop. */ - drwav_uint32 firstSampleByteOffset; + /* The offset of the first sample to be played in the loop. */ + drwav_uint32 firstSampleOffset; - /* The byte offset into the audio data of the last sample to be played in the loop. */ - drwav_uint32 lastSampleByteOffset; + /* The offset into the audio data of the last sample to be played in the loop. */ + drwav_uint32 lastSampleOffset; /* A value to represent that playback should occur at a point between samples. This value ranges from 0 to UINT32_MAX. Where a value of 0 means no fraction, and a value of (UINT32_MAX / 2) would mean half a sample. */ drwav_uint32 sampleFraction; @@ -637,8 +658,8 @@ typedef struct /* Set to 0 for uncompressed formats. Else the last byte in compressed wave data where decompression can begin to find the value of the corresponding sample value. */ drwav_uint32 blockStart; - /* For uncompressed formats this is the byte offset of the cue point into the audio data. For compressed formats this is relative to the block specified with blockStart. */ - drwav_uint32 sampleByteOffset; + /* For uncompressed formats this is the offset of the cue point into the audio data. For compressed formats this is relative to the block specified with blockStart. */ + drwav_uint32 sampleOffset; } drwav_cue_point; typedef struct @@ -846,6 +867,9 @@ typedef struct /* A pointer to the function to call when the wav file needs to be seeked. */ drwav_seek_proc onSeek; + /* A pointer to the function to call when the position of the stream needs to be retrieved. */ + drwav_tell_proc onTell; + /* The user data to pass to callbacks. */ void* pUserData; @@ -968,9 +992,9 @@ after the function returns. See also: drwav_init_file(), drwav_init_memory(), drwav_uninit() */ -DRWAV_API drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_with_metadata(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_tell_proc onTell, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_tell_proc onTell, drwav_chunk_proc onChunk, void* pReadSeekTellUserData, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_with_metadata(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_tell_proc onTell, void* pUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); /* Initializes a pre-allocated drwav object for writing. @@ -1273,9 +1297,9 @@ Opens and reads an entire wav file in a single operation. The return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer. */ -DRWAV_API drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); #ifndef DR_WAV_NO_STDIO /* Opens and decodes an entire wav file in a single operation. @@ -1384,7 +1408,7 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b); #define DRWAV_MAX_SIMD_VECTOR_SIZE 32 /* Architecture Detection */ -#if defined(__x86_64__) || defined(_M_X64) +#if defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC)) #define DRWAV_X64 #elif defined(__i386) || defined(_M_IX86) #define DRWAV_X86 @@ -1962,12 +1986,12 @@ DRWAV_PRIVATE drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uin drwav_uint64 bytesRemainingToSeek = offset; while (bytesRemainingToSeek > 0) { if (bytesRemainingToSeek > 0x7FFFFFFF) { - if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { + if (!onSeek(pUserData, 0x7FFFFFFF, DRWAV_SEEK_CUR)) { return DRWAV_FALSE; } bytesRemainingToSeek -= 0x7FFFFFFF; } else { - if (!onSeek(pUserData, (int)bytesRemainingToSeek, drwav_seek_origin_current)) { + if (!onSeek(pUserData, (int)bytesRemainingToSeek, DRWAV_SEEK_CUR)) { return DRWAV_FALSE; } bytesRemainingToSeek = 0; @@ -1980,21 +2004,21 @@ DRWAV_PRIVATE drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uin DRWAV_PRIVATE drwav_bool32 drwav__seek_from_start(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) { if (offset <= 0x7FFFFFFF) { - return onSeek(pUserData, (int)offset, drwav_seek_origin_start); + return onSeek(pUserData, (int)offset, DRWAV_SEEK_SET); } /* Larger than 32-bit seek. */ - if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_start)) { + if (!onSeek(pUserData, 0x7FFFFFFF, DRWAV_SEEK_SET)) { return DRWAV_FALSE; } offset -= 0x7FFFFFFF; for (;;) { if (offset <= 0x7FFFFFFF) { - return onSeek(pUserData, (int)offset, drwav_seek_origin_current); + return onSeek(pUserData, (int)offset, DRWAV_SEEK_CUR); } - if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { + if (!onSeek(pUserData, 0x7FFFFFFF, DRWAV_SEEK_CUR)) { return DRWAV_FALSE; } offset -= 0x7FFFFFFF; @@ -2028,7 +2052,7 @@ DRWAV_PRIVATE drwav_bool32 drwav__on_seek(drwav_seek_proc onSeek, void* pUserDat return DRWAV_FALSE; } - if (origin == drwav_seek_origin_start) { + if (origin == DRWAV_SEEK_SET) { *pCursor = offset; } else { *pCursor += offset; @@ -2096,7 +2120,7 @@ DRWAV_PRIVATE drwav_uint8* drwav__metadata_get_memory(drwav__metadata_parser* pP pParser->pDataCursor += align - modulo; } } - + pResult = pParser->pDataCursor; /* @@ -2189,12 +2213,12 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_pars bytesJustRead = drwav__metadata_parser_read(pParser, smplLoopData, sizeof(smplLoopData), &totalBytesRead); if (bytesJustRead == sizeof(smplLoopData)) { - pMetadata->data.smpl.pLoops[iSampleLoop].cuePointId = drwav_bytes_to_u32(smplLoopData + 0); - pMetadata->data.smpl.pLoops[iSampleLoop].type = drwav_bytes_to_u32(smplLoopData + 4); - pMetadata->data.smpl.pLoops[iSampleLoop].firstSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 8); - pMetadata->data.smpl.pLoops[iSampleLoop].lastSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 12); - pMetadata->data.smpl.pLoops[iSampleLoop].sampleFraction = drwav_bytes_to_u32(smplLoopData + 16); - pMetadata->data.smpl.pLoops[iSampleLoop].playCount = drwav_bytes_to_u32(smplLoopData + 20); + pMetadata->data.smpl.pLoops[iSampleLoop].cuePointId = drwav_bytes_to_u32(smplLoopData + 0); + pMetadata->data.smpl.pLoops[iSampleLoop].type = drwav_bytes_to_u32(smplLoopData + 4); + pMetadata->data.smpl.pLoops[iSampleLoop].firstSampleOffset = drwav_bytes_to_u32(smplLoopData + 8); + pMetadata->data.smpl.pLoops[iSampleLoop].lastSampleOffset = drwav_bytes_to_u32(smplLoopData + 12); + pMetadata->data.smpl.pLoops[iSampleLoop].sampleFraction = drwav_bytes_to_u32(smplLoopData + 16); + pMetadata->data.smpl.pLoops[iSampleLoop].playCount = drwav_bytes_to_u32(smplLoopData + 20); } else { break; } @@ -2254,7 +2278,7 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_cue_to_metadata_obj(drwav__metadata_parse pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[3] = cuePointData[11]; pMetadata->data.cue.pCuePoints[iCuePoint].chunkStart = drwav_bytes_to_u32(cuePointData + 12); pMetadata->data.cue.pCuePoints[iCuePoint].blockStart = drwav_bytes_to_u32(cuePointData + 16); - pMetadata->data.cue.pCuePoints[iCuePoint].sampleByteOffset = drwav_bytes_to_u32(cuePointData + 20); + pMetadata->data.cue.pCuePoints[iCuePoint].sampleOffset = drwav_bytes_to_u32(cuePointData + 20); } else { break; } @@ -2407,7 +2431,7 @@ DRWAV_PRIVATE drwav_result drwav_buffer_reader_read(drwav_buffer_reader* pReader size_t bytesRemaining; DRWAV_ASSERT(pReader != NULL); - + if (pBytesRead != NULL) { *pBytesRead = 0; } @@ -2487,7 +2511,7 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_bext_to_metadata_obj(drwav__metadata_pars size_t bytesRead = drwav__metadata_parser_read(pParser, bextData, sizeof(bextData), NULL); DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); - + if (bytesRead == sizeof(bextData)) { drwav_buffer_reader reader; drwav_uint32 timeReferenceLow; @@ -2549,7 +2573,7 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_list_label_or_note_to_metadata_obj(drwav_ drwav_uint64 totalBytesRead = 0; size_t bytesJustRead = drwav__metadata_parser_read(pParser, cueIDBuffer, sizeof(cueIDBuffer), &totalBytesRead); - DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); if (bytesJustRead == sizeof(cueIDBuffer)) { drwav_uint32 sizeIncludingNullTerminator; @@ -2698,7 +2722,7 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* drwav_uint8 buffer[4]; size_t bytesJustRead; - if (!pParser->onSeek(pParser->pReadSeekUserData, 28, drwav_seek_origin_current)) { + if (!pParser->onSeek(pParser->pReadSeekUserData, 28, DRWAV_SEEK_CUR)) { return bytesRead; } bytesRead += 28; @@ -2721,7 +2745,7 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* } } else { /* Loop count in header does not match the size of the chunk. */ - } + } } } else { bytesRead = drwav__read_smpl_to_metadata_obj(pParser, pChunkHeader, &pParser->pMetadata[pParser->metadataCursor]); @@ -2811,7 +2835,7 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* return bytesRead; } allocSizeNeeded += drwav__strlen(buffer) + 1; - allocSizeNeeded += (size_t)pChunkHeader->sizeInBytes - DRWAV_BEXT_BYTES; /* Coding history. */ + allocSizeNeeded += (size_t)pChunkHeader->sizeInBytes - DRWAV_BEXT_BYTES + 1; /* Coding history. */ drwav__metadata_request_extra_memory_for_stage_2(pParser, allocSizeNeeded, 1); @@ -2916,6 +2940,16 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_album); } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_tracknumber, "ITRK")) { subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_tracknumber); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_location, "IARL")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_location); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_organization, "ICMS")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_organization); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_keywords, "IKEY")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_keywords); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_medium, "IMED")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_medium); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_description, "ISBJ")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_description); } else if ((allowedMetadataTypes & drwav_metadata_type_unknown) != 0) { subchunkBytesRead = drwav__metadata_process_unknown_chunk(pParser, subchunkId, subchunkDataSize, listType); } @@ -2926,14 +2960,14 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* if (subchunkBytesRead < subchunkDataSize) { drwav_uint64 bytesToSeek = subchunkDataSize - subchunkBytesRead; - if (!pParser->onSeek(pParser->pReadSeekUserData, (int)bytesToSeek, drwav_seek_origin_current)) { + if (!pParser->onSeek(pParser->pReadSeekUserData, (int)bytesToSeek, DRWAV_SEEK_CUR)) { break; } bytesRead += bytesToSeek; } if ((subchunkDataSize % 2) == 1) { - if (!pParser->onSeek(pParser->pReadSeekUserData, 1, drwav_seek_origin_current)) { + if (!pParser->onSeek(pParser->pReadSeekUserData, 1, DRWAV_SEEK_CUR)) { break; } bytesRead += 1; @@ -2985,16 +3019,17 @@ DRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT) } } -DRWAV_PRIVATE drwav_bool32 drwav_preinit(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_bool32 drwav_preinit(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_tell_proc onTell, void* pReadSeekTellUserData, const drwav_allocation_callbacks* pAllocationCallbacks) { - if (pWav == NULL || onRead == NULL || onSeek == NULL) { + if (pWav == NULL || onRead == NULL || onSeek == NULL) { /* <-- onTell is optional. */ return DRWAV_FALSE; } DRWAV_ZERO_MEMORY(pWav, sizeof(*pWav)); pWav->onRead = onRead; pWav->onSeek = onSeek; - pWav->pUserData = pReadSeekUserData; + pWav->onTell = onTell; + pWav->pUserData = pReadSeekTellUserData; pWav->allocationCallbacks = drwav_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); if (pWav->allocationCallbacks.onFree == NULL || (pWav->allocationCallbacks.onMalloc == NULL && pWav->allocationCallbacks.onRealloc == NULL)) { @@ -3311,7 +3346,7 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on fmt.channelMask = drwav_bytes_to_u32_ex(fmtext + 2, pWav->container); drwav_bytes_to_guid(fmtext + 6, fmt.subFormat); } else { - if (pWav->onSeek(pWav->pUserData, fmt.extendedSize, drwav_seek_origin_current) == DRWAV_FALSE) { + if (pWav->onSeek(pWav->pUserData, fmt.extendedSize, DRWAV_SEEK_CUR) == DRWAV_FALSE) { return DRWAV_FALSE; } } @@ -3321,7 +3356,7 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on } /* Seek past any leftover bytes. For w64 the leftover will be defined based on the chunk size. */ - if (pWav->onSeek(pWav->pUserData, (int)(header.sizeInBytes - bytesReadSoFar), drwav_seek_origin_current) == DRWAV_FALSE) { + if (pWav->onSeek(pWav->pUserData, (int)(header.sizeInBytes - bytesReadSoFar), DRWAV_SEEK_CUR) == DRWAV_FALSE) { return DRWAV_FALSE; } cursor += (header.sizeInBytes - bytesReadSoFar); @@ -3342,7 +3377,7 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on if (((pWav->container == drwav_container_riff || pWav->container == drwav_container_rifx || pWav->container == drwav_container_rf64) && drwav_fourcc_equal(header.id.fourcc, "data")) || ((pWav->container == drwav_container_w64) && drwav_guid_equal(header.id.guid, drwavGUID_W64_DATA))) { foundChunk_data = DRWAV_TRUE; - + pWav->dataChunkDataPos = cursor; if (pWav->container != drwav_container_rf64) { /* The data chunk size for RF64 will always be set to 0xFFFFFFFF here. It was set to it's true value earlier. */ @@ -3432,7 +3467,7 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on return DRWAV_FALSE; } - + channels = drwav_bytes_to_u16_ex (commData + 0, pWav->container); frameCount = drwav_bytes_to_u32_ex (commData + 2, pWav->container); sampleSizeInBits = drwav_bytes_to_u16_ex (commData + 6, pWav->container); @@ -3465,12 +3500,15 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on compressionFormat = DR_WAVE_FORMAT_MULAW; } else if (drwav_fourcc_equal(type, "ima4")) { compressionFormat = DR_WAVE_FORMAT_DVI_ADPCM; - sampleSizeInBits = 4; + sampleSizeInBits = 4; /* I haven't been able to figure out how to get correct decoding for IMA ADPCM. Until this is figured out we'll need to abort when we encounter such an encoding. Advice welcome! */ + (void)compressionFormat; + (void)sampleSizeInBits; + return DRWAV_FALSE; } else { return DRWAV_FALSE; /* Unknown or unsupported compression format. Need to abort. */ @@ -3507,7 +3545,7 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on /* In AIFF, samples are padded to 8 byte boundaries. We need to round up our bits per sample here. */ fmt.bitsPerSample += (fmt.bitsPerSample & 7); - + /* If the form type is AIFC there will be some additional data in the chunk. We need to seek past it. */ if (isAIFCFormType) { @@ -3533,20 +3571,46 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on return DRWAV_FALSE; } - /* We need to seek forward by the offset. */ + /* The position of the audio data starts at an offset. */ offset = drwav_bytes_to_u32_ex(offsetAndBlockSizeData + 0, pWav->container); - if (drwav__seek_forward(pWav->onSeek, offset, pWav->pUserData) == DRWAV_FALSE) { - return DRWAV_FALSE; - } - cursor += offset; + pWav->dataChunkDataPos = cursor + offset; - pWav->dataChunkDataPos = cursor; + /* The data chunk size needs to be reduced by the offset or else seeking will break. */ dataChunkSize = chunkSize; - - /* If we're running in sequential mode, or we're not reading metadata, we have enough now that we can get out of the loop. */ - if (sequential || !isProcessingMetadata) { - break; /* No need to keep reading beyond the data chunk. */ + if (dataChunkSize > offset) { + dataChunkSize -= offset; } else { + dataChunkSize = 0; + } + + if (sequential) { + if (foundChunk_fmt) { /* <-- Name is misleading, but will be set to true if the COMM chunk has been parsed. */ + /* + Getting here means we're opening in sequential mode and we've found the SSND (data) and COMM (fmt) chunks. We need + to get out of the loop here or else we'll end up going past the data chunk and will have no way of getting back to + it since we're not allowed to seek backwards. + + One subtle detail here is that there is an offset with the SSND chunk. We need to make sure we seek past this offset + so we're left sitting on the first byte of actual audio data. + */ + if (drwav__seek_forward(pWav->onSeek, offset, pWav->pUserData) == DRWAV_FALSE) { + return DRWAV_FALSE; + } + cursor += offset; + + break; + } else { + /* + Getting here means the COMM chunk was not found. In sequential mode, if we haven't yet found the COMM chunk + we'll need to abort because we can't be doing a backwards seek back to the SSND chunk in order to read the + data. For this reason, this configuration of AIFF files are not supported with sequential mode. + */ + return DRWAV_FALSE; + } + } else { + chunkSize += header.paddingSize; /* <-- Make sure we seek past the padding. */ + chunkSize -= sizeof(offsetAndBlockSizeData); /* <-- This was read earlier. */ + if (drwav__seek_forward(pWav->onSeek, chunkSize, pWav->pUserData) == DRWAV_FALSE) { break; } @@ -3557,7 +3621,6 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on } - /* Getting here means it's not a chunk that we care about internally, but might need to be handled as metadata by the caller. */ if (isProcessingMetadata) { drwav__metadata_process_chunk(&metadataParser, &header, drwav_metadata_type_all_including_unknown); @@ -3647,8 +3710,26 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on pWav->metadataCount = metadataParser.metadataCount; } - - /* At this point we should be sitting on the first byte of the raw audio data. */ + /* + It's possible for the size reported in the data chunk to be greater than that of the file. We + need to do a validation check here to make sure we don't exceed the file size. To skip this + check, set the onTell callback to NULL. + */ + if (pWav->onTell != NULL && pWav->onSeek != NULL) { + if (pWav->onSeek(pWav->pUserData, 0, DRWAV_SEEK_END) == DRWAV_TRUE) { + drwav_int64 fileSize; + if (pWav->onTell(pWav->pUserData, &fileSize)) { + if (dataChunkSize + pWav->dataChunkDataPos > (drwav_uint64)fileSize) { + dataChunkSize = (drwav_uint64)fileSize - pWav->dataChunkDataPos; + } + } + } else { + /* + Failed to seek to the end of the file. It might not be supported by the backend so in + this case we cannot perform the validation check. + */ + } + } /* I've seen a WAV file in the wild where a RIFF-ecapsulated file has the size of it's "RIFF" and @@ -3670,6 +3751,7 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on } } + /* At this point we want to be sitting on the first byte of the raw audio data. */ if (drwav__seek_from_start(pWav->onSeek, pWav->dataChunkDataPos, pWav->pUserData) == DRWAV_FALSE) { drwav_free(pWav->pMetadata, &pWav->allocationCallbacks); return DRWAV_FALSE; @@ -3680,8 +3762,26 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on pWav->sampleRate = fmt.sampleRate; pWav->channels = fmt.channels; pWav->bitsPerSample = fmt.bitsPerSample; - pWav->bytesRemaining = dataChunkSize; pWav->translatedFormatTag = translatedFormatTag; + + /* + I've had a report where files would start glitching after seeking. The reason for this is the data + chunk is not a clean multiple of the PCM frame size in bytes. Where this becomes a problem is when + seeking, because the number of bytes remaining in the data chunk is used to calculate the current + byte position. If this byte position is not aligned to the number of bytes in a PCM frame, it will + result in the seek not being cleanly positioned at the start of the PCM frame thereby resulting in + all decoded frames after that being corrupted. + + To address this, we need to round the data chunk size down to the nearest multiple of the frame size. + */ + if (!drwav__is_compressed_format_tag(translatedFormatTag)) { + drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame > 0) { + dataChunkSize -= (dataChunkSize % bytesPerFrame); + } + } + + pWav->bytesRemaining = dataChunkSize; pWav->dataChunkDataSize = dataChunkSize; if (sampleCountFromFactChunk != 0) { @@ -3764,23 +3864,23 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on return DRWAV_TRUE; } -DRWAV_API drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_API drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_tell_proc onTell, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) { - return drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0, pAllocationCallbacks); + return drwav_init_ex(pWav, onRead, onSeek, onTell, NULL, pUserData, NULL, 0, pAllocationCallbacks); } -DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_tell_proc onTell, drwav_chunk_proc onChunk, void* pReadSeekTellUserData, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) { - if (!drwav_preinit(pWav, onRead, onSeek, pReadSeekUserData, pAllocationCallbacks)) { + if (!drwav_preinit(pWav, onRead, onSeek, onTell, pReadSeekTellUserData, pAllocationCallbacks)) { return DRWAV_FALSE; } return drwav_init__internal(pWav, onChunk, pChunkUserData, flags); } -DRWAV_API drwav_bool32 drwav_init_with_metadata(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_API drwav_bool32 drwav_init_with_metadata(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_tell_proc onTell, void* pUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) { - if (!drwav_preinit(pWav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + if (!drwav_preinit(pWav, onRead, onSeek, onTell, pUserData, pAllocationCallbacks)) { return DRWAV_FALSE; } @@ -3995,8 +4095,8 @@ DRWAV_PRIVATE size_t drwav__write_or_count_metadata(drwav* pWav, drwav_metadata* for (iLoop = 0; iLoop < pMetadata->data.smpl.sampleLoopCount; ++iLoop) { bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].cuePointId); bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].type); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].firstSampleByteOffset); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].lastSampleByteOffset); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].firstSampleOffset); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].lastSampleOffset); bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].sampleFraction); bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].playCount); } @@ -4036,7 +4136,7 @@ DRWAV_PRIVATE size_t drwav__write_or_count_metadata(drwav* pWav, drwav_metadata* bytesWritten += drwav__write_or_count(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId, 4); bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].chunkStart); bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].blockStart); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].sampleByteOffset); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].sampleOffset); } } break; @@ -4142,15 +4242,20 @@ DRWAV_PRIVATE size_t drwav__write_or_count_metadata(drwav* pWav, drwav_metadata* const char* pID = NULL; switch (pMetadata->type) { - case drwav_metadata_type_list_info_software: pID = "ISFT"; break; - case drwav_metadata_type_list_info_copyright: pID = "ICOP"; break; - case drwav_metadata_type_list_info_title: pID = "INAM"; break; - case drwav_metadata_type_list_info_artist: pID = "IART"; break; - case drwav_metadata_type_list_info_comment: pID = "ICMT"; break; - case drwav_metadata_type_list_info_date: pID = "ICRD"; break; - case drwav_metadata_type_list_info_genre: pID = "IGNR"; break; - case drwav_metadata_type_list_info_album: pID = "IPRD"; break; - case drwav_metadata_type_list_info_tracknumber: pID = "ITRK"; break; + case drwav_metadata_type_list_info_software: pID = "ISFT"; break; + case drwav_metadata_type_list_info_copyright: pID = "ICOP"; break; + case drwav_metadata_type_list_info_title: pID = "INAM"; break; + case drwav_metadata_type_list_info_artist: pID = "IART"; break; + case drwav_metadata_type_list_info_comment: pID = "ICMT"; break; + case drwav_metadata_type_list_info_date: pID = "ICRD"; break; + case drwav_metadata_type_list_info_genre: pID = "IGNR"; break; + case drwav_metadata_type_list_info_album: pID = "IPRD"; break; + case drwav_metadata_type_list_info_tracknumber: pID = "ITRK"; break; + case drwav_metadata_type_list_info_location: pID = "IARL"; break; + case drwav_metadata_type_list_info_organization: pID = "ICMS"; break; + case drwav_metadata_type_list_info_keywords: pID = "IKEY"; break; + case drwav_metadata_type_list_info_medium: pID = "IMED"; break; + case drwav_metadata_type_list_info_description: pID = "ISBJ"; break; default: break; } @@ -4195,7 +4300,7 @@ DRWAV_PRIVATE size_t drwav__write_or_count_metadata(drwav* pWav, drwav_metadata* if (pMetadata->data.labelOrNote.stringLength > 0) { chunkSize += pMetadata->data.labelOrNote.stringLength + 1; - } + } } break; case drwav_metadata_type_list_labelled_cue_region: @@ -4434,7 +4539,7 @@ DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_d /* "RIFF" chunk. */ if (pFormat->container == drwav_container_riff) { - drwav_uint32 chunkSizeRIFF = 28 + (drwav_uint32)initialDataChunkSize; /* +28 = "WAVE" + [sizeof "fmt " chunk] */ + drwav_uint32 chunkSizeRIFF = 36 + (drwav_uint32)initialDataChunkSize; /* +36 = "WAVE" + [sizeof "fmt " chunk] + [data chunk header] */ runningPos += drwav__write(pWav, "RIFF", 4); runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeRIFF); runningPos += drwav__write(pWav, "WAVE", 4); @@ -4704,7 +4809,7 @@ DRWAV_PRIVATE drwav_result drwav_result_from_errno(int e) #ifdef ENOSYS case ENOSYS: return DRWAV_NOT_IMPLEMENTED; #endif - #ifdef ENOTEMPTY + #if defined(ENOTEMPTY) && ENOTEMPTY != EEXIST /* In AIX, ENOTEMPTY and EEXIST use the same value. */ case ENOTEMPTY: return DRWAV_DIRECTORY_NOT_EMPTY; #endif #ifdef ELOOP @@ -5161,7 +5266,38 @@ DRWAV_PRIVATE size_t drwav__on_write_stdio(void* pUserData, const void* pData, s DRWAV_PRIVATE drwav_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek_origin origin) { - return fseek((FILE*)pUserData, offset, (origin == drwav_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; + int whence = SEEK_SET; + if (origin == DRWAV_SEEK_CUR) { + whence = SEEK_CUR; + } else if (origin == DRWAV_SEEK_END) { + whence = SEEK_END; + } + + return fseek((FILE*)pUserData, offset, whence) == 0; +} + +DRWAV_PRIVATE drwav_bool32 drwav__on_tell_stdio(void* pUserData, drwav_int64* pCursor) +{ + FILE* pFileStdio = (FILE*)pUserData; + drwav_int64 result; + + /* These were all validated at a higher level. */ + DRWAV_ASSERT(pFileStdio != NULL); + DRWAV_ASSERT(pCursor != NULL); + +#if defined(_WIN32) + #if defined(_MSC_VER) && _MSC_VER > 1200 + result = _ftelli64(pFileStdio); + #else + result = ftell(pFileStdio); + #endif +#else + result = ftell(pFileStdio); +#endif + + *pCursor = result; + + return DRWAV_TRUE; } DRWAV_API drwav_bool32 drwav_init_file(drwav* pWav, const char* filename, const drwav_allocation_callbacks* pAllocationCallbacks) @@ -5174,12 +5310,12 @@ DRWAV_PRIVATE drwav_bool32 drwav_init_file__internal_FILE(drwav* pWav, FILE* pFi { drwav_bool32 result; - result = drwav_preinit(pWav, drwav__on_read_stdio, drwav__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + result = drwav_preinit(pWav, drwav__on_read_stdio, drwav__on_seek_stdio, drwav__on_tell_stdio, (void*)pFile, pAllocationCallbacks); if (result != DRWAV_TRUE) { fclose(pFile); return result; } - + result = drwav_init__internal(pWav, onChunk, pChunkUserData, flags); if (result != DRWAV_TRUE) { fclose(pFile); @@ -5352,29 +5488,34 @@ DRWAV_PRIVATE size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, si DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_seek_origin origin) { drwav* pWav = (drwav*)pUserData; + drwav_int64 newCursor; + DRWAV_ASSERT(pWav != NULL); - if (origin == drwav_seek_origin_current) { - if (offset > 0) { - if (pWav->memoryStream.currentReadPos + offset > pWav->memoryStream.dataSize) { - return DRWAV_FALSE; /* Trying to seek too far forward. */ - } - } else { - if (pWav->memoryStream.currentReadPos < (size_t)-offset) { - return DRWAV_FALSE; /* Trying to seek too far backwards. */ - } - } + newCursor = pWav->memoryStream.currentReadPos; - /* This will never underflow thanks to the clamps above. */ - pWav->memoryStream.currentReadPos += offset; + if (origin == DRWAV_SEEK_SET) { + newCursor = 0; + } else if (origin == DRWAV_SEEK_CUR) { + newCursor = (drwav_int64)pWav->memoryStream.currentReadPos; + } else if (origin == DRWAV_SEEK_END) { + newCursor = (drwav_int64)pWav->memoryStream.dataSize; } else { - if ((drwav_uint32)offset <= pWav->memoryStream.dataSize) { - pWav->memoryStream.currentReadPos = offset; - } else { - return DRWAV_FALSE; /* Trying to seek too far forward. */ - } + DRWAV_ASSERT(!"Invalid seek origin"); + return DRWAV_FALSE; } + newCursor += offset; + + if (newCursor < 0) { + return DRWAV_FALSE; /* Trying to seek prior to the start of the buffer. */ + } + if ((size_t)newCursor > pWav->memoryStream.dataSize) { + return DRWAV_FALSE; /* Trying to seek beyond the end of the buffer. */ + } + + pWav->memoryStream.currentReadPos = (size_t)newCursor; + return DRWAV_TRUE; } @@ -5421,29 +5562,45 @@ DRWAV_PRIVATE size_t drwav__on_write_memory(void* pUserData, const void* pDataIn DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drwav_seek_origin origin) { drwav* pWav = (drwav*)pUserData; + drwav_int64 newCursor; + DRWAV_ASSERT(pWav != NULL); - if (origin == drwav_seek_origin_current) { - if (offset > 0) { - if (pWav->memoryStreamWrite.currentWritePos + offset > pWav->memoryStreamWrite.dataSize) { - offset = (int)(pWav->memoryStreamWrite.dataSize - pWav->memoryStreamWrite.currentWritePos); /* Trying to seek too far forward. */ - } - } else { - if (pWav->memoryStreamWrite.currentWritePos < (size_t)-offset) { - offset = -(int)pWav->memoryStreamWrite.currentWritePos; /* Trying to seek too far backwards. */ - } - } + newCursor = pWav->memoryStreamWrite.currentWritePos; - /* This will never underflow thanks to the clamps above. */ - pWav->memoryStreamWrite.currentWritePos += offset; + if (origin == DRWAV_SEEK_SET) { + newCursor = 0; + } else if (origin == DRWAV_SEEK_CUR) { + newCursor = (drwav_int64)pWav->memoryStreamWrite.currentWritePos; + } else if (origin == DRWAV_SEEK_END) { + newCursor = (drwav_int64)pWav->memoryStreamWrite.dataSize; } else { - if ((drwav_uint32)offset <= pWav->memoryStreamWrite.dataSize) { - pWav->memoryStreamWrite.currentWritePos = offset; - } else { - pWav->memoryStreamWrite.currentWritePos = pWav->memoryStreamWrite.dataSize; /* Trying to seek too far forward. */ - } + DRWAV_ASSERT(!"Invalid seek origin"); + return DRWAV_INVALID_ARGS; } + newCursor += offset; + + if (newCursor < 0) { + return DRWAV_FALSE; /* Trying to seek prior to the start of the buffer. */ + } + if ((size_t)newCursor > pWav->memoryStreamWrite.dataSize) { + return DRWAV_FALSE; /* Trying to seek beyond the end of the buffer. */ + } + + pWav->memoryStreamWrite.currentWritePos = (size_t)newCursor; + + return DRWAV_TRUE; +} + +DRWAV_PRIVATE drwav_bool32 drwav__on_tell_memory(void* pUserData, drwav_int64* pCursor) +{ + drwav* pWav = (drwav*)pUserData; + + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pCursor != NULL); + + *pCursor = (drwav_int64)pWav->memoryStream.currentReadPos; return DRWAV_TRUE; } @@ -5458,7 +5615,7 @@ DRWAV_API drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_ return DRWAV_FALSE; } - if (!drwav_preinit(pWav, drwav__on_read_memory, drwav__on_seek_memory, pWav, pAllocationCallbacks)) { + if (!drwav_preinit(pWav, drwav__on_read_memory, drwav__on_seek_memory, drwav__on_tell_memory, pWav, pAllocationCallbacks)) { return DRWAV_FALSE; } @@ -5475,7 +5632,7 @@ DRWAV_API drwav_bool32 drwav_init_memory_with_metadata(drwav* pWav, const void* return DRWAV_FALSE; } - if (!drwav_preinit(pWav, drwav__on_read_memory, drwav__on_seek_memory, pWav, pAllocationCallbacks)) { + if (!drwav_preinit(pWav, drwav__on_read_memory, drwav__on_seek_memory, drwav__on_tell_memory, pWav, pAllocationCallbacks)) { return DRWAV_FALSE; } @@ -5565,25 +5722,25 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) if (pWav->onSeek && !pWav->isSequentialWrite) { if (pWav->container == drwav_container_riff) { /* The "RIFF" chunk size. */ - if (pWav->onSeek(pWav->pUserData, 4, drwav_seek_origin_start)) { + if (pWav->onSeek(pWav->pUserData, 4, DRWAV_SEEK_SET)) { drwav_uint32 riffChunkSize = drwav__riff_chunk_size_riff(pWav->dataChunkDataSize, pWav->pMetadata, pWav->metadataCount); drwav__write_u32ne_to_le(pWav, riffChunkSize); } /* The "data" chunk size. */ - if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 4, drwav_seek_origin_start)) { + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 4, DRWAV_SEEK_SET)) { drwav_uint32 dataChunkSize = drwav__data_chunk_size_riff(pWav->dataChunkDataSize); drwav__write_u32ne_to_le(pWav, dataChunkSize); } } else if (pWav->container == drwav_container_w64) { /* The "RIFF" chunk size. */ - if (pWav->onSeek(pWav->pUserData, 16, drwav_seek_origin_start)) { + if (pWav->onSeek(pWav->pUserData, 16, DRWAV_SEEK_SET)) { drwav_uint64 riffChunkSize = drwav__riff_chunk_size_w64(pWav->dataChunkDataSize); drwav__write_u64ne_to_le(pWav, riffChunkSize); } /* The "data" chunk size. */ - if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 8, drwav_seek_origin_start)) { + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 8, DRWAV_SEEK_SET)) { drwav_uint64 dataChunkSize = drwav__data_chunk_size_w64(pWav->dataChunkDataSize); drwav__write_u64ne_to_le(pWav, dataChunkSize); } @@ -5592,13 +5749,13 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) int ds64BodyPos = 12 + 8; /* The "RIFF" chunk size. */ - if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, drwav_seek_origin_start)) { + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, DRWAV_SEEK_SET)) { drwav_uint64 riffChunkSize = drwav__riff_chunk_size_rf64(pWav->dataChunkDataSize, pWav->pMetadata, pWav->metadataCount); drwav__write_u64ne_to_le(pWav, riffChunkSize); } /* The "data" chunk size. */ - if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, drwav_seek_origin_start)) { + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, DRWAV_SEEK_SET)) { drwav_uint64 dataChunkSize = drwav__data_chunk_size_rf64(pWav->dataChunkDataSize); drwav__write_u64ne_to_le(pWav, dataChunkSize); } @@ -5663,7 +5820,7 @@ DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOu bytesToSeek = 0x7FFFFFFF; } - if (pWav->onSeek(pWav->pUserData, (int)bytesToSeek, drwav_seek_origin_current) == DRWAV_FALSE) { + if (pWav->onSeek(pWav->pUserData, (int)bytesToSeek, DRWAV_SEEK_CUR) == DRWAV_FALSE) { break; } @@ -5810,7 +5967,7 @@ DRWAV_PRIVATE drwav_bool32 drwav_seek_to_first_pcm_frame(drwav* pWav) return DRWAV_FALSE; /* No seeking in write mode. */ } - if (!pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos, drwav_seek_origin_start)) { + if (!pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos, DRWAV_SEEK_SET)) { return DRWAV_FALSE; } @@ -5928,7 +6085,7 @@ DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetF while (offset > 0) { int offset32 = ((offset > INT_MAX) ? INT_MAX : (int)offset); - if (!pWav->onSeek(pWav->pUserData, offset32, drwav_seek_origin_current)) { + if (!pWav->onSeek(pWav->pUserData, offset32, DRWAV_SEEK_CUR)) { return DRWAV_FALSE; } @@ -6101,6 +6258,13 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav { drwav_uint64 totalFramesRead = 0; + static drwav_int32 adaptationTable[] = { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 + }; + static drwav_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; + static drwav_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; + DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(framesToRead > 0); @@ -6126,6 +6290,11 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][0]; pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.cachedFrameCount = 2; + + /* The predictor is used as an index into coeff1Table so we'll need to validate to ensure it never overflows. */ + if (pWav->msadpcm.predictor[0] >= drwav_countof(coeff1Table)) { + return totalFramesRead; /* Invalid file. */ + } } else { /* Stereo. */ drwav_uint8 header[14]; @@ -6148,6 +6317,11 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[1][1]; pWav->msadpcm.cachedFrameCount = 2; + + /* The predictor is used as an index into coeff1Table so we'll need to validate to ensure it never overflows. */ + if (pWav->msadpcm.predictor[0] >= drwav_countof(coeff1Table) || pWav->msadpcm.predictor[1] >= drwav_countof(coeff2Table)) { + return totalFramesRead; /* Invalid file. */ + } } } @@ -6181,13 +6355,6 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav if (pWav->msadpcm.bytesRemainingInBlock == 0) { continue; } else { - static drwav_int32 adaptationTable[] = { - 230, 230, 230, 230, 307, 409, 512, 614, - 768, 614, 512, 409, 307, 230, 230, 230 - }; - static drwav_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; - static drwav_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; - drwav_uint8 nibbles; drwav_int32 nibble0; drwav_int32 nibble1; @@ -6320,7 +6487,7 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uin pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); if (header[2] >= drwav_countof(stepTable)) { - pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, drwav_seek_origin_current); + pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, DRWAV_SEEK_CUR); pWav->ima.bytesRemainingInBlock = 0; return totalFramesRead; /* Invalid data. */ } @@ -6338,7 +6505,7 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uin pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); if (header[2] >= drwav_countof(stepTable) || header[6] >= drwav_countof(stepTable)) { - pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, drwav_seek_origin_current); + pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, DRWAV_SEEK_CUR); pWav->ima.bytesRemainingInBlock = 0; return totalFramesRead; /* Invalid data. */ } @@ -8006,7 +8173,7 @@ DRWAV_PRIVATE drwav_int32* drwav__read_pcm_frames_and_close_s32(drwav* pWav, uns -DRWAV_API drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_API drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav wav; @@ -8020,14 +8187,14 @@ DRWAV_API drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead *totalFrameCountOut = 0; } - if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + if (!drwav_init(&wav, onRead, onSeek, onTell, pUserData, pAllocationCallbacks)) { return NULL; } return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); } -DRWAV_API float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_API float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav wav; @@ -8041,14 +8208,14 @@ DRWAV_API float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwa *totalFrameCountOut = 0; } - if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + if (!drwav_init(&wav, onRead, onSeek, onTell, pUserData, pAllocationCallbacks)) { return NULL; } return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); } -DRWAV_API drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_API drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav wav; @@ -8062,7 +8229,7 @@ DRWAV_API drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead *totalFrameCountOut = 0; } - if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + if (!drwav_init(&wav, onRead, onSeek, onTell, pUserData, pAllocationCallbacks)) { return NULL; } @@ -8350,6 +8517,27 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) /* REVISION HISTORY ================ +v0.14.0 - TBD + - API CHANGE: Seek origin enums have been renamed to the following: + - drwav_seek_origin_start -> DRWAV_SEEK_SET + - drwav_seek_origin_current -> DRWAV_SEEK_CUR + - DRWAV_SEEK_END (new) + - API CHANGE: A new seek origin has been added to allow seeking from the end of the file. If you implement your own `onSeek` callback, you must now handle `DRWAV_SEEK_END`. If you only use `*_init_file()` or `*_init_memory()`, you need not change anything. + - API CHANGE: An `onTell` callback has been added to the following functions: + - drwav_init() + - drwav_init_ex() + - drwav_init_with_metadata() + - drwav_open_and_read_pcm_frames_s16() + - drwav_open_and_read_pcm_frames_f32() + - drwav_open_and_read_pcm_frames_s32() + - API CHANGE: The `firstSampleByteOffset`, `lastSampleByteOffset` and `sampleByteOffset` members of `drwav_cue_point` have been renamed to `firstSampleOffset`, `lastSampleOffset` and `sampleOffset`, respectively. + - Fix a static analysis warning. + - Fix compilation for AIX OS. + +v0.13.17 - 2024-12-17 + - Fix a possible crash when reading from MS-ADPCM encoded files. + - Improve detection of ARM64EC + v0.13.16 - 2024-02-27 - Fix a Wdouble-promotion warning. From e91a3697ffce4c51c5805278a1703bf90286f3bf Mon Sep 17 00:00:00 2001 From: Jordan Zedeck <98184287+zedeckj@users.noreply.github.com> Date: Sun, 6 Jul 2025 01:59:03 -0400 Subject: [PATCH 066/242] Fixed typo --- examples/text/text_codepoints_loading.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/text/text_codepoints_loading.c b/examples/text/text_codepoints_loading.c index aa9f7bb76..a176148ea 100644 --- a/examples/text/text_codepoints_loading.c +++ b/examples/text/text_codepoints_loading.c @@ -108,7 +108,7 @@ int main(void) } else { - // Draw provided text with laoded font, containing all required codepoint glyphs + // Draw provided text with loaded font, containing all required codepoint glyphs DrawTextEx(font, text, (Vector2) { 160, 110 }, 48, 5, BLACK); } From 79c29cbe247a3977ff5c5327b9b8d4f4d79ea51c Mon Sep 17 00:00:00 2001 From: jonathandw743 Date: Sun, 6 Jul 2025 11:36:44 +0100 Subject: [PATCH 067/242] fixed compile error for PLATFORM sdl --- src/platforms/rcore_desktop_sdl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 60753caf4..59f286307 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -1612,7 +1612,7 @@ void PollInputEvents(void) unsigned int codepoint = (unsigned int)SDL_StepUTF8(&event.text.text, textLen); #else int codepointSize = 0; - codepoint = GetCodepointNextSDL(event.text.text, &codepointSize); + int codepoint = GetCodepointNextSDL(event.text.text, &codepointSize); #endif CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = codepoint; CORE.Input.Keyboard.charPressedQueueCount++; From de62be0ec5695e849a0f0c56aa72b7c64b2442ab Mon Sep 17 00:00:00 2001 From: katonar Date: Mon, 7 Jul 2025 17:28:23 +0200 Subject: [PATCH 068/242] - created complier flag SUPPORT_DRM_CACHE, to enable triple buffered DRM caching --- src/config.h | 5 +++++ src/platforms/rcore_drm.c | 42 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/config.h b/src/config.h index dc6cc5893..0fd685e7a 100644 --- a/src/config.h +++ b/src/config.h @@ -299,4 +299,9 @@ //------------------------------------------------------------------------------------ #define MAX_TRACELOG_MSG_LENGTH 256 // Max length of one trace-log message +//DRM configuration +#if defined(PLATFORM_DRM) +//#define SUPPORT_DRM_CACHE 1 //enable triple buffered DRM caching +#endif //PLATFORM_DRM + #endif // CONFIG_H diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 9aebe6cf0..33b5e2a05 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -71,6 +71,7 @@ #include "EGL/egl.h" // Native platform windowing system interface #include "EGL/eglext.h" // EGL extensions +#if defined(SUPPORT_DRM_CACHE) #include // for drmHandleEvent poll #include //for EBUSY, EAGAIN @@ -86,6 +87,8 @@ static volatile int fbCacheCount = 0; static volatile bool pendingFlip = false; static bool crtcSet = false; +#endif //SUPPORT_DRM_CACHE + #ifndef EGL_OPENGL_ES3_BIT #define EGL_OPENGL_ES3_BIT 0x40 #endif @@ -232,7 +235,6 @@ static const short linuxToRaylibMap[KEYMAP_SIZE] = { //---------------------------------------------------------------------------------- // Module Internal Functions Declaration //---------------------------------------------------------------------------------- -int InitSwapScreenBuffer(void); int InitPlatform(void); // Initialize platform (graphics, inputs and more) void ClosePlatform(void); // Close platform @@ -567,6 +569,7 @@ void DisableCursor(void) CORE.Input.Mouse.cursorHidden = true; } +#if defined(SUPPORT_DRM_CACHE) static void drm_fb_destroy_callback(struct gbm_bo *bo, void *data) { uint32_t fb_id = (uintptr_t)data; // Remove from cache @@ -769,7 +772,37 @@ void SwapScreenBuffer() { loopCnt = 0; } } +#else //SUPPORT_DRM_CACHE is not defined +// Swap back buffer with front buffer (screen drawing) +void SwapScreenBuffer(void) +{ + eglSwapBuffers(platform.device, platform.surface); + if (!platform.gbmSurface || (-1 == platform.fd) || !platform.connector || !platform.crtc) TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap"); + + struct gbm_bo *bo = gbm_surface_lock_front_buffer(platform.gbmSurface); + if (!bo) TRACELOG(LOG_ERROR, "DISPLAY: Failed GBM to lock front buffer"); + + uint32_t fb = 0; + int result = drmModeAddFB(platform.fd, platform.connector->modes[platform.modeIndex].hdisplay, platform.connector->modes[platform.modeIndex].vdisplay, 24, 32, gbm_bo_get_stride(bo), gbm_bo_get_handle(bo).u32, &fb); + if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d", result); + + result = drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, fb, 0, 0, &platform.connector->connector_id, 1, &platform.connector->modes[platform.modeIndex]); + if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d", result); + + if (platform.prevFB) + { + result = drmModeRmFB(platform.fd, platform.prevFB); + if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeRmFB() failed with result: %d", result); + } + + platform.prevFB = fb; + + if (platform.prevBO) gbm_surface_release_buffer(platform.gbmSurface, platform.prevBO); + + platform.prevBO = bo; +} +#endif //SUPPORT_DRM_CACHE //---------------------------------------------------------------------------------- // Module Functions Definition: Misc //---------------------------------------------------------------------------------- @@ -1099,8 +1132,7 @@ int InitPlatform(void) EGL_BLUE_SIZE, 8, // BLUE color bit depth (alternative: 5) EGL_ALPHA_SIZE, 8, // ALPHA bit depth (required for transparent framebuffer) //EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI) - //ToDo: verify this. In 5.5 it is 16, in master it was 24 - EGL_DEPTH_SIZE, 16, // Depth buffer size (Required to use Depth testing!) + EGL_DEPTH_SIZE, 24, // Depth buffer size (Required to use Depth testing!) //EGL_STENCIL_SIZE, 8, // Stencil buffer size EGL_SAMPLE_BUFFERS, sampleBuffer, // Activate MSAA EGL_SAMPLES, samples, // 4x Antialiasing if activated (Free on MALI GPUs) @@ -1273,13 +1305,17 @@ int InitPlatform(void) CORE.Storage.basePath = GetWorkingDirectory(); //---------------------------------------------------------------------------- +#if defined(SUPPORT_DRM_CACHE) if(InitSwapScreenBuffer() == 0) { TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized successfully"); return 0; } else { +#endif//SUPPORT_DRM_CACHE TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized failed"); +#if defined(SUPPORT_DRM_CACHE) return -1; } +#endif //SUPPORT_DRM_CACHE } From 5b182139ae3e3223398af825fec8082e5a98f748 Mon Sep 17 00:00:00 2001 From: katonar Date: Mon, 7 Jul 2025 17:55:32 +0200 Subject: [PATCH 069/242] - implementing Raylib coding convention --- src/platforms/rcore_drm.c | 45 ++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 33b5e2a05..09684291c 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -79,10 +79,10 @@ typedef struct { struct gbm_bo *bo; - uint32_t fb_id; // DRM framebuffer ID + uint32_t fbId; // DRM framebuffer ID } FramebufferCache; -static FramebufferCache fbCache[MAX_CACHED_BOS]; +static FramebufferCache fbCache[MAX_CACHED_BOS] = {0}; static volatile int fbCacheCount = 0; static volatile bool pendingFlip = false; static bool crtcSet = false; @@ -570,13 +570,14 @@ void DisableCursor(void) } #if defined(SUPPORT_DRM_CACHE) -static void drm_fb_destroy_callback(struct gbm_bo *bo, void *data) { - uint32_t fb_id = (uintptr_t)data; +//callback to destroy cached framebuffer, set by gbm_bo_set_user_data() +static void DestroyFrameBufferCallback(struct gbm_bo *bo, void *data) { + uint32_t fbId = (uintptr_t)data; // Remove from cache for (int i = 0; i < fbCacheCount; i++) { if (fbCache[i].bo == bo) { - TRACELOG(LOG_INFO, "DRM: fb removed %u", (uintptr_t)fb_id); - drmModeRmFB(platform.fd, fbCache[i].fb_id); // Release DRM FB + TRACELOG(LOG_INFO, "DRM: fb removed %u", (uintptr_t)fbId); + drmModeRmFB(platform.fd, fbCache[i].fbId); // Release DRM FB // Shift remaining entries for (int j = i; j < fbCacheCount - 1; j++) { fbCache[j] = fbCache[j + 1]; @@ -588,11 +589,11 @@ static void drm_fb_destroy_callback(struct gbm_bo *bo, void *data) { } // Create or retrieve cached DRM FB for BO -static uint32_t get_or_create_fb_for_bo(struct gbm_bo *bo) { +static uint32_t GetOrCreateFbForBo(struct gbm_bo *bo) { // Try to find existing cache entry for (int i = 0; i < fbCacheCount; i++) { if (fbCache[i].bo == bo) { - return fbCache[i].fb_id; + return fbCache[i].fbId; } } @@ -607,21 +608,21 @@ static uint32_t get_or_create_fb_for_bo(struct gbm_bo *bo) { uint32_t width = gbm_bo_get_width(bo); uint32_t height = gbm_bo_get_height(bo); - uint32_t fb_id; - if (drmModeAddFB(platform.fd, width, height, 24, 32, stride, handle, &fb_id)) { + uint32_t fbId; + if (drmModeAddFB(platform.fd, width, height, 24, 32, stride, handle, &fbId)) { //rmModeAddFB failed return 0; } // Store in cache - fbCache[fbCacheCount] = (FramebufferCache){ .bo = bo, .fb_id = fb_id }; + fbCache[fbCacheCount] = (FramebufferCache){ .bo = bo, .fbId = fbId }; fbCacheCount++; // Set destroy callback to auto-cleanup - gbm_bo_set_user_data(bo, (void*)(uintptr_t)fb_id, drm_fb_destroy_callback); + gbm_bo_set_user_data(bo, (void*)(uintptr_t)fbId, DestroyFrameBufferCallback); - TRACELOG(LOG_INFO, "DRM: added new bo %u" , (uintptr_t)fb_id); - return fb_id; + TRACELOG(LOG_INFO, "DRM: added new bo %u" , (uintptr_t)fbId); + return fbId; } // Renders a blank frame to allocate initial buffers @@ -652,14 +653,14 @@ int InitSwapScreenBuffer() { } // Create FB for first buffer - uint32_t fb_id = get_or_create_fb_for_bo(bo); - if (!fb_id) { + uint32_t fbId = GetOrCreateFbForBo(bo); + if (!fbId) { gbm_surface_release_buffer(platform.gbmSurface, bo); return -1; } // Initial CRTC setup - if (drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, fb_id, + if (drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, fbId, 0, 0, &platform.connector->connector_id, 1, &platform.connector->modes[platform.modeIndex])) { TRACELOG(LOG_ERROR, "Initial CRTC setup failed: %s", strerror(errno)); @@ -675,7 +676,7 @@ int InitSwapScreenBuffer() { // Static page flip handler // this will be called once the drmModePageFlip() finished from the drmHandleEvent(platform.fd, &evctx); context -static void page_flip_handler(int fd, unsigned int frame, +static void PageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { (void)fd; (void)frame; (void)sec; (void)usec; // Unused @@ -701,7 +702,7 @@ void SwapScreenBuffer() { // Process pending events non-blocking drmEventContext evctx = { .version = DRM_EVENT_CONTEXT_VERSION, - .page_flip_handler = page_flip_handler + .page_flip_handler = PageFlipHandler }; struct pollfd pfd = { .fd = platform.fd, .events = POLLIN }; @@ -726,8 +727,8 @@ void SwapScreenBuffer() { } // Get FB ID (creates new one if needed) - uint32_t fb_id = get_or_create_fb_for_bo(next_bo); - if (!fb_id) { + uint32_t fbId = GetOrCreateFbForBo(next_bo); + if (!fbId) { gbm_surface_release_buffer(platform.gbmSurface, next_bo); errCnt[2]++; return; @@ -744,7 +745,7 @@ void SwapScreenBuffer() { * is rendered.. * returns immediately. */ - if (drmModePageFlip(platform.fd, platform.crtc->crtc_id, fb_id, + if (drmModePageFlip(platform.fd, platform.crtc->crtc_id, fbId, DRM_MODE_PAGE_FLIP_EVENT, platform.prevBO)) { if (errno == EBUSY) { //Display busy - skip flip From d4f09984acb427f5ab67ee49235546b01ad09199 Mon Sep 17 00:00:00 2001 From: Amy Wilder Date: Mon, 7 Jul 2025 21:51:27 -0400 Subject: [PATCH 070/242] Add safety notes to 'Update_' functions --- src/raudio.c | 2 ++ src/raylib.h | 6 +++--- src/rtextures.c | 7 +++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/raudio.c b/src/raudio.c index 2b6628a24..8929c9ef9 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -1033,6 +1033,8 @@ void UnloadSoundAlias(Sound alias) } // Update sound buffer with new data +// NOTE 1: data format must match sound.stream.sampleSize +// NOTE 2: frameCount must not exceed sound.frameCount void UpdateSound(Sound sound, const void *data, int frameCount) { if (sound.stream.buffer != NULL) diff --git a/src/raylib.h b/src/raylib.h index 7919db775..38b703e98 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1422,8 +1422,8 @@ RLAPI bool IsTextureValid(Texture2D texture); RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) RLAPI bool IsRenderTextureValid(RenderTexture2D target); // Check if a render texture is valid (loaded in GPU) RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) -RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data -RLAPI void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels); // Update GPU texture rectangle with new data +RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data (pixels should be able to fill texture) +RLAPI void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels); // Update GPU texture rectangle with new data (pixels and rec should fit in texture) // Texture configuration functions RLAPI void GenTextureMipmaps(Texture2D *texture); // Generate GPU mipmaps for a texture @@ -1644,7 +1644,7 @@ RLAPI Sound LoadSound(const char *fileName); // Load so RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data RLAPI Sound LoadSoundAlias(Sound source); // Create a new sound that shares the same sample data as the source sound, does not own the sound data RLAPI bool IsSoundValid(Sound sound); // Checks if a sound is valid (data loaded and buffers initialized) -RLAPI void UpdateSound(Sound sound, const void *data, int sampleCount); // Update sound buffer with new data +RLAPI void UpdateSound(Sound sound, const void *data, int sampleCount); // Update sound buffer with new data (data and frame count should fit in sound) RLAPI void UnloadWave(Wave wave); // Unload wave data RLAPI void UnloadSound(Sound sound); // Unload sound RLAPI void UnloadSoundAlias(Sound alias); // Unload a sound alias (does not deallocate sample data) diff --git a/src/rtextures.c b/src/rtextures.c index ee116b0e5..337d01b1d 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -4337,14 +4337,17 @@ void UnloadRenderTexture(RenderTexture2D target) } // Update GPU texture with new data -// NOTE: pixels data must match texture.format +// NOTE 1: pixels data must match texture.format +// NOTE 2: pixels data must contain at least as many pixels as texture void UpdateTexture(Texture2D texture, const void *pixels) { rlUpdateTexture(texture.id, 0, 0, texture.width, texture.height, texture.format, pixels); } // Update GPU texture rectangle with new data -// NOTE: pixels data must match texture.format +// NOTE 1: pixels data must match texture.format +// NOTE 2: pixels data must contain as many pixels as rec contains +// NOTE 3: rec must fit completely within texture's width and height void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels) { rlUpdateTexture(texture.id, (int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, texture.format, pixels); From 9f6d37ecb4774a5f1f00c53fd85dfc15db69467b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 8 Jul 2025 01:51:52 +0000 Subject: [PATCH 071/242] Update raylib_api.* by CI --- parser/output/raylib_api.json | 6 +++--- parser/output/raylib_api.lua | 6 +++--- parser/output/raylib_api.txt | 6 +++--- parser/output/raylib_api.xml | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index f5872ec8a..2fddc75e7 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -8483,7 +8483,7 @@ }, { "name": "UpdateTexture", - "description": "Update GPU texture with new data", + "description": "Update GPU texture with new data (pixels should be able to fill texture)", "returnType": "void", "params": [ { @@ -8498,7 +8498,7 @@ }, { "name": "UpdateTextureRec", - "description": "Update GPU texture rectangle with new data", + "description": "Update GPU texture rectangle with new data (pixels and rec should fit in texture)", "returnType": "void", "params": [ { @@ -11465,7 +11465,7 @@ }, { "name": "UpdateSound", - "description": "Update sound buffer with new data", + "description": "Update sound buffer with new data (data and frame count should fit in sound)", "returnType": "void", "params": [ { diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 81dd7f932..49e2e1f47 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -6242,7 +6242,7 @@ return { }, { name = "UpdateTexture", - description = "Update GPU texture with new data", + description = "Update GPU texture with new data (pixels should be able to fill texture)", returnType = "void", params = { {type = "Texture2D", name = "texture"}, @@ -6251,7 +6251,7 @@ return { }, { name = "UpdateTextureRec", - description = "Update GPU texture rectangle with new data", + description = "Update GPU texture rectangle with new data (pixels and rec should fit in texture)", returnType = "void", params = { {type = "Texture2D", name = "texture"}, @@ -7850,7 +7850,7 @@ return { }, { name = "UpdateSound", - description = "Update sound buffer with new data", + description = "Update sound buffer with new data (data and frame count should fit in sound)", returnType = "void", params = { {type = "Sound", name = "sound"}, diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index c74a79a5f..a92a96de3 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -3255,13 +3255,13 @@ Function 362: UnloadRenderTexture() (1 input parameters) Function 363: UpdateTexture() (2 input parameters) Name: UpdateTexture Return type: void - Description: Update GPU texture with new data + Description: Update GPU texture with new data (pixels should be able to fill texture) Param[1]: texture (type: Texture2D) Param[2]: pixels (type: const void *) Function 364: UpdateTextureRec() (3 input parameters) Name: UpdateTextureRec Return type: void - Description: Update GPU texture rectangle with new data + Description: Update GPU texture rectangle with new data (pixels and rec should fit in texture) Param[1]: texture (type: Texture2D) Param[2]: rec (type: Rectangle) Param[3]: pixels (type: const void *) @@ -4383,7 +4383,7 @@ Function 528: IsSoundValid() (1 input parameters) Function 529: UpdateSound() (3 input parameters) Name: UpdateSound Return type: void - Description: Update sound buffer with new data + Description: Update sound buffer with new data (data and frame count should fit in sound) Param[1]: sound (type: Sound) Param[2]: data (type: const void *) Param[3]: sampleCount (type: int) diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index b7af0c41a..e9c83ad9c 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -2138,11 +2138,11 @@ - + - + @@ -2928,7 +2928,7 @@ - + From 510dc763e98edf494b01d8c0e8d7318b593375e6 Mon Sep 17 00:00:00 2001 From: sir-irk Date: Tue, 8 Jul 2025 05:05:30 -0500 Subject: [PATCH 072/242] adding normal map example --- examples/Makefile | 1 + examples/Makefile.Web | 1 + .../resources/shaders/glsl100/normalmap.fs | 64 +++++++ .../resources/shaders/glsl100/normalmap.vs | 76 ++++++++ .../resources/shaders/glsl120/normalmap.fs | 62 +++++++ .../resources/shaders/glsl120/normalmap.vs | 76 ++++++++ .../resources/shaders/glsl330/normalmap.fs | 67 +++++++ .../resources/shaders/glsl330/normalmap.vs | 48 +++++ examples/shaders/resources/tiles_diffuse.png | Bin 0 -> 540633 bytes examples/shaders/resources/tiles_normal.png | Bin 0 -> 679312 bytes examples/shaders/shaders_normalmap.c | 171 ++++++++++++++++++ 11 files changed, 566 insertions(+) create mode 100644 examples/shaders/resources/shaders/glsl100/normalmap.fs create mode 100644 examples/shaders/resources/shaders/glsl100/normalmap.vs create mode 100644 examples/shaders/resources/shaders/glsl120/normalmap.fs create mode 100644 examples/shaders/resources/shaders/glsl120/normalmap.vs create mode 100644 examples/shaders/resources/shaders/glsl330/normalmap.fs create mode 100644 examples/shaders/resources/shaders/glsl330/normalmap.vs create mode 100644 examples/shaders/resources/tiles_diffuse.png create mode 100644 examples/shaders/resources/tiles_normal.png create mode 100644 examples/shaders/shaders_normalmap.c diff --git a/examples/Makefile b/examples/Makefile index 32a3a75ab..a77a7ce65 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -631,6 +631,7 @@ SHADERS = \ shaders/shaders_mesh_instancing \ shaders/shaders_model_shader \ shaders/shaders_multi_sample2d \ + shaders/shaders_normalmap \ shaders/shaders_palette_switch \ shaders/shaders_postprocessing \ shaders/shaders_raymarching \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 35ae70a18..267832d25 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -513,6 +513,7 @@ SHADERS = \ shaders/shaders_mesh_instancing \ shaders/shaders_model_shader \ shaders/shaders_multi_sample2d \ + shaders/shaders_normalmap \ shaders/shaders_palette_switch \ shaders/shaders_postprocessing \ shaders/shaders_raymarching \ diff --git a/examples/shaders/resources/shaders/glsl100/normalmap.fs b/examples/shaders/resources/shaders/glsl100/normalmap.fs new file mode 100644 index 000000000..02902a61f --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/normalmap.fs @@ -0,0 +1,64 @@ +#version 100 + +precision mediump float; + +// Input vertex attributes (from vertex shader) +varying vec3 fragPosition; +varying vec2 fragTexCoord; +varying vec3 fragNormal; //used for when normal mapping is toggled off +varying vec4 fragColor; +varying mat3 TBN; + +// Input uniform values +uniform sampler2D texture0; +uniform sampler2D normalMap; +uniform vec4 colDiffuse; +uniform vec3 viewPos; + +// NOTE: Add your custom variables here + +uniform vec3 lightPos; +uniform bool useNormalMap; +uniform float specularExponent; + +void main() +{ + vec4 texelColor = texture(texture0, vec2(fragTexCoord.x, fragTexCoord.y)); + vec3 specular = vec3(0.0); + vec3 viewDir = normalize(viewPos - fragPosition); + vec3 lightDir = normalize(lightPos - fragPosition); + + vec3 normal; + if (useNormalMap) + { + normal = texture(normalMap, vec2(fragTexCoord.x, fragTexCoord.y)).rgb; + + //Transform normal values to the range -1.0 ... 1.0 + normal = normalize(normal * 2.0 - 1.0); + + //Transform the normal from tangent-space to world-space for lighting calculation + normal = normalize(normal * TBN); + } + else + { + normal = normalize(fragNormal); + } + + vec4 tint = colDiffuse * fragColor; + + vec3 lightColor = vec3(1.0, 1.0, 1.0); + float NdotL = max(dot(normal, lightDir), 0.0); + vec3 lightDot = lightColor * NdotL; + + float specCo = 0.0; + + if (NdotL > 0.0) specCo = pow(max(0.0, dot(viewDir, reflect(-lightDir, normal))), specularExponent); // 16 refers to shine + + specular += specCo; + + finalColor = (texelColor * ((tint + vec4(specular, 1.0)) * vec4(lightDot, 1.0))); + finalColor += texelColor * (vec4(1.0, 1.0, 1.0, 1.0) / 40.0) * tint; + + // Gamma correction + gl_FragColor = pow(finalColor, vec4(1.0 / 2.2)); +} diff --git a/examples/shaders/resources/shaders/glsl100/normalmap.vs b/examples/shaders/resources/shaders/glsl100/normalmap.vs new file mode 100644 index 000000000..072e0a6c2 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/normalmap.vs @@ -0,0 +1,76 @@ +#version 100 + +// Input vertex attributes +attribute vec3 vertexPosition; +attribute vec2 vertexTexCoord; +attribute vec3 vertexNormal; +attribute vec4 vertexTangent; +attribute vec4 vertexColor; + +// Input uniform values +uniform mat4 mvp; +uniform mat4 matModel; + +// Output vertex attributes (to fragment shader) +varying vec3 fragPosition; +varying vec2 fragTexCoord; +varying vec3 fragNormal; //used for when normal mapping is toggled off +varying vec4 fragColor; +varying mat3 TBN; + +// NOTE: Add your custom variables here + +// https://github.com/glslify/glsl-inverse +mat3 inverse(mat3 m) +{ + float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2]; + float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2]; + float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2]; + + float b01 = a22 * a11 - a12 * a21; + float b11 = -a22 * a10 + a12 * a20; + float b21 = a21 * a10 - a11 * a20; + + float det = a00 * b01 + a01 * b11 + a02 * b21; + + return mat3(b01, (-a22 * a01 + a02 * a21), (a12 * a01 - a02 * a11), + b11, (a22 * a00 - a02 * a20), (-a12 * a00 + a02 * a10), + b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)) / det; +} + +// https://github.com/glslify/glsl-transpose +mat3 transpose(mat3 m) +{ + return mat3(m[0][0], m[1][0], m[2][0], + m[0][1], m[1][1], m[2][1], + m[0][2], m[1][2], m[2][2]); +} + +void main() +{ + // Compute binormal from vertex normal and tangent. W component is the tangent handedness + vec3 vertexBinormal = cross(vertexNormal, vertexTangent.xyz) * vertexTangent.w; + + // Compute fragment normal based on normal transformations + mat3 normalMatrix = transpose(inverse(mat3(matModel))); + + // Compute fragment position based on model transformations + fragPosition = vec3(matModel * vec4(vertexPosition, 1.0)); + + //Create TBN matrix for transforming the normal map values from tangent-space to world-space + fragNormal = normalize(normalMatrix * vertexNormal); + + vec3 fragTangent = normalize(normalMatrix * vertexTangent.xyz); + fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal) * fragNormal); + + vec3 fragBinormal = normalize(normalMatrix * vertexBinormal); + fragBinormal = cross(fragNormal, fragTangent); + + TBN = transpose(mat3(fragTangent, fragBinormal, fragNormal)); + + fragColor = vertexColor; + + fragTexCoord = vertexTexCoord; + + gl_Position = mvp * vec4(vertexPosition, 1.0); +} diff --git a/examples/shaders/resources/shaders/glsl120/normalmap.fs b/examples/shaders/resources/shaders/glsl120/normalmap.fs new file mode 100644 index 000000000..caeb4d5c1 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl120/normalmap.fs @@ -0,0 +1,62 @@ +#version 120 + +// Input vertex attributes (from vertex shader) +varying vec3 fragPosition; +varying vec2 fragTexCoord; +varying vec3 fragNormal; //used for when normal mapping is toggled off +varying vec4 fragColor; +varying mat3 TBN; + +// Input uniform values +uniform sampler2D texture0; +uniform sampler2D normalMap; +uniform vec4 colDiffuse; +uniform vec3 viewPos; + +// NOTE: Add your custom variables here + +uniform vec3 lightPos; +uniform bool useNormalMap; +uniform float specularExponent; + +void main() +{ + vec4 texelColor = texture(texture0, vec2(fragTexCoord.x, fragTexCoord.y)); + vec3 specular = vec3(0.0); + vec3 viewDir = normalize(viewPos - fragPosition); + vec3 lightDir = normalize(lightPos - fragPosition); + + vec3 normal; + if (useNormalMap) + { + normal = texture(normalMap, vec2(fragTexCoord.x, fragTexCoord.y)).rgb; + + //Transform normal values to the range -1.0 ... 1.0 + normal = normalize(normal * 2.0 - 1.0); + + //Transform the normal from tangent-space to world-space for lighting calculation + normal = normalize(normal * TBN); + } + else + { + normal = normalize(fragNormal); + } + + vec4 tint = colDiffuse * fragColor; + + vec3 lightColor = vec3(1.0, 1.0, 1.0); + float NdotL = max(dot(normal, lightDir), 0.0); + vec3 lightDot = lightColor * NdotL; + + float specCo = 0.0; + + if (NdotL > 0.0) specCo = pow(max(0.0, dot(viewDir, reflect(-lightDir, normal))), specularExponent); // 16 refers to shine + + specular += specCo; + + finalColor = (texelColor * ((tint + vec4(specular, 1.0)) * vec4(lightDot, 1.0))); + finalColor += texelColor * (vec4(1.0, 1.0, 1.0, 1.0) / 40.0) * tint; + + // Gamma correction + gl_FragColor = pow(finalColor, vec4(1.0 / 2.2)); +} diff --git a/examples/shaders/resources/shaders/glsl120/normalmap.vs b/examples/shaders/resources/shaders/glsl120/normalmap.vs new file mode 100644 index 000000000..d8921a68f --- /dev/null +++ b/examples/shaders/resources/shaders/glsl120/normalmap.vs @@ -0,0 +1,76 @@ +#version 120 + +// Input vertex attributes +attribute vec3 vertexPosition; +attribute vec2 vertexTexCoord; +attribute vec3 vertexNormal; +attribute vec4 vertexTangent; +attribute vec4 vertexColor; + +// Input uniform values +uniform mat4 mvp; +uniform mat4 matModel; + +// Output vertex attributes (to fragment shader) +varying vec3 fragPosition; +varying vec2 fragTexCoord; +varying vec3 fragNormal; //used for when normal mapping is toggled off +varying vec4 fragColor; +varying mat3 TBN; + +// NOTE: Add your custom variables here + +// https://github.com/glslify/glsl-inverse +mat3 inverse(mat3 m) +{ + float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2]; + float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2]; + float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2]; + + float b01 = a22 * a11 - a12 * a21; + float b11 = -a22 * a10 + a12 * a20; + float b21 = a21 * a10 - a11 * a20; + + float det = a00 * b01 + a01 * b11 + a02 * b21; + + return mat3(b01, (-a22 * a01 + a02 * a21), (a12 * a01 - a02 * a11), + b11, (a22 * a00 - a02 * a20), (-a12 * a00 + a02 * a10), + b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)) / det; +} + +// https://github.com/glslify/glsl-transpose +mat3 transpose(mat3 m) +{ + return mat3(m[0][0], m[1][0], m[2][0], + m[0][1], m[1][1], m[2][1], + m[0][2], m[1][2], m[2][2]); +} + +void main() +{ + // Compute binormal from vertex normal and tangent. W component is the tangent handedness + vec3 vertexBinormal = cross(vertexNormal, vertexTangent.xyz) * vertexTangent.w; + + // Compute fragment normal based on normal transformations + mat3 normalMatrix = transpose(inverse(mat3(matModel))); + + // Compute fragment position based on model transformations + fragPosition = vec3(matModel * vec4(vertexPosition, 1.0)); + + //Create TBN matrix for transforming the normal map values from tangent-space to world-space + fragNormal = normalize(normalMatrix * vertexNormal); + + vec3 fragTangent = normalize(normalMatrix * vertexTangent.xyz); + fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal) * fragNormal); + + vec3 fragBinormal = normalize(normalMatrix * vertexBinormal); + fragBinormal = cross(fragNormal, fragTangent); + + TBN = transpose(mat3(fragTangent, fragBinormal, fragNormal)); + + fragColor = vertexColor; + + fragTexCoord = vertexTexCoord; + + gl_Position = mvp * vec4(vertexPosition, 1.0); +} diff --git a/examples/shaders/resources/shaders/glsl330/normalmap.fs b/examples/shaders/resources/shaders/glsl330/normalmap.fs new file mode 100644 index 000000000..644130ab6 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/normalmap.fs @@ -0,0 +1,67 @@ +#version 330 + +// Input vertex attributes (from vertex shader) +in vec3 fragPosition; +in vec2 fragTexCoord; +in vec3 fragNormal; //used for when normal mapping is toggled off +in vec4 fragColor; + +// Input uniform values +uniform sampler2D texture0; +uniform sampler2D normalMap; +uniform vec4 colDiffuse; + +uniform vec3 viewPos; +uniform vec4 tintColor; + +uniform vec3 lightPos; +uniform bool useNormalMap; +uniform float specularExponent; + +// Output fragment color +out vec4 finalColor; + +in mat3 TBN; + +void main() +{ + vec4 texelColor = texture(texture0, vec2(fragTexCoord.x, fragTexCoord.y)); + vec3 specular = vec3(0.0); + vec3 viewDir = normalize(viewPos - fragPosition); + vec3 lightDir = normalize(lightPos - fragPosition); + + vec3 normal; + if (useNormalMap) + { + normal = texture(normalMap, vec2(fragTexCoord.x, fragTexCoord.y)).rgb; + + //Transform normal values to the range -1.0 ... 1.0 + normal = normalize(normal * 2.0 - 1.0); + + //Transform the normal from tangent-space to world-space for lighting calculation + normal = normalize(normal * TBN); + } + else + { + normal = normalize(fragNormal); + } + + vec4 tint = colDiffuse * fragColor; + + vec3 lightColor = vec3(1.0, 1.0, 1.0); + float NdotL = max(dot(normal, lightDir), 0.0); + vec3 lightDot = lightColor * NdotL; + + float specCo = 0.0; + + if (NdotL > 0.0) specCo = pow(max(0.0, dot(viewDir, reflect(-lightDir, normal))), specularExponent); // 16 refers to shine + + specular += specCo; + + finalColor = (texelColor * ((tint + vec4(specular, 1.0)) * vec4(lightDot, 1.0))); + finalColor += texelColor * (vec4(1.0, 1.0, 1.0, 1.0) / 40.0) * tint; + + // Gamma correction + finalColor = pow(finalColor, vec4(1.0 / 2.2)); + //finalColor = vec4(normal, 1.0); +} diff --git a/examples/shaders/resources/shaders/glsl330/normalmap.vs b/examples/shaders/resources/shaders/glsl330/normalmap.vs new file mode 100644 index 000000000..1e0a161b8 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/normalmap.vs @@ -0,0 +1,48 @@ +#version 330 + +// Input vertex attributes +in vec3 vertexPosition; +in vec2 vertexTexCoord; +in vec3 vertexNormal; +in vec4 vertexTangent; +in vec4 vertexColor; + +// Input uniform values +uniform mat4 mvp; +uniform mat4 matModel; + +// Output vertex attributes (to fragment shader) +out vec3 fragPosition; +out vec2 fragTexCoord; +out vec3 fragNormal; //used for when normal mapping is toggled off +out vec4 fragColor; +out mat3 TBN; + +void main() +{ + // Compute binormal from vertex normal and tangent. W component is the tangent handedness + vec3 vertexBinormal = cross(vertexNormal, vertexTangent.xyz) * vertexTangent.w; + + // Compute fragment normal based on normal transformations + mat3 normalMatrix = transpose(inverse(mat3(matModel))); + + // Compute fragment position based on model transformations + fragPosition = vec3(matModel * vec4(vertexPosition, 1.0)); + + //Create TBN matrix for transforming the normal map values from tangent-space to world-space + fragNormal = normalize(normalMatrix * vertexNormal); + + vec3 fragTangent = normalize(normalMatrix * vertexTangent.xyz); + fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal) * fragNormal); + + vec3 fragBinormal = normalize(normalMatrix * vertexBinormal); + fragBinormal = cross(fragNormal, fragTangent); + + TBN = transpose(mat3(fragTangent, fragBinormal, fragNormal)); + + fragColor = vertexColor; + + fragTexCoord = vertexTexCoord; + + gl_Position = mvp * vec4(vertexPosition, 1.0); +} diff --git a/examples/shaders/resources/tiles_diffuse.png b/examples/shaders/resources/tiles_diffuse.png new file mode 100644 index 0000000000000000000000000000000000000000..1c7b840354fc248d99d46eee19cbaa776b6fd130 GIT binary patch literal 540633 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
-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

@~{F>$degNPiVSm1C<|+a-(gc@wuC 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 G&;$T6oF8uh literal 0 HcmV?d00001 diff --git a/examples/shaders/resources/tiles_normal.png b/examples/shaders/resources/tiles_normal.png new file mode 100644 index 0000000000000000000000000000000000000000..719cb2e0d09a0b6234798bc276c2e274f3bb9c79 GIT binary patch literal 679312 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υm9Y7IS`E|9PP{e=<+5o;@jF=P@Eq^DrO!2hc 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 kUZ_vK*(~?8siJg*~dFzYM-2cD*|LgZs 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

w4@~ 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

iga@)I; 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

l3+8tuZBYo}DqAI%&*XxeYmU-o7vRl#G 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 z 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 x0TnQ1}E^DSXDBXJ6G-++%6Mff8 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