mirror of
https://github.com/glfw/glfw.git
synced 2024-11-26 06:14:35 +00:00
Merge branch 'master' into EGL
Conflicts: src/glx_opengl.c src/opengl.c tests/glfwinfo.c
This commit is contained in:
commit
0272ce2e98
19
.gitignore
vendored
19
.gitignore
vendored
@ -6,13 +6,13 @@ cmake_uninstall.cmake
|
||||
.DS_Store
|
||||
docs/Doxyfile
|
||||
src/config.h
|
||||
src/libglfw.pc
|
||||
src/libglfw.so
|
||||
src/libglfw.a
|
||||
src/libglfw.dylib
|
||||
src/glfw.lib
|
||||
src/glfw.dll
|
||||
src/glfwdll.lib
|
||||
src/glfw3.pc
|
||||
src/libglfw3.so
|
||||
src/libglfw3.a
|
||||
src/libglfw3.dylib
|
||||
src/glfw3.lib
|
||||
src/glfw3.dll
|
||||
src/glfw3dll.lib
|
||||
examples/boing
|
||||
examples/gears
|
||||
examples/heightmap
|
||||
@ -22,8 +22,8 @@ examples/wave
|
||||
examples/*.app
|
||||
examples/*.exe
|
||||
tests/accuracy
|
||||
tests/clipboard
|
||||
tests/defaults
|
||||
tests/dynamic
|
||||
tests/events
|
||||
tests/fsaa
|
||||
tests/fsfocus
|
||||
@ -31,11 +31,12 @@ tests/gamma
|
||||
tests/glfwinfo
|
||||
tests/iconify
|
||||
tests/joysticks
|
||||
tests/listmodes
|
||||
tests/modes
|
||||
tests/peter
|
||||
tests/reopen
|
||||
tests/sharing
|
||||
tests/tearing
|
||||
tests/threads
|
||||
tests/title
|
||||
tests/windows
|
||||
tests/*.app
|
||||
|
@ -22,6 +22,12 @@ else()
|
||||
find_package(OpenGL REQUIRED)
|
||||
endif()
|
||||
|
||||
set(CMAKE_THREAD_PREFER_PTHREADS YES)
|
||||
find_package(Threads)
|
||||
if (CMAKE_THREAD_LIBS_INIT)
|
||||
list(APPEND glfw_LIBRARIES ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Enable all warnings on GCC, regardless of OS
|
||||
#--------------------------------------------------------------------
|
||||
|
@ -287,6 +287,7 @@ version of GLFW.</p>
|
||||
<li>Added <code>windows</code> simple multi-window test program</li>
|
||||
<li>Added <code>sharing</code> simple OpenGL object sharing test program</li>
|
||||
<li>Added <code>modes</code> video mode enumeration and setting test program</li>
|
||||
<li>Added <code>threads</code> simple multi-threaded rendering test program</li>
|
||||
<li>Added <code>glfw3native.h</code> header and platform-specific functions for explicit access to native display, window and context handles</li>
|
||||
<li>Added <code>glfwSetGamma</code>, <code>glfwSetGammaRamp</code> and <code>glfwGetGammaRamp</code> functions and <code>GLFWgammaramp</code> type for monitor gamma ramp control</li>
|
||||
<li>Added window parameter to <code>glfwSwapBuffers</code></li>
|
||||
@ -349,6 +350,7 @@ version of GLFW.</p>
|
||||
<li>[X11] Bugfix: Some window properties required by the ICCCM were not set</li>
|
||||
<li>[X11] Bugfix: Calling <code>glXCreateContextAttribsARB</code> with an unavailable OpenGL version caused the application to terminate with a <code>BadMatch</code> Xlib error</li>
|
||||
<li>[X11] Bugfix: A synchronization point necessary for jitter-free locked cursor mode was incorrectly removed</li>
|
||||
<li>[X11] Bugfix: The window size hints were not updated when calling <code>glfwSetWindowSize</code> on a non-resizable window</li>
|
||||
<li>[Win32] Changed port to use Unicode mode only</li>
|
||||
<li>[Win32] Removed explicit support for versions of Windows older than Windows XP</li>
|
||||
<li>[Win32] Bugfix: Window activation and iconification did not work as expected</li>
|
||||
@ -907,7 +909,7 @@ their skills. Special thanks go out to:</p>
|
||||
language</li>
|
||||
|
||||
<li>Shane Liesegang, for providing a bug fix relating to Cocoa window
|
||||
restoration</li>
|
||||
restoration and reporting a bug on 32-bit Cocoa builds</li>
|
||||
|
||||
<li>Tristam MacDonald, for his bug reports and feedback on the Cocoa port</li>
|
||||
|
||||
@ -924,9 +926,14 @@ their skills. Special thanks go out to:</p>
|
||||
Much of the Windows code of GLFW was originally based on Jeff's
|
||||
code</li>
|
||||
|
||||
<li>Julian Møller, for reporting a bug in the Cocoa joystick code</li>
|
||||
|
||||
<li>Arturo J. Pérez, for a bug fix for cursor tracking on Mac OS X 10.6 Snow
|
||||
Leopard</li>
|
||||
|
||||
<li>Riku Salminen, for making the X11 event processing able to support
|
||||
multi-threaded rendering</li>
|
||||
|
||||
<li>Douglas C. Schmidt and Irfan Pyarali, for their excellent article
|
||||
<a href="http://www.cs.wustl.edu/~schmidt/win32-cv-1.html">Strategies for Implementing POSIX Condition Variables on Win32</a></li>
|
||||
|
||||
|
@ -103,6 +103,9 @@ int _glfwPlatformInit(void)
|
||||
|
||||
_glfwInitJoysticks();
|
||||
|
||||
if (!_glfwInitOpenGL())
|
||||
return GL_FALSE;
|
||||
|
||||
_glfwLibrary.NS.eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
|
||||
if (!_glfwLibrary.NS.eventSource)
|
||||
return GL_FALSE;
|
||||
@ -143,6 +146,8 @@ int _glfwPlatformTerminate(void)
|
||||
|
||||
_glfwTerminateJoysticks();
|
||||
|
||||
_glfwTerminateOpenGL();
|
||||
|
||||
return GL_TRUE;
|
||||
}
|
||||
|
||||
|
@ -550,15 +550,15 @@ int _glfwPlatformGetJoystickAxes(int joy, float* axes, int numaxes)
|
||||
|
||||
for (i = 0; i < numaxes; i++)
|
||||
{
|
||||
_glfwJoystickElement* axes =
|
||||
_glfwJoystickElement* elements =
|
||||
(_glfwJoystickElement*) CFArrayGetValueAtIndex(joystick.axes, i);
|
||||
|
||||
long readScale = axes->maxReport - axes->minReport;
|
||||
long readScale = elements->maxReport - elements->minReport;
|
||||
|
||||
if (readScale == 0)
|
||||
axes[i] = axes->value;
|
||||
axes[i] = elements->value;
|
||||
else
|
||||
axes[i] = (2.0f * (axes->value - axes->minReport) / readScale) - 1.0f;
|
||||
axes[i] = (2.0f * (elements->value - elements->minReport) / readScale) - 1.0f;
|
||||
|
||||
if (i & 1)
|
||||
axes[i] = -axes[i];
|
||||
|
@ -29,11 +29,46 @@
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
|
||||
//========================================================================
|
||||
// The per-thread current context/window pointer
|
||||
//========================================================================
|
||||
static pthread_key_t _glfwCurrentTLS;
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//========================================================================
|
||||
// Initialize OpenGL support
|
||||
//========================================================================
|
||||
|
||||
int _glfwInitOpenGL(void)
|
||||
{
|
||||
if (pthread_key_create(&_glfwCurrentTLS, NULL) != 0)
|
||||
{
|
||||
_glfwSetError(GLFW_PLATFORM_ERROR,
|
||||
"Cocoa/NSGL: Failed to create context TLS");
|
||||
return GL_FALSE;
|
||||
}
|
||||
|
||||
return GL_TRUE;
|
||||
}
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Terminate OpenGL support
|
||||
//========================================================================
|
||||
|
||||
void _glfwTerminateOpenGL(void)
|
||||
{
|
||||
pthread_key_delete(_glfwCurrentTLS);
|
||||
}
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Make the OpenGL context associated with the specified window current
|
||||
//========================================================================
|
||||
@ -44,6 +79,18 @@ void _glfwPlatformMakeContextCurrent(_GLFWwindow* window)
|
||||
[window->NSGL.context makeCurrentContext];
|
||||
else
|
||||
[NSOpenGLContext clearCurrentContext];
|
||||
|
||||
pthread_setspecific(_glfwCurrentTLS, window);
|
||||
}
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Return the window object whose context is current
|
||||
//========================================================================
|
||||
|
||||
_GLFWwindow* _glfwPlatformGetCurrentContext(void)
|
||||
{
|
||||
return (_GLFWwindow*) pthread_getspecific(_glfwCurrentTLS);
|
||||
}
|
||||
|
||||
|
||||
@ -64,7 +111,7 @@ void _glfwPlatformSwapBuffers(_GLFWwindow* window)
|
||||
|
||||
void _glfwPlatformSwapInterval(int interval)
|
||||
{
|
||||
_GLFWwindow* window = _glfwLibrary.currentWindow;
|
||||
_GLFWwindow* window = _glfwPlatformGetCurrentContext();
|
||||
|
||||
GLint sync = interval;
|
||||
[window->NSGL.context setValues:&sync forParameter:NSOpenGLCPSwapInterval];
|
||||
|
@ -123,4 +123,8 @@ void _glfwTerminateJoysticks(void);
|
||||
GLboolean _glfwSetVideoMode(int* width, int* height, int* bpp, int* refreshRate);
|
||||
void _glfwRestoreVideoMode(void);
|
||||
|
||||
// OpenGL support
|
||||
int _glfwInitOpenGL(void);
|
||||
void _glfwTerminateOpenGL(void);
|
||||
|
||||
#endif // _platform_h_
|
||||
|
@ -686,7 +686,7 @@ static GLboolean createWindow(_GLFWwindow* window,
|
||||
[window->NS.object setAcceptsMouseMovedEvents:YES];
|
||||
[window->NS.object center];
|
||||
|
||||
if ([window->NS.object respondsToSelector:@selector(setRestorable)])
|
||||
if ([window->NS.object respondsToSelector:@selector(setRestorable:)])
|
||||
[window->NS.object setRestorable:NO];
|
||||
|
||||
return GL_TRUE;
|
||||
@ -1100,7 +1100,7 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, int x, int y)
|
||||
CGPoint mainScreenOrigin = CGDisplayBounds(CGMainDisplayID()).origin;
|
||||
double mainScreenHeight = CGDisplayBounds(CGMainDisplayID()).size.height;
|
||||
CGPoint targetPoint = CGPointMake(globalPoint.x - mainScreenOrigin.x,
|
||||
mainScreenHeight - globalPoint.y -
|
||||
mainScreenHeight - globalPoint.y -
|
||||
mainScreenOrigin.y);
|
||||
CGDisplayMoveCursorToPoint(CGMainDisplayID(), targetPoint);
|
||||
}
|
||||
|
@ -34,6 +34,24 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Thread local storage attribute macro
|
||||
//========================================================================
|
||||
#if defined(_MSC_VER)
|
||||
#define _GLFW_TLS __declspec(thread)
|
||||
#elif defined(__GNUC__)
|
||||
#define _GLFW_TLS __thread
|
||||
#else
|
||||
#define _GLFW_TLS
|
||||
#endif
|
||||
|
||||
|
||||
//========================================================================
|
||||
// The per-thread current context/window pointer
|
||||
//========================================================================
|
||||
static _GLFW_TLS _GLFWwindow* _glfwCurrentWindow = NULL;
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Returns the specified attribute of the specified EGLConfig
|
||||
//========================================================================
|
||||
@ -508,6 +526,18 @@ void _glfwPlatformMakeContextCurrent(_GLFWwindow* window)
|
||||
eglMakeCurrent(_glfwLibrary.EGL.display,
|
||||
EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
}
|
||||
|
||||
_glfwCurrentWindow = window;
|
||||
}
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Return the window object whose context is current
|
||||
//========================================================================
|
||||
|
||||
_GLFWwindow* _glfwPlatformGetCurrentContext(void)
|
||||
{
|
||||
return _glfwCurrentWindow;
|
||||
}
|
||||
|
||||
|
||||
|
@ -32,10 +32,8 @@
|
||||
#include "internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#ifdef __APPLE__
|
||||
#include <sys/malloc.h>
|
||||
#else
|
||||
#include <malloc.h>
|
||||
#if _WIN32
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
@ -38,6 +38,22 @@
|
||||
void (*glXGetProcAddressEXT(const GLubyte* procName))();
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Thread local storage attribute macro
|
||||
//========================================================================
|
||||
#if defined(__GNUC__)
|
||||
#define _GLFW_TLS __thread
|
||||
#else
|
||||
#define _GLFW_TLS
|
||||
#endif
|
||||
|
||||
|
||||
//========================================================================
|
||||
// The per-thread current context/window pointer
|
||||
//========================================================================
|
||||
static _GLFW_TLS _GLFWwindow* _glfwCurrentWindow = NULL;
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Returns the specified attribute of the specified GLXFBConfig
|
||||
// NOTE: Do not call this unless we have found GLX 1.3+ or GLX_SGIX_fbconfig
|
||||
@ -611,6 +627,10 @@ void _glfwDestroyContext(_GLFWwindow* window)
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//========================================================================
|
||||
// Make the OpenGL context associated with the specified window current
|
||||
//========================================================================
|
||||
@ -625,6 +645,18 @@ void _glfwPlatformMakeContextCurrent(_GLFWwindow* window)
|
||||
}
|
||||
else
|
||||
glXMakeCurrent(_glfwLibrary.X11.display, None, NULL);
|
||||
|
||||
_glfwCurrentWindow = window;
|
||||
}
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Return the window object whose context is current
|
||||
//========================================================================
|
||||
|
||||
_GLFWwindow* _glfwPlatformGetCurrentContext(void)
|
||||
{
|
||||
return _glfwCurrentWindow;
|
||||
}
|
||||
|
||||
|
||||
@ -644,7 +676,7 @@ void _glfwPlatformSwapBuffers(_GLFWwindow* window)
|
||||
|
||||
void _glfwPlatformSwapInterval(int interval)
|
||||
{
|
||||
_GLFWwindow* window = _glfwLibrary.currentWindow;
|
||||
_GLFWwindow* window = _glfwCurrentWindow;
|
||||
|
||||
if (_glfwLibrary.GLX.EXT_swap_control)
|
||||
{
|
||||
|
@ -208,7 +208,6 @@ struct _GLFWlibrary
|
||||
_GLFWhints hints;
|
||||
|
||||
_GLFWwindow* windowListHead;
|
||||
_GLFWwindow* currentWindow;
|
||||
_GLFWwindow* activeWindow;
|
||||
|
||||
GLFWwindowsizefun windowSizeCallback;
|
||||
@ -298,6 +297,7 @@ void _glfwPlatformWaitEvents(void);
|
||||
|
||||
// OpenGL context management
|
||||
void _glfwPlatformMakeContextCurrent(_GLFWwindow* window);
|
||||
_GLFWwindow* _glfwPlatformGetCurrentContext(void);
|
||||
void _glfwPlatformSwapBuffers(_GLFWwindow* window);
|
||||
void _glfwPlatformSwapInterval(int interval);
|
||||
void _glfwPlatformRefreshWindowParams(_GLFWwindow* window);
|
||||
|
17
src/opengl.c
17
src/opengl.c
@ -392,7 +392,7 @@ GLboolean _glfwIsValidContextConfig(_GLFWwndconfig* wndconfig)
|
||||
|
||||
GLboolean _glfwRefreshContextParams(void)
|
||||
{
|
||||
_GLFWwindow* window = _glfwLibrary.currentWindow;
|
||||
_GLFWwindow* window = _glfwPlatformGetCurrentContext();
|
||||
|
||||
if (!parseGLVersion(&window->clientAPI,
|
||||
&window->glMajor,
|
||||
@ -460,7 +460,7 @@ GLboolean _glfwRefreshContextParams(void)
|
||||
|
||||
GLboolean _glfwIsValidContext(_GLFWwndconfig* wndconfig)
|
||||
{
|
||||
_GLFWwindow* window = _glfwLibrary.currentWindow;
|
||||
_GLFWwindow* window = _glfwPlatformGetCurrentContext();
|
||||
|
||||
if (window->glMajor < wndconfig->glMajor ||
|
||||
(window->glMajor == wndconfig->glMajor &&
|
||||
@ -535,11 +535,10 @@ GLFWAPI void glfwMakeContextCurrent(GLFWwindow handle)
|
||||
return;
|
||||
}
|
||||
|
||||
if (_glfwLibrary.currentWindow == window)
|
||||
if (_glfwPlatformGetCurrentContext() == window)
|
||||
return;
|
||||
|
||||
_glfwPlatformMakeContextCurrent(window);
|
||||
_glfwLibrary.currentWindow = window;
|
||||
}
|
||||
|
||||
|
||||
@ -555,7 +554,7 @@ GLFWAPI GLFWwindow glfwGetCurrentContext(void)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return _glfwLibrary.currentWindow;
|
||||
return _glfwPlatformGetCurrentContext();
|
||||
}
|
||||
|
||||
|
||||
@ -589,7 +588,7 @@ GLFWAPI void glfwSwapInterval(int interval)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_glfwLibrary.currentWindow)
|
||||
if (!_glfwPlatformGetCurrentContext())
|
||||
{
|
||||
_glfwSetError(GLFW_NO_CURRENT_CONTEXT, NULL);
|
||||
return;
|
||||
@ -614,7 +613,7 @@ GLFWAPI int glfwExtensionSupported(const char* extension)
|
||||
return GL_FALSE;
|
||||
}
|
||||
|
||||
window = _glfwLibrary.currentWindow;
|
||||
window = _glfwPlatformGetCurrentContext();
|
||||
if (!window)
|
||||
{
|
||||
_glfwSetError(GLFW_NO_CURRENT_CONTEXT, NULL);
|
||||
@ -675,7 +674,7 @@ GLFWAPI GLFWglproc glfwGetProcAddress(const char* procname)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!_glfwLibrary.currentWindow)
|
||||
if (!_glfwPlatformGetCurrentContext())
|
||||
{
|
||||
_glfwSetError(GLFW_NO_CURRENT_CONTEXT, NULL);
|
||||
return NULL;
|
||||
@ -703,7 +702,7 @@ GLFWAPI void glfwCopyContext(GLFWwindow hsrc, GLFWwindow hdst, unsigned long mas
|
||||
src = (_GLFWwindow*) hsrc;
|
||||
dst = (_GLFWwindow*) hdst;
|
||||
|
||||
if (_glfwLibrary.currentWindow == dst)
|
||||
if (_glfwPlatformGetCurrentContext() == dst)
|
||||
{
|
||||
_glfwSetError(GLFW_INVALID_VALUE,
|
||||
"glfwCopyContext: Cannot copy OpenGL state to a current context");
|
||||
|
@ -34,6 +34,24 @@
|
||||
#include <malloc.h>
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Thread local storage attribute macro
|
||||
//========================================================================
|
||||
#if defined(_MSC_VER)
|
||||
#define _GLFW_TLS __declspec(thread)
|
||||
#elif defined(__GNUC__)
|
||||
#define _GLFW_TLS __thread
|
||||
#else
|
||||
#define _GLFW_TLS
|
||||
#endif
|
||||
|
||||
|
||||
//========================================================================
|
||||
// The per-thread current context/window pointer
|
||||
//========================================================================
|
||||
static _GLFW_TLS _GLFWwindow* _glfwCurrentWindow = NULL;
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Initialize WGL-specific extensions
|
||||
// This function is called once before initial context creation, i.e. before
|
||||
@ -447,7 +465,7 @@ static GLboolean createContext(_GLFWwindow* window,
|
||||
}
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent(window);
|
||||
_glfwPlatformMakeContextCurrent(window);
|
||||
initWGLExtensions(window);
|
||||
|
||||
return GL_TRUE;
|
||||
@ -512,8 +530,8 @@ void _glfwDestroyContext(_GLFWwindow* window)
|
||||
{
|
||||
// This is duplicated from glfwDestroyWindow
|
||||
// TODO: Stop duplicating code
|
||||
if (window == _glfwLibrary.currentWindow)
|
||||
glfwMakeContextCurrent(NULL);
|
||||
if (window == _glfwCurrentWindow)
|
||||
_glfwPlatformMakeContextCurrent(NULL);
|
||||
|
||||
if (window->WGL.context)
|
||||
{
|
||||
@ -543,6 +561,18 @@ void _glfwPlatformMakeContextCurrent(_GLFWwindow* window)
|
||||
wglMakeCurrent(window->WGL.DC, window->WGL.context);
|
||||
else
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
|
||||
_glfwCurrentWindow = window;
|
||||
}
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Return the window object whose context is current
|
||||
//========================================================================
|
||||
|
||||
_GLFWwindow* _glfwPlatformGetCurrentContext(void)
|
||||
{
|
||||
return _glfwCurrentWindow;
|
||||
}
|
||||
|
||||
|
||||
@ -562,7 +592,7 @@ void _glfwPlatformSwapBuffers(_GLFWwindow* window)
|
||||
|
||||
void _glfwPlatformSwapInterval(int interval)
|
||||
{
|
||||
_GLFWwindow* window = _glfwLibrary.currentWindow;
|
||||
_GLFWwindow* window = _glfwCurrentWindow;
|
||||
|
||||
if (window->WGL.EXT_swap_control)
|
||||
window->WGL.SwapIntervalEXT(interval);
|
||||
@ -577,7 +607,7 @@ int _glfwPlatformExtensionSupported(const char* extension)
|
||||
{
|
||||
const GLubyte* extensions;
|
||||
|
||||
_GLFWwindow* window = _glfwLibrary.currentWindow;
|
||||
_GLFWwindow* window = _glfwCurrentWindow;
|
||||
|
||||
if (window->WGL.GetExtensionsStringEXT != NULL)
|
||||
{
|
||||
|
11
src/window.c
11
src/window.c
@ -33,10 +33,8 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#ifdef __APPLE__
|
||||
#include <sys/malloc.h>
|
||||
#else
|
||||
#include <malloc.h>
|
||||
#if _WIN32
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
|
||||
@ -468,8 +466,9 @@ GLFWAPI void glfwDestroyWindow(GLFWwindow handle)
|
||||
return;
|
||||
|
||||
// Clear the current context if this window's context is current
|
||||
if (window == _glfwLibrary.currentWindow)
|
||||
glfwMakeContextCurrent(NULL);
|
||||
// TODO: Re-examine this in light of multithreading
|
||||
if (window == _glfwPlatformGetCurrentContext())
|
||||
_glfwPlatformMakeContextCurrent(NULL);
|
||||
|
||||
// Clear the active window pointer if this is the active window
|
||||
if (window == _glfwLibrary.activeWindow)
|
||||
|
@ -634,6 +634,8 @@ static void terminateDisplay(void)
|
||||
|
||||
int _glfwPlatformInit(void)
|
||||
{
|
||||
XInitThreads();
|
||||
|
||||
if (!initDisplay())
|
||||
return GL_FALSE;
|
||||
|
||||
@ -646,7 +648,8 @@ int _glfwPlatformInit(void)
|
||||
|
||||
_glfwLibrary.X11.cursor = createNULLCursor();
|
||||
|
||||
_glfwInitJoysticks();
|
||||
if (!_glfwInitJoysticks())
|
||||
return GL_FALSE;
|
||||
|
||||
// Start the timer
|
||||
_glfwInitTimer();
|
||||
|
@ -37,7 +37,8 @@
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <regex.h>
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#endif // _GLFW_USE_LINUX_JOYSTICKS
|
||||
@ -53,7 +54,7 @@ static int openJoystickDevice(int joy, const char* path)
|
||||
char numAxes, numButtons;
|
||||
int fd, version;
|
||||
|
||||
fd = open(path, O_NONBLOCK);
|
||||
fd = open(path, O_RDONLY | O_NONBLOCK);
|
||||
if (fd == -1)
|
||||
return GL_FALSE;
|
||||
|
||||
@ -127,7 +128,7 @@ static void pollJoystickEvents(void)
|
||||
if (errno == ENODEV)
|
||||
_glfwLibrary.X11.joystick[i].present = GL_FALSE;
|
||||
|
||||
if (result < sizeof(e))
|
||||
if (result == -1)
|
||||
break;
|
||||
|
||||
// We don't care if it's an init event or not
|
||||
@ -171,30 +172,52 @@ static void pollJoystickEvents(void)
|
||||
// Initialize joystick interface
|
||||
//========================================================================
|
||||
|
||||
void _glfwInitJoysticks(void)
|
||||
int _glfwInitJoysticks(void)
|
||||
{
|
||||
#ifdef _GLFW_USE_LINUX_JOYSTICKS
|
||||
int i, j, joy = 0;
|
||||
char path[20];
|
||||
const char* bases[] =
|
||||
int i, joy = 0;
|
||||
regex_t regex;
|
||||
DIR* dir;
|
||||
const char* dirs[] =
|
||||
{
|
||||
"/dev/input/js",
|
||||
"/dev/js"
|
||||
"/dev/input",
|
||||
"/dev"
|
||||
};
|
||||
|
||||
for (i = 0; i < sizeof(bases) / sizeof(bases[0]); i++)
|
||||
if (regcomp(®ex, "^js[0-9]\\+$", 0) != 0)
|
||||
{
|
||||
for (j = 0; j < 50; j++)
|
||||
{
|
||||
if (joy > GLFW_JOYSTICK_LAST)
|
||||
break;
|
||||
_glfwSetError(GLFW_PLATFORM_ERROR, "X11: Failed to compile regex");
|
||||
return GL_FALSE;
|
||||
}
|
||||
|
||||
sprintf(path, "%s%i", bases[i], j);
|
||||
for (i = 0; i < sizeof(dirs) / sizeof(dirs[0]); i++)
|
||||
{
|
||||
struct dirent* entry;
|
||||
|
||||
dir = opendir(dirs[i]);
|
||||
if (!dir)
|
||||
continue;
|
||||
|
||||
while ((entry = readdir(dir)))
|
||||
{
|
||||
char path[20];
|
||||
regmatch_t match;
|
||||
|
||||
if (regexec(®ex, entry->d_name, 1, &match, 0) != 0)
|
||||
continue;
|
||||
|
||||
snprintf(path, sizeof(path), "%s/%s", dirs[i], entry->d_name);
|
||||
if (openJoystickDevice(joy, path))
|
||||
joy++;
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
regfree(®ex);
|
||||
#endif // _GLFW_USE_LINUX_JOYSTICKS
|
||||
|
||||
return GL_TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
@ -235,7 +235,7 @@ void _glfwSetVideoMode(int* width, int* height, int* rate);
|
||||
void _glfwRestoreVideoMode(void);
|
||||
|
||||
// Joystick input
|
||||
void _glfwInitJoysticks(void);
|
||||
int _glfwInitJoysticks(void);
|
||||
void _glfwTerminateJoysticks(void);
|
||||
|
||||
// Unicode support
|
||||
|
158
src/x11_window.c
158
src/x11_window.c
@ -30,6 +30,8 @@
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#include <sys/select.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -460,34 +462,31 @@ static _GLFWwindow* findWindow(Window handle)
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Get and process next X event (called by _glfwPlatformPollEvents)
|
||||
// Process the specified X event
|
||||
//========================================================================
|
||||
|
||||
static void processSingleEvent(void)
|
||||
static void processEvent(XEvent *event)
|
||||
{
|
||||
_GLFWwindow* window;
|
||||
|
||||
XEvent event;
|
||||
XNextEvent(_glfwLibrary.X11.display, &event);
|
||||
|
||||
switch (event.type)
|
||||
switch (event->type)
|
||||
{
|
||||
case KeyPress:
|
||||
{
|
||||
// A keyboard key was pressed
|
||||
window = findWindow(event.xkey.window);
|
||||
window = findWindow(event->xkey.window);
|
||||
if (window == NULL)
|
||||
return;
|
||||
|
||||
_glfwInputKey(window, translateKey(event.xkey.keycode), GLFW_PRESS);
|
||||
_glfwInputChar(window, translateChar(&event.xkey));
|
||||
_glfwInputKey(window, translateKey(event->xkey.keycode), GLFW_PRESS);
|
||||
_glfwInputChar(window, translateChar(&event->xkey));
|
||||
break;
|
||||
}
|
||||
|
||||
case KeyRelease:
|
||||
{
|
||||
// A keyboard key was released
|
||||
window = findWindow(event.xkey.window);
|
||||
window = findWindow(event->xkey.window);
|
||||
if (window == NULL)
|
||||
return;
|
||||
|
||||
@ -501,15 +500,15 @@ static void processSingleEvent(void)
|
||||
XPeekEvent(_glfwLibrary.X11.display, &nextEvent);
|
||||
|
||||
if (nextEvent.type == KeyPress &&
|
||||
nextEvent.xkey.window == event.xkey.window &&
|
||||
nextEvent.xkey.keycode == event.xkey.keycode)
|
||||
nextEvent.xkey.window == event->xkey.window &&
|
||||
nextEvent.xkey.keycode == event->xkey.keycode)
|
||||
{
|
||||
// This last check is a hack to work around key repeats
|
||||
// leaking through due to some sort of time drift
|
||||
// Toshiyuki Takahashi can press a button 16 times per
|
||||
// second so it's fairly safe to assume that no human is
|
||||
// pressing the key 50 times per second (value is ms)
|
||||
if ((nextEvent.xkey.time - event.xkey.time) < 20)
|
||||
if ((nextEvent.xkey.time - event->xkey.time) < 20)
|
||||
{
|
||||
// Do not report anything for this event
|
||||
break;
|
||||
@ -517,34 +516,34 @@ static void processSingleEvent(void)
|
||||
}
|
||||
}
|
||||
|
||||
_glfwInputKey(window, translateKey(event.xkey.keycode), GLFW_RELEASE);
|
||||
_glfwInputKey(window, translateKey(event->xkey.keycode), GLFW_RELEASE);
|
||||
break;
|
||||
}
|
||||
|
||||
case ButtonPress:
|
||||
{
|
||||
// A mouse button was pressed or a scrolling event occurred
|
||||
window = findWindow(event.xbutton.window);
|
||||
window = findWindow(event->xbutton.window);
|
||||
if (window == NULL)
|
||||
return;
|
||||
|
||||
if (event.xbutton.button == Button1)
|
||||
if (event->xbutton.button == Button1)
|
||||
_glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS);
|
||||
else if (event.xbutton.button == Button2)
|
||||
else if (event->xbutton.button == Button2)
|
||||
_glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS);
|
||||
else if (event.xbutton.button == Button3)
|
||||
else if (event->xbutton.button == Button3)
|
||||
_glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_PRESS);
|
||||
|
||||
// XFree86 3.3.2 and later translates mouse wheel up/down into
|
||||
// mouse button 4 & 5 presses
|
||||
else if (event.xbutton.button == Button4)
|
||||
else if (event->xbutton.button == Button4)
|
||||
_glfwInputScroll(window, 0.0, 1.0);
|
||||
else if (event.xbutton.button == Button5)
|
||||
else if (event->xbutton.button == Button5)
|
||||
_glfwInputScroll(window, 0.0, -1.0);
|
||||
|
||||
else if (event.xbutton.button == Button6)
|
||||
else if (event->xbutton.button == Button6)
|
||||
_glfwInputScroll(window, -1.0, 0.0);
|
||||
else if (event.xbutton.button == Button7)
|
||||
else if (event->xbutton.button == Button7)
|
||||
_glfwInputScroll(window, 1.0, 0.0);
|
||||
|
||||
break;
|
||||
@ -553,23 +552,23 @@ static void processSingleEvent(void)
|
||||
case ButtonRelease:
|
||||
{
|
||||
// A mouse button was released
|
||||
window = findWindow(event.xbutton.window);
|
||||
window = findWindow(event->xbutton.window);
|
||||
if (window == NULL)
|
||||
return;
|
||||
|
||||
if (event.xbutton.button == Button1)
|
||||
if (event->xbutton.button == Button1)
|
||||
{
|
||||
_glfwInputMouseClick(window,
|
||||
GLFW_MOUSE_BUTTON_LEFT,
|
||||
GLFW_RELEASE);
|
||||
}
|
||||
else if (event.xbutton.button == Button2)
|
||||
else if (event->xbutton.button == Button2)
|
||||
{
|
||||
_glfwInputMouseClick(window,
|
||||
GLFW_MOUSE_BUTTON_MIDDLE,
|
||||
GLFW_RELEASE);
|
||||
}
|
||||
else if (event.xbutton.button == Button3)
|
||||
else if (event->xbutton.button == Button3)
|
||||
{
|
||||
_glfwInputMouseClick(window,
|
||||
GLFW_MOUSE_BUTTON_RIGHT,
|
||||
@ -581,7 +580,7 @@ static void processSingleEvent(void)
|
||||
case EnterNotify:
|
||||
{
|
||||
// The cursor entered the window
|
||||
window = findWindow(event.xcrossing.window);
|
||||
window = findWindow(event->xcrossing.window);
|
||||
if (window == NULL)
|
||||
return;
|
||||
|
||||
@ -595,7 +594,7 @@ static void processSingleEvent(void)
|
||||
case LeaveNotify:
|
||||
{
|
||||
// The cursor left the window
|
||||
window = findWindow(event.xcrossing.window);
|
||||
window = findWindow(event->xcrossing.window);
|
||||
if (window == NULL)
|
||||
return;
|
||||
|
||||
@ -609,12 +608,12 @@ static void processSingleEvent(void)
|
||||
case MotionNotify:
|
||||
{
|
||||
// The cursor was moved
|
||||
window = findWindow(event.xmotion.window);
|
||||
window = findWindow(event->xmotion.window);
|
||||
if (window == NULL)
|
||||
return;
|
||||
|
||||
if (event.xmotion.x != window->X11.cursorPosX ||
|
||||
event.xmotion.y != window->X11.cursorPosY)
|
||||
if (event->xmotion.x != window->X11.cursorPosX ||
|
||||
event->xmotion.y != window->X11.cursorPosY)
|
||||
{
|
||||
// The cursor was moved by something other than GLFW
|
||||
|
||||
@ -625,17 +624,17 @@ static void processSingleEvent(void)
|
||||
if (_glfwLibrary.activeWindow != window)
|
||||
break;
|
||||
|
||||
x = event.xmotion.x - window->X11.cursorPosX;
|
||||
y = event.xmotion.y - window->X11.cursorPosY;
|
||||
x = event->xmotion.x - window->X11.cursorPosX;
|
||||
y = event->xmotion.y - window->X11.cursorPosY;
|
||||
}
|
||||
else
|
||||
{
|
||||
x = event.xmotion.x;
|
||||
y = event.xmotion.y;
|
||||
x = event->xmotion.x;
|
||||
y = event->xmotion.y;
|
||||
}
|
||||
|
||||
window->X11.cursorPosX = event.xmotion.x;
|
||||
window->X11.cursorPosY = event.xmotion.y;
|
||||
window->X11.cursorPosX = event->xmotion.x;
|
||||
window->X11.cursorPosY = event->xmotion.y;
|
||||
window->X11.cursorCentered = GL_FALSE;
|
||||
|
||||
_glfwInputCursorMotion(window, x, y);
|
||||
@ -647,17 +646,17 @@ static void processSingleEvent(void)
|
||||
case ConfigureNotify:
|
||||
{
|
||||
// The window configuration changed somehow
|
||||
window = findWindow(event.xconfigure.window);
|
||||
window = findWindow(event->xconfigure.window);
|
||||
if (window == NULL)
|
||||
return;
|
||||
|
||||
_glfwInputWindowSize(window,
|
||||
event.xconfigure.width,
|
||||
event.xconfigure.height);
|
||||
event->xconfigure.width,
|
||||
event->xconfigure.height);
|
||||
|
||||
_glfwInputWindowPos(window,
|
||||
event.xconfigure.x,
|
||||
event.xconfigure.y);
|
||||
event->xconfigure.x,
|
||||
event->xconfigure.y);
|
||||
|
||||
break;
|
||||
}
|
||||
@ -665,11 +664,11 @@ static void processSingleEvent(void)
|
||||
case ClientMessage:
|
||||
{
|
||||
// Custom client message, probably from the window manager
|
||||
window = findWindow(event.xclient.window);
|
||||
window = findWindow(event->xclient.window);
|
||||
if (window == NULL)
|
||||
return;
|
||||
|
||||
if ((Atom) event.xclient.data.l[0] == _glfwLibrary.X11.wmDeleteWindow)
|
||||
if ((Atom) event->xclient.data.l[0] == _glfwLibrary.X11.wmDeleteWindow)
|
||||
{
|
||||
// The window manager was asked to close the window, for example by
|
||||
// the user pressing a 'close' window decoration button
|
||||
@ -677,17 +676,17 @@ static void processSingleEvent(void)
|
||||
_glfwInputWindowCloseRequest(window);
|
||||
}
|
||||
else if (_glfwLibrary.X11.wmPing != None &&
|
||||
(Atom) event.xclient.data.l[0] == _glfwLibrary.X11.wmPing)
|
||||
(Atom) event->xclient.data.l[0] == _glfwLibrary.X11.wmPing)
|
||||
{
|
||||
// The window manager is pinging the application to ensure it's
|
||||
// still responding to events
|
||||
|
||||
event.xclient.window = _glfwLibrary.X11.root;
|
||||
event->xclient.window = _glfwLibrary.X11.root;
|
||||
XSendEvent(_glfwLibrary.X11.display,
|
||||
event.xclient.window,
|
||||
event->xclient.window,
|
||||
False,
|
||||
SubstructureNotifyMask | SubstructureRedirectMask,
|
||||
&event);
|
||||
event);
|
||||
}
|
||||
|
||||
break;
|
||||
@ -696,7 +695,7 @@ static void processSingleEvent(void)
|
||||
case MapNotify:
|
||||
{
|
||||
// The window was mapped
|
||||
window = findWindow(event.xmap.window);
|
||||
window = findWindow(event->xmap.window);
|
||||
if (window == NULL)
|
||||
return;
|
||||
|
||||
@ -707,7 +706,7 @@ static void processSingleEvent(void)
|
||||
case UnmapNotify:
|
||||
{
|
||||
// The window was unmapped
|
||||
window = findWindow(event.xmap.window);
|
||||
window = findWindow(event->xmap.window);
|
||||
if (window == NULL)
|
||||
return;
|
||||
|
||||
@ -718,7 +717,7 @@ static void processSingleEvent(void)
|
||||
case FocusIn:
|
||||
{
|
||||
// The window gained focus
|
||||
window = findWindow(event.xfocus.window);
|
||||
window = findWindow(event->xfocus.window);
|
||||
if (window == NULL)
|
||||
return;
|
||||
|
||||
@ -733,7 +732,7 @@ static void processSingleEvent(void)
|
||||
case FocusOut:
|
||||
{
|
||||
// The window lost focus
|
||||
window = findWindow(event.xfocus.window);
|
||||
window = findWindow(event->xfocus.window);
|
||||
if (window == NULL)
|
||||
return;
|
||||
|
||||
@ -748,7 +747,7 @@ static void processSingleEvent(void)
|
||||
case Expose:
|
||||
{
|
||||
// The window's contents was damaged
|
||||
window = findWindow(event.xexpose.window);
|
||||
window = findWindow(event->xexpose.window);
|
||||
if (window == NULL)
|
||||
return;
|
||||
|
||||
@ -769,7 +768,7 @@ static void processSingleEvent(void)
|
||||
{
|
||||
// The selection conversion status is available
|
||||
|
||||
XSelectionEvent* request = &event.xselection;
|
||||
XSelectionEvent* request = &event->xselection;
|
||||
|
||||
if (_glfwReadSelection(request))
|
||||
_glfwLibrary.X11.selection.status = _GLFW_CONVERSION_SUCCEEDED;
|
||||
@ -783,7 +782,7 @@ static void processSingleEvent(void)
|
||||
{
|
||||
// The contents of the selection was requested
|
||||
|
||||
XSelectionRequestEvent* request = &event.xselectionrequest;
|
||||
XSelectionRequestEvent* request = &event->xselectionrequest;
|
||||
|
||||
XEvent response;
|
||||
memset(&response, 0, sizeof(response));
|
||||
@ -808,11 +807,11 @@ static void processSingleEvent(void)
|
||||
default:
|
||||
{
|
||||
#if defined(_GLFW_HAS_XRANDR)
|
||||
switch (event.type - _glfwLibrary.X11.RandR.eventBase)
|
||||
switch (event->type - _glfwLibrary.X11.RandR.eventBase)
|
||||
{
|
||||
case RRScreenChangeNotify:
|
||||
{
|
||||
XRRUpdateConfiguration(&event);
|
||||
XRRUpdateConfiguration(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -957,7 +956,6 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
|
||||
void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
|
||||
{
|
||||
int mode = 0, rate, sizeChanged = GL_FALSE;
|
||||
XSizeHints* sizehints;
|
||||
|
||||
rate = window->refreshRate;
|
||||
|
||||
@ -971,14 +969,14 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
|
||||
{
|
||||
// Update window size restrictions to match new window size
|
||||
|
||||
sizehints = XAllocSizeHints();
|
||||
sizehints->flags = 0;
|
||||
XSizeHints* hints = XAllocSizeHints();
|
||||
|
||||
sizehints->min_width = sizehints->max_width = width;
|
||||
sizehints->min_height = sizehints->max_height = height;
|
||||
hints->flags |= (PMinSize | PMaxSize);
|
||||
hints->min_width = hints->max_width = width;
|
||||
hints->min_height = hints->max_height = height;
|
||||
|
||||
XSetWMNormalHints(_glfwLibrary.X11.display, window->X11.handle, sizehints);
|
||||
XFree(sizehints);
|
||||
XSetWMNormalHints(_glfwLibrary.X11.display, window->X11.handle, hints);
|
||||
XFree(hints);
|
||||
}
|
||||
|
||||
// Change window size before changing fullscreen mode?
|
||||
@ -1093,13 +1091,18 @@ void _glfwPlatformRefreshWindowParams(_GLFWwindow* window)
|
||||
|
||||
void _glfwPlatformPollEvents(void)
|
||||
{
|
||||
XEvent event;
|
||||
|
||||
while (XCheckMaskEvent(_glfwLibrary.X11.display, ~0, &event) ||
|
||||
XCheckTypedEvent(_glfwLibrary.X11.display, ClientMessage, &event))
|
||||
{
|
||||
processEvent(&event);
|
||||
}
|
||||
|
||||
// Check whether the cursor has moved inside an active window that has
|
||||
// captured the cursor (because then it needs to be re-centered)
|
||||
|
||||
_GLFWwindow* window;
|
||||
|
||||
// Process all pending events
|
||||
while (XPending(_glfwLibrary.X11.display))
|
||||
processSingleEvent();
|
||||
|
||||
// Did the cursor move in an active window that has captured the cursor
|
||||
window = _glfwLibrary.activeWindow;
|
||||
if (window)
|
||||
{
|
||||
@ -1126,13 +1129,18 @@ void _glfwPlatformPollEvents(void)
|
||||
|
||||
void _glfwPlatformWaitEvents(void)
|
||||
{
|
||||
XEvent event;
|
||||
int fd;
|
||||
fd_set fds;
|
||||
|
||||
// Block waiting for an event to arrive
|
||||
XNextEvent(_glfwLibrary.X11.display, &event);
|
||||
XPutBackEvent(_glfwLibrary.X11.display, &event);
|
||||
fd = ConnectionNumber(_glfwLibrary.X11.display);
|
||||
|
||||
_glfwPlatformPollEvents();
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(fd, &fds);
|
||||
|
||||
XFlush(_glfwLibrary.X11.display);
|
||||
|
||||
if (select(fd + 1, &fds, NULL, NULL, NULL) > 0)
|
||||
_glfwPlatformPollEvents();
|
||||
}
|
||||
|
||||
|
||||
|
588
support/tinycthread.c
Normal file
588
support/tinycthread.c
Normal file
@ -0,0 +1,588 @@
|
||||
/* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*-
|
||||
Copyright (c) 2012 Marcus Geelnard
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#include "tinycthread.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
/* Platform specific includes */
|
||||
#if defined(_TTHREAD_POSIX_)
|
||||
#include <signal.h>
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#include <errno.h>
|
||||
#elif defined(_TTHREAD_WIN32_)
|
||||
#include <process.h>
|
||||
#include <sys/timeb.h>
|
||||
#endif
|
||||
|
||||
/* Standard, good-to-have defines */
|
||||
#ifndef NULL
|
||||
#define NULL (void*)0
|
||||
#endif
|
||||
#ifndef TRUE
|
||||
#define TRUE 1
|
||||
#endif
|
||||
#ifndef FALSE
|
||||
#define FALSE 0
|
||||
#endif
|
||||
|
||||
int mtx_init(mtx_t *mtx, int type)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
mtx->mAlreadyLocked = FALSE;
|
||||
mtx->mRecursive = type & mtx_recursive;
|
||||
InitializeCriticalSection(&mtx->mHandle);
|
||||
return thrd_success;
|
||||
#else
|
||||
int ret;
|
||||
pthread_mutexattr_t attr;
|
||||
pthread_mutexattr_init(&attr);
|
||||
if (type & mtx_recursive)
|
||||
{
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
}
|
||||
ret = pthread_mutex_init(mtx, &attr);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
return ret == 0 ? thrd_success : thrd_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
void mtx_destroy(mtx_t *mtx)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
DeleteCriticalSection(&mtx->mHandle);
|
||||
#else
|
||||
pthread_mutex_destroy(mtx);
|
||||
#endif
|
||||
}
|
||||
|
||||
int mtx_lock(mtx_t *mtx)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
EnterCriticalSection(&mtx->mHandle);
|
||||
if (!mtx->mRecursive)
|
||||
{
|
||||
while(mtx->mAlreadyLocked) Sleep(1000); /* Simulate deadlock... */
|
||||
mtx->mAlreadyLocked = TRUE;
|
||||
}
|
||||
return thrd_success;
|
||||
#else
|
||||
return pthread_mutex_lock(mtx) == 0 ? thrd_success : thrd_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
int mtx_timedlock(mtx_t *mtx, const struct timespec *ts)
|
||||
{
|
||||
/* FIXME! */
|
||||
(void)mtx;
|
||||
(void)ts;
|
||||
return thrd_error;
|
||||
}
|
||||
|
||||
int mtx_trylock(mtx_t *mtx)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
int ret = TryEnterCriticalSection(&mtx->mHandle) ? thrd_success : thrd_busy;
|
||||
if ((!mtx->mRecursive) && (ret == thrd_success) && mtx->mAlreadyLocked)
|
||||
{
|
||||
LeaveCriticalSection(&mtx->mHandle);
|
||||
ret = thrd_busy;
|
||||
}
|
||||
return ret;
|
||||
#else
|
||||
return (pthread_mutex_trylock(mtx) == 0) ? thrd_success : thrd_busy;
|
||||
#endif
|
||||
}
|
||||
|
||||
int mtx_unlock(mtx_t *mtx)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
mtx->mAlreadyLocked = FALSE;
|
||||
LeaveCriticalSection(&mtx->mHandle);
|
||||
return thrd_success;
|
||||
#else
|
||||
return pthread_mutex_unlock(mtx) == 0 ? thrd_success : thrd_error;;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
#define _CONDITION_EVENT_ONE 0
|
||||
#define _CONDITION_EVENT_ALL 1
|
||||
#endif
|
||||
|
||||
int cnd_init(cnd_t *cond)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
cond->mWaitersCount = 0;
|
||||
|
||||
/* Init critical section */
|
||||
InitializeCriticalSection(&cond->mWaitersCountLock);
|
||||
|
||||
/* Init events */
|
||||
cond->mEvents[_CONDITION_EVENT_ONE] = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
if (cond->mEvents[_CONDITION_EVENT_ONE] == NULL)
|
||||
{
|
||||
cond->mEvents[_CONDITION_EVENT_ALL] = NULL;
|
||||
return thrd_error;
|
||||
}
|
||||
cond->mEvents[_CONDITION_EVENT_ALL] = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
if (cond->mEvents[_CONDITION_EVENT_ALL] == NULL)
|
||||
{
|
||||
CloseHandle(cond->mEvents[_CONDITION_EVENT_ONE]);
|
||||
cond->mEvents[_CONDITION_EVENT_ONE] = NULL;
|
||||
return thrd_error;
|
||||
}
|
||||
|
||||
return thrd_success;
|
||||
#else
|
||||
return pthread_cond_init(cond, NULL) == 0 ? thrd_success : thrd_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
void cnd_destroy(cnd_t *cond)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
if (cond->mEvents[_CONDITION_EVENT_ONE] != NULL)
|
||||
{
|
||||
CloseHandle(cond->mEvents[_CONDITION_EVENT_ONE]);
|
||||
}
|
||||
if (cond->mEvents[_CONDITION_EVENT_ALL] != NULL)
|
||||
{
|
||||
CloseHandle(cond->mEvents[_CONDITION_EVENT_ALL]);
|
||||
}
|
||||
DeleteCriticalSection(&cond->mWaitersCountLock);
|
||||
#else
|
||||
pthread_cond_destroy(cond);
|
||||
#endif
|
||||
}
|
||||
|
||||
int cnd_signal(cnd_t *cond)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
int haveWaiters;
|
||||
|
||||
/* Are there any waiters? */
|
||||
EnterCriticalSection(&cond->mWaitersCountLock);
|
||||
haveWaiters = (cond->mWaitersCount > 0);
|
||||
LeaveCriticalSection(&cond->mWaitersCountLock);
|
||||
|
||||
/* If we have any waiting threads, send them a signal */
|
||||
if(haveWaiters)
|
||||
{
|
||||
if (SetEvent(cond->mEvents[_CONDITION_EVENT_ONE]) == 0)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
}
|
||||
|
||||
return thrd_success;
|
||||
#else
|
||||
return pthread_cond_signal(cond) == 0 ? thrd_success : thrd_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
int cnd_broadcast(cnd_t *cond)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
int haveWaiters;
|
||||
|
||||
/* Are there any waiters? */
|
||||
EnterCriticalSection(&cond->mWaitersCountLock);
|
||||
haveWaiters = (cond->mWaitersCount > 0);
|
||||
LeaveCriticalSection(&cond->mWaitersCountLock);
|
||||
|
||||
/* If we have any waiting threads, send them a signal */
|
||||
if(haveWaiters)
|
||||
{
|
||||
if (SetEvent(cond->mEvents[_CONDITION_EVENT_ALL]) == 0)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
}
|
||||
|
||||
return thrd_success;
|
||||
#else
|
||||
return pthread_cond_signal(cond) == 0 ? thrd_success : thrd_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
static int _cnd_timedwait_win32(cnd_t *cond, mtx_t *mtx, DWORD timeout)
|
||||
{
|
||||
int result, lastWaiter;
|
||||
|
||||
/* Increment number of waiters */
|
||||
EnterCriticalSection(&cond->mWaitersCountLock);
|
||||
++ cond->mWaitersCount;
|
||||
LeaveCriticalSection(&cond->mWaitersCountLock);
|
||||
|
||||
/* Release the mutex while waiting for the condition (will decrease
|
||||
the number of waiters when done)... */
|
||||
mtx_unlock(mtx);
|
||||
|
||||
/* Wait for either event to become signaled due to cnd_signal() or
|
||||
cnd_broadcast() being called */
|
||||
result = WaitForMultipleObjects(2, cond->mEvents, FALSE, timeout);
|
||||
if (result == WAIT_TIMEOUT)
|
||||
{
|
||||
return thrd_timeout;
|
||||
}
|
||||
else if (result == (int)WAIT_FAILED)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
|
||||
/* Check if we are the last waiter */
|
||||
EnterCriticalSection(&cond->mWaitersCountLock);
|
||||
-- cond->mWaitersCount;
|
||||
lastWaiter = (result == (WAIT_OBJECT_0 + _CONDITION_EVENT_ALL)) &&
|
||||
(cond->mWaitersCount == 0);
|
||||
LeaveCriticalSection(&cond->mWaitersCountLock);
|
||||
|
||||
/* If we are the last waiter to be notified to stop waiting, reset the event */
|
||||
if (lastWaiter)
|
||||
{
|
||||
if (ResetEvent(cond->mEvents[_CONDITION_EVENT_ALL]) == 0)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
}
|
||||
|
||||
/* Re-acquire the mutex */
|
||||
mtx_lock(mtx);
|
||||
|
||||
return thrd_success;
|
||||
}
|
||||
#endif
|
||||
|
||||
int cnd_wait(cnd_t *cond, mtx_t *mtx)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
return _cnd_timedwait_win32(cond, mtx, INFINITE);
|
||||
#else
|
||||
return pthread_cond_wait(cond, mtx) == 0 ? thrd_success : thrd_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const struct timespec *ts)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
struct timespec now;
|
||||
if (clock_gettime(TIME_UTC, &now) == 0)
|
||||
{
|
||||
DWORD delta = (ts->tv_sec - now.tv_sec) * 1000 +
|
||||
(ts->tv_nsec - now.tv_nsec + 500000) / 1000000;
|
||||
return _cnd_timedwait_win32(cond, mtx, delta);
|
||||
}
|
||||
else
|
||||
return thrd_error;
|
||||
#else
|
||||
int ret;
|
||||
ret = pthread_cond_timedwait(cond, mtx, ts);
|
||||
if (ret == ETIMEDOUT)
|
||||
{
|
||||
return thrd_timeout;
|
||||
}
|
||||
return ret == 0 ? thrd_success : thrd_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/** Information to pass to the new thread (what to run). */
|
||||
typedef struct {
|
||||
thrd_start_t mFunction; /**< Pointer to the function to be executed. */
|
||||
void * mArg; /**< Function argument for the thread function. */
|
||||
} _thread_start_info;
|
||||
|
||||
/* Thread wrapper function. */
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
static unsigned WINAPI _thrd_wrapper_function(void * aArg)
|
||||
#elif defined(_TTHREAD_POSIX_)
|
||||
static void * _thrd_wrapper_function(void * aArg)
|
||||
#endif
|
||||
{
|
||||
thrd_start_t fun;
|
||||
void *arg;
|
||||
int res;
|
||||
#if defined(_TTHREAD_POSIX_)
|
||||
void *pres;
|
||||
#endif
|
||||
|
||||
/* Get thread startup information */
|
||||
_thread_start_info *ti = (_thread_start_info *) aArg;
|
||||
fun = ti->mFunction;
|
||||
arg = ti->mArg;
|
||||
|
||||
/* The thread is responsible for freeing the startup information */
|
||||
free((void *)ti);
|
||||
|
||||
/* Call the actual client thread function */
|
||||
res = fun(arg);
|
||||
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
return res;
|
||||
#else
|
||||
pres = malloc(sizeof(int));
|
||||
if (pres != NULL)
|
||||
{
|
||||
*(int*)pres = res;
|
||||
}
|
||||
return pres;
|
||||
#endif
|
||||
}
|
||||
|
||||
int thrd_create(thrd_t *thr, thrd_start_t func, void *arg)
|
||||
{
|
||||
/* Fill out the thread startup information (passed to the thread wrapper,
|
||||
which will eventually free it) */
|
||||
_thread_start_info* ti = (_thread_start_info*)malloc(sizeof(_thread_start_info));
|
||||
if (ti == NULL)
|
||||
{
|
||||
return thrd_nomem;
|
||||
}
|
||||
ti->mFunction = func;
|
||||
ti->mArg = arg;
|
||||
|
||||
/* Create the thread */
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
*thr = (HANDLE)_beginthreadex(NULL, 0, _thrd_wrapper_function, (void *)ti, 0, NULL);
|
||||
#elif defined(_TTHREAD_POSIX_)
|
||||
if(pthread_create(thr, NULL, _thrd_wrapper_function, (void *)ti) != 0)
|
||||
{
|
||||
*thr = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Did we fail to create the thread? */
|
||||
if(!*thr)
|
||||
{
|
||||
free(ti);
|
||||
return thrd_error;
|
||||
}
|
||||
|
||||
return thrd_success;
|
||||
}
|
||||
|
||||
thrd_t thrd_current(void)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
return GetCurrentThread();
|
||||
#else
|
||||
return pthread_self();
|
||||
#endif
|
||||
}
|
||||
|
||||
int thrd_detach(thrd_t thr)
|
||||
{
|
||||
/* FIXME! */
|
||||
(void)thr;
|
||||
return thrd_error;
|
||||
}
|
||||
|
||||
int thrd_equal(thrd_t thr0, thrd_t thr1)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
return thr0 == thr1;
|
||||
#else
|
||||
return pthread_equal(thr0, thr1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void thrd_exit(int res)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
ExitThread(res);
|
||||
#else
|
||||
void *pres = malloc(sizeof(int));
|
||||
if (pres != NULL)
|
||||
{
|
||||
*(int*)pres = res;
|
||||
}
|
||||
pthread_exit(pres);
|
||||
#endif
|
||||
}
|
||||
|
||||
int thrd_join(thrd_t thr, int *res)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
if (WaitForSingleObject(thr, INFINITE) == WAIT_FAILED)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
if (res != NULL)
|
||||
{
|
||||
DWORD dwRes;
|
||||
GetExitCodeThread(thr, &dwRes);
|
||||
*res = dwRes;
|
||||
}
|
||||
#elif defined(_TTHREAD_POSIX_)
|
||||
void *pres;
|
||||
int ires = 0;
|
||||
if (pthread_join(thr, &pres) != 0)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
if (pres != NULL)
|
||||
{
|
||||
ires = *(int*)pres;
|
||||
free(pres);
|
||||
}
|
||||
if (res != NULL)
|
||||
{
|
||||
*res = ires;
|
||||
}
|
||||
#endif
|
||||
return thrd_success;
|
||||
}
|
||||
|
||||
int thrd_sleep(const struct timespec *time_point, struct timespec *remaining)
|
||||
{
|
||||
struct timespec now;
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
DWORD delta;
|
||||
#else
|
||||
long delta;
|
||||
#endif
|
||||
|
||||
/* Get the current time */
|
||||
if (clock_gettime(TIME_UTC, &now) != 0)
|
||||
return -2; // FIXME: Some specific error code?
|
||||
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
/* Delta in milliseconds */
|
||||
delta = (time_point->tv_sec - now.tv_sec) * 1000 +
|
||||
(time_point->tv_nsec - now.tv_nsec + 500000) / 1000000;
|
||||
if (delta > 0)
|
||||
{
|
||||
Sleep(delta);
|
||||
}
|
||||
#else
|
||||
/* Delta in microseconds */
|
||||
delta = (time_point->tv_sec - now.tv_sec) * 1000000L +
|
||||
(time_point->tv_nsec - now.tv_nsec + 500L) / 1000L;
|
||||
|
||||
/* On some systems, the usleep argument must be < 1000000 */
|
||||
while (delta > 999999L)
|
||||
{
|
||||
usleep(999999);
|
||||
delta -= 999999L;
|
||||
}
|
||||
if (delta > 0L)
|
||||
{
|
||||
usleep((useconds_t)delta);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* We don't support waking up prematurely (yet) */
|
||||
if (remaining)
|
||||
{
|
||||
remaining->tv_sec = 0;
|
||||
remaining->tv_nsec = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void thrd_yield(void)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
Sleep(0);
|
||||
#else
|
||||
sched_yield();
|
||||
#endif
|
||||
}
|
||||
|
||||
int tss_create(tss_t *key, tss_dtor_t dtor)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
/* FIXME: The destructor function is not supported yet... */
|
||||
if (dtor != NULL)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
*key = TlsAlloc();
|
||||
if (*key == TLS_OUT_OF_INDEXES)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
#else
|
||||
if (pthread_key_create(key, dtor) != 0)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
#endif
|
||||
return thrd_success;
|
||||
}
|
||||
|
||||
void tss_delete(tss_t key)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
TlsFree(key);
|
||||
#else
|
||||
pthread_key_delete(key);
|
||||
#endif
|
||||
}
|
||||
|
||||
void *tss_get(tss_t key)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
return TlsGetValue(key);
|
||||
#else
|
||||
return pthread_getspecific(key);
|
||||
#endif
|
||||
}
|
||||
|
||||
int tss_set(tss_t key, void *val)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
if (TlsSetValue(key, val) == 0)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
#else
|
||||
if (pthread_setspecific(key, val) != 0)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
#endif
|
||||
return thrd_success;
|
||||
}
|
||||
|
||||
#if defined(_TTHREAD_EMULATE_CLOCK_GETTIME_)
|
||||
int _tthread_clock_gettime(clockid_t clk_id, struct timespec *ts)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
struct _timeb tb;
|
||||
_ftime(&tb);
|
||||
ts->tv_sec = (time_t)tb.time;
|
||||
ts->tv_nsec = 1000000L * (long)tb.millitm;
|
||||
#else
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
ts->tv_sec = (time_t)tv.tv_sec;
|
||||
ts->tv_nsec = 1000L * (long)tv.tv_usec;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
#endif // _TTHREAD_EMULATE_CLOCK_GETTIME_
|
||||
|
439
support/tinycthread.h
Normal file
439
support/tinycthread.h
Normal file
@ -0,0 +1,439 @@
|
||||
/* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*-
|
||||
Copyright (c) 2012 Marcus Geelnard
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#ifndef _TINYCTHREAD_H_
|
||||
#define _TINYCTHREAD_H_
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @mainpage TinyCThread API Reference
|
||||
*
|
||||
* @section intro_sec Introduction
|
||||
* TinyCThread is a minimal, portable implementation of basic threading
|
||||
* classes for C.
|
||||
*
|
||||
* They closely mimic the functionality and naming of the C11 standard, and
|
||||
* should be easily replaceable with the corresponding standard variants.
|
||||
*
|
||||
* @section port_sec Portability
|
||||
* The Win32 variant uses the native Win32 API for implementing the thread
|
||||
* classes, while for other systems, the POSIX threads API (pthread) is used.
|
||||
*
|
||||
* @section misc_sec Miscellaneous
|
||||
* The following special keywords are available: #_Thread_local.
|
||||
*
|
||||
* For more detailed information, browse the different sections of this
|
||||
* documentation. A good place to start is:
|
||||
* tinycthread.h.
|
||||
*/
|
||||
|
||||
/* Which platform are we on? */
|
||||
#if !defined(_TTHREAD_PLATFORM_DEFINED_)
|
||||
#if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__)
|
||||
#define _TTHREAD_WIN32_
|
||||
#else
|
||||
#define _TTHREAD_POSIX_
|
||||
#endif
|
||||
#define _TTHREAD_PLATFORM_DEFINED_
|
||||
#endif
|
||||
|
||||
/* Activate some POSIX functionality (e.g. clock_gettime and recursive mutexes) */
|
||||
#if defined(_TTHREAD_POSIX_)
|
||||
#undef _FEATURES_H
|
||||
#if !defined(_GNU_SOURCE)
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#if !defined(_POSIX_C_SOURCE) || ((_POSIX_C_SOURCE - 0) < 199309L)
|
||||
#undef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 199309L
|
||||
#endif
|
||||
#if !defined(_XOPEN_SOURCE) || ((_XOPEN_SOURCE - 0) < 500)
|
||||
#undef _XOPEN_SOURCE
|
||||
#define _XOPEN_SOURCE 500
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Generic includes */
|
||||
#include <time.h>
|
||||
|
||||
/* Platform specific includes */
|
||||
#if defined(_TTHREAD_POSIX_)
|
||||
#include <pthread.h>
|
||||
#elif defined(_TTHREAD_WIN32_)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define __UNDEF_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#ifdef __UNDEF_LEAN_AND_MEAN
|
||||
#undef WIN32_LEAN_AND_MEAN
|
||||
#undef __UNDEF_LEAN_AND_MEAN
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Workaround for missing TIME_UTC: If time.h doesn't provide TIME_UTC,
|
||||
it's quite likely that libc does not support it either. Hence, fall back to
|
||||
the only other supported time specifier: CLOCK_REALTIME (and if that fails,
|
||||
we're probably emulating clock_gettime anyway, so anything goes). */
|
||||
#ifndef TIME_UTC
|
||||
#ifdef CLOCK_REALTIME
|
||||
#define TIME_UTC CLOCK_REALTIME
|
||||
#else
|
||||
#define TIME_UTC 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Workaround for missing clock_gettime (most Windows compilers, afaik) */
|
||||
#if defined(_TTHREAD_WIN32_) || defined(__APPLE_CC__)
|
||||
#define _TTHREAD_EMULATE_CLOCK_GETTIME_
|
||||
/* Emulate struct timespec */
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
struct _ttherad_timespec {
|
||||
time_t tv_sec;
|
||||
long tv_nsec;
|
||||
};
|
||||
#define timespec _ttherad_timespec
|
||||
#endif
|
||||
|
||||
/* Emulate clockid_t */
|
||||
typedef int _tthread_clockid_t;
|
||||
#define clockid_t _tthread_clockid_t
|
||||
|
||||
/* Emulate clock_gettime */
|
||||
int _tthread_clock_gettime(clockid_t clk_id, struct timespec *ts);
|
||||
#define clock_gettime _tthread_clock_gettime
|
||||
#endif
|
||||
|
||||
|
||||
/** TinyCThread version (major number). */
|
||||
#define TINYCTHREAD_VERSION_MAJOR 1
|
||||
/** TinyCThread version (minor number). */
|
||||
#define TINYCTHREAD_VERSION_MINOR 1
|
||||
/** TinyCThread version (full version). */
|
||||
#define TINYCTHREAD_VERSION (TINYCTHREAD_VERSION_MAJOR * 100 + TINYCTHREAD_VERSION_MINOR)
|
||||
|
||||
/**
|
||||
* @def _Thread_local
|
||||
* Thread local storage keyword.
|
||||
* A variable that is declared with the @c _Thread_local keyword makes the
|
||||
* value of the variable local to each thread (known as thread-local storage,
|
||||
* or TLS). Example usage:
|
||||
* @code
|
||||
* // This variable is local to each thread.
|
||||
* _Thread_local int variable;
|
||||
* @endcode
|
||||
* @note The @c _Thread_local keyword is a macro that maps to the corresponding
|
||||
* compiler directive (e.g. @c __declspec(thread)).
|
||||
* @note This directive is currently not supported on Mac OS X (it will give
|
||||
* a compiler error), since compile-time TLS is not supported in the Mac OS X
|
||||
* executable format. Also, some older versions of MinGW (before GCC 4.x) do
|
||||
* not support this directive.
|
||||
* @hideinitializer
|
||||
*/
|
||||
|
||||
/* FIXME: Check for a PROPER value of __STDC_VERSION__ to know if we have C11 */
|
||||
#if !(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201102L)) && !defined(_Thread_local)
|
||||
#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__)
|
||||
#define _Thread_local __thread
|
||||
#else
|
||||
#define _Thread_local __declspec(thread)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Macros */
|
||||
#define TSS_DTOR_ITERATIONS 0
|
||||
|
||||
/* Function return values */
|
||||
#define thrd_error 0 /**< The requested operation failed */
|
||||
#define thrd_success 1 /**< The requested operation succeeded */
|
||||
#define thrd_timeout 2 /**< The time specified in the call was reached without acquiring the requested resource */
|
||||
#define thrd_busy 3 /**< The requested operation failed because a tesource requested by a test and return function is already in use */
|
||||
#define thrd_nomem 4 /**< The requested operation failed because it was unable to allocate memory */
|
||||
|
||||
/* Mutex types */
|
||||
#define mtx_plain 1
|
||||
#define mtx_timed 2
|
||||
#define mtx_try 4
|
||||
#define mtx_recursive 8
|
||||
|
||||
/* Mutex */
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
typedef struct {
|
||||
CRITICAL_SECTION mHandle; /* Critical section handle */
|
||||
int mAlreadyLocked; /* TRUE if the mutex is already locked */
|
||||
int mRecursive; /* TRUE if the mutex is recursive */
|
||||
} mtx_t;
|
||||
#else
|
||||
typedef pthread_mutex_t mtx_t;
|
||||
#endif
|
||||
|
||||
/** Create a mutex object.
|
||||
* @param mtx A mutex object.
|
||||
* @param type Bit-mask that must have one of the following six values:
|
||||
* @li @c mtx_plain for a simple non-recursive mutex
|
||||
* @li @c mtx_timed for a non-recursive mutex that supports timeout
|
||||
* @li @c mtx_try for a non-recursive mutex that supports test and return
|
||||
* @li @c mtx_plain | @c mtx_recursive (same as @c mtx_plain, but recursive)
|
||||
* @li @c mtx_timed | @c mtx_recursive (same as @c mtx_timed, but recursive)
|
||||
* @li @c mtx_try | @c mtx_recursive (same as @c mtx_try, but recursive)
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int mtx_init(mtx_t *mtx, int type);
|
||||
|
||||
/** Release any resources used by the given mutex.
|
||||
* @param mtx A mutex object.
|
||||
*/
|
||||
void mtx_destroy(mtx_t *mtx);
|
||||
|
||||
/** Lock the given mutex.
|
||||
* Blocks until the given mutex can be locked. If the mutex is non-recursive, and
|
||||
* the calling thread already has a lock on the mutex, this call will block
|
||||
* forever.
|
||||
* @param mtx A mutex object.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int mtx_lock(mtx_t *mtx);
|
||||
|
||||
/** NOT YET IMPLEMENTED.
|
||||
*/
|
||||
int mtx_timedlock(mtx_t *mtx, const struct timespec *ts);
|
||||
|
||||
/** Try to lock the given mutex.
|
||||
* The specified mutex shall support either test and return or timeout. If the
|
||||
* mutex is already locked, the function returns without blocking.
|
||||
* @param mtx A mutex object.
|
||||
* @return @ref thrd_success on success, or @ref thrd_busy if the resource
|
||||
* requested is already in use, or @ref thrd_error if the request could not be
|
||||
* honored.
|
||||
*/
|
||||
int mtx_trylock(mtx_t *mtx);
|
||||
|
||||
/** Unlock the given mutex.
|
||||
* @param mtx A mutex object.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int mtx_unlock(mtx_t *mtx);
|
||||
|
||||
/* Condition variable */
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
typedef struct {
|
||||
HANDLE mEvents[2]; /* Signal and broadcast event HANDLEs. */
|
||||
unsigned int mWaitersCount; /* Count of the number of waiters. */
|
||||
CRITICAL_SECTION mWaitersCountLock; /* Serialize access to mWaitersCount. */
|
||||
} cnd_t;
|
||||
#else
|
||||
typedef pthread_cond_t cnd_t;
|
||||
#endif
|
||||
|
||||
/** Create a condition variable object.
|
||||
* @param cond A condition variable object.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int cnd_init(cnd_t *cond);
|
||||
|
||||
/** Release any resources used by the given condition variable.
|
||||
* @param cond A condition variable object.
|
||||
*/
|
||||
void cnd_destroy(cnd_t *cond);
|
||||
|
||||
/** Signal a condition variable.
|
||||
* Unblocks one of the threads that are blocked on the given condition variable
|
||||
* at the time of the call. If no threads are blocked on the condition variable
|
||||
* at the time of the call, the function does nothing and return success.
|
||||
* @param cond A condition variable object.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int cnd_signal(cnd_t *cond);
|
||||
|
||||
/** Broadcast a condition variable.
|
||||
* Unblocks all of the threads that are blocked on the given condition variable
|
||||
* at the time of the call. If no threads are blocked on the condition variable
|
||||
* at the time of the call, the function does nothing and return success.
|
||||
* @param cond A condition variable object.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int cnd_broadcast(cnd_t *cond);
|
||||
|
||||
/** Wait for a condition variable to become signaled.
|
||||
* The function atomically unlocks the given mutex and endeavors to block until
|
||||
* the given condition variable is signaled by a call to cnd_signal or to
|
||||
* cnd_broadcast. When the calling thread becomes unblocked it locks the mutex
|
||||
* before it returns.
|
||||
* @param cond A condition variable object.
|
||||
* @param mtx A mutex object.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int cnd_wait(cnd_t *cond, mtx_t *mtx);
|
||||
|
||||
/** Wait for a condition variable to become signaled.
|
||||
* The function atomically unlocks the given mutex and endeavors to block until
|
||||
* the given condition variable is signaled by a call to cnd_signal or to
|
||||
* cnd_broadcast, or until after the specified time. When the calling thread
|
||||
* becomes unblocked it locks the mutex before it returns.
|
||||
* @param cond A condition variable object.
|
||||
* @param mtx A mutex object.
|
||||
* @param xt A point in time at which the request will time out (absolute time).
|
||||
* @return @ref thrd_success upon success, or @ref thrd_timeout if the time
|
||||
* specified in the call was reached without acquiring the requested resource, or
|
||||
* @ref thrd_error if the request could not be honored.
|
||||
*/
|
||||
int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const struct timespec *ts);
|
||||
|
||||
/* Thread */
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
typedef HANDLE thrd_t;
|
||||
#else
|
||||
typedef pthread_t thrd_t;
|
||||
#endif
|
||||
|
||||
/** Thread start function.
|
||||
* Any thread that is started with the @ref thrd_create() function must be
|
||||
* started through a function of this type.
|
||||
* @param arg The thread argument (the @c arg argument of the corresponding
|
||||
* @ref thrd_create() call).
|
||||
* @return The thread return value, which can be obtained by another thread
|
||||
* by using the @ref thrd_join() function.
|
||||
*/
|
||||
typedef int (*thrd_start_t)(void *arg);
|
||||
|
||||
/** Create a new thread.
|
||||
* @param thr Identifier of the newly created thread.
|
||||
* @param func A function pointer to the function that will be executed in
|
||||
* the new thread.
|
||||
* @param arg An argument to the thread function.
|
||||
* @return @ref thrd_success on success, or @ref thrd_nomem if no memory could
|
||||
* be allocated for the thread requested, or @ref thrd_error if the request
|
||||
* could not be honored.
|
||||
* @note A thread’s identifier may be reused for a different thread once the
|
||||
* original thread has exited and either been detached or joined to another
|
||||
* thread.
|
||||
*/
|
||||
int thrd_create(thrd_t *thr, thrd_start_t func, void *arg);
|
||||
|
||||
/** Identify the calling thread.
|
||||
* @return The identifier of the calling thread.
|
||||
*/
|
||||
thrd_t thrd_current(void);
|
||||
|
||||
/** NOT YET IMPLEMENTED.
|
||||
*/
|
||||
int thrd_detach(thrd_t thr);
|
||||
|
||||
/** Compare two thread identifiers.
|
||||
* The function determines if two thread identifiers refer to the same thread.
|
||||
* @return Zero if the two thread identifiers refer to different threads.
|
||||
* Otherwise a nonzero value is returned.
|
||||
*/
|
||||
int thrd_equal(thrd_t thr0, thrd_t thr1);
|
||||
|
||||
/** Terminate execution of the calling thread.
|
||||
* @param res Result code of the calling thread.
|
||||
*/
|
||||
void thrd_exit(int res);
|
||||
|
||||
/** Wait for a thread to terminate.
|
||||
* The function joins the given thread with the current thread by blocking
|
||||
* until the other thread has terminated.
|
||||
* @param thr The thread to join with.
|
||||
* @param res If this pointer is not NULL, the function will store the result
|
||||
* code of the given thread in the integer pointed to by @c res.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int thrd_join(thrd_t thr, int *res);
|
||||
|
||||
/** Put the calling thread to sleep.
|
||||
* Suspend execution of the calling thread.
|
||||
* @param time_point A point in time at which the thread will resume (absolute time).
|
||||
* @param remaining If non-NULL, this parameter will hold the remaining time until
|
||||
* time_point upon return. This will typically be zero, but if
|
||||
* the thread was woken up by a signal that is not ignored before
|
||||
* time_point was reached @c remaining will hold a positive
|
||||
* time.
|
||||
* @return 0 (zero) on successful sleep, or -1 if an interrupt occurred.
|
||||
*/
|
||||
int thrd_sleep(const struct timespec *time_point, struct timespec *remaining);
|
||||
|
||||
/** Yield execution to another thread.
|
||||
* Permit other threads to run, even if the current thread would ordinarily
|
||||
* continue to run.
|
||||
*/
|
||||
void thrd_yield(void);
|
||||
|
||||
/* Thread local storage */
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
typedef DWORD tss_t;
|
||||
#else
|
||||
typedef pthread_key_t tss_t;
|
||||
#endif
|
||||
|
||||
/** Destructor function for a thread-specific storage.
|
||||
* @param val The value of the destructed thread-specific storage.
|
||||
*/
|
||||
typedef void (*tss_dtor_t)(void *val);
|
||||
|
||||
/** Create a thread-specific storage.
|
||||
* @param key The unique key identifier that will be set if the function is
|
||||
* successful.
|
||||
* @param dtor Destructor function. This can be NULL.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
* @note The destructor function is not supported under Windows. If @c dtor is
|
||||
* not NULL when calling this function under Windows, the function will fail
|
||||
* and return @ref thrd_error.
|
||||
*/
|
||||
int tss_create(tss_t *key, tss_dtor_t dtor);
|
||||
|
||||
/** Delete a thread-specific storage.
|
||||
* The function releases any resources used by the given thread-specific
|
||||
* storage.
|
||||
* @param key The key that shall be deleted.
|
||||
*/
|
||||
void tss_delete(tss_t key);
|
||||
|
||||
/** Get the value for a thread-specific storage.
|
||||
* @param key The thread-specific storage identifier.
|
||||
* @return The value for the current thread held in the given thread-specific
|
||||
* storage.
|
||||
*/
|
||||
void *tss_get(tss_t key);
|
||||
|
||||
/** Set the value for a thread-specific storage.
|
||||
* @param key The thread-specific storage identifier.
|
||||
* @param val The value of the thread-specific storage to set for the current
|
||||
* thread.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int tss_set(tss_t key, void *val);
|
||||
|
||||
|
||||
#endif /* _TINYTHREAD_H_ */
|
||||
|
@ -8,12 +8,19 @@ else()
|
||||
link_libraries(${glfw_LIBRARIES})
|
||||
endif()
|
||||
|
||||
list(APPEND thread_LIBRARIES ${CMAKE_THREAD_LIBS_INIT})
|
||||
if (UNIX AND NOT APPLE)
|
||||
list(APPEND thread_LIBRARIES ${RT_LIBRARY})
|
||||
endif()
|
||||
|
||||
include_directories(${GLFW_SOURCE_DIR}/include
|
||||
${GLFW_SOURCE_DIR}/support
|
||||
${OPENGL_INCLUDE_DIR})
|
||||
|
||||
set(GETOPT ${GLFW_SOURCE_DIR}/support/getopt.h
|
||||
${GLFW_SOURCE_DIR}/support/getopt.c)
|
||||
set(TINYCTHREAD ${GLFW_SOURCE_DIR}/support/tinycthread.h
|
||||
${GLFW_SOURCE_DIR}/support/tinycthread.c)
|
||||
|
||||
add_executable(clipboard clipboard.c ${GETOPT})
|
||||
add_executable(defaults defaults.c)
|
||||
@ -37,13 +44,20 @@ set_target_properties(sharing PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Sharing")
|
||||
add_executable(tearing WIN32 MACOSX_BUNDLE tearing.c)
|
||||
set_target_properties(tearing PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Tearing")
|
||||
|
||||
add_executable(threads WIN32 MACOSX_BUNDLE threads.c ${TINYCTHREAD})
|
||||
set_target_properties(threads PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Threads")
|
||||
|
||||
add_executable(title WIN32 MACOSX_BUNDLE title.c)
|
||||
set_target_properties(title PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Title")
|
||||
|
||||
add_executable(windows WIN32 MACOSX_BUNDLE windows.c)
|
||||
set_target_properties(windows PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Windows")
|
||||
|
||||
set(WINDOWS_BINARIES accuracy sharing tearing title windows)
|
||||
if (BUILD_SHARED_LIBS)
|
||||
target_link_libraries(threads ${thread_LIBRARIES})
|
||||
endif()
|
||||
|
||||
set(WINDOWS_BINARIES accuracy sharing tearing threads title windows)
|
||||
set(CONSOLE_BINARIES clipboard defaults events fsaa fsfocus gamma glfwinfo
|
||||
iconify joysticks modes peter reopen)
|
||||
|
||||
|
@ -317,6 +317,9 @@ int main(int argc, char** argv)
|
||||
get_client_api_name(api),
|
||||
glGetString(GL_VENDOR));
|
||||
|
||||
printf("OpenGL context debug flag saved by GLFW: %s\n",
|
||||
glfwGetWindowParam(window, GLFW_OPENGL_DEBUG_CONTEXT) ? "true" : "false");
|
||||
|
||||
if (major > 1)
|
||||
{
|
||||
printf("%s context shading language version: \"%s\"\n",
|
||||
|
131
tests/threads.c
Normal file
131
tests/threads.c
Normal file
@ -0,0 +1,131 @@
|
||||
//========================================================================
|
||||
// Multithreading test
|
||||
// Copyright (c) Camilla Berglund <elmindreda@elmindreda.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.
|
||||
//
|
||||
//========================================================================
|
||||
//
|
||||
// This test is intended to verify whether the OpenGL context part of
|
||||
// the GLFW API is able to be used from multiple threads
|
||||
//
|
||||
//========================================================================
|
||||
|
||||
#include <GL/glfw3.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "tinycthread.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GLFWwindow window;
|
||||
const char* title;
|
||||
float r, g, b;
|
||||
thrd_t id;
|
||||
} Thread;
|
||||
|
||||
static volatile GLboolean running = GL_TRUE;
|
||||
|
||||
static int thread_main(void* data)
|
||||
{
|
||||
const Thread* thread = (const Thread*) data;
|
||||
|
||||
glfwMakeContextCurrent(thread->window);
|
||||
assert(glfwGetCurrentContext() == thread->window);
|
||||
|
||||
glfwSwapInterval(1);
|
||||
|
||||
while (running)
|
||||
{
|
||||
const float v = (float) fabs(sin(glfwGetTime() * 2.f));
|
||||
glClearColor(thread->r * v, thread->g * v, thread->b * v, 0.f);
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glfwSwapBuffers(thread->window);
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent(NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int i, result;
|
||||
Thread threads[] =
|
||||
{
|
||||
{ NULL, "Red", 1.f, 0.f, 0.f, 0 },
|
||||
{ NULL, "Green", 0.f, 1.f, 0.f, 0 },
|
||||
{ NULL, "Blue", 0.f, 0.f, 1.f, 0 }
|
||||
};
|
||||
const int count = sizeof(threads) / sizeof(Thread);
|
||||
|
||||
if (!glfwInit())
|
||||
{
|
||||
fprintf(stderr, "Failed to initialize GLFW: %s\n",
|
||||
glfwErrorString(glfwGetError()));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
threads[i].window = glfwCreateWindow(200, 200,
|
||||
GLFW_WINDOWED,
|
||||
threads[i].title,
|
||||
NULL);
|
||||
if (!threads[i].window)
|
||||
{
|
||||
fprintf(stderr, "Failed to open GLFW window: %s\n",
|
||||
glfwErrorString(glfwGetError()));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
glfwSetWindowPos(threads[i].window, 200 + 250 * i, 200);
|
||||
|
||||
if (thrd_create(&threads[i].id, thread_main, threads + i) !=
|
||||
thrd_success)
|
||||
{
|
||||
fprintf(stderr, "Failed to create secondary thread\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
while (running)
|
||||
{
|
||||
assert(glfwGetCurrentContext() == NULL);
|
||||
|
||||
glfwWaitEvents();
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
if (glfwGetWindowParam(threads[i].window, GLFW_CLOSE_REQUESTED))
|
||||
running = GL_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
thrd_join(threads[i].id, &result);
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user