diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 22ce68f1b..0414da5fa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,13 +18,13 @@ elseif (_GLFW_WIN32) win32_monitor.c win32_time.c win32_thread.c win32_window.c wgl_context.c egl_context.c osmesa_context.c) elseif (_GLFW_X11) - set(glfw_HEADERS ${common_HEADERS} x11_platform.h xkb_unicode.h posix_time.h + set(glfw_HEADERS ${common_HEADERS} x11_platform.h xkb_unicode.h unix_commons.h posix_time.h posix_thread.h glx_context.h egl_context.h osmesa_context.h) set(glfw_SOURCES ${common_SOURCES} x11_init.c x11_monitor.c x11_window.c xkb_unicode.c posix_time.c posix_thread.c glx_context.c egl_context.c osmesa_context.c) elseif (_GLFW_WAYLAND) - set(glfw_HEADERS ${common_HEADERS} wl_platform.h + set(glfw_HEADERS ${common_HEADERS} wl_platform.h unix_commons.h posix_time.h posix_thread.h xkb_unicode.h egl_context.h osmesa_context.h) set(glfw_SOURCES ${common_SOURCES} wl_init.c wl_monitor.c wl_window.c diff --git a/src/unix_commons.h b/src/unix_commons.h new file mode 100644 index 000000000..b7acc8f50 --- /dev/null +++ b/src/unix_commons.h @@ -0,0 +1,102 @@ +//======================================================================== +// GLFW 3.3 Unix commons - www.glfw.org +//------------------------------------------------------------------------ +// Copyright (c) 2014 Jonas Ådahl +// +// 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. +// +//======================================================================== + +#pragma once +#include +#include +#include +#include + +#ifdef __NetBSD__ +#define ppoll pollts +#endif + +static inline int +initWakeup(int fds[2]) +{ + return pipe2(fds, O_CLOEXEC | O_NONBLOCK); +} + +static inline void +wakeUp(int fd) { + while (write(fd, "w", 1) < 0 && errno == EINTR); +} + +/* + * Uses ppoll to wait until an event occurs. + * + * If an event is received from the "wakeup" file descriptor, + * this file descriptor is drained. + * + * @param timeout: timeout in seconds. strictly negative + * values mean "no timeout". + */ +static inline int +ppollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout) +{ + for (nfds_t i = 0; i < nfds; i++) + { + fds[i].revents = 0; + } + + int res; + if(timeout >= 0.0) + { + const long seconds = (long) timeout; + const long nanoseconds = (long) ((timeout - seconds) * 1e9); + struct timespec tv = { seconds, nanoseconds }; + res = ppoll(fds, nfds, &tv, NULL); + } + else + { + res = ppoll(fds, nfds, NULL, NULL); + } + + if(res > 0) + { + if (fds[0].revents & POLLIN) + { + // an empty event has been posted: now that we are woken up, + // we can ignore other potential empty events. + static char drain_buf[64]; + while(read(fds[0].fd, drain_buf, sizeof(drain_buf)) < 0 && errno == EINTR); + } + } + return res; +} + +static inline void +closeFds(int *fds, size_t count) +{ + while(count--) { + if (*fds > 0) { + close(*fds); + *fds = -1; + } + fds++; + } +} + diff --git a/src/wl_init.c b/src/wl_init.c index c19184d0d..affe7b32a 100644 --- a/src/wl_init.c +++ b/src/wl_init.c @@ -24,7 +24,9 @@ // //======================================================================== +#define _GNU_SOURCE #include "internal.h" +#include "unix_commons.h" #include #include @@ -34,6 +36,7 @@ #include #include #include +#include #include @@ -939,6 +942,13 @@ static void createKeyTables(void) int _glfwPlatformInit(void) { + if(initWakeup(_glfw.wl.eventLoopData.wakeupFds) != 0) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: failed to create self pipe"); + return GLFW_FALSE; + } + _glfw.wl.cursor.handle = _glfw_dlopen("libwayland-cursor.so.0"); if (!_glfw.wl.cursor.handle) { @@ -1054,9 +1064,30 @@ int _glfwPlatformInit(void) _glfwInitTimerPOSIX(); _glfw.wl.timerfd = -1; + // The repeat_info event is only available since wl_keyboard version 4, + // so there is no need to create a timer if we won’t have any way to use it, + // see: https://cgit.freedesktop.org/wayland/wayland/tree/protocol/wayland.xml#n2184 if (_glfw.wl.seatVersion >= 4) _glfw.wl.timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); + _glfw.wl.eventLoopData.fds[0].events = POLLIN; + _glfw.wl.eventLoopData.fds[0].fd = _glfw.wl.eventLoopData.wakeupFds[0]; + + _glfw.wl.eventLoopData.fds[1].events = POLLIN; + _glfw.wl.eventLoopData.fds[1].fd = wl_display_get_fd(_glfw.wl.display); + + _glfw.wl.eventLoopData.fds[2].events = POLLIN; + if (_glfw.wl.seatVersion >= 4) + { + _glfw.wl.eventLoopData.nFds = 3; + _glfw.wl.eventLoopData.fds[2].fd = _glfw.wl.timerfd; + } + else + { + _glfw.wl.eventLoopData.nFds = 2; + _glfw.wl.eventLoopData.fds[2].fd = 0; + } + if (_glfw.wl.pointer && _glfw.wl.shm) { _glfw.wl.cursorTheme = wl_cursor_theme_load(NULL, 32, _glfw.wl.shm); @@ -1142,6 +1173,8 @@ void _glfwPlatformTerminate(void) wl_display_flush(_glfw.wl.display); wl_display_disconnect(_glfw.wl.display); } + closeFds(_glfw.wl.eventLoopData.wakeupFds + , sizeof(_glfw.wl.eventLoopData.wakeupFds)/sizeof(_glfw.wl.eventLoopData.wakeupFds[0])); } const char* _glfwPlatformGetVersionString(void) diff --git a/src/wl_platform.h b/src/wl_platform.h index ef8419e7b..350ed70a9 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -30,6 +30,7 @@ #include #endif #include +#include typedef VkFlags VkWaylandSurfaceCreateFlagsKHR; @@ -312,6 +313,12 @@ typedef struct _GLFWlibraryWayland PFN_wl_egl_window_resize window_resize; } egl; + struct { + nfds_t nFds; // the number of fds that need to be polled + struct pollfd fds[3]; + int wakeupFds[2]; + } eventLoopData; + } _GLFWlibraryWayland; // Wayland-specific per-monitor data diff --git a/src/wl_window.c b/src/wl_window.c index c165ba73a..91b3e6684 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -27,16 +27,14 @@ #define _GNU_SOURCE #include "internal.h" +#include "unix_commons.h" #include #include #include -#include #include -#include #include #include -#include static void handlePing(void* data, @@ -691,15 +689,9 @@ static GLFWbool createXdgSurface(_GLFWwindow* window) } static void -handleEvents(int timeout) +handleEvents(double timeout) { struct wl_display* display = _glfw.wl.display; - struct pollfd fds[] = { - { wl_display_get_fd(display), POLLIN }, - { _glfw.wl.timerfd, POLLIN }, - }; - ssize_t read_ret; - uint64_t repeats, i; while (wl_display_prepare_read(display) != 0) wl_display_dispatch_pending(display); @@ -719,34 +711,37 @@ handleEvents(int timeout) return; } - if (poll(fds, 2, timeout) > 0) + if (ppollWithTimeout(_glfw.wl.eventLoopData.fds + , _glfw.wl.eventLoopData.nFds + , timeout) <= 0) { - if (fds[0].revents & POLLIN) - { - wl_display_read_events(display); - wl_display_dispatch_pending(display); - } - else - { - wl_display_cancel_read(display); - } + // ppoll errored or timeout-ed + wl_display_cancel_read(display); + return; + } - if (fds[1].revents & POLLIN) - { - read_ret = read(_glfw.wl.timerfd, &repeats, sizeof(repeats)); - if (read_ret != 8) - return; - - for (i = 0; i < repeats; ++i) - _glfwInputKey(_glfw.wl.keyboardFocus, _glfw.wl.keyboardLastKey, - _glfw.wl.keyboardLastScancode, GLFW_REPEAT, - _glfw.wl.xkb.modifiers); - } + if(_glfw.wl.eventLoopData.fds[1].revents & POLLIN) + { + wl_display_read_events(display); + wl_display_dispatch_pending(display); } else { wl_display_cancel_read(display); } + + if (_glfw.wl.seatVersion >= 4 && + (_glfw.wl.eventLoopData.fds[2].revents & POLLIN)) + { + uint64_t repeats; + ssize_t read_ret = read(_glfw.wl.timerfd, &repeats, sizeof(repeats)); + if (read_ret != sizeof(repeats)) + return; + for (uint64_t i = 0; i < repeats; ++i) + _glfwInputKey(_glfw.wl.keyboardFocus, _glfw.wl.keyboardLastKey, + _glfw.wl.keyboardLastScancode, GLFW_REPEAT, + _glfw.wl.xkb.modifiers); + } } // Translates a GLFW standard cursor to a theme cursor name @@ -1176,22 +1171,26 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) void _glfwPlatformPollEvents(void) { - handleEvents(0); + handleEvents(0.0); } void _glfwPlatformWaitEvents(void) { - handleEvents(-1); + handleEvents(-1.0); } void _glfwPlatformWaitEventsTimeout(double timeout) { - handleEvents((int) (timeout * 1e3)); + handleEvents(timeout); } void _glfwPlatformPostEmptyEvent(void) { wl_display_sync(_glfw.wl.display); + // To avoid a race condition between glfwPostEmptyEvent and glfwWaitEvents[Timeout] + // (see https://github.com/glfw/glfw/issues/1281), + // we use a secondary mechanism to notify that an empty event has been posted: + wakeUp(_glfw.wl.eventLoopData.wakeupFds[1]); } void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) diff --git a/src/x11_init.c b/src/x11_init.c index c949916d3..45c3b2801 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -25,7 +25,9 @@ // //======================================================================== +#define _GNU_SOURCE #include "internal.h" +#include "unix_commons.h" #include @@ -34,6 +36,7 @@ #include #include #include +#include // Translate an X11 key code to a GLFW key code. @@ -913,7 +916,6 @@ Cursor _glfwCreateCursorX11(const GLFWimage* image, int xhot, int yhot) return cursor; } - ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// @@ -951,6 +953,31 @@ int _glfwPlatformInit(void) return GLFW_FALSE; } + if(initWakeup(_glfw.x11.eventLoopData.wakeupFds) != 0) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "X11: failed to create self pipe"); + return GLFW_FALSE; + } + + _glfw.x11.eventLoopData.nFds = 2; + + _glfw.x11.eventLoopData.fds[0].events = POLLIN; + _glfw.x11.eventLoopData.fds[0].fd = _glfw.x11.eventLoopData.wakeupFds[0]; + + _glfw.x11.eventLoopData.fds[1].events = POLLIN; + _glfw.x11.eventLoopData.fds[1].fd = ConnectionNumber(_glfw.x11.display); + + _glfw.x11.eventLoopData.fds[2].events = POLLIN; + _glfw.x11.eventLoopData.fds[2].fd = 0; +#if defined (__linux__) + if(_glfw.linjs.inotify > 0) + { + _glfw.x11.eventLoopData.nFds = 3; + _glfw.x11.eventLoopData.fds[2].fd = _glfw.linjs.inotify; + } +#endif + _glfw.x11.screen = DefaultScreen(_glfw.x11.display); _glfw.x11.root = RootWindow(_glfw.x11.display, _glfw.x11.screen); _glfw.x11.context = XUniqueContext(); @@ -1018,10 +1045,16 @@ void _glfwPlatformTerminate(void) _glfw.x11.im = NULL; } + closeFds(_glfw.x11.eventLoopData.wakeupFds + , sizeof(_glfw.x11.eventLoopData.wakeupFds)/ + sizeof(_glfw.x11.eventLoopData.wakeupFds[0])); + + if (_glfw.x11.display) { XCloseDisplay(_glfw.x11.display); _glfw.x11.display = NULL; + _glfw.x11.eventLoopData.fds[0].fd = -1; } if (_glfw.x11.x11xcb.handle) diff --git a/src/x11_platform.h b/src/x11_platform.h index c37c740e4..6409094a2 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -399,6 +400,12 @@ typedef struct _GLFWlibraryX11 PFN_XRenderFindVisualFormat FindVisualFormat; } xrender; + struct { + nfds_t nFds; // the number of fds that need to be polled + struct pollfd fds[3]; + int wakeupFds[2]; + } eventLoopData; + } _GLFWlibraryX11; // X11-specific per-monitor data diff --git a/src/x11_window.c b/src/x11_window.c index 1c4e9c3c0..efae23553 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -25,13 +25,13 @@ // //======================================================================== +#define _GNU_SOURCE #include "internal.h" +#include "unix_commons.h" #include #include -#include - #include #include #include @@ -50,50 +50,57 @@ #define _GLFW_XDND_VERSION 5 +static int waitForEventOnceWithPpoll(double* pTimeout) +{ + if (pTimeout) + { + const uint64_t base = _glfwPlatformGetTimerValue(); -// Wait for data to arrive using select + int r = ppollWithTimeout(_glfw.x11.eventLoopData.fds + , _glfw.x11.eventLoopData.nFds + , *pTimeout); + + const uint64_t now = _glfwPlatformGetTimerValue(); + *pTimeout -= (now - base) / (double) _glfwPlatformGetTimerFrequency(); + return r; + } + else + { + return ppollWithTimeout(_glfw.x11.eventLoopData.fds + , _glfw.x11.eventLoopData.nFds + , -1.0); + } +} + +// Wait for data to arrive using ppoll // This avoids blocking other threads via the per-display Xlib lock that also // covers GLX functions // -static GLFWbool waitForEvent(double* timeout) +static GLFWbool waitForEvent(double* pTimeout) { - fd_set fds; - const int fd = ConnectionNumber(_glfw.x11.display); - int count = fd + 1; - -#if defined(__linux__) - if (_glfw.linjs.inotify > fd) - count = _glfw.linjs.inotify + 1; -#endif for (;;) { - FD_ZERO(&fds); - FD_SET(fd, &fds); -#if defined(__linux__) - if (_glfw.linjs.inotify > 0) - FD_SET(_glfw.linjs.inotify, &fds); -#endif + int res = waitForEventOnceWithPpoll(pTimeout); - if (timeout) + if(res > 0) { - const long seconds = (long) *timeout; - const long microseconds = (long) ((*timeout - seconds) * 1e6); - struct timeval tv = { seconds, microseconds }; - const uint64_t base = _glfwPlatformGetTimerValue(); - - const int result = select(count, &fds, NULL, NULL, &tv); - const int error = errno; - - *timeout -= (_glfwPlatformGetTimerValue() - base) / - (double) _glfwPlatformGetTimerFrequency(); - - if (result > 0) - return GLFW_TRUE; - if ((result == -1 && error == EINTR) || *timeout <= 0.0) - return GLFW_FALSE; - } - else if (select(count, &fds, NULL, NULL, NULL) != -1 || errno != EINTR) + // ppoll was successfull return GLFW_TRUE; + } + else if (res == 0) + { + // ppoll timed out + return GLFW_FALSE; + } + else if (errno != EINTR && errno != EAGAIN) + { + return GLFW_FALSE; + } + else if (pTimeout && *pTimeout <= 0.0) + { + // the timeout expired + return GLFW_FALSE; + } } } @@ -2730,6 +2737,11 @@ void _glfwPlatformPostEmptyEvent(void) XSendEvent(_glfw.x11.display, _glfw.x11.helperWindowHandle, False, 0, &event); XFlush(_glfw.x11.display); + + // To avoid a race condition between glfwPostEmptyEvent and glfwWaitEvents[Timeout] + // (see https://github.com/glfw/glfw/issues/1281), + // we use a secondary mechanism to notify that an empty event has been posted: + wakeUp(_glfw.x11.eventLoopData.wakeupFds[1]); } void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos)