mirror of
https://github.com/glfw/glfw.git
synced 2024-12-02 06:47:10 +00:00
fbdb53b9ca
This is partly based on the implementation of libdecor support in PR #1693 by @ christianrauch. Where available, the libdecor library is loaded at init and becomes the preferred method for window decorations. On compositors that support XDG decorations, libdecor in turn uses those. If not, libdecor has a plug-in archtecture and may load additional libraries to either use compositor-specific decorations or draw its own. If necessary, support for libdecor can be disabled with the GLFW_WAYLAND_LIBDECOR init hint. This is mostly in case some part of the dynamic loading or duplication of header material added here turns out to cause problems with future versions of libdecor-0.so.0. Fixes #1639 Closes #1693 Related to #1725
552 lines
15 KiB
C
552 lines
15 KiB
C
//========================================================================
|
|
// GLFW 3.4 - www.glfw.org
|
|
//------------------------------------------------------------------------
|
|
// Copyright (c) 2002-2006 Marcus Geelnard
|
|
// Copyright (c) 2006-2018 Camilla Löwy <elmindreda@glfw.org>
|
|
//
|
|
// 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.
|
|
//
|
|
//========================================================================
|
|
// Please use C89 style variable declarations in this file because VS 2010
|
|
//========================================================================
|
|
|
|
#include "internal.h"
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <assert.h>
|
|
|
|
|
|
// NOTE: The global variables below comprise all mutable global data in GLFW
|
|
// Any other mutable global variable is a bug
|
|
|
|
// This contains all mutable state shared between compilation units of GLFW
|
|
//
|
|
_GLFWlibrary _glfw = { GLFW_FALSE };
|
|
|
|
// These are outside of _glfw so they can be used before initialization and
|
|
// after termination without special handling when _glfw is cleared to zero
|
|
//
|
|
static _GLFWerror _glfwMainThreadError;
|
|
static GLFWerrorfun _glfwErrorCallback;
|
|
static GLFWallocator _glfwInitAllocator;
|
|
static _GLFWinitconfig _glfwInitHints =
|
|
{
|
|
GLFW_TRUE, // hat buttons
|
|
GLFW_ANGLE_PLATFORM_TYPE_NONE, // ANGLE backend
|
|
GLFW_ANY_PLATFORM, // preferred platform
|
|
NULL, // vkGetInstanceProcAddr function
|
|
{
|
|
GLFW_TRUE, // macOS menu bar
|
|
GLFW_TRUE // macOS bundle chdir
|
|
},
|
|
{
|
|
GLFW_TRUE, // X11 XCB Vulkan surface
|
|
},
|
|
{
|
|
GLFW_WAYLAND_PREFER_LIBDECOR // Wayland libdecor mode
|
|
},
|
|
};
|
|
|
|
// The allocation function used when no custom allocator is set
|
|
//
|
|
static void* defaultAllocate(size_t size, void* user)
|
|
{
|
|
return malloc(size);
|
|
}
|
|
|
|
// The deallocation function used when no custom allocator is set
|
|
//
|
|
static void defaultDeallocate(void* block, void* user)
|
|
{
|
|
free(block);
|
|
}
|
|
|
|
// The reallocation function used when no custom allocator is set
|
|
//
|
|
static void* defaultReallocate(void* block, size_t size, void* user)
|
|
{
|
|
return realloc(block, size);
|
|
}
|
|
|
|
// Terminate the library
|
|
//
|
|
static void terminate(void)
|
|
{
|
|
int i;
|
|
|
|
memset(&_glfw.callbacks, 0, sizeof(_glfw.callbacks));
|
|
|
|
while (_glfw.windowListHead)
|
|
glfwDestroyWindow((GLFWwindow*) _glfw.windowListHead);
|
|
|
|
while (_glfw.cursorListHead)
|
|
glfwDestroyCursor((GLFWcursor*) _glfw.cursorListHead);
|
|
|
|
for (i = 0; i < _glfw.monitorCount; i++)
|
|
{
|
|
_GLFWmonitor* monitor = _glfw.monitors[i];
|
|
if (monitor->originalRamp.size)
|
|
_glfw.platform.setGammaRamp(monitor, &monitor->originalRamp);
|
|
_glfwFreeMonitor(monitor);
|
|
}
|
|
|
|
_glfw_free(_glfw.monitors);
|
|
_glfw.monitors = NULL;
|
|
_glfw.monitorCount = 0;
|
|
|
|
_glfw_free(_glfw.mappings);
|
|
_glfw.mappings = NULL;
|
|
_glfw.mappingCount = 0;
|
|
|
|
_glfwTerminateVulkan();
|
|
_glfw.platform.terminateJoysticks();
|
|
_glfw.platform.terminate();
|
|
|
|
_glfw.initialized = GLFW_FALSE;
|
|
|
|
while (_glfw.errorListHead)
|
|
{
|
|
_GLFWerror* error = _glfw.errorListHead;
|
|
_glfw.errorListHead = error->next;
|
|
_glfw_free(error);
|
|
}
|
|
|
|
_glfwPlatformDestroyTls(&_glfw.contextSlot);
|
|
_glfwPlatformDestroyTls(&_glfw.errorSlot);
|
|
_glfwPlatformDestroyMutex(&_glfw.errorLock);
|
|
|
|
memset(&_glfw, 0, sizeof(_glfw));
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
////// GLFW internal API //////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Encode a Unicode code point to a UTF-8 stream
|
|
// Based on cutef8 by Jeff Bezanson (Public Domain)
|
|
//
|
|
size_t _glfwEncodeUTF8(char* s, uint32_t codepoint)
|
|
{
|
|
size_t count = 0;
|
|
|
|
if (codepoint < 0x80)
|
|
s[count++] = (char) codepoint;
|
|
else if (codepoint < 0x800)
|
|
{
|
|
s[count++] = (codepoint >> 6) | 0xc0;
|
|
s[count++] = (codepoint & 0x3f) | 0x80;
|
|
}
|
|
else if (codepoint < 0x10000)
|
|
{
|
|
s[count++] = (codepoint >> 12) | 0xe0;
|
|
s[count++] = ((codepoint >> 6) & 0x3f) | 0x80;
|
|
s[count++] = (codepoint & 0x3f) | 0x80;
|
|
}
|
|
else if (codepoint < 0x110000)
|
|
{
|
|
s[count++] = (codepoint >> 18) | 0xf0;
|
|
s[count++] = ((codepoint >> 12) & 0x3f) | 0x80;
|
|
s[count++] = ((codepoint >> 6) & 0x3f) | 0x80;
|
|
s[count++] = (codepoint & 0x3f) | 0x80;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
// Splits and translates a text/uri-list into separate file paths
|
|
// NOTE: This function destroys the provided string
|
|
//
|
|
char** _glfwParseUriList(char* text, int* count)
|
|
{
|
|
const char* prefix = "file://";
|
|
char** paths = NULL;
|
|
char* line;
|
|
|
|
*count = 0;
|
|
|
|
while ((line = strtok(text, "\r\n")))
|
|
{
|
|
char* path;
|
|
|
|
text = NULL;
|
|
|
|
if (line[0] == '#')
|
|
continue;
|
|
|
|
if (strncmp(line, prefix, strlen(prefix)) == 0)
|
|
{
|
|
line += strlen(prefix);
|
|
// TODO: Validate hostname
|
|
while (*line != '/')
|
|
line++;
|
|
}
|
|
|
|
(*count)++;
|
|
|
|
path = _glfw_calloc(strlen(line) + 1, 1);
|
|
paths = _glfw_realloc(paths, *count * sizeof(char*));
|
|
paths[*count - 1] = path;
|
|
|
|
while (*line)
|
|
{
|
|
if (line[0] == '%' && line[1] && line[2])
|
|
{
|
|
const char digits[3] = { line[1], line[2], '\0' };
|
|
*path = (char) strtol(digits, NULL, 16);
|
|
line += 2;
|
|
}
|
|
else
|
|
*path = *line;
|
|
|
|
path++;
|
|
line++;
|
|
}
|
|
}
|
|
|
|
return paths;
|
|
}
|
|
|
|
char* _glfw_strdup(const char* source)
|
|
{
|
|
const size_t length = strlen(source);
|
|
char* result = _glfw_calloc(length + 1, 1);
|
|
strcpy(result, source);
|
|
return result;
|
|
}
|
|
|
|
int _glfw_min(int a, int b)
|
|
{
|
|
return a < b ? a : b;
|
|
}
|
|
|
|
int _glfw_max(int a, int b)
|
|
{
|
|
return a > b ? a : b;
|
|
}
|
|
|
|
float _glfw_fminf(float a, float b)
|
|
{
|
|
if (a != a)
|
|
return b;
|
|
else if (b != b)
|
|
return a;
|
|
else if (a < b)
|
|
return a;
|
|
else
|
|
return b;
|
|
}
|
|
|
|
float _glfw_fmaxf(float a, float b)
|
|
{
|
|
if (a != a)
|
|
return b;
|
|
else if (b != b)
|
|
return a;
|
|
else if (a > b)
|
|
return a;
|
|
else
|
|
return b;
|
|
}
|
|
|
|
void* _glfw_calloc(size_t count, size_t size)
|
|
{
|
|
if (count && size)
|
|
{
|
|
void* block;
|
|
|
|
if (count > SIZE_MAX / size)
|
|
{
|
|
_glfwInputError(GLFW_INVALID_VALUE, "Allocation size overflow");
|
|
return NULL;
|
|
}
|
|
|
|
block = _glfw.allocator.allocate(count * size, _glfw.allocator.user);
|
|
if (block)
|
|
return memset(block, 0, count * size);
|
|
else
|
|
{
|
|
_glfwInputError(GLFW_OUT_OF_MEMORY, NULL);
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
void* _glfw_realloc(void* block, size_t size)
|
|
{
|
|
if (block && size)
|
|
{
|
|
void* resized = _glfw.allocator.reallocate(block, size, _glfw.allocator.user);
|
|
if (resized)
|
|
return resized;
|
|
else
|
|
{
|
|
_glfwInputError(GLFW_OUT_OF_MEMORY, NULL);
|
|
return NULL;
|
|
}
|
|
}
|
|
else if (block)
|
|
{
|
|
_glfw_free(block);
|
|
return NULL;
|
|
}
|
|
else
|
|
return _glfw_calloc(1, size);
|
|
}
|
|
|
|
void _glfw_free(void* block)
|
|
{
|
|
if (block)
|
|
_glfw.allocator.deallocate(block, _glfw.allocator.user);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
////// GLFW event API //////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Notifies shared code of an error
|
|
//
|
|
void _glfwInputError(int code, const char* format, ...)
|
|
{
|
|
_GLFWerror* error;
|
|
char description[_GLFW_MESSAGE_SIZE];
|
|
|
|
if (format)
|
|
{
|
|
va_list vl;
|
|
|
|
va_start(vl, format);
|
|
vsnprintf(description, sizeof(description), format, vl);
|
|
va_end(vl);
|
|
|
|
description[sizeof(description) - 1] = '\0';
|
|
}
|
|
else
|
|
{
|
|
if (code == GLFW_NOT_INITIALIZED)
|
|
strcpy(description, "The GLFW library is not initialized");
|
|
else if (code == GLFW_NO_CURRENT_CONTEXT)
|
|
strcpy(description, "There is no current context");
|
|
else if (code == GLFW_INVALID_ENUM)
|
|
strcpy(description, "Invalid argument for enum parameter");
|
|
else if (code == GLFW_INVALID_VALUE)
|
|
strcpy(description, "Invalid value for parameter");
|
|
else if (code == GLFW_OUT_OF_MEMORY)
|
|
strcpy(description, "Out of memory");
|
|
else if (code == GLFW_API_UNAVAILABLE)
|
|
strcpy(description, "The requested API is unavailable");
|
|
else if (code == GLFW_VERSION_UNAVAILABLE)
|
|
strcpy(description, "The requested API version is unavailable");
|
|
else if (code == GLFW_PLATFORM_ERROR)
|
|
strcpy(description, "A platform-specific error occurred");
|
|
else if (code == GLFW_FORMAT_UNAVAILABLE)
|
|
strcpy(description, "The requested format is unavailable");
|
|
else if (code == GLFW_NO_WINDOW_CONTEXT)
|
|
strcpy(description, "The specified window has no context");
|
|
else if (code == GLFW_CURSOR_UNAVAILABLE)
|
|
strcpy(description, "The specified cursor shape is unavailable");
|
|
else if (code == GLFW_FEATURE_UNAVAILABLE)
|
|
strcpy(description, "The requested feature cannot be implemented for this platform");
|
|
else if (code == GLFW_FEATURE_UNIMPLEMENTED)
|
|
strcpy(description, "The requested feature has not yet been implemented for this platform");
|
|
else if (code == GLFW_PLATFORM_UNAVAILABLE)
|
|
strcpy(description, "The requested platform is unavailable");
|
|
else
|
|
strcpy(description, "ERROR: UNKNOWN GLFW ERROR");
|
|
}
|
|
|
|
if (_glfw.initialized)
|
|
{
|
|
error = _glfwPlatformGetTls(&_glfw.errorSlot);
|
|
if (!error)
|
|
{
|
|
error = _glfw_calloc(1, sizeof(_GLFWerror));
|
|
_glfwPlatformSetTls(&_glfw.errorSlot, error);
|
|
_glfwPlatformLockMutex(&_glfw.errorLock);
|
|
error->next = _glfw.errorListHead;
|
|
_glfw.errorListHead = error;
|
|
_glfwPlatformUnlockMutex(&_glfw.errorLock);
|
|
}
|
|
}
|
|
else
|
|
error = &_glfwMainThreadError;
|
|
|
|
error->code = code;
|
|
strcpy(error->description, description);
|
|
|
|
if (_glfwErrorCallback)
|
|
_glfwErrorCallback(code, description);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
////// GLFW public API //////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
GLFWAPI int glfwInit(void)
|
|
{
|
|
if (_glfw.initialized)
|
|
return GLFW_TRUE;
|
|
|
|
memset(&_glfw, 0, sizeof(_glfw));
|
|
_glfw.hints.init = _glfwInitHints;
|
|
|
|
_glfw.allocator = _glfwInitAllocator;
|
|
if (!_glfw.allocator.allocate)
|
|
{
|
|
_glfw.allocator.allocate = defaultAllocate;
|
|
_glfw.allocator.reallocate = defaultReallocate;
|
|
_glfw.allocator.deallocate = defaultDeallocate;
|
|
}
|
|
|
|
if (!_glfwSelectPlatform(_glfw.hints.init.platformID, &_glfw.platform))
|
|
return GLFW_FALSE;
|
|
|
|
if (!_glfw.platform.init())
|
|
{
|
|
terminate();
|
|
return GLFW_FALSE;
|
|
}
|
|
|
|
if (!_glfwPlatformCreateMutex(&_glfw.errorLock) ||
|
|
!_glfwPlatformCreateTls(&_glfw.errorSlot) ||
|
|
!_glfwPlatformCreateTls(&_glfw.contextSlot))
|
|
{
|
|
terminate();
|
|
return GLFW_FALSE;
|
|
}
|
|
|
|
_glfwPlatformSetTls(&_glfw.errorSlot, &_glfwMainThreadError);
|
|
|
|
_glfwInitGamepadMappings();
|
|
|
|
_glfwPlatformInitTimer();
|
|
_glfw.timer.offset = _glfwPlatformGetTimerValue();
|
|
|
|
_glfw.initialized = GLFW_TRUE;
|
|
|
|
glfwDefaultWindowHints();
|
|
return GLFW_TRUE;
|
|
}
|
|
|
|
GLFWAPI void glfwTerminate(void)
|
|
{
|
|
if (!_glfw.initialized)
|
|
return;
|
|
|
|
terminate();
|
|
}
|
|
|
|
GLFWAPI void glfwInitHint(int hint, int value)
|
|
{
|
|
switch (hint)
|
|
{
|
|
case GLFW_JOYSTICK_HAT_BUTTONS:
|
|
_glfwInitHints.hatButtons = value;
|
|
return;
|
|
case GLFW_ANGLE_PLATFORM_TYPE:
|
|
_glfwInitHints.angleType = value;
|
|
return;
|
|
case GLFW_PLATFORM:
|
|
_glfwInitHints.platformID = value;
|
|
return;
|
|
case GLFW_COCOA_CHDIR_RESOURCES:
|
|
_glfwInitHints.ns.chdir = value;
|
|
return;
|
|
case GLFW_COCOA_MENUBAR:
|
|
_glfwInitHints.ns.menubar = value;
|
|
return;
|
|
case GLFW_X11_XCB_VULKAN_SURFACE:
|
|
_glfwInitHints.x11.xcbVulkanSurface = value;
|
|
return;
|
|
case GLFW_WAYLAND_LIBDECOR:
|
|
_glfwInitHints.wl.libdecorMode = value;
|
|
return;
|
|
}
|
|
|
|
_glfwInputError(GLFW_INVALID_ENUM,
|
|
"Invalid init hint 0x%08X", hint);
|
|
}
|
|
|
|
GLFWAPI void glfwInitAllocator(const GLFWallocator* allocator)
|
|
{
|
|
if (allocator)
|
|
{
|
|
if (allocator->allocate && allocator->reallocate && allocator->deallocate)
|
|
_glfwInitAllocator = *allocator;
|
|
else
|
|
_glfwInputError(GLFW_INVALID_VALUE, "Missing function in allocator");
|
|
}
|
|
else
|
|
memset(&_glfwInitAllocator, 0, sizeof(GLFWallocator));
|
|
}
|
|
|
|
GLFWAPI void glfwInitVulkanLoader(PFN_vkGetInstanceProcAddr loader)
|
|
{
|
|
_glfwInitHints.vulkanLoader = loader;
|
|
}
|
|
|
|
GLFWAPI void glfwGetVersion(int* major, int* minor, int* rev)
|
|
{
|
|
if (major != NULL)
|
|
*major = GLFW_VERSION_MAJOR;
|
|
if (minor != NULL)
|
|
*minor = GLFW_VERSION_MINOR;
|
|
if (rev != NULL)
|
|
*rev = GLFW_VERSION_REVISION;
|
|
}
|
|
|
|
GLFWAPI int glfwGetError(const char** description)
|
|
{
|
|
_GLFWerror* error;
|
|
int code = GLFW_NO_ERROR;
|
|
|
|
if (description)
|
|
*description = NULL;
|
|
|
|
if (_glfw.initialized)
|
|
error = _glfwPlatformGetTls(&_glfw.errorSlot);
|
|
else
|
|
error = &_glfwMainThreadError;
|
|
|
|
if (error)
|
|
{
|
|
code = error->code;
|
|
error->code = GLFW_NO_ERROR;
|
|
if (description && code)
|
|
*description = error->description;
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
GLFWAPI GLFWerrorfun glfwSetErrorCallback(GLFWerrorfun cbfun)
|
|
{
|
|
_GLFW_SWAP(GLFWerrorfun, _glfwErrorCallback, cbfun);
|
|
return cbfun;
|
|
}
|
|
|