diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index cc77ff13..c93c187e 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -162,6 +162,7 @@ video tutorials. - Orson Peters - Emmanuel Gil Peyrot - Cyril Pichard + - Pilzschaf - Keith Pitt - Stanislav Podgorskiy - Konstantin Podsvirov diff --git a/README.md b/README.md index 745c4823..1c6e41bd 100644 --- a/README.md +++ b/README.md @@ -291,6 +291,7 @@ information on what to include when reporting a bug. - [X11] Bugfix: Left shift of int constant relied on undefined behavior (#1951) - [Wayland] Added dynamic loading of all Wayland libraries - [Wayland] Added support for key names via xkbcommon + - [Wayland] Added support for file path drop events (#2040) - [Wayland] Removed support for `wl_shell` (#1443) - [Wayland] Bugfix: The `GLFW_HAND_CURSOR` shape used the wrong image (#1432) - [Wayland] Bugfix: `CLOCK_MONOTONIC` was not correctly enabled @@ -314,6 +315,13 @@ information on what to include when reporting a bug. - [Wayland] Bugfix: Some keys were reported as wrong key or `GLFW_KEY_UNKNOWN` - [Wayland] Bugfix: Text input did not repeat along with key repeat - [Wayland] Bugfix: `glfwPostEmptyEvent` sometimes had no effect (#1520,#1521) + - [Wayland] Bugfix: `glfwSetClipboardString` would fail if set to result of + `glfwGetClipboardString` + - [Wayland] Bugfix: Data source creation error would cause double free at termination + - [Wayland] Bugfix: Partial writes of clipboard string would cause beginning to repeat + - [Wayland] Bugfix: Some errors would cause clipboard string transfer to hang + - [Wayland] Bugfix: Drag and drop data was misinterpreted as clipboard string + - [Wayland] Bugfix: MIME type matching was not performed for clipboard string - [POSIX] Removed use of deprecated function `gettimeofday` - [POSIX] Bugfix: `CLOCK_MONOTONIC` was not correctly tested for or enabled - [WGL] Disabled the DWM swap interval hack for Windows 8 and later (#1072) diff --git a/src/init.c b/src/init.c index 20f85102..ef555425 100644 --- a/src/init.c +++ b/src/init.c @@ -172,6 +172,59 @@ size_t _glfwEncodeUTF8(char* s, uint32_t codepoint) return count; } +// Splits and translates a text/uri-list into separate file paths +// NOTE: This function destroys the provided string +// +char** _glfwParseUriList(char* text, int* count) +{ + const char* prefix = "file://"; + char** paths = NULL; + char* line; + + *count = 0; + + while ((line = strtok(text, "\r\n"))) + { + char* path; + + text = NULL; + + if (line[0] == '#') + continue; + + if (strncmp(line, prefix, strlen(prefix)) == 0) + { + line += strlen(prefix); + // TODO: Validate hostname + while (*line != '/') + line++; + } + + (*count)++; + + path = _glfw_calloc(strlen(line) + 1, 1); + paths = _glfw_realloc(paths, *count * sizeof(char*)); + paths[*count - 1] = path; + + while (*line) + { + if (line[0] == '%' && line[1] && line[2]) + { + const char digits[3] = { line[1], line[2], '\0' }; + *path = (char) strtol(digits, NULL, 16); + line += 2; + } + else + *path = *line; + + path++; + line++; + } + } + + return paths; +} + char* _glfw_strdup(const char* source) { const size_t length = strlen(source); diff --git a/src/input.c b/src/input.c index 90ee5273..e0a12cce 100644 --- a/src/input.c +++ b/src/input.c @@ -1428,3 +1428,4 @@ GLFWAPI uint64_t glfwGetTimerFrequency(void) _GLFW_REQUIRE_INIT_OR_RETURN(0); return _glfwPlatformGetTimerFrequency(); } + diff --git a/src/internal.h b/src/internal.h index 9e3ee273..f6345e12 100644 --- a/src/internal.h +++ b/src/internal.h @@ -1033,6 +1033,7 @@ void _glfwTerminateVulkan(void); const char* _glfwGetVulkanResultString(VkResult result); size_t _glfwEncodeUTF8(char* s, uint32_t codepoint); +char** _glfwParseUriList(char* text, int* count); char* _glfw_strdup(const char* source); int _glfw_min(int a, int b); diff --git a/src/wl_init.c b/src/wl_init.c index 16570829..bf671edd 100644 --- a/src/wl_init.c +++ b/src/wl_init.c @@ -28,7 +28,6 @@ #include "internal.h" -#include #include #include #include @@ -56,726 +55,19 @@ #include "wayland-pointer-constraints-unstable-v1-client-protocol-code.h" #include "wayland-idle-inhibit-unstable-v1-client-protocol-code.h" - -static _GLFWwindow* findWindowFromDecorationSurface(struct wl_surface* surface, - int* which) -{ - int focus; - _GLFWwindow* window = _glfw.windowListHead; - if (!which) - which = &focus; - while (window) - { - if (surface == window->wl.decorations.top.surface) - { - *which = topDecoration; - break; - } - if (surface == window->wl.decorations.left.surface) - { - *which = leftDecoration; - break; - } - if (surface == window->wl.decorations.right.surface) - { - *which = rightDecoration; - break; - } - if (surface == window->wl.decorations.bottom.surface) - { - *which = bottomDecoration; - break; - } - window = window->next; - } - return window; -} - -static void pointerHandleEnter(void* data, - struct wl_pointer* pointer, - uint32_t serial, - struct wl_surface* surface, - wl_fixed_t sx, - wl_fixed_t sy) -{ - // Happens in the case we just destroyed the surface. - if (!surface) - return; - - int focus = 0; - _GLFWwindow* window = wl_surface_get_user_data(surface); - if (!window) - { - window = findWindowFromDecorationSurface(surface, &focus); - if (!window) - return; - } - - window->wl.decorations.focus = focus; - _glfw.wl.serial = serial; - _glfw.wl.pointerEnterSerial = serial; - _glfw.wl.pointerFocus = window; - - window->wl.hovered = GLFW_TRUE; - - _glfwSetCursorWayland(window, window->wl.currentCursor); - _glfwInputCursorEnter(window, GLFW_TRUE); -} - -static void pointerHandleLeave(void* data, - struct wl_pointer* pointer, - uint32_t serial, - struct wl_surface* surface) -{ - _GLFWwindow* window = _glfw.wl.pointerFocus; - - if (!window) - return; - - window->wl.hovered = GLFW_FALSE; - - _glfw.wl.serial = serial; - _glfw.wl.pointerFocus = NULL; - _glfwInputCursorEnter(window, GLFW_FALSE); - _glfw.wl.cursorPreviousName = NULL; -} - -static void setCursor(_GLFWwindow* window, const char* name) -{ - struct wl_buffer* buffer; - struct wl_cursor* cursor; - struct wl_cursor_image* image; - struct wl_surface* surface = _glfw.wl.cursorSurface; - struct wl_cursor_theme* theme = _glfw.wl.cursorTheme; - int scale = 1; - - if (window->wl.scale > 1 && _glfw.wl.cursorThemeHiDPI) - { - // We only support up to scale=2 for now, since libwayland-cursor - // requires us to load a different theme for each size. - scale = 2; - theme = _glfw.wl.cursorThemeHiDPI; - } - - cursor = wl_cursor_theme_get_cursor(theme, name); - if (!cursor) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Standard cursor not found"); - return; - } - // TODO: handle animated cursors too. - image = cursor->images[0]; - - if (!image) - return; - - buffer = wl_cursor_image_get_buffer(image); - if (!buffer) - return; - wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerEnterSerial, - surface, - image->hotspot_x / scale, - image->hotspot_y / scale); - wl_surface_set_buffer_scale(surface, scale); - wl_surface_attach(surface, buffer, 0, 0); - wl_surface_damage(surface, 0, 0, - image->width, image->height); - wl_surface_commit(surface); - _glfw.wl.cursorPreviousName = name; -} - -static void pointerHandleMotion(void* data, - struct wl_pointer* pointer, - uint32_t time, - wl_fixed_t sx, - wl_fixed_t sy) -{ - _GLFWwindow* window = _glfw.wl.pointerFocus; - const char* cursorName = NULL; - double x, y; - - if (!window) - return; - - if (window->cursorMode == GLFW_CURSOR_DISABLED) - return; - x = wl_fixed_to_double(sx); - y = wl_fixed_to_double(sy); - window->wl.cursorPosX = x; - window->wl.cursorPosY = y; - - switch (window->wl.decorations.focus) - { - case mainWindow: - _glfwInputCursorPos(window, x, y); - _glfw.wl.cursorPreviousName = NULL; - return; - case topDecoration: - if (y < _GLFW_DECORATION_WIDTH) - cursorName = "n-resize"; - else - cursorName = "left_ptr"; - break; - case leftDecoration: - if (y < _GLFW_DECORATION_WIDTH) - cursorName = "nw-resize"; - else - cursorName = "w-resize"; - break; - case rightDecoration: - if (y < _GLFW_DECORATION_WIDTH) - cursorName = "ne-resize"; - else - cursorName = "e-resize"; - break; - case bottomDecoration: - if (x < _GLFW_DECORATION_WIDTH) - cursorName = "sw-resize"; - else if (x > window->wl.width + _GLFW_DECORATION_WIDTH) - cursorName = "se-resize"; - else - cursorName = "s-resize"; - break; - default: - assert(0); - } - if (_glfw.wl.cursorPreviousName != cursorName) - setCursor(window, cursorName); -} - -static void pointerHandleButton(void* data, - struct wl_pointer* pointer, - uint32_t serial, - uint32_t time, - uint32_t button, - uint32_t state) -{ - _GLFWwindow* window = _glfw.wl.pointerFocus; - int glfwButton; - uint32_t edges = XDG_TOPLEVEL_RESIZE_EDGE_NONE; - - if (!window) - return; - if (button == BTN_LEFT) - { - switch (window->wl.decorations.focus) - { - case mainWindow: - break; - case topDecoration: - if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH) - edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP; - else - xdg_toplevel_move(window->wl.xdg.toplevel, _glfw.wl.seat, serial); - break; - case leftDecoration: - if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH) - edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; - else - edges = XDG_TOPLEVEL_RESIZE_EDGE_LEFT; - break; - case rightDecoration: - if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH) - edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; - else - edges = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; - break; - case bottomDecoration: - if (window->wl.cursorPosX < _GLFW_DECORATION_WIDTH) - edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; - else if (window->wl.cursorPosX > window->wl.width + _GLFW_DECORATION_WIDTH) - edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; - else - edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; - break; - default: - assert(0); - } - if (edges != XDG_TOPLEVEL_RESIZE_EDGE_NONE) - { - xdg_toplevel_resize(window->wl.xdg.toplevel, _glfw.wl.seat, - serial, edges); - return; - } - } - else if (button == BTN_RIGHT) - { - if (window->wl.decorations.focus != mainWindow && window->wl.xdg.toplevel) - { - xdg_toplevel_show_window_menu(window->wl.xdg.toplevel, - _glfw.wl.seat, serial, - window->wl.cursorPosX, - window->wl.cursorPosY); - return; - } - } - - // Don’t pass the button to the user if it was related to a decoration. - if (window->wl.decorations.focus != mainWindow) - return; - - _glfw.wl.serial = serial; - - /* Makes left, right and middle 0, 1 and 2. Overall order follows evdev - * codes. */ - glfwButton = button - BTN_LEFT; - - _glfwInputMouseClick(window, - glfwButton, - state == WL_POINTER_BUTTON_STATE_PRESSED - ? GLFW_PRESS - : GLFW_RELEASE, - _glfw.wl.xkb.modifiers); -} - -static void pointerHandleAxis(void* data, - struct wl_pointer* pointer, - uint32_t time, - uint32_t axis, - wl_fixed_t value) -{ - _GLFWwindow* window = _glfw.wl.pointerFocus; - double x = 0.0, y = 0.0; - // Wayland scroll events are in pointer motion coordinate space (think two - // finger scroll). The factor 10 is commonly used to convert to "scroll - // step means 1.0. - const double scrollFactor = 1.0 / 10.0; - - if (!window) - return; - - assert(axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL || - axis == WL_POINTER_AXIS_VERTICAL_SCROLL); - - if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) - x = -wl_fixed_to_double(value) * scrollFactor; - else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) - y = -wl_fixed_to_double(value) * scrollFactor; - - _glfwInputScroll(window, x, y); -} - -static const struct wl_pointer_listener pointerListener = { - pointerHandleEnter, - pointerHandleLeave, - pointerHandleMotion, - pointerHandleButton, - pointerHandleAxis, -}; - -static void keyboardHandleKeymap(void* data, - struct wl_keyboard* keyboard, - uint32_t format, - int fd, - uint32_t size) -{ - struct xkb_keymap* keymap; - struct xkb_state* state; - struct xkb_compose_table* composeTable; - struct xkb_compose_state* composeState; - - char* mapStr; - const char* locale; - - if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) - { - close(fd); - return; - } - - mapStr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); - if (mapStr == MAP_FAILED) { - close(fd); - return; - } - - keymap = xkb_keymap_new_from_string(_glfw.wl.xkb.context, - mapStr, - XKB_KEYMAP_FORMAT_TEXT_V1, - 0); - munmap(mapStr, size); - close(fd); - - if (!keymap) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Failed to compile keymap"); - return; - } - - state = xkb_state_new(keymap); - if (!state) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Failed to create XKB state"); - xkb_keymap_unref(keymap); - return; - } - - // Look up the preferred locale, falling back to "C" as default. - locale = getenv("LC_ALL"); - if (!locale) - locale = getenv("LC_CTYPE"); - if (!locale) - locale = getenv("LANG"); - if (!locale) - locale = "C"; - - composeTable = - xkb_compose_table_new_from_locale(_glfw.wl.xkb.context, locale, - XKB_COMPOSE_COMPILE_NO_FLAGS); - if (composeTable) - { - composeState = - xkb_compose_state_new(composeTable, XKB_COMPOSE_STATE_NO_FLAGS); - xkb_compose_table_unref(composeTable); - if (composeState) - _glfw.wl.xkb.composeState = composeState; - else - _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Failed to create XKB compose state"); - } - else - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Failed to create XKB compose table"); - } - - xkb_keymap_unref(_glfw.wl.xkb.keymap); - xkb_state_unref(_glfw.wl.xkb.state); - _glfw.wl.xkb.keymap = keymap; - _glfw.wl.xkb.state = state; - - _glfw.wl.xkb.controlMask = - 1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Control"); - _glfw.wl.xkb.altMask = - 1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod1"); - _glfw.wl.xkb.shiftMask = - 1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Shift"); - _glfw.wl.xkb.superMask = - 1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod4"); - _glfw.wl.xkb.capsLockMask = - 1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Lock"); - _glfw.wl.xkb.numLockMask = - 1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod2"); -} - -static void keyboardHandleEnter(void* data, - struct wl_keyboard* keyboard, - uint32_t serial, - struct wl_surface* surface, - struct wl_array* keys) -{ - // Happens in the case we just destroyed the surface. - if (!surface) - return; - - _GLFWwindow* window = wl_surface_get_user_data(surface); - if (!window) - { - window = findWindowFromDecorationSurface(surface, NULL); - if (!window) - return; - } - - _glfw.wl.serial = serial; - _glfw.wl.keyboardFocus = window; - _glfwInputWindowFocus(window, GLFW_TRUE); -} - -static void keyboardHandleLeave(void* data, - struct wl_keyboard* keyboard, - uint32_t serial, - struct wl_surface* surface) -{ - _GLFWwindow* window = _glfw.wl.keyboardFocus; - - if (!window) - return; - - struct itimerspec timer = {}; - timerfd_settime(_glfw.wl.timerfd, 0, &timer, NULL); - - _glfw.wl.serial = serial; - _glfw.wl.keyboardFocus = NULL; - _glfwInputWindowFocus(window, GLFW_FALSE); -} - -static int translateKey(uint32_t scancode) -{ - if (scancode < sizeof(_glfw.wl.keycodes) / sizeof(_glfw.wl.keycodes[0])) - return _glfw.wl.keycodes[scancode]; - - return GLFW_KEY_UNKNOWN; -} - -static xkb_keysym_t composeSymbol(xkb_keysym_t sym) -{ - if (sym == XKB_KEY_NoSymbol || !_glfw.wl.xkb.composeState) - return sym; - if (xkb_compose_state_feed(_glfw.wl.xkb.composeState, sym) - != XKB_COMPOSE_FEED_ACCEPTED) - return sym; - switch (xkb_compose_state_get_status(_glfw.wl.xkb.composeState)) - { - case XKB_COMPOSE_COMPOSED: - return xkb_compose_state_get_one_sym(_glfw.wl.xkb.composeState); - case XKB_COMPOSE_COMPOSING: - case XKB_COMPOSE_CANCELLED: - return XKB_KEY_NoSymbol; - case XKB_COMPOSE_NOTHING: - default: - return sym; - } -} - -GLFWbool _glfwInputTextWayland(_GLFWwindow* window, uint32_t scancode) -{ - const xkb_keysym_t* keysyms; - const xkb_keycode_t keycode = scancode + 8; - - if (xkb_state_key_get_syms(_glfw.wl.xkb.state, keycode, &keysyms) == 1) - { - const xkb_keysym_t keysym = composeSymbol(keysyms[0]); - const uint32_t codepoint = _glfwKeySym2Unicode(keysym); - if (codepoint != GLFW_INVALID_CODEPOINT) - { - const int mods = _glfw.wl.xkb.modifiers; - const int plain = !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT)); - _glfwInputChar(window, codepoint, mods, plain); - } - } - - return xkb_keymap_key_repeats(_glfw.wl.xkb.keymap, keycode); -} - -static void keyboardHandleKey(void* data, - struct wl_keyboard* keyboard, - uint32_t serial, - uint32_t time, - uint32_t scancode, - uint32_t state) -{ - _GLFWwindow* window = _glfw.wl.keyboardFocus; - if (!window) - return; - - const int key = translateKey(scancode); - const int action = - state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE; - - _glfw.wl.serial = serial; - _glfwInputKey(window, key, scancode, action, _glfw.wl.xkb.modifiers); - - struct itimerspec timer = {}; - - if (action == GLFW_PRESS) - { - const GLFWbool shouldRepeat = _glfwInputTextWayland(window, scancode); - - if (shouldRepeat && _glfw.wl.keyboardRepeatRate > 0) - { - _glfw.wl.keyboardLastKey = key; - _glfw.wl.keyboardLastScancode = scancode; - if (_glfw.wl.keyboardRepeatRate > 1) - timer.it_interval.tv_nsec = 1000000000 / _glfw.wl.keyboardRepeatRate; - else - timer.it_interval.tv_sec = 1; - - timer.it_value.tv_sec = _glfw.wl.keyboardRepeatDelay / 1000; - timer.it_value.tv_nsec = (_glfw.wl.keyboardRepeatDelay % 1000) * 1000000; - } - } - - timerfd_settime(_glfw.wl.timerfd, 0, &timer, NULL); -} - -static void keyboardHandleModifiers(void* data, - struct wl_keyboard* keyboard, - uint32_t serial, - uint32_t modsDepressed, - uint32_t modsLatched, - uint32_t modsLocked, - uint32_t group) -{ - _glfw.wl.serial = serial; - - if (!_glfw.wl.xkb.keymap) - return; - - xkb_state_update_mask(_glfw.wl.xkb.state, - modsDepressed, - modsLatched, - modsLocked, - 0, - 0, - group); - - const xkb_mod_mask_t mask = - xkb_state_serialize_mods(_glfw.wl.xkb.state, - XKB_STATE_MODS_DEPRESSED | - XKB_STATE_LAYOUT_DEPRESSED | - XKB_STATE_MODS_LATCHED | - XKB_STATE_LAYOUT_LATCHED); - - unsigned int mods = 0; - - if (mask & _glfw.wl.xkb.controlMask) - mods |= GLFW_MOD_CONTROL; - if (mask & _glfw.wl.xkb.altMask) - mods |= GLFW_MOD_ALT; - if (mask & _glfw.wl.xkb.shiftMask) - mods |= GLFW_MOD_SHIFT; - if (mask & _glfw.wl.xkb.superMask) - mods |= GLFW_MOD_SUPER; - if (mask & _glfw.wl.xkb.capsLockMask) - mods |= GLFW_MOD_CAPS_LOCK; - if (mask & _glfw.wl.xkb.numLockMask) - mods |= GLFW_MOD_NUM_LOCK; - - _glfw.wl.xkb.modifiers = mods; -} - -#ifdef WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION -static void keyboardHandleRepeatInfo(void* data, - struct wl_keyboard* keyboard, - int32_t rate, - int32_t delay) -{ - if (keyboard != _glfw.wl.keyboard) - return; - - _glfw.wl.keyboardRepeatRate = rate; - _glfw.wl.keyboardRepeatDelay = delay; -} -#endif - -static const struct wl_keyboard_listener keyboardListener = { - keyboardHandleKeymap, - keyboardHandleEnter, - keyboardHandleLeave, - keyboardHandleKey, - keyboardHandleModifiers, -#ifdef WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION - keyboardHandleRepeatInfo, -#endif -}; - -static void seatHandleCapabilities(void* data, - struct wl_seat* seat, - enum wl_seat_capability caps) -{ - if ((caps & WL_SEAT_CAPABILITY_POINTER) && !_glfw.wl.pointer) - { - _glfw.wl.pointer = wl_seat_get_pointer(seat); - wl_pointer_add_listener(_glfw.wl.pointer, &pointerListener, NULL); - } - else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && _glfw.wl.pointer) - { - wl_pointer_destroy(_glfw.wl.pointer); - _glfw.wl.pointer = NULL; - } - - if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !_glfw.wl.keyboard) - { - _glfw.wl.keyboard = wl_seat_get_keyboard(seat); - wl_keyboard_add_listener(_glfw.wl.keyboard, &keyboardListener, NULL); - } - else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && _glfw.wl.keyboard) - { - wl_keyboard_destroy(_glfw.wl.keyboard); - _glfw.wl.keyboard = NULL; - } -} - -static void seatHandleName(void* data, - struct wl_seat* seat, - const char* name) -{ -} - -static const struct wl_seat_listener seatListener = { - seatHandleCapabilities, - seatHandleName, -}; - -static void dataOfferHandleOffer(void* data, - struct wl_data_offer* dataOffer, - const char* mimeType) -{ -} - -static const struct wl_data_offer_listener dataOfferListener = { - dataOfferHandleOffer, -}; - -static void dataDeviceHandleDataOffer(void* data, - struct wl_data_device* dataDevice, - struct wl_data_offer* id) -{ - if (_glfw.wl.dataOffer) - wl_data_offer_destroy(_glfw.wl.dataOffer); - - _glfw.wl.dataOffer = id; - wl_data_offer_add_listener(_glfw.wl.dataOffer, &dataOfferListener, NULL); -} - -static void dataDeviceHandleEnter(void* data, - struct wl_data_device* dataDevice, - uint32_t serial, - struct wl_surface *surface, - wl_fixed_t x, - wl_fixed_t y, - struct wl_data_offer *id) -{ -} - -static void dataDeviceHandleLeave(void* data, - struct wl_data_device* dataDevice) -{ -} - -static void dataDeviceHandleMotion(void* data, - struct wl_data_device* dataDevice, - uint32_t time, - wl_fixed_t x, - wl_fixed_t y) -{ -} - -static void dataDeviceHandleDrop(void* data, - struct wl_data_device* dataDevice) -{ -} - -static void dataDeviceHandleSelection(void* data, - struct wl_data_device* dataDevice, - struct wl_data_offer* id) -{ -} - -static const struct wl_data_device_listener dataDeviceListener = { - dataDeviceHandleDataOffer, - dataDeviceHandleEnter, - dataDeviceHandleLeave, - dataDeviceHandleMotion, - dataDeviceHandleDrop, - dataDeviceHandleSelection, -}; - -static void wmBaseHandlePing(void* data, +static void wmBaseHandlePing(void* userData, struct xdg_wm_base* wmBase, uint32_t serial) { xdg_wm_base_pong(wmBase, serial); } -static const struct xdg_wm_base_listener wmBaseListener = { +static const struct xdg_wm_base_listener wmBaseListener = +{ wmBaseHandlePing }; -static void registryHandleGlobal(void* data, +static void registryHandleGlobal(void* userData, struct wl_registry* registry, uint32_t name, const char* interface, @@ -810,7 +102,7 @@ static void registryHandleGlobal(void* data, _glfw.wl.seat = wl_registry_bind(registry, name, &wl_seat_interface, _glfw.wl.seatVersion); - wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL); + _glfwAddSeatListenerWayland(_glfw.wl.seat); } } else if (strcmp(interface, "wl_data_device_manager") == 0) @@ -863,8 +155,8 @@ static void registryHandleGlobal(void* data, } } -static void registryHandleGlobalRemove(void *data, - struct wl_registry *registry, +static void registryHandleGlobalRemove(void* userData, + struct wl_registry* registry, uint32_t name) { _GLFWmonitor* monitor; @@ -881,7 +173,8 @@ static void registryHandleGlobalRemove(void *data, } -static const struct wl_registry_listener registryListener = { +static const struct wl_registry_listener registryListener = +{ registryHandleGlobal, registryHandleGlobalRemove }; @@ -1117,7 +410,7 @@ GLFWbool _glfwConnectWayland(int platformID, _GLFWplatform* platform) if (platformID == GLFW_PLATFORM_WAYLAND) { _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Failed to open libwayland-client"); + "Wayland: Failed to load libwayland-client"); } return GLFW_FALSE; @@ -1156,9 +449,9 @@ GLFWbool _glfwConnectWayland(int platformID, _GLFWplatform* platform) int _glfwInitWayland(void) { - const char *cursorTheme; - const char *cursorSizeStr; - char *cursorSizeEnd; + const char* cursorTheme; + const char* cursorSizeStr; + char* cursorSizeEnd; long cursorSizeLong; int cursorSize; @@ -1222,7 +515,7 @@ int _glfwInitWayland(void) if (!_glfw.wl.cursor.handle) { _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Failed to open libwayland-cursor"); + "Wayland: Failed to load libwayland-cursor"); return GLFW_FALSE; } @@ -1239,7 +532,7 @@ int _glfwInitWayland(void) if (!_glfw.wl.egl.handle) { _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Failed to open libwayland-egl"); + "Wayland: Failed to load libwayland-egl"); return GLFW_FALSE; } @@ -1254,7 +547,7 @@ int _glfwInitWayland(void) if (!_glfw.wl.xkb.handle) { _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Failed to open libxkbcommon"); + "Wayland: Failed to load libxkbcommon"); return GLFW_FALSE; } @@ -1346,7 +639,7 @@ int _glfwInitWayland(void) if (!_glfw.wl.cursorTheme) { _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Unable to load default cursor theme"); + "Wayland: Failed to load default cursor theme"); return GLFW_FALSE; } // If this happens to be NULL, we just fallback to the scale=1 version. @@ -1362,16 +655,7 @@ int _glfwInitWayland(void) _glfw.wl.dataDevice = wl_data_device_manager_get_data_device(_glfw.wl.dataDeviceManager, _glfw.wl.seat); - wl_data_device_add_listener(_glfw.wl.dataDevice, &dataDeviceListener, NULL); - - _glfw.wl.clipboardSize = 4096; - _glfw.wl.clipboardString = _glfw_calloc(_glfw.wl.clipboardSize, 1); - if (!_glfw.wl.clipboardString) - { - _glfwInputError(GLFW_OUT_OF_MEMORY, - "Wayland: Unable to allocate clipboard memory"); - return GLFW_FALSE; - } + _glfwAddDataDeviceListenerWayland(_glfw.wl.dataDevice); } return GLFW_TRUE; @@ -1410,6 +694,11 @@ void _glfwTerminateWayland(void) _glfw.wl.cursor.handle = NULL; } + for (unsigned int i = 0; i < _glfw.wl.offerCount; i++) + wl_data_offer_destroy(_glfw.wl.offers[i].offer); + + _glfw_free(_glfw.wl.offers); + if (_glfw.wl.cursorSurface) wl_surface_destroy(_glfw.wl.cursorSurface); if (_glfw.wl.subcompositor) @@ -1424,12 +713,14 @@ void _glfwTerminateWayland(void) zxdg_decoration_manager_v1_destroy(_glfw.wl.decorationManager); if (_glfw.wl.wmBase) xdg_wm_base_destroy(_glfw.wl.wmBase); - if (_glfw.wl.dataSource) - wl_data_source_destroy(_glfw.wl.dataSource); + if (_glfw.wl.selectionOffer) + wl_data_offer_destroy(_glfw.wl.selectionOffer); + if (_glfw.wl.dragOffer) + wl_data_offer_destroy(_glfw.wl.dragOffer); + if (_glfw.wl.selectionSource) + wl_data_source_destroy(_glfw.wl.selectionSource); if (_glfw.wl.dataDevice) wl_data_device_destroy(_glfw.wl.dataDevice); - if (_glfw.wl.dataOffer) - wl_data_offer_destroy(_glfw.wl.dataOffer); if (_glfw.wl.dataDeviceManager) wl_data_device_manager_destroy(_glfw.wl.dataDeviceManager); if (_glfw.wl.pointer) @@ -1458,6 +749,5 @@ void _glfwTerminateWayland(void) close(_glfw.wl.cursorTimerfd); _glfw_free(_glfw.wl.clipboardString); - _glfw_free(_glfw.wl.clipboardSendString); } diff --git a/src/wl_monitor.c b/src/wl_monitor.c index 5bdda918..568bdc5f 100644 --- a/src/wl_monitor.c +++ b/src/wl_monitor.c @@ -37,7 +37,7 @@ #include "wayland-client-protocol.h" -static void outputHandleGeometry(void* data, +static void outputHandleGeometry(void* userData, struct wl_output* output, int32_t x, int32_t y, @@ -48,7 +48,7 @@ static void outputHandleGeometry(void* data, const char* model, int32_t transform) { - struct _GLFWmonitor *monitor = data; + struct _GLFWmonitor* monitor = userData; monitor->wl.x = x; monitor->wl.y = y; @@ -58,14 +58,14 @@ static void outputHandleGeometry(void* data, snprintf(monitor->name, sizeof(monitor->name), "%s %s", make, model); } -static void outputHandleMode(void* data, +static void outputHandleMode(void* userData, struct wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { - struct _GLFWmonitor *monitor = data; + struct _GLFWmonitor* monitor = userData; GLFWvidmode mode; mode.width = width; @@ -84,9 +84,9 @@ static void outputHandleMode(void* data, monitor->wl.currentMode = monitor->modeCount - 1; } -static void outputHandleDone(void* data, struct wl_output* output) +static void outputHandleDone(void* userData, struct wl_output* output) { - struct _GLFWmonitor *monitor = data; + struct _GLFWmonitor* monitor = userData; if (monitor->widthMM <= 0 || monitor->heightMM <= 0) { @@ -99,16 +99,17 @@ static void outputHandleDone(void* data, struct wl_output* output) _glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST); } -static void outputHandleScale(void* data, +static void outputHandleScale(void* userData, struct wl_output* output, int32_t factor) { - struct _GLFWmonitor *monitor = data; + struct _GLFWmonitor* monitor = userData; monitor->wl.scale = factor; } -static const struct wl_output_listener outputListener = { +static const struct wl_output_listener outputListener = +{ outputHandleGeometry, outputHandleMode, outputHandleDone, @@ -122,8 +123,8 @@ static const struct wl_output_listener outputListener = { void _glfwAddOutputWayland(uint32_t name, uint32_t version) { - _GLFWmonitor *monitor; - struct wl_output *output; + _GLFWmonitor* monitor; + struct wl_output* output; if (version < 2) { diff --git a/src/wl_platform.h b/src/wl_platform.h index 2b84a10c..20156e92 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -45,10 +45,10 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR #include "xkb_unicode.h" #include "posix_poll.h" -typedef int (* PFN_wl_display_flush)(struct wl_display *display); -typedef void (* PFN_wl_display_cancel_read)(struct wl_display *display); -typedef int (* PFN_wl_display_dispatch_pending)(struct wl_display *display); -typedef int (* PFN_wl_display_read_events)(struct wl_display *display); +typedef int (* PFN_wl_display_flush)(struct wl_display* display); +typedef void (* PFN_wl_display_cancel_read)(struct wl_display* display); +typedef int (* PFN_wl_display_dispatch_pending)(struct wl_display* display); +typedef int (* PFN_wl_display_read_events)(struct wl_display* display); typedef struct wl_display* (* PFN_wl_display_connect)(const char*); typedef void (* PFN_wl_display_disconnect)(struct wl_display*); typedef int (* PFN_wl_display_roundtrip)(struct wl_display*); @@ -219,6 +219,13 @@ typedef struct _GLFWdecorationWayland struct wp_viewport* viewport; } _GLFWdecorationWayland; +typedef struct _GLFWofferWayland +{ + struct wl_data_offer* offer; + GLFWbool text_plain_utf8; + GLFWbool text_uri_list; +} _GLFWofferWayland; + // Wayland-specific per-window data // typedef struct _GLFWwindowWayland @@ -281,8 +288,6 @@ typedef struct _GLFWlibraryWayland struct wl_keyboard* keyboard; struct wl_data_device_manager* dataDeviceManager; struct wl_data_device* dataDevice; - struct wl_data_offer* dataOffer; - struct wl_data_source* dataSource; struct xdg_wm_base* wmBase; struct zxdg_decoration_manager_v1* decorationManager; struct wp_viewporter* viewporter; @@ -290,6 +295,16 @@ typedef struct _GLFWlibraryWayland struct zwp_pointer_constraints_v1* pointerConstraints; struct zwp_idle_inhibit_manager_v1* idleInhibitManager; + _GLFWofferWayland* offers; + unsigned int offerCount; + + struct wl_data_offer* selectionOffer; + struct wl_data_source* selectionSource; + + struct wl_data_offer* dragOffer; + _GLFWwindow* dragFocus; + uint32_t dragSerial; + int compositorVersion; int seatVersion; @@ -306,9 +321,6 @@ typedef struct _GLFWlibraryWayland int keyboardLastKey; int keyboardLastScancode; char* clipboardString; - size_t clipboardSize; - char* clipboardSendString; - size_t clipboardSendSize; int timerfd; short int keycodes[256]; short int scancodes[GLFW_KEY_LAST + 1]; @@ -458,7 +470,7 @@ float _glfwGetWindowOpacityWayland(_GLFWwindow* window); void _glfwSetWindowOpacityWayland(_GLFWwindow* window, float opacity); void _glfwSetWindowMousePassthroughWayland(_GLFWwindow* window, GLFWbool enabled); -void _glfwSetRawMouseMotionWayland(_GLFWwindow *window, GLFWbool enabled); +void _glfwSetRawMouseMotionWayland(_GLFWwindow* window, GLFWbool enabled); GLFWbool _glfwRawMouseMotionSupportedWayland(void); void _glfwPollEventsWayland(void); @@ -499,3 +511,7 @@ void _glfwAddOutputWayland(uint32_t name, uint32_t version); GLFWbool _glfwInputTextWayland(_GLFWwindow* window, uint32_t scancode); _GLFWusercontext* _glfwCreateUserContextWayland(_GLFWwindow* window); + +void _glfwAddSeatListenerWayland(struct wl_seat* seat); +void _glfwAddDataDeviceListenerWayland(struct wl_data_device* device); + diff --git a/src/wl_window.c b/src/wl_window.c index f3f5648a..ab67d80d 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -149,7 +150,7 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image) if (fd < 0) { _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Creating a buffer file for %d B failed: %s", + "Wayland: Failed to create buffer file of size %d: %s", length, strerror(errno)); return NULL; } @@ -158,7 +159,7 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image) if (data == MAP_FAILED) { _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: mmap failed: %s", strerror(errno)); + "Wayland: Failed to map file: %s", strerror(errno)); close(fd); return NULL; } @@ -272,11 +273,11 @@ static void destroyDecorations(_GLFWwindow* window) destroyDecoration(&window->wl.decorations.bottom); } -static void xdgDecorationHandleConfigure(void* data, +static void xdgDecorationHandleConfigure(void* userData, struct zxdg_toplevel_decoration_v1* decoration, uint32_t mode) { - _GLFWwindow* window = data; + _GLFWwindow* window = userData; window->wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); @@ -284,7 +285,8 @@ static void xdgDecorationHandleConfigure(void* data, createDecorations(window); } -static const struct zxdg_toplevel_decoration_v1_listener xdgDecorationListener = { +static const struct zxdg_toplevel_decoration_v1_listener xdgDecorationListener = +{ xdgDecorationHandleConfigure, }; @@ -364,11 +366,11 @@ static void checkScaleChange(_GLFWwindow* window) } } -static void surfaceHandleEnter(void *data, - struct wl_surface *surface, - struct wl_output *output) +static void surfaceHandleEnter(void* userData, + struct wl_surface* surface, + struct wl_output* output) { - _GLFWwindow* window = data; + _GLFWwindow* window = userData; _GLFWmonitor* monitor = wl_output_get_user_data(output); if (window->wl.monitorsCount + 1 > window->wl.monitorsSize) @@ -384,11 +386,11 @@ static void surfaceHandleEnter(void *data, checkScaleChange(window); } -static void surfaceHandleLeave(void *data, - struct wl_surface *surface, - struct wl_output *output) +static void surfaceHandleLeave(void* userData, + struct wl_surface* surface, + struct wl_output* output) { - _GLFWwindow* window = data; + _GLFWwindow* window = userData; _GLFWmonitor* monitor = wl_output_get_user_data(output); GLFWbool found = GLFW_FALSE; @@ -418,7 +420,7 @@ static void setIdleInhibitor(_GLFWwindow* window, GLFWbool enable) _glfw.wl.idleInhibitManager, window->wl.surface); if (!window->wl.idleInhibitor) _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Idle inhibitor creation failed"); + "Wayland: Failed to create idle inhibitor"); } else if (!enable && window->wl.idleInhibitor) { @@ -441,13 +443,13 @@ static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, destroyDecorations(window); } -static void xdgToplevelHandleConfigure(void* data, +static void xdgToplevelHandleConfigure(void* userData, struct xdg_toplevel* toplevel, int32_t width, int32_t height, struct wl_array* states) { - _GLFWwindow* window = data; + _GLFWwindow* window = userData; float aspectRatio; float targetRatio; uint32_t* state; @@ -505,19 +507,20 @@ static void xdgToplevelHandleConfigure(void* data, window->wl.wasFullscreen = GLFW_TRUE; } -static void xdgToplevelHandleClose(void* data, +static void xdgToplevelHandleClose(void* userData, struct xdg_toplevel* toplevel) { - _GLFWwindow* window = data; + _GLFWwindow* window = userData; _glfwInputWindowCloseRequest(window); } -static const struct xdg_toplevel_listener xdgToplevelListener = { +static const struct xdg_toplevel_listener xdgToplevelListener = +{ xdgToplevelHandleConfigure, xdgToplevelHandleClose }; -static void xdgSurfaceHandleConfigure(void* data, +static void xdgSurfaceHandleConfigure(void* userData, struct xdg_surface* surface, uint32_t serial) { @@ -556,7 +559,7 @@ static GLFWbool createXdgSurface(_GLFWwindow* window) if (!window->wl.xdg.surface) { _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: xdg-surface creation failed"); + "Wayland: Failed to create xdg-surface for window"); return GLFW_FALSE; } @@ -568,7 +571,7 @@ static GLFWbool createXdgSurface(_GLFWwindow* window) if (!window->wl.xdg.toplevel) { _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: xdg-toplevel creation failed"); + "Wayland: Failed to create xdg-toplevel for window"); return GLFW_FALSE; } @@ -811,6 +814,893 @@ static void handleEvents(double* timeout) } } +// Reads the specified data offer as the specified MIME type +// +static char* readDataOfferAsString(struct wl_data_offer* offer, const char* mimeType) +{ + int fds[2]; + + if (pipe2(fds, O_CLOEXEC) == -1) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: Failed to create pipe for data offer: %s", + strerror(errno)); + return NULL; + } + + wl_data_offer_receive(offer, mimeType, fds[1]); + flushDisplay(); + close(fds[1]); + + char* string = NULL; + size_t size = 0; + size_t length = 0; + + for (;;) + { + const size_t readSize = 4096; + const size_t requiredSize = length + readSize + 1; + if (requiredSize > size) + { + char* longer = _glfw_realloc(string, requiredSize); + if (!longer) + { + _glfwInputError(GLFW_OUT_OF_MEMORY, NULL); + close(fds[0]); + return NULL; + } + + string = longer; + size = requiredSize; + } + + const ssize_t result = read(fds[0], string + length, readSize); + if (result == 0) + break; + else if (result == -1) + { + if (errno == EINTR) + continue; + + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: Failed to read from data offer pipe: %s", + strerror(errno)); + close(fds[0]); + return NULL; + } + + length += result; + } + + close(fds[0]); + + string[length] = '\0'; + return string; +} + +static _GLFWwindow* findWindowFromDecorationSurface(struct wl_surface* surface, + int* which) +{ + int focus; + _GLFWwindow* window = _glfw.windowListHead; + if (!which) + which = &focus; + while (window) + { + if (surface == window->wl.decorations.top.surface) + { + *which = topDecoration; + break; + } + if (surface == window->wl.decorations.left.surface) + { + *which = leftDecoration; + break; + } + if (surface == window->wl.decorations.right.surface) + { + *which = rightDecoration; + break; + } + if (surface == window->wl.decorations.bottom.surface) + { + *which = bottomDecoration; + break; + } + window = window->next; + } + return window; +} + +static void pointerHandleEnter(void* userData, + struct wl_pointer* pointer, + uint32_t serial, + struct wl_surface* surface, + wl_fixed_t sx, + wl_fixed_t sy) +{ + // Happens in the case we just destroyed the surface. + if (!surface) + return; + + int focus = 0; + _GLFWwindow* window = wl_surface_get_user_data(surface); + if (!window) + { + window = findWindowFromDecorationSurface(surface, &focus); + if (!window) + return; + } + + window->wl.decorations.focus = focus; + _glfw.wl.serial = serial; + _glfw.wl.pointerEnterSerial = serial; + _glfw.wl.pointerFocus = window; + + window->wl.hovered = GLFW_TRUE; + + _glfwSetCursorWayland(window, window->wl.currentCursor); + _glfwInputCursorEnter(window, GLFW_TRUE); +} + +static void pointerHandleLeave(void* userData, + struct wl_pointer* pointer, + uint32_t serial, + struct wl_surface* surface) +{ + _GLFWwindow* window = _glfw.wl.pointerFocus; + + if (!window) + return; + + window->wl.hovered = GLFW_FALSE; + + _glfw.wl.serial = serial; + _glfw.wl.pointerFocus = NULL; + _glfwInputCursorEnter(window, GLFW_FALSE); + _glfw.wl.cursorPreviousName = NULL; +} + +static void setCursor(_GLFWwindow* window, const char* name) +{ + struct wl_buffer* buffer; + struct wl_cursor* cursor; + struct wl_cursor_image* image; + struct wl_surface* surface = _glfw.wl.cursorSurface; + struct wl_cursor_theme* theme = _glfw.wl.cursorTheme; + int scale = 1; + + if (window->wl.scale > 1 && _glfw.wl.cursorThemeHiDPI) + { + // We only support up to scale=2 for now, since libwayland-cursor + // requires us to load a different theme for each size. + scale = 2; + theme = _glfw.wl.cursorThemeHiDPI; + } + + cursor = wl_cursor_theme_get_cursor(theme, name); + if (!cursor) + { + _glfwInputError(GLFW_CURSOR_UNAVAILABLE, + "Wayland: Standard cursor shape unavailable"); + return; + } + // TODO: handle animated cursors too. + image = cursor->images[0]; + + if (!image) + return; + + buffer = wl_cursor_image_get_buffer(image); + if (!buffer) + return; + wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerEnterSerial, + surface, + image->hotspot_x / scale, + image->hotspot_y / scale); + wl_surface_set_buffer_scale(surface, scale); + wl_surface_attach(surface, buffer, 0, 0); + wl_surface_damage(surface, 0, 0, + image->width, image->height); + wl_surface_commit(surface); + _glfw.wl.cursorPreviousName = name; +} + +static void pointerHandleMotion(void* userData, + struct wl_pointer* pointer, + uint32_t time, + wl_fixed_t sx, + wl_fixed_t sy) +{ + _GLFWwindow* window = _glfw.wl.pointerFocus; + const char* cursorName = NULL; + double x, y; + + if (!window) + return; + + if (window->cursorMode == GLFW_CURSOR_DISABLED) + return; + x = wl_fixed_to_double(sx); + y = wl_fixed_to_double(sy); + window->wl.cursorPosX = x; + window->wl.cursorPosY = y; + + switch (window->wl.decorations.focus) + { + case mainWindow: + _glfwInputCursorPos(window, x, y); + _glfw.wl.cursorPreviousName = NULL; + return; + case topDecoration: + if (y < _GLFW_DECORATION_WIDTH) + cursorName = "n-resize"; + else + cursorName = "left_ptr"; + break; + case leftDecoration: + if (y < _GLFW_DECORATION_WIDTH) + cursorName = "nw-resize"; + else + cursorName = "w-resize"; + break; + case rightDecoration: + if (y < _GLFW_DECORATION_WIDTH) + cursorName = "ne-resize"; + else + cursorName = "e-resize"; + break; + case bottomDecoration: + if (x < _GLFW_DECORATION_WIDTH) + cursorName = "sw-resize"; + else if (x > window->wl.width + _GLFW_DECORATION_WIDTH) + cursorName = "se-resize"; + else + cursorName = "s-resize"; + break; + default: + assert(0); + } + if (_glfw.wl.cursorPreviousName != cursorName) + setCursor(window, cursorName); +} + +static void pointerHandleButton(void* userData, + struct wl_pointer* pointer, + uint32_t serial, + uint32_t time, + uint32_t button, + uint32_t state) +{ + _GLFWwindow* window = _glfw.wl.pointerFocus; + int glfwButton; + uint32_t edges = XDG_TOPLEVEL_RESIZE_EDGE_NONE; + + if (!window) + return; + if (button == BTN_LEFT) + { + switch (window->wl.decorations.focus) + { + case mainWindow: + break; + case topDecoration: + if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH) + edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP; + else + xdg_toplevel_move(window->wl.xdg.toplevel, _glfw.wl.seat, serial); + break; + case leftDecoration: + if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH) + edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; + else + edges = XDG_TOPLEVEL_RESIZE_EDGE_LEFT; + break; + case rightDecoration: + if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH) + edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; + else + edges = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; + break; + case bottomDecoration: + if (window->wl.cursorPosX < _GLFW_DECORATION_WIDTH) + edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; + else if (window->wl.cursorPosX > window->wl.width + _GLFW_DECORATION_WIDTH) + edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; + else + edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; + break; + default: + assert(0); + } + if (edges != XDG_TOPLEVEL_RESIZE_EDGE_NONE) + { + xdg_toplevel_resize(window->wl.xdg.toplevel, _glfw.wl.seat, + serial, edges); + return; + } + } + else if (button == BTN_RIGHT) + { + if (window->wl.decorations.focus != mainWindow && window->wl.xdg.toplevel) + { + xdg_toplevel_show_window_menu(window->wl.xdg.toplevel, + _glfw.wl.seat, serial, + window->wl.cursorPosX, + window->wl.cursorPosY); + return; + } + } + + // Don’t pass the button to the user if it was related to a decoration. + if (window->wl.decorations.focus != mainWindow) + return; + + _glfw.wl.serial = serial; + + /* Makes left, right and middle 0, 1 and 2. Overall order follows evdev + * codes. */ + glfwButton = button - BTN_LEFT; + + _glfwInputMouseClick(window, + glfwButton, + state == WL_POINTER_BUTTON_STATE_PRESSED + ? GLFW_PRESS + : GLFW_RELEASE, + _glfw.wl.xkb.modifiers); +} + +static void pointerHandleAxis(void* userData, + struct wl_pointer* pointer, + uint32_t time, + uint32_t axis, + wl_fixed_t value) +{ + _GLFWwindow* window = _glfw.wl.pointerFocus; + double x = 0.0, y = 0.0; + // Wayland scroll events are in pointer motion coordinate space (think two + // finger scroll). The factor 10 is commonly used to convert to "scroll + // step means 1.0. + const double scrollFactor = 1.0 / 10.0; + + if (!window) + return; + + assert(axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL || + axis == WL_POINTER_AXIS_VERTICAL_SCROLL); + + if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) + x = -wl_fixed_to_double(value) * scrollFactor; + else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) + y = -wl_fixed_to_double(value) * scrollFactor; + + _glfwInputScroll(window, x, y); +} + +static const struct wl_pointer_listener pointerListener = +{ + pointerHandleEnter, + pointerHandleLeave, + pointerHandleMotion, + pointerHandleButton, + pointerHandleAxis, +}; + +static void keyboardHandleKeymap(void* userData, + struct wl_keyboard* keyboard, + uint32_t format, + int fd, + uint32_t size) +{ + struct xkb_keymap* keymap; + struct xkb_state* state; + struct xkb_compose_table* composeTable; + struct xkb_compose_state* composeState; + + char* mapStr; + const char* locale; + + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) + { + close(fd); + return; + } + + mapStr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (mapStr == MAP_FAILED) { + close(fd); + return; + } + + keymap = xkb_keymap_new_from_string(_glfw.wl.xkb.context, + mapStr, + XKB_KEYMAP_FORMAT_TEXT_V1, + 0); + munmap(mapStr, size); + close(fd); + + if (!keymap) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: Failed to compile keymap"); + return; + } + + state = xkb_state_new(keymap); + if (!state) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: Failed to create XKB state"); + xkb_keymap_unref(keymap); + return; + } + + // Look up the preferred locale, falling back to "C" as default. + locale = getenv("LC_ALL"); + if (!locale) + locale = getenv("LC_CTYPE"); + if (!locale) + locale = getenv("LANG"); + if (!locale) + locale = "C"; + + composeTable = + xkb_compose_table_new_from_locale(_glfw.wl.xkb.context, locale, + XKB_COMPOSE_COMPILE_NO_FLAGS); + if (composeTable) + { + composeState = + xkb_compose_state_new(composeTable, XKB_COMPOSE_STATE_NO_FLAGS); + xkb_compose_table_unref(composeTable); + if (composeState) + _glfw.wl.xkb.composeState = composeState; + else + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: Failed to create XKB compose state"); + } + else + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: Failed to create XKB compose table"); + } + + xkb_keymap_unref(_glfw.wl.xkb.keymap); + xkb_state_unref(_glfw.wl.xkb.state); + _glfw.wl.xkb.keymap = keymap; + _glfw.wl.xkb.state = state; + + _glfw.wl.xkb.controlMask = + 1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Control"); + _glfw.wl.xkb.altMask = + 1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod1"); + _glfw.wl.xkb.shiftMask = + 1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Shift"); + _glfw.wl.xkb.superMask = + 1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod4"); + _glfw.wl.xkb.capsLockMask = + 1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Lock"); + _glfw.wl.xkb.numLockMask = + 1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod2"); +} + +static void keyboardHandleEnter(void* userData, + struct wl_keyboard* keyboard, + uint32_t serial, + struct wl_surface* surface, + struct wl_array* keys) +{ + // Happens in the case we just destroyed the surface. + if (!surface) + return; + + _GLFWwindow* window = wl_surface_get_user_data(surface); + if (!window) + { + window = findWindowFromDecorationSurface(surface, NULL); + if (!window) + return; + } + + _glfw.wl.serial = serial; + _glfw.wl.keyboardFocus = window; + _glfwInputWindowFocus(window, GLFW_TRUE); +} + +static void keyboardHandleLeave(void* userData, + struct wl_keyboard* keyboard, + uint32_t serial, + struct wl_surface* surface) +{ + _GLFWwindow* window = _glfw.wl.keyboardFocus; + + if (!window) + return; + + struct itimerspec timer = {}; + timerfd_settime(_glfw.wl.timerfd, 0, &timer, NULL); + + _glfw.wl.serial = serial; + _glfw.wl.keyboardFocus = NULL; + _glfwInputWindowFocus(window, GLFW_FALSE); +} + +static int translateKey(uint32_t scancode) +{ + if (scancode < sizeof(_glfw.wl.keycodes) / sizeof(_glfw.wl.keycodes[0])) + return _glfw.wl.keycodes[scancode]; + + return GLFW_KEY_UNKNOWN; +} + +static xkb_keysym_t composeSymbol(xkb_keysym_t sym) +{ + if (sym == XKB_KEY_NoSymbol || !_glfw.wl.xkb.composeState) + return sym; + if (xkb_compose_state_feed(_glfw.wl.xkb.composeState, sym) + != XKB_COMPOSE_FEED_ACCEPTED) + return sym; + switch (xkb_compose_state_get_status(_glfw.wl.xkb.composeState)) + { + case XKB_COMPOSE_COMPOSED: + return xkb_compose_state_get_one_sym(_glfw.wl.xkb.composeState); + case XKB_COMPOSE_COMPOSING: + case XKB_COMPOSE_CANCELLED: + return XKB_KEY_NoSymbol; + case XKB_COMPOSE_NOTHING: + default: + return sym; + } +} + +GLFWbool _glfwInputTextWayland(_GLFWwindow* window, uint32_t scancode) +{ + const xkb_keysym_t* keysyms; + const xkb_keycode_t keycode = scancode + 8; + + if (xkb_state_key_get_syms(_glfw.wl.xkb.state, keycode, &keysyms) == 1) + { + const xkb_keysym_t keysym = composeSymbol(keysyms[0]); + const uint32_t codepoint = _glfwKeySym2Unicode(keysym); + if (codepoint != GLFW_INVALID_CODEPOINT) + { + const int mods = _glfw.wl.xkb.modifiers; + const int plain = !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT)); + _glfwInputChar(window, codepoint, mods, plain); + } + } + + return xkb_keymap_key_repeats(_glfw.wl.xkb.keymap, keycode); +} + +static void keyboardHandleKey(void* userData, + struct wl_keyboard* keyboard, + uint32_t serial, + uint32_t time, + uint32_t scancode, + uint32_t state) +{ + _GLFWwindow* window = _glfw.wl.keyboardFocus; + if (!window) + return; + + const int key = translateKey(scancode); + const int action = + state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE; + + _glfw.wl.serial = serial; + _glfwInputKey(window, key, scancode, action, _glfw.wl.xkb.modifiers); + + struct itimerspec timer = {}; + + if (action == GLFW_PRESS) + { + const GLFWbool shouldRepeat = _glfwInputTextWayland(window, scancode); + + if (shouldRepeat && _glfw.wl.keyboardRepeatRate > 0) + { + _glfw.wl.keyboardLastKey = key; + _glfw.wl.keyboardLastScancode = scancode; + if (_glfw.wl.keyboardRepeatRate > 1) + timer.it_interval.tv_nsec = 1000000000 / _glfw.wl.keyboardRepeatRate; + else + timer.it_interval.tv_sec = 1; + + timer.it_value.tv_sec = _glfw.wl.keyboardRepeatDelay / 1000; + timer.it_value.tv_nsec = (_glfw.wl.keyboardRepeatDelay % 1000) * 1000000; + } + } + + timerfd_settime(_glfw.wl.timerfd, 0, &timer, NULL); +} + +static void keyboardHandleModifiers(void* userData, + struct wl_keyboard* keyboard, + uint32_t serial, + uint32_t modsDepressed, + uint32_t modsLatched, + uint32_t modsLocked, + uint32_t group) +{ + _glfw.wl.serial = serial; + + if (!_glfw.wl.xkb.keymap) + return; + + xkb_state_update_mask(_glfw.wl.xkb.state, + modsDepressed, + modsLatched, + modsLocked, + 0, + 0, + group); + + const xkb_mod_mask_t mask = + xkb_state_serialize_mods(_glfw.wl.xkb.state, + XKB_STATE_MODS_DEPRESSED | + XKB_STATE_LAYOUT_DEPRESSED | + XKB_STATE_MODS_LATCHED | + XKB_STATE_LAYOUT_LATCHED); + + unsigned int mods = 0; + + if (mask & _glfw.wl.xkb.controlMask) + mods |= GLFW_MOD_CONTROL; + if (mask & _glfw.wl.xkb.altMask) + mods |= GLFW_MOD_ALT; + if (mask & _glfw.wl.xkb.shiftMask) + mods |= GLFW_MOD_SHIFT; + if (mask & _glfw.wl.xkb.superMask) + mods |= GLFW_MOD_SUPER; + if (mask & _glfw.wl.xkb.capsLockMask) + mods |= GLFW_MOD_CAPS_LOCK; + if (mask & _glfw.wl.xkb.numLockMask) + mods |= GLFW_MOD_NUM_LOCK; + + _glfw.wl.xkb.modifiers = mods; +} + +#ifdef WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION +static void keyboardHandleRepeatInfo(void* userData, + struct wl_keyboard* keyboard, + int32_t rate, + int32_t delay) +{ + if (keyboard != _glfw.wl.keyboard) + return; + + _glfw.wl.keyboardRepeatRate = rate; + _glfw.wl.keyboardRepeatDelay = delay; +} +#endif + +static const struct wl_keyboard_listener keyboardListener = +{ + keyboardHandleKeymap, + keyboardHandleEnter, + keyboardHandleLeave, + keyboardHandleKey, + keyboardHandleModifiers, +#ifdef WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION + keyboardHandleRepeatInfo, +#endif +}; + +static void seatHandleCapabilities(void* userData, + struct wl_seat* seat, + enum wl_seat_capability caps) +{ + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !_glfw.wl.pointer) + { + _glfw.wl.pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(_glfw.wl.pointer, &pointerListener, NULL); + } + else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && _glfw.wl.pointer) + { + wl_pointer_destroy(_glfw.wl.pointer); + _glfw.wl.pointer = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !_glfw.wl.keyboard) + { + _glfw.wl.keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(_glfw.wl.keyboard, &keyboardListener, NULL); + } + else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && _glfw.wl.keyboard) + { + wl_keyboard_destroy(_glfw.wl.keyboard); + _glfw.wl.keyboard = NULL; + } +} + +static void seatHandleName(void* userData, + struct wl_seat* seat, + const char* name) +{ +} + +static const struct wl_seat_listener seatListener = +{ + seatHandleCapabilities, + seatHandleName, +}; + +static void dataOfferHandleOffer(void* userData, + struct wl_data_offer* offer, + const char* mimeType) +{ + for (unsigned int i = 0; i < _glfw.wl.offerCount; i++) + { + if (_glfw.wl.offers[i].offer == offer) + { + if (strcmp(mimeType, "text/plain;charset=utf-8") == 0) + _glfw.wl.offers[i].text_plain_utf8 = GLFW_TRUE; + else if (strcmp(mimeType, "text/uri-list") == 0) + _glfw.wl.offers[i].text_uri_list = GLFW_TRUE; + + break; + } + } +} + +static const struct wl_data_offer_listener dataOfferListener = +{ + dataOfferHandleOffer +}; + +static void dataDeviceHandleDataOffer(void* userData, + struct wl_data_device* device, + struct wl_data_offer* offer) +{ + _GLFWofferWayland* offers = + _glfw_realloc(_glfw.wl.offers, _glfw.wl.offerCount + 1); + if (!offers) + { + _glfwInputError(GLFW_OUT_OF_MEMORY, NULL); + return; + } + + _glfw.wl.offers = offers; + _glfw.wl.offerCount++; + + _glfw.wl.offers[_glfw.wl.offerCount - 1] = (_GLFWofferWayland) { offer }; + wl_data_offer_add_listener(offer, &dataOfferListener, NULL); +} + +static void dataDeviceHandleEnter(void* userData, + struct wl_data_device* device, + uint32_t serial, + struct wl_surface* surface, + wl_fixed_t x, + wl_fixed_t y, + struct wl_data_offer* offer) +{ + if (_glfw.wl.dragOffer) + { + wl_data_offer_destroy(_glfw.wl.dragOffer); + _glfw.wl.dragOffer = NULL; + _glfw.wl.dragFocus = NULL; + } + + for (unsigned int i = 0; i < _glfw.wl.offerCount; i++) + { + if (_glfw.wl.offers[i].offer == offer) + { + _GLFWwindow* window = NULL; + + if (surface) + window = wl_surface_get_user_data(surface); + + if (window && _glfw.wl.offers[i].text_uri_list) + { + _glfw.wl.dragOffer = offer; + _glfw.wl.dragFocus = window; + _glfw.wl.dragSerial = serial; + } + + _glfw.wl.offers[i] = _glfw.wl.offers[_glfw.wl.offerCount - 1]; + _glfw.wl.offerCount--; + break; + } + } + + if (_glfw.wl.dragOffer) + wl_data_offer_accept(offer, serial, "text/uri-list"); + else + { + wl_data_offer_accept(offer, serial, NULL); + wl_data_offer_destroy(offer); + } +} + +static void dataDeviceHandleLeave(void* userData, + struct wl_data_device* device) +{ + if (_glfw.wl.dragOffer) + { + wl_data_offer_destroy(_glfw.wl.dragOffer); + _glfw.wl.dragOffer = NULL; + _glfw.wl.dragFocus = NULL; + } +} + +static void dataDeviceHandleMotion(void* userData, + struct wl_data_device* device, + uint32_t time, + wl_fixed_t x, + wl_fixed_t y) +{ +} + +static void dataDeviceHandleDrop(void* userData, + struct wl_data_device* device) +{ + if (!_glfw.wl.dragOffer) + return; + + char* string = readDataOfferAsString(_glfw.wl.dragOffer, "text/uri-list"); + if (string) + { + int count; + char** paths = _glfwParseUriList(string, &count); + if (paths) + _glfwInputDrop(_glfw.wl.dragFocus, count, (const char**) paths); + + for (int i = 0; i < count; i++) + _glfw_free(paths[i]); + + _glfw_free(paths); + } + + _glfw_free(string); +} + +static void dataDeviceHandleSelection(void* userData, + struct wl_data_device* device, + struct wl_data_offer* offer) +{ + if (_glfw.wl.selectionOffer) + { + wl_data_offer_destroy(_glfw.wl.selectionOffer); + _glfw.wl.selectionOffer = NULL; + } + + for (unsigned int i = 0; i < _glfw.wl.offerCount; i++) + { + if (_glfw.wl.offers[i].offer == offer) + { + if (_glfw.wl.offers[i].text_plain_utf8) + _glfw.wl.selectionOffer = offer; + else + wl_data_offer_destroy(offer); + + _glfw.wl.offers[i] = _glfw.wl.offers[_glfw.wl.offerCount - 1]; + _glfw.wl.offerCount--; + break; + } + } +} + +const struct wl_data_device_listener dataDeviceListener = +{ + dataDeviceHandleDataOffer, + dataDeviceHandleEnter, + dataDeviceHandleLeave, + dataDeviceHandleMotion, + dataDeviceHandleDrop, + dataDeviceHandleSelection, +}; + +void _glfwAddSeatListenerWayland(struct wl_seat* seat) +{ + wl_seat_add_listener(seat, &seatListener, NULL); +} + +void _glfwAddDataDeviceListenerWayland(struct wl_data_device* device) +{ + wl_data_device_add_listener(device, &dataDeviceListener, NULL); +} + + ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// @@ -1163,7 +2053,7 @@ void _glfwSetWindowOpacityWayland(_GLFWwindow* window, float opacity) "Wayland: The platform does not support setting the window opacity"); } -void _glfwSetRawMouseMotionWayland(_GLFWwindow *window, GLFWbool enabled) +void _glfwSetRawMouseMotionWayland(_GLFWwindow* window, GLFWbool enabled) { // This is handled in relativePointerHandleRelativeMotion } @@ -1378,7 +2268,7 @@ int _glfwCreateStandardCursorWayland(_GLFWcursor* cursor, int shape) cursor->wl.cursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme, name); if (!cursor->wl.cursor) { - _glfwInputError(GLFW_PLATFORM_ERROR, + _glfwInputError(GLFW_CURSOR_UNAVAILABLE, "Wayland: Failed to create standard cursor \"%s\"", name); return GLFW_FALSE; @@ -1407,7 +2297,7 @@ void _glfwDestroyCursorWayland(_GLFWcursor* cursor) wl_buffer_destroy(cursor->wl.buffer); } -static void relativePointerHandleRelativeMotion(void* data, +static void relativePointerHandleRelativeMotion(void* userData, struct zwp_relative_pointer_v1* pointer, uint32_t timeHi, uint32_t timeLo, @@ -1416,7 +2306,7 @@ static void relativePointerHandleRelativeMotion(void* data, wl_fixed_t dxUnaccel, wl_fixed_t dyUnaccel) { - _GLFWwindow* window = data; + _GLFWwindow* window = userData; double xpos = window->virtualCursorPosX; double ypos = window->virtualCursorPosY; @@ -1437,11 +2327,12 @@ static void relativePointerHandleRelativeMotion(void* data, _glfwInputCursorPos(window, xpos, ypos); } -static const struct zwp_relative_pointer_v1_listener relativePointerListener = { +static const struct zwp_relative_pointer_v1_listener relativePointerListener = +{ relativePointerHandleRelativeMotion }; -static void lockedPointerHandleLocked(void* data, +static void lockedPointerHandleLocked(void* userData, struct zwp_locked_pointer_v1* lockedPointer) { } @@ -1462,12 +2353,13 @@ static void unlockPointer(_GLFWwindow* window) static void lockPointer(_GLFWwindow* window); -static void lockedPointerHandleUnlocked(void* data, +static void lockedPointerHandleUnlocked(void* userData, struct zwp_locked_pointer_v1* lockedPointer) { } -static const struct zwp_locked_pointer_v1_listener lockedPointerListener = { +static const struct zwp_locked_pointer_v1_listener lockedPointerListener = +{ lockedPointerHandleLocked, lockedPointerHandleUnlocked }; @@ -1574,11 +2466,11 @@ void _glfwSetCursorWayland(_GLFWwindow* window, _GLFWcursor* cursor) } } -static void dataSourceHandleTarget(void* data, - struct wl_data_source* dataSource, +static void dataSourceHandleTarget(void* userData, + struct wl_data_source* source, const char* mimeType) { - if (_glfw.wl.dataSource != dataSource) + if (_glfw.wl.selectionSource != source) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Unknown clipboard data source"); @@ -1586,71 +2478,56 @@ static void dataSourceHandleTarget(void* data, } } -static void dataSourceHandleSend(void* data, - struct wl_data_source* dataSource, +static void dataSourceHandleSend(void* userData, + struct wl_data_source* source, const char* mimeType, int fd) { - const char* string = _glfw.wl.clipboardSendString; - size_t len = _glfw.wl.clipboardSendSize; - int ret; - - if (_glfw.wl.dataSource != dataSource) + // Ignore it if this is an outdated or invalid request + if (_glfw.wl.selectionSource != source || + strcmp(mimeType, "text/plain;charset=utf-8") != 0) { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Unknown clipboard data source"); - return; - } - - if (!string) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Copy requested from an invalid string"); - return; - } - - if (strcmp(mimeType, "text/plain;charset=utf-8") != 0) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Wrong MIME type asked from clipboard"); close(fd); return; } - while (len > 0) + char* string = _glfw.wl.clipboardString; + size_t length = strlen(string); + + while (length > 0) { - ret = write(fd, string, len); - if (ret == -1 && errno == EINTR) - continue; - if (ret == -1) + const ssize_t result = write(fd, string, length); + if (result == -1) { - // TODO: also report errno maybe. + if (errno == EINTR) + continue; + _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Error while writing the clipboard"); - close(fd); - return; + "Wayland: Error while writing the clipboard: %s", + strerror(errno)); + break; } - len -= ret; + + length -= result; + string += result; } + close(fd); } -static void dataSourceHandleCancelled(void* data, - struct wl_data_source* dataSource) +static void dataSourceHandleCancelled(void* userData, + struct wl_data_source* source) { - wl_data_source_destroy(dataSource); + wl_data_source_destroy(source); - if (_glfw.wl.dataSource != dataSource) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Unknown clipboard data source"); + if (_glfw.wl.selectionSource != source) return; - } - _glfw.wl.dataSource = NULL; + _glfw.wl.selectionSource = NULL; } -static const struct wl_data_source_listener dataSourceListener = { +static const struct wl_data_source_listener dataSourceListener = +{ dataSourceHandleTarget, dataSourceHandleSend, dataSourceHandleCancelled, @@ -1658,124 +2535,54 @@ static const struct wl_data_source_listener dataSourceListener = { void _glfwSetClipboardStringWayland(const char* string) { - if (_glfw.wl.dataSource) + if (_glfw.wl.selectionSource) { - wl_data_source_destroy(_glfw.wl.dataSource); - _glfw.wl.dataSource = NULL; + wl_data_source_destroy(_glfw.wl.selectionSource); + _glfw.wl.selectionSource = NULL; } - if (_glfw.wl.clipboardSendString) + char* copy = _glfw_strdup(string); + if (!copy) { - _glfw_free(_glfw.wl.clipboardSendString); - _glfw.wl.clipboardSendString = NULL; - } - - _glfw.wl.clipboardSendString = _glfw_strdup(string); - if (!_glfw.wl.clipboardSendString) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Impossible to allocate clipboard string"); + _glfwInputError(GLFW_OUT_OF_MEMORY, NULL); return; } - _glfw.wl.clipboardSendSize = strlen(string); - _glfw.wl.dataSource = + + _glfw_free(_glfw.wl.clipboardString); + _glfw.wl.clipboardString = copy; + + _glfw.wl.selectionSource = wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager); - if (!_glfw.wl.dataSource) + if (!_glfw.wl.selectionSource) { _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Impossible to create clipboard source"); - _glfw_free(_glfw.wl.clipboardSendString); + "Wayland: Failed to create clipboard data source"); return; } - wl_data_source_add_listener(_glfw.wl.dataSource, + wl_data_source_add_listener(_glfw.wl.selectionSource, &dataSourceListener, NULL); - wl_data_source_offer(_glfw.wl.dataSource, "text/plain;charset=utf-8"); + wl_data_source_offer(_glfw.wl.selectionSource, "text/plain;charset=utf-8"); wl_data_device_set_selection(_glfw.wl.dataDevice, - _glfw.wl.dataSource, + _glfw.wl.selectionSource, _glfw.wl.serial); } -static GLFWbool growClipboardString(void) -{ - char* clipboard = _glfw.wl.clipboardString; - - clipboard = _glfw_realloc(clipboard, _glfw.wl.clipboardSize * 2); - if (!clipboard) - { - _glfwInputError(GLFW_OUT_OF_MEMORY, - "Wayland: Impossible to grow clipboard string"); - return GLFW_FALSE; - } - _glfw.wl.clipboardString = clipboard; - _glfw.wl.clipboardSize = _glfw.wl.clipboardSize * 2; - return GLFW_TRUE; -} - const char* _glfwGetClipboardStringWayland(void) { - int fds[2]; - int ret; - size_t len = 0; - - if (!_glfw.wl.dataOffer) + if (!_glfw.wl.selectionOffer) { _glfwInputError(GLFW_FORMAT_UNAVAILABLE, - "No clipboard data has been sent yet"); + "Wayland: No clipboard data available"); return NULL; } - ret = pipe2(fds, O_CLOEXEC); - if (ret < 0) - { - // TODO: also report errno maybe? - _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Impossible to create clipboard pipe fds"); - return NULL; - } + if (_glfw.wl.selectionSource) + return _glfw.wl.clipboardString; - wl_data_offer_receive(_glfw.wl.dataOffer, "text/plain;charset=utf-8", fds[1]); - close(fds[1]); - - // XXX: this is a huge hack, this function shouldn’t be synchronous! - handleEvents(NULL); - - for (;;) - { - // Grow the clipboard if we need to paste something bigger, there is no - // shrink operation yet. - if (len + 4096 > _glfw.wl.clipboardSize) - { - if (!growClipboardString()) - { - close(fds[0]); - return NULL; - } - } - - // Then read from the fd to the clipboard, handling all known errors. - ret = read(fds[0], _glfw.wl.clipboardString + len, 4096); - if (ret == 0) - break; - if (ret == -1 && errno == EINTR) - continue; - if (ret == -1) - { - // TODO: also report errno maybe. - _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Impossible to read from clipboard fd"); - close(fds[0]); - return NULL; - } - len += ret; - } - close(fds[0]); - if (len + 1 > _glfw.wl.clipboardSize) - { - if (!growClipboardString()) - return NULL; - } - _glfw.wl.clipboardString[len] = '\0'; + _glfw_free(_glfw.wl.clipboardString); + _glfw.wl.clipboardString = + readDataOfferAsString(_glfw.wl.selectionOffer, "text/plain;charset=utf-8"); return _glfw.wl.clipboardString; } diff --git a/src/x11_window.c b/src/x11_window.c index 78e2cb83..04a05a66 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -113,7 +113,7 @@ static void writeEmptyEvent(void) for (;;) { const char byte = 0; - const int result = write(_glfw.x11.emptyEventPipe[1], &byte, 1); + const ssize_t result = write(_glfw.x11.emptyEventPipe[1], &byte, 1); if (result == 1 || (result == -1 && errno != EINTR)) break; } @@ -126,7 +126,7 @@ static void drainEmptyEvents(void) for (;;) { char dummy[64]; - const int result = read(_glfw.x11.emptyEventPipe[0], dummy, sizeof(dummy)); + const ssize_t result = read(_glfw.x11.emptyEventPipe[0], dummy, sizeof(dummy)); if (result == -1 && errno != EINTR) break; } @@ -407,57 +407,6 @@ static void updateWindowMode(_GLFWwindow* window) } } -// Splits and translates a text/uri-list into separate file paths -// NOTE: This function destroys the provided string -// -static char** parseUriList(char* text, int* count) -{ - const char* prefix = "file://"; - char** paths = NULL; - char* line; - - *count = 0; - - while ((line = strtok(text, "\r\n"))) - { - text = NULL; - - if (line[0] == '#') - continue; - - if (strncmp(line, prefix, strlen(prefix)) == 0) - { - line += strlen(prefix); - // TODO: Validate hostname - while (*line != '/') - line++; - } - - (*count)++; - - char* path = _glfw_calloc(strlen(line) + 1, 1); - paths = _glfw_realloc(paths, *count * sizeof(char*)); - paths[*count - 1] = path; - - while (*line) - { - if (line[0] == '%' && line[1] && line[2]) - { - const char digits[3] = { line[1], line[2], '\0' }; - *path = strtol(digits, NULL, 16); - line += 2; - } - else - *path = *line; - - path++; - line++; - } - } - - return paths; -} - // Decode a Unicode code point from a UTF-8 stream // Based on cutef8 by Jeff Bezanson (Public Domain) // @@ -1715,7 +1664,7 @@ static void processEvent(XEvent *event) if (result) { int count; - char** paths = parseUriList(data, &count); + char** paths = _glfwParseUriList(data, &count); _glfwInputDrop(window, count, (const char**) paths);