From 25204b1ec7bf0c404bb79a756452e4903bb64c8e Mon Sep 17 00:00:00 2001 From: Ricardo Vieira Date: Tue, 9 Sep 2014 16:37:55 +0200 Subject: [PATCH] wayland: Support for setting a cursor image Closes #346. --- CMake/modules/FindWayland.cmake | 10 +- src/wl_init.c | 27 ++++ src/wl_platform.h | 13 +- src/wl_window.c | 221 +++++++++++++++++++++++++++++--- 4 files changed, 248 insertions(+), 23 deletions(-) diff --git a/CMake/modules/FindWayland.cmake b/CMake/modules/FindWayland.cmake index 00c17a3c..71f51675 100644 --- a/CMake/modules/FindWayland.cmake +++ b/CMake/modules/FindWayland.cmake @@ -27,21 +27,23 @@ IF (NOT WIN32) # Use pkg-config to get the directories and then use these values # in the FIND_PATH() and FIND_LIBRARY() calls FIND_PACKAGE(PkgConfig) - PKG_CHECK_MODULES(PKG_WAYLAND QUIET wayland-client wayland-server wayland-egl) + PKG_CHECK_MODULES(PKG_WAYLAND QUIET wayland-client wayland-server wayland-egl wayland-cursor) SET(WAYLAND_DEFINITIONS ${PKG_WAYLAND_CFLAGS}) FIND_PATH(WAYLAND_CLIENT_INCLUDE_DIR NAMES wayland-client.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS}) FIND_PATH(WAYLAND_SERVER_INCLUDE_DIR NAMES wayland-server.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS}) FIND_PATH(WAYLAND_EGL_INCLUDE_DIR NAMES wayland-egl.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS}) + FIND_PATH(WAYLAND_CURSOR_INCLUDE_DIR NAMES wayland-cursor.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS}) FIND_LIBRARY(WAYLAND_CLIENT_LIBRARIES NAMES wayland-client HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) FIND_LIBRARY(WAYLAND_SERVER_LIBRARIES NAMES wayland-server HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) FIND_LIBRARY(WAYLAND_EGL_LIBRARIES NAMES wayland-egl HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) + FIND_LIBRARY(WAYLAND_CURSOR_LIBRARIES NAMES wayland-cursor HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) - set(WAYLAND_INCLUDE_DIR ${WAYLAND_CLIENT_INCLUDE_DIR} ${WAYLAND_SERVER_INCLUDE_DIR} ${WAYLAND_EGL_INCLUDE_DIR}) + set(WAYLAND_INCLUDE_DIR ${WAYLAND_CLIENT_INCLUDE_DIR} ${WAYLAND_SERVER_INCLUDE_DIR} ${WAYLAND_EGL_INCLUDE_DIR} ${WAYLAND_CURSOR_INCLUDE_DIR}) - set(WAYLAND_LIBRARIES ${WAYLAND_CLIENT_LIBRARIES} ${WAYLAND_SERVER_LIBRARIES} ${WAYLAND_EGL_LIBRARIES}) + set(WAYLAND_LIBRARIES ${WAYLAND_CLIENT_LIBRARIES} ${WAYLAND_SERVER_LIBRARIES} ${WAYLAND_EGL_LIBRARIES} ${WAYLAND_CURSOR_LIBRARIES}) list(REMOVE_DUPLICATES WAYLAND_INCLUDE_DIR) @@ -50,6 +52,7 @@ IF (NOT WIN32) FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND_CLIENT DEFAULT_MSG WAYLAND_CLIENT_LIBRARIES WAYLAND_CLIENT_INCLUDE_DIR) FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND_SERVER DEFAULT_MSG WAYLAND_SERVER_LIBRARIES WAYLAND_SERVER_INCLUDE_DIR) FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND_EGL DEFAULT_MSG WAYLAND_EGL_LIBRARIES WAYLAND_EGL_INCLUDE_DIR) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND_EGL DEFAULT_MSG WAYLAND_CURSOR_LIBRARIES WAYLAND_CURSOR_INCLUDE_DIR) FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND DEFAULT_MSG WAYLAND_LIBRARIES WAYLAND_INCLUDE_DIR) MARK_AS_ADVANCED( @@ -57,6 +60,7 @@ IF (NOT WIN32) WAYLAND_CLIENT_INCLUDE_DIR WAYLAND_CLIENT_LIBRARIES WAYLAND_SERVER_INCLUDE_DIR WAYLAND_SERVER_LIBRARIES WAYLAND_EGL_INCLUDE_DIR WAYLAND_EGL_LIBRARIES + WAYLAND_CURSOR_INCLUDE_DIR WAYLAND_CURSOR_LIBRARIES ) ENDIF () diff --git a/src/wl_init.c b/src/wl_init.c index b8d5d0d1..46f8179d 100644 --- a/src/wl_init.c +++ b/src/wl_init.c @@ -46,7 +46,10 @@ static void pointerHandleEnter(void* data, { _GLFWwindow* window = wl_surface_get_user_data(surface); + _glfw.wl.pointerSerial = serial; _glfw.wl.pointerFocus = window; + + _glfwPlatformSetCursor(window, window->wl.currentCursor); _glfwInputCursorEnter(window, GL_TRUE); } @@ -60,6 +63,7 @@ static void pointerHandleLeave(void* data, if (!window) return; + _glfw.wl.pointerSerial = serial; _glfw.wl.pointerFocus = NULL; _glfwInputCursorEnter(window, GL_FALSE); } @@ -491,6 +495,11 @@ static void registryHandleGlobal(void* data, _glfw.wl.compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1); } + else if (strcmp(interface, "wl_shm") == 0) + { + _glfw.wl.shm = + wl_registry_bind(registry, name, &wl_shm_interface, 1); + } else if (strcmp(interface, "wl_shell") == 0) { _glfw.wl.shell = @@ -564,6 +573,20 @@ int _glfwPlatformInit(void) _glfwInitTimer(); _glfwInitJoysticks(); + if (_glfw.wl.pointer && _glfw.wl.shm){ + _glfw.wl.cursorTheme = wl_cursor_theme_load(NULL, 24, _glfw.wl.shm); + if (!_glfw.wl.cursorTheme) { + _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Unable to load default cursor theme\n"); + return GL_FALSE; + } + _glfw.wl.defaultCursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme, "left_ptr"); + if (!_glfw.wl.defaultCursor) { + _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Unable to load default left pointer\n"); + return GL_FALSE; + } + _glfw.wl.cursorSurface = wl_compositor_create_surface(_glfw.wl.compositor); + } + return GL_TRUE; } @@ -571,6 +594,10 @@ void _glfwPlatformTerminate(void) { _glfwTerminateContextAPI(); + if (_glfw.wl.cursorTheme) + wl_cursor_theme_destroy(_glfw.wl.cursorTheme); + if (_glfw.wl.cursorSurface) + wl_surface_destroy(_glfw.wl.cursorSurface); if (_glfw.wl.registry) wl_registry_destroy(_glfw.wl.registry); if (_glfw.wl.display) diff --git a/src/wl_platform.h b/src/wl_platform.h index 8179917f..a498e386 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -66,7 +66,7 @@ typedef struct _GLFWwindowWayland struct wl_shell_surface* shell_surface; EGLSurface egl_surface; struct wl_callback* callback; - + _GLFWcursor* currentCursor; } _GLFWwindowWayland; @@ -78,10 +78,16 @@ typedef struct _GLFWlibraryWayland struct wl_registry* registry; struct wl_compositor* compositor; struct wl_shell* shell; + struct wl_shm* shm; struct wl_seat* seat; struct wl_pointer* pointer; struct wl_keyboard* keyboard; + struct wl_cursor_theme* cursorTheme; + struct wl_cursor* defaultCursor; + struct wl_surface* cursorSurface; + uint32_t pointerSerial; + _GLFWmonitor** monitors; int monitorsCount; int monitorsSize; @@ -124,8 +130,9 @@ typedef struct _GLFWmonitorWayland // typedef struct _GLFWcursorWayland { - int dummy; - + struct wl_buffer* buffer; + int width, height; + int xhot, yhot; } _GLFWcursorWayland; diff --git a/src/wl_window.c b/src/wl_window.c index e02b0cdf..c9d6b6d7 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -27,9 +27,16 @@ #include "internal.h" #include +#include +#include +#include +#include +#include +#include #include #include #include +#include static void handlePing(void* data, @@ -93,6 +100,105 @@ static GLboolean createSurface(_GLFWwindow* window, return GL_TRUE; } +static int +set_cloexec_or_close(int fd) +{ + long flags; + + if (fd == -1) + return -1; + + flags = fcntl(fd, F_GETFD); + if (flags == -1) + goto err; + + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + goto err; + + return fd; + +err: + close(fd); + return -1; +} + +static int +create_tmpfile_cloexec(char *tmpname) +{ + int fd; + +#ifdef HAVE_MKOSTEMP + fd = mkostemp(tmpname, O_CLOEXEC); + if (fd >= 0) + unlink(tmpname); +#else + fd = mkstemp(tmpname); + if (fd >= 0) { + fd = set_cloexec_or_close(fd); + unlink(tmpname); + } +#endif + + return fd; +} + +/* + * Create a new, unique, anonymous file of the given size, and + * return the file descriptor for it. The file descriptor is set + * CLOEXEC. The file is immediately suitable for mmap()'ing + * the given size at offset zero. + * + * The file should not have a permanent backing store like a disk, + * but may have if XDG_RUNTIME_DIR is not properly implemented in OS. + * + * The file name is deleted from the file system. + * + * The file is suitable for buffer sharing between processes by + * transmitting the file descriptor over Unix sockets using the + * SCM_RIGHTS methods. + * + * If the C library implements posix_fallocate(), it is used to + * guarantee that disk space is available for the file at the + * given size. If disk space is insufficent, errno is set to ENOSPC. + * If posix_fallocate() is not supported, program may receive + * SIGBUS on accessing mmap()'ed file contents instead. + */ +int +os_create_anonymous_file(off_t size) +{ + static const char template[] = "/glfw-shared-XXXXXX"; + const char *path; + char *name; + int fd; + int ret; + + path = getenv("XDG_RUNTIME_DIR"); + if (!path) { + errno = ENOENT; + return -1; + } + + name = malloc(strlen(path) + sizeof(template)); + if (!name) + return -1; + + strcpy(name, path); + strcat(name, template); + + fd = create_tmpfile_cloexec(name); + + free(name); + + if (fd < 0) + return -1; + ret = posix_fallocate(fd, 0, size); + if (ret != 0) { + close(fd); + errno = ret; + return -1; + } + return fd; +} ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// @@ -122,6 +228,8 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, wl_shell_surface_set_toplevel(window->wl.shell_surface); } + window->wl.currentCursor = NULL; + return GL_TRUE; } @@ -279,37 +387,116 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) void _glfwPlatformApplyCursorMode(_GLFWwindow* window) { - fprintf(stderr, "_glfwPlatformApplyCursorMode not implemented yet\n"); - switch (window->cursorMode) - { - case GLFW_CURSOR_NORMAL: - // TODO: enable showing cursor - break; - case GLFW_CURSOR_HIDDEN: - // TODO: enable not showing cursor - break; - case GLFW_CURSOR_DISABLED: - // TODO: enable pointer lock and hide cursor - break; - } + _glfwPlatformSetCursor(window, window->wl.currentCursor); } int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot) { - fprintf(stderr, "_glfwPlatformCreateCursor not implemented yet\n"); - return GL_FALSE; + struct wl_shm_pool *pool; + int stride = image->width * 4; + int length = image->width * image->height * 4; + void *data; + int fd, i; + + fd = os_create_anonymous_file(length); + if (fd < 0) { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: Creating a buffer file for %d B failed: %m\n", + length); + return GL_FALSE; + } + + data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: Cursor mmap failed: %m\n"); + close(fd); + return GL_FALSE; + } + + pool = wl_shm_create_pool(_glfw.wl.shm, fd, length); + + close(fd); + unsigned char* source = (unsigned char*) image->pixels; + unsigned char* target = data; + for (i = 0; i < image->width * image->height; i++, source += 4) + { + *target++ = source[2]; + *target++ = source[1]; + *target++ = source[0]; + *target++ = source[3]; + } + + cursor->wl.buffer = wl_shm_pool_create_buffer(pool, 0, + image->width, + image->height, + stride, WL_SHM_FORMAT_ARGB8888); + munmap(data, length); + wl_shm_pool_destroy(pool); + + cursor->wl.width = image->width; + cursor->wl.height = image->height; + cursor->wl.xhot = xhot; + cursor->wl.yhot = yhot; + return GL_TRUE; } void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) { - fprintf(stderr, "_glfwPlatformDestroyCursor not implemented yet\n"); + wl_buffer_destroy(cursor->wl.buffer); } void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) { - fprintf(stderr, "_glfwPlatformSetCursor not implemented yet\n"); + struct wl_buffer *buffer; + struct wl_cursor_image *image; + struct wl_surface *surface = _glfw.wl.cursorSurface; + + if (!_glfw.wl.pointer) + return; + + window->wl.currentCursor = cursor; + + // If we're not in the correct window just save the cursor + // the next time the pointer enters the window the cursor will change + if (window != _glfw.wl.pointerFocus) + return; + + if (window->cursorMode == GLFW_CURSOR_NORMAL) + { + if (cursor == NULL) + { + image = _glfw.wl.defaultCursor->images[0]; + buffer = wl_cursor_image_get_buffer(image); + if (!buffer) + return; + wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial, + surface, + image->hotspot_x, + image->hotspot_y); + wl_surface_attach(surface, buffer, 0, 0); + wl_surface_damage(surface, 0, 0, + image->width, image->height); + wl_surface_commit(surface); + } + else + { + wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial, + surface, + cursor->wl.xhot, + cursor->wl.yhot); + wl_surface_attach(surface, cursor->wl.buffer, 0, 0); + wl_surface_damage(surface, 0, 0, + cursor->wl.width, cursor->wl.height); + wl_surface_commit(surface); + } + } + else /* Cursor is hidden set cursor surface to NULL */ + { + wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial, NULL, 0, 0); + } } void _glfwPlatformSetClipboardString(_GLFWwindow* window, const char* string)