Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'intern/ghost/intern/GHOST_SystemWayland.cpp')
-rw-r--r--intern/ghost/intern/GHOST_SystemWayland.cpp3544
1 files changed, 2784 insertions, 760 deletions
diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp
index 24d3f525c97..d7520f1243f 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.cpp
+++ b/intern/ghost/intern/GHOST_SystemWayland.cpp
@@ -13,10 +13,19 @@
#include "GHOST_EventWheel.h"
#include "GHOST_TimerManager.h"
#include "GHOST_WindowManager.h"
+#include "GHOST_utildefines.h"
#include "GHOST_ContextEGL.h"
+#ifdef WITH_GHOST_WAYLAND_DYNLOAD
+# include <wayland_dynload_API.h> /* For `ghost_wl_dynload_libraries`. */
+#endif
+
#include <EGL/egl.h>
+
+#ifdef WITH_GHOST_WAYLAND_DYNLOAD
+# include <wayland_dynload_egl.h>
+#endif
#include <wayland-egl.h>
#include <algorithm>
@@ -26,22 +35,63 @@
#include <unordered_map>
#include <unordered_set>
-#include "GHOST_WaylandCursorSettings.h"
-#include <pointer-constraints-client-protocol.h>
-#include <relative-pointer-client-protocol.h>
+#ifdef WITH_GHOST_WAYLAND_DYNLOAD
+# include <wayland_dynload_cursor.h>
+#endif
#include <wayland-cursor.h>
+
+#include "GHOST_WaylandCursorSettings.h"
+
#include <xkbcommon/xkbcommon.h>
+/* Generated by `wayland-scanner`. */
+#include <pointer-constraints-unstable-v1-client-protocol.h>
+#include <relative-pointer-unstable-v1-client-protocol.h>
+#include <tablet-unstable-v2-client-protocol.h>
+#include <xdg-output-unstable-v1-client-protocol.h>
+
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <cstring>
+#include <mutex>
+
+/* Logging, use `ghost.wl.*` prefix. */
+#include "CLG_log.h"
+
+static void keyboard_handle_key_repeat_cancel(struct input_t *input);
+
+static void output_handle_done(void *data, struct wl_output *wl_output);
/**
+ * GNOME (mutter 42.2 had a bug with confine not respecting scale - Hi-DPI), See: T98793.
+ * Even though this has been fixed, at time of writing it's not yet in a release.
+ * Workaround the problem by implementing confine with a software cursor.
+ * While this isn't ideal, it's not adding a lot of overhead as software
+ * cursors are already used for warping (which WAYLAND doesn't support).
+ */
+#define USE_GNOME_CONFINE_HACK
+/**
+ * Always use software confine (not just in GNOME).
+ * Useful for developing with compositors that don't need this workaround.
+ */
+// #define USE_GNOME_CONFINE_HACK_ALWAYS_ON
+
+#ifdef USE_GNOME_CONFINE_HACK
+static bool use_gnome_confine_hack = false;
+#endif
+
+/* -------------------------------------------------------------------- */
+/** \name Inline Event Codes
+ *
* Selected input event code defines from `linux/input-event-codes.h`
* We include some of the button input event codes here, since the header is
- * only available in more recent kernel versions. The event codes are used to
+ * only available in more recent kernel versions.
+ * \{ */
+
+/**
+ * The event codes are used to
* to differentiate from which mouse button an event comes from.
*/
#define BTN_LEFT 0x110
@@ -53,167 +103,394 @@
#define BTN_BACK 0x116
// #define BTN_TASK 0x117 /* UNUSED. */
-struct buffer_t {
- void *data;
- size_t size;
-};
+/**
+ * Tablet events.
+ */
+#define BTN_STYLUS 0x14b /* Use as right-mouse. */
+#define BTN_STYLUS2 0x14c /* Use as middle-mouse. */
+/* NOTE(@campbellbarton): Map to an additional button (not sure which hardware uses this). */
+#define BTN_STYLUS3 0x149
+
+/**
+ * Keyboard scan-codes.
+ */
+#define KEY_GRAVE 41
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Private Types & Defines
+ * \{ */
+
+/**
+ * From XKB internals, use for converting a scan-code from WAYLAND to a #xkb_keycode_t.
+ * Ideally this wouldn't need a local define.
+ */
+#define EVDEV_OFFSET 8
struct cursor_t {
- bool visible;
- struct wl_surface *surface = nullptr;
- struct wl_buffer *buffer;
- struct wl_cursor_image image;
- struct buffer_t *file_buffer = nullptr;
- struct wl_cursor_theme *theme = nullptr;
- int size;
+ bool visible = false;
+ /**
+ * When false, hide the hardware cursor, while the cursor is still considered to be `visible`,
+ * since the grab-mode determines the state of the software cursor,
+ * this may change - removing the need for a software cursor and in this case it's important
+ * the hardware cursor is used.
+ */
+ bool is_hardware = true;
+ bool is_custom = false;
+ struct wl_surface *wl_surface = nullptr;
+ struct wl_buffer *wl_buffer = nullptr;
+ struct wl_cursor_image wl_image = {0};
+ struct wl_cursor_theme *wl_theme = nullptr;
+ void *custom_data = nullptr;
+ size_t custom_data_size = 0;
+ int size = 0;
std::string theme_name;
- // outputs on which the cursor is visible
- std::unordered_set<const output_t *> outputs;
- int scale = 1;
+
+ int custom_scale = 1;
+};
+
+/**
+ * A single tablet can have multiple tools (pen, eraser, brush... etc).
+ * WAYLAND exposes tools via #zwp_tablet_tool_v2.
+ * Since are no API's to access properties of the tool, store the values here.
+ */
+struct tablet_tool_input_t {
+ struct input_t *input = nullptr;
+ struct wl_surface *cursor_surface = nullptr;
+ /** Used to delay clearing tablet focused surface until the frame is handled. */
+ bool proximity = false;
+
+ GHOST_TabletData data = GHOST_TABLET_DATA_NONE;
};
struct data_offer_t {
std::unordered_set<std::string> types;
- uint32_t source_actions;
- uint32_t dnd_action;
- struct wl_data_offer *id;
- std::atomic<bool> in_use;
+ uint32_t source_actions = 0;
+ uint32_t dnd_action = 0;
+ struct wl_data_offer *id = nullptr;
+ std::atomic<bool> in_use = false;
struct {
- int x, y;
+ /** Compatible with #input_t.xy coordinates. */
+ wl_fixed_t xy[2] = {0, 0};
} dnd;
};
struct data_source_t {
- struct wl_data_source *data_source;
- /** Last device that was active. */
- uint32_t source_serial;
- char *buffer_out;
+ struct wl_data_source *data_source = nullptr;
+ char *buffer_out = nullptr;
};
+/**
+ * Data used to implement client-side key-repeat.
+ *
+ * \note it's important not to store the target window here
+ * as it can be closed while the key is repeating,
+ * instead use the focused keyboard from #intput_t which is cleared when windows are closed.
+ * Therefor keyboard events must always check the window has not been cleared.
+ */
struct key_repeat_payload_t {
- GHOST_SystemWayland *system;
- GHOST_IWindow *window;
- GHOST_TKey key;
- GHOST_TEventKeyData key_data;
+ struct input_t *input = nullptr;
+
+ xkb_keycode_t key_code;
+
+ /**
+ * Don't cache the `utf8_buf` as this changes based on modifiers which may be pressed
+ * while key repeat is enabled.
+ */
+ struct {
+ GHOST_TKey gkey;
+ } key_data;
+};
+
+/** Internal variables used to track grab-state. */
+struct input_grab_state_t {
+ bool use_lock = false;
+ bool use_confine = false;
+};
+
+/**
+ * State of the pointing device (tablet or mouse).
+ */
+struct input_state_pointer_t {
+ /**
+ * High precision coordinates.
+ *
+ * Mapping to pixels requires the window scale.
+ * The following example converts these values to screen coordinates.
+ * \code{.cc}
+ * const wl_fixed_t scale = win->scale();
+ * const int event_xy[2] = {
+ * wl_fixed_to_int(scale * input_state->xy[0]),
+ * wl_fixed_to_int(scale * input_state->xy[1]),
+ * };
+ * \endcode
+ */
+ wl_fixed_t xy[2] = {0, 0};
+
+ /** Outputs on which the cursor is visible. */
+ std::unordered_set<const output_t *> outputs;
+
+ int theme_scale = 1;
+
+ /** The serial of the last used pointer or tablet. */
+ uint32_t serial = 0;
+
+ /**
+ * The surface last used with this pointing device
+ * (events with this pointing device will be sent here).
+ */
+ struct wl_surface *wl_surface = nullptr;
+
+ GHOST_Buttons buttons = GHOST_Buttons();
+};
+
+/**
+ * State of the keyboard.
+ */
+struct input_state_keyboard_t {
+ /** The serial of the last used pointer or tablet. */
+ uint32_t serial = 0;
+
+ /**
+ * The surface last used with this pointing device
+ * (events with this pointing device will be sent here).
+ */
+ struct wl_surface *wl_surface = nullptr;
};
struct input_t {
- GHOST_SystemWayland *system;
+ GHOST_SystemWayland *system = nullptr;
std::string name;
- struct wl_seat *seat;
- struct wl_pointer *pointer = nullptr;
- struct wl_keyboard *keyboard = nullptr;
+ struct wl_seat *wl_seat = nullptr;
+ struct wl_pointer *wl_pointer = nullptr;
+ struct wl_keyboard *wl_keyboard = nullptr;
+ struct zwp_tablet_seat_v2 *tablet_seat = nullptr;
+
+ /** All currently active tablet tools (needed for changing the cursor). */
+ std::unordered_set<zwp_tablet_tool_v2 *> tablet_tools;
+
+ /** Use to check if the last cursor input was tablet or pointer. */
+ uint32_t cursor_source_serial = 0;
+
+ input_state_pointer_t pointer;
+
+ /** Mostly this can be interchanged with `pointer` however it can't be locked/confined. */
+ input_state_pointer_t tablet;
+
+ input_state_keyboard_t keyboard;
+
+#ifdef USE_GNOME_CONFINE_HACK
+ bool use_pointer_software_confine = false;
+#endif
+ /** The cursor location (in pixel-space) when hidden grab started (#GHOST_kGrabHide). */
+ wl_fixed_t grab_lock_xy[2] = {0, 0};
- uint32_t pointer_serial;
- int x, y;
- GHOST_Buttons buttons;
struct cursor_t cursor;
- struct zwp_relative_pointer_v1 *relative_pointer;
- struct zwp_locked_pointer_v1 *locked_pointer;
+ struct zwp_relative_pointer_v1 *relative_pointer = nullptr;
+ struct zwp_locked_pointer_v1 *locked_pointer = nullptr;
+ struct zwp_confined_pointer_v1 *confined_pointer = nullptr;
+
+ struct xkb_context *xkb_context = nullptr;
+
+ struct xkb_state *xkb_state = nullptr;
+ /**
+ * Keep a state with no modifiers active, use for symbol lookups.
+ */
+ struct xkb_state *xkb_state_empty = nullptr;
+ /**
+ * Keep a state with number-lock enabled, use to access predictable key-pad symbols.
+ * If number-lock is not supported by the key-map, this is set to NULL.
+ */
+ struct xkb_state *xkb_state_empty_with_numlock = nullptr;
- struct xkb_context *xkb_context;
- struct xkb_state *xkb_state;
struct {
- /* Key repetition in character per second. */
- int32_t rate;
- /* Time (milliseconds) after which to start repeating keys. */
- int32_t delay;
- /* Timer for key repeats. */
+ /** Key repetition in character per second. */
+ int32_t rate = 0;
+ /** Time (milliseconds) after which to start repeating keys. */
+ int32_t delay = 0;
+ /** Timer for key repeats. */
GHOST_ITimerTask *timer = nullptr;
} key_repeat;
- struct wl_surface *focus_pointer = nullptr;
- struct wl_surface *focus_keyboard = nullptr;
+ struct wl_surface *focus_dnd = nullptr;
struct wl_data_device *data_device = nullptr;
- struct data_offer_t *data_offer_dnd; /* Drag & Drop. */
- struct data_offer_t *data_offer_copy_paste; /* Copy & Paste. */
+ /** Drag & Drop. */
+ struct data_offer_t *data_offer_dnd = nullptr;
+ std::mutex data_offer_dnd_mutex;
+
+ /** Copy & Paste. */
+ struct data_offer_t *data_offer_copy_paste = nullptr;
+ std::mutex data_offer_copy_paste_mutex;
- struct data_source_t *data_source;
+ struct data_source_t *data_source = nullptr;
+ std::mutex data_source_mutex;
+
+ /** Last device that was active. */
+ uint32_t data_source_serial = 0;
};
struct display_t {
- GHOST_SystemWayland *system;
+ GHOST_SystemWayland *system = nullptr;
- struct wl_display *display;
+ struct wl_display *display = nullptr;
struct wl_compositor *compositor = nullptr;
+
+#ifdef WITH_GHOST_WAYLAND_LIBDECOR
+ struct libdecor *decor_context = nullptr;
+#else
struct xdg_wm_base *xdg_shell = nullptr;
struct zxdg_decoration_manager_v1 *xdg_decoration_manager = nullptr;
+#endif
+
+ struct zxdg_output_manager_v1 *xdg_output_manager = nullptr;
struct wl_shm *shm = nullptr;
std::vector<output_t *> outputs;
std::vector<input_t *> inputs;
- struct {
- std::string theme;
- int size;
- } cursor;
+
struct wl_data_device_manager *data_device_manager = nullptr;
+ struct zwp_tablet_manager_v2 *tablet_manager = nullptr;
struct zwp_relative_pointer_manager_v1 *relative_pointer_manager = nullptr;
struct zwp_pointer_constraints_v1 *pointer_constraints = nullptr;
-
- std::vector<struct wl_surface *> os_surfaces;
- std::vector<struct wl_egl_window *> os_egl_windows;
};
+#undef LOG
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Private Utility Functions
+ * \{ */
+
static GHOST_WindowManager *window_manager = nullptr;
+/** Check this lock before accessing `GHOST_SystemWayland::selection` from a thread. */
+static std::mutex system_selection_mutex;
+
+/**
+ * Callback for WAYLAND to run when there is an error.
+ *
+ * \note It's useful to set a break-point on this function as some errors are fatal
+ * (for all intents and purposes) but don't crash the process.
+ */
+static void ghost_wayland_log_handler(const char *msg, va_list arg)
+{
+ fprintf(stderr, "GHOST/Wayland: ");
+ vfprintf(stderr, msg, arg); /* Includes newline. */
+
+ GHOST_TBacktraceFn backtrace_fn = GHOST_ISystem::getBacktraceFn();
+ if (backtrace_fn) {
+ backtrace_fn(stderr); /* Includes newline. */
+ }
+}
+
+static input_state_pointer_t *input_state_pointer_active(input_t *input)
+{
+ if (input->pointer.serial == input->cursor_source_serial) {
+ return &input->pointer;
+ }
+ if (input->tablet.serial == input->cursor_source_serial) {
+ return &input->tablet;
+ }
+ return nullptr;
+}
+
+static input_state_pointer_t *input_state_pointer_from_cursor_surface(input_t *input,
+ const wl_surface *wl_surface)
+{
+ if (ghost_wl_surface_own_cursor_pointer(wl_surface)) {
+ return &input->pointer;
+ }
+ if (ghost_wl_surface_own_cursor_tablet(wl_surface)) {
+ return &input->tablet;
+ }
+ GHOST_ASSERT(0, "Surface found without pointer/tablet tag");
+ return nullptr;
+}
+
static void display_destroy(display_t *d)
{
if (d->data_device_manager) {
wl_data_device_manager_destroy(d->data_device_manager);
}
+ if (d->tablet_manager) {
+ zwp_tablet_manager_v2_destroy(d->tablet_manager);
+ }
+
for (output_t *output : d->outputs) {
- wl_output_destroy(output->output);
+ wl_output_destroy(output->wl_output);
delete output;
}
for (input_t *input : d->inputs) {
- if (input->data_source) {
- free(input->data_source->buffer_out);
- if (input->data_source->data_source) {
- wl_data_source_destroy(input->data_source->data_source);
+
+ /* First handle members that require locking.
+ * While highly unlikely, it's possible they are being used while this function runs. */
+ {
+ std::lock_guard lock{input->data_source_mutex};
+ if (input->data_source) {
+ free(input->data_source->buffer_out);
+ if (input->data_source->data_source) {
+ wl_data_source_destroy(input->data_source->data_source);
+ }
+ delete input->data_source;
+ }
+ }
+
+ {
+ std::lock_guard lock{input->data_offer_dnd_mutex};
+ if (input->data_offer_dnd) {
+ wl_data_offer_destroy(input->data_offer_dnd->id);
+ delete input->data_offer_dnd;
}
- delete input->data_source;
}
- if (input->data_offer_copy_paste) {
- wl_data_offer_destroy(input->data_offer_copy_paste->id);
- delete input->data_offer_copy_paste;
+
+ {
+ std::lock_guard lock{input->data_offer_copy_paste_mutex};
+ if (input->data_offer_copy_paste) {
+ wl_data_offer_destroy(input->data_offer_copy_paste->id);
+ delete input->data_offer_copy_paste;
+ }
}
+
if (input->data_device) {
wl_data_device_release(input->data_device);
}
- if (input->pointer) {
- if (input->cursor.file_buffer) {
- munmap(input->cursor.file_buffer->data, input->cursor.file_buffer->size);
- delete input->cursor.file_buffer;
- }
- if (input->cursor.surface) {
- wl_surface_destroy(input->cursor.surface);
+
+ if (input->cursor.custom_data) {
+ munmap(input->cursor.custom_data, input->cursor.custom_data_size);
+ }
+
+ if (input->wl_pointer) {
+ if (input->cursor.wl_surface) {
+ wl_surface_destroy(input->cursor.wl_surface);
}
- if (input->cursor.theme) {
- wl_cursor_theme_destroy(input->cursor.theme);
+ if (input->cursor.wl_theme) {
+ wl_cursor_theme_destroy(input->cursor.wl_theme);
}
- if (input->pointer) {
- wl_pointer_destroy(input->pointer);
+ if (input->wl_pointer) {
+ wl_pointer_destroy(input->wl_pointer);
}
}
- if (input->keyboard) {
+ if (input->wl_keyboard) {
if (input->key_repeat.timer) {
- delete static_cast<key_repeat_payload_t *>(input->key_repeat.timer->getUserData());
- input->system->removeTimer(input->key_repeat.timer);
- input->key_repeat.timer = nullptr;
+ keyboard_handle_key_repeat_cancel(input);
}
- wl_keyboard_destroy(input->keyboard);
- }
- if (input->xkb_state) {
- xkb_state_unref(input->xkb_state);
+ wl_keyboard_destroy(input->wl_keyboard);
}
- if (input->xkb_context) {
- xkb_context_unref(input->xkb_context);
- }
- wl_seat_destroy(input->seat);
+
+ /* Un-referencing checks for NULL case. */
+ xkb_state_unref(input->xkb_state);
+ xkb_state_unref(input->xkb_state_empty);
+ xkb_state_unref(input->xkb_state_empty_with_numlock);
+
+ xkb_context_unref(input->xkb_context);
+
+ wl_seat_destroy(input->wl_seat);
delete input;
}
@@ -229,18 +506,15 @@ static void display_destroy(display_t *d)
zwp_pointer_constraints_v1_destroy(d->pointer_constraints);
}
- for (wl_egl_window *os_egl_window : d->os_egl_windows) {
- wl_egl_window_destroy(os_egl_window);
- }
-
- for (wl_surface *os_surface : d->os_surfaces) {
- wl_surface_destroy(os_surface);
- }
-
if (d->compositor) {
wl_compositor_destroy(d->compositor);
}
+#ifdef WITH_GHOST_WAYLAND_LIBDECOR
+ if (d->decor_context) {
+ libdecor_unref(d->decor_context);
+ }
+#else
if (d->xdg_decoration_manager) {
zxdg_decoration_manager_v1_destroy(d->xdg_decoration_manager);
}
@@ -248,6 +522,7 @@ static void display_destroy(display_t *d)
if (d->xdg_shell) {
xdg_wm_base_destroy(d->xdg_shell);
}
+#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */
if (eglGetDisplay) {
::eglTerminate(eglGetDisplay(EGLNativeDisplayType(d->display)));
@@ -260,7 +535,7 @@ static void display_destroy(display_t *d)
delete d;
}
-static GHOST_TKey xkb_map_gkey(const xkb_keysym_t &sym)
+static GHOST_TKey xkb_map_gkey(const xkb_keysym_t sym)
{
GHOST_TKey gkey;
@@ -351,8 +626,7 @@ static GHOST_TKey xkb_map_gkey(const xkb_keysym_t &sym)
GXMAP(gkey, XKB_KEY_XF86AudioPrev, GHOST_kKeyMediaFirst);
GXMAP(gkey, XKB_KEY_XF86AudioNext, GHOST_kKeyMediaLast);
default:
- GHOST_PRINT("unhandled key: " << std::hex << std::showbase << sym << std::dec << " ("
- << sym << ")" << std::endl);
+ /* Rely on #xkb_map_gkey_or_scan_code to report when no key can be found. */
gkey = GHOST_kKeyUnknown;
}
#undef GXMAP
@@ -361,14 +635,70 @@ static GHOST_TKey xkb_map_gkey(const xkb_keysym_t &sym)
return gkey;
}
+/**
+ * Map the keys using the users keyboard layout, if that fails fall back to physical locations.
+ * This is needed so users with keyboard layouts that don't expose #GHOST_kKeyAccentGrave
+ * (typically the key under escape) in the layout can still use this key in keyboard shortcuts.
+ *
+ * \param key: The key's scan-code, compatible with values in `linux/input-event-codes.h`.
+ */
+static GHOST_TKey xkb_map_gkey_or_scan_code(const xkb_keysym_t sym, const uint32_t key)
+{
+ GHOST_TKey gkey = xkb_map_gkey(sym);
+
+ if (UNLIKELY(gkey == GHOST_kKeyUnknown)) {
+ /* Fall back to physical location for keys that would otherwise do nothing. */
+ switch (key) {
+ case KEY_GRAVE: {
+ gkey = GHOST_kKeyAccentGrave;
+ break;
+ }
+ default: {
+ GHOST_PRINT(
+ /* Key-code. */
+ "unhandled key: " << std::hex << std::showbase << sym << /* Hex. */
+ std::dec << " (" << sym << "), " << /* Decimal. */
+ /* Scan-code. */
+ "scan-code: " << std::hex << std::showbase << key << /* Hex. */
+ std::dec << " (" << key << ")" << /* Decimal. */
+ std::endl);
+ break;
+ }
+ }
+ }
+
+ return gkey;
+}
+
+static GHOST_TTabletMode tablet_tool_map_type(enum zwp_tablet_tool_v2_type wl_tablet_tool_type)
+{
+ switch (wl_tablet_tool_type) {
+ case ZWP_TABLET_TOOL_V2_TYPE_ERASER: {
+ return GHOST_kTabletModeEraser;
+ }
+ case ZWP_TABLET_TOOL_V2_TYPE_PEN:
+ case ZWP_TABLET_TOOL_V2_TYPE_BRUSH:
+ case ZWP_TABLET_TOOL_V2_TYPE_PENCIL:
+ case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH:
+ case ZWP_TABLET_TOOL_V2_TYPE_FINGER:
+ case ZWP_TABLET_TOOL_V2_TYPE_MOUSE:
+ case ZWP_TABLET_TOOL_V2_TYPE_LENS: {
+ return GHOST_kTabletModeStylus;
+ }
+ }
+
+ GHOST_PRINT("unknown tablet tool: " << wl_tablet_tool_type << std::endl);
+ return GHOST_kTabletModeStylus;
+}
+
static const int default_cursor_size = 24;
-static const std::unordered_map<GHOST_TStandardCursor, std::string> cursors = {
+static const std::unordered_map<GHOST_TStandardCursor, const char *> cursors = {
{GHOST_kStandardCursorDefault, "left_ptr"},
{GHOST_kStandardCursorRightArrow, "right_ptr"},
{GHOST_kStandardCursorLeftArrow, "left_ptr"},
{GHOST_kStandardCursorInfo, ""},
- {GHOST_kStandardCursorDestroy, ""},
+ {GHOST_kStandardCursorDestroy, "pirate"},
{GHOST_kStandardCursorHelp, "question_arrow"},
{GHOST_kStandardCursorWait, "watch"},
{GHOST_kStandardCursorText, "xterm"},
@@ -376,21 +706,21 @@ static const std::unordered_map<GHOST_TStandardCursor, std::string> cursors = {
{GHOST_kStandardCursorCrosshairA, ""},
{GHOST_kStandardCursorCrosshairB, ""},
{GHOST_kStandardCursorCrosshairC, ""},
- {GHOST_kStandardCursorPencil, ""},
+ {GHOST_kStandardCursorPencil, "pencil"},
{GHOST_kStandardCursorUpArrow, "sb_up_arrow"},
{GHOST_kStandardCursorDownArrow, "sb_down_arrow"},
- {GHOST_kStandardCursorVerticalSplit, ""},
- {GHOST_kStandardCursorHorizontalSplit, ""},
+ {GHOST_kStandardCursorVerticalSplit, "split_v"},
+ {GHOST_kStandardCursorHorizontalSplit, "split_h"},
{GHOST_kStandardCursorEraser, ""},
{GHOST_kStandardCursorKnife, ""},
- {GHOST_kStandardCursorEyedropper, ""},
- {GHOST_kStandardCursorZoomIn, ""},
- {GHOST_kStandardCursorZoomOut, ""},
+ {GHOST_kStandardCursorEyedropper, "color-picker"},
+ {GHOST_kStandardCursorZoomIn, "zoom-in"},
+ {GHOST_kStandardCursorZoomOut, "zoom-out"},
{GHOST_kStandardCursorMove, "move"},
- {GHOST_kStandardCursorNSEWScroll, ""},
- {GHOST_kStandardCursorNSScroll, ""},
- {GHOST_kStandardCursorEWScroll, ""},
- {GHOST_kStandardCursorStop, ""},
+ {GHOST_kStandardCursorNSEWScroll, "size_all"}, /* Not an exact match. */
+ {GHOST_kStandardCursorNSScroll, "size_ver"}, /* Not an exact match. */
+ {GHOST_kStandardCursorEWScroll, "size_hor"}, /* Not an exact match. */
+ {GHOST_kStandardCursorStop, "not-allowed"},
{GHOST_kStandardCursorUpDown, "sb_v_double_arrow"},
{GHOST_kStandardCursorLeftRight, "sb_h_double_arrow"},
{GHOST_kStandardCursorTopSide, "top_side"},
@@ -429,68 +759,209 @@ static const std::vector<std::string> mime_send = {
"text/plain",
};
+static int memfd_create_sealed(const char *name)
+{
+#ifdef HAVE_MEMFD_CREATE
+ const int fd = memfd_create(name, MFD_CLOEXEC | MFD_ALLOW_SEALING);
+ if (fd >= 0) {
+ fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
+ }
+ return fd;
+#else /* HAVE_MEMFD_CREATE */
+ char *path = getenv("XDG_RUNTIME_DIR");
+ if (!path) {
+ errno = ENOENT;
+ return -1;
+ }
+ char *tmpname;
+ asprintf(&tmpname, "%s/%s-XXXXXX", path, name);
+ const int fd = mkostemp(tmpname, O_CLOEXEC);
+ if (fd >= 0) {
+ unlink(tmpname);
+ }
+ free(tmpname);
+ return fd;
+#endif /* !HAVE_MEMFD_CREATE */
+}
+
+static size_t ghost_wl_shm_format_as_size(enum wl_shm_format format)
+{
+ switch (format) {
+ case WL_SHM_FORMAT_ARGB8888: {
+ return 4;
+ }
+ default: {
+ /* Support other formats as needed. */
+ GHOST_ASSERT(0, "Unexpected format passed in!");
+ return 4;
+ }
+ }
+}
+
+/**
+ * Return a #wl_buffer, ready to have it's data filled in or NULL in case of failure.
+ * The caller is responsible for calling `unmap(buffer_data, buffer_size)`.
+ *
+ * \param r_buffer_data: The buffer to be filled.
+ * \param r_buffer_data_size: The size of `r_buffer_data` in bytes.
+ */
+static wl_buffer *ghost_wl_buffer_create_for_image(struct wl_shm *shm,
+ const int32_t size_xy[2],
+ enum wl_shm_format format,
+ void **r_buffer_data,
+ size_t *r_buffer_data_size)
+{
+ const int fd = memfd_create_sealed("ghost-wl-buffer");
+ wl_buffer *buffer = nullptr;
+ if (fd >= 0) {
+ const int32_t buffer_stride = size_xy[0] * ghost_wl_shm_format_as_size(format);
+ const int32_t buffer_size = buffer_stride * size_xy[1];
+ if (posix_fallocate(fd, 0, buffer_size) == 0) {
+ void *buffer_data = mmap(nullptr, buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (buffer_data != MAP_FAILED) {
+ struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, buffer_size);
+ buffer = wl_shm_pool_create_buffer(pool, 0, UNPACK2(size_xy), buffer_stride, format);
+ wl_shm_pool_destroy(pool);
+ if (buffer) {
+ *r_buffer_data = buffer_data;
+ *r_buffer_data_size = (size_t)buffer_size;
+ }
+ else {
+ /* Highly unlikely. */
+ munmap(buffer_data, buffer_size);
+ }
+ }
+ }
+ close(fd);
+ }
+ return buffer;
+}
+
+/** \} */
+
/* -------------------------------------------------------------------- */
-/** \name Interface Callbacks
+/** \name Listener (Relative Motion), #zwp_relative_pointer_v1_listener
*
* These callbacks are registered for Wayland interfaces and called when
* an event is received from the compositor.
* \{ */
-static void relative_pointer_relative_motion(
- void *data,
- struct zwp_relative_pointer_v1 * /*zwp_relative_pointer_v1*/,
- uint32_t /*utime_hi*/,
- uint32_t /*utime_lo*/,
- wl_fixed_t dx,
- wl_fixed_t dy,
- wl_fixed_t /*dx_unaccel*/,
- wl_fixed_t /*dy_unaccel*/)
+static CLG_LogRef LOG_WL_RELATIVE_POINTER = {"ghost.wl.handle.relative_pointer"};
+#define LOG (&LOG_WL_RELATIVE_POINTER)
+
+/**
+ * The caller is responsible for setting the value of `input->xy`.
+ */
+static void relative_pointer_handle_relative_motion_impl(input_t *input,
+ GHOST_WindowWayland *win,
+ const wl_fixed_t xy[2])
{
- input_t *input = static_cast<input_t *>(data);
+ const wl_fixed_t scale = win->scale();
- input->x += wl_fixed_to_int(dx);
- input->y += wl_fixed_to_int(dy);
+ input->pointer.xy[0] = xy[0];
+ input->pointer.xy[1] = xy[1];
- GHOST_IWindow *win = static_cast<GHOST_WindowWayland *>(
- wl_surface_get_user_data(input->focus_pointer));
+#ifdef USE_GNOME_CONFINE_HACK
+ if (input->use_pointer_software_confine) {
+ GHOST_Rect bounds;
+ win->getClientBounds(bounds);
+ /* Needed or the cursor is considered outside the window and doesn't restore the location. */
+ bounds.m_r -= 1;
+ bounds.m_b -= 1;
+ bounds.m_l = wl_fixed_from_int(bounds.m_l) / scale;
+ bounds.m_t = wl_fixed_from_int(bounds.m_t) / scale;
+ bounds.m_r = wl_fixed_from_int(bounds.m_r) / scale;
+ bounds.m_b = wl_fixed_from_int(bounds.m_b) / scale;
+ bounds.clampPoint(UNPACK2(input->pointer.xy));
+ }
+#endif
input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
GHOST_kEventCursorMove,
win,
- input->x,
- input->y,
+ wl_fixed_to_int(scale * input->pointer.xy[0]),
+ wl_fixed_to_int(scale * input->pointer.xy[1]),
GHOST_TABLET_DATA_NONE));
}
+static void relative_pointer_handle_relative_motion(
+ void *data,
+ struct zwp_relative_pointer_v1 * /*zwp_relative_pointer_v1*/,
+ const uint32_t /*utime_hi*/,
+ const uint32_t /*utime_lo*/,
+ const wl_fixed_t dx,
+ const wl_fixed_t dy,
+ const wl_fixed_t /*dx_unaccel*/,
+ const wl_fixed_t /*dy_unaccel*/)
+{
+ input_t *input = static_cast<input_t *>(data);
+ if (wl_surface *focus_surface = input->pointer.wl_surface) {
+ CLOG_INFO(LOG, 2, "relative_motion");
+ GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
+ const wl_fixed_t scale = win->scale();
+ const wl_fixed_t xy_next[2] = {
+ input->pointer.xy[0] + (dx / scale),
+ input->pointer.xy[1] + (dy / scale),
+ };
+ relative_pointer_handle_relative_motion_impl(input, win, xy_next);
+ }
+ else {
+ CLOG_INFO(LOG, 2, "relative_motion (skipped)");
+ }
+}
+
static const zwp_relative_pointer_v1_listener relative_pointer_listener = {
- relative_pointer_relative_motion,
+ relative_pointer_handle_relative_motion,
};
+#undef LOG
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Listener (Data Source), #wl_data_source_listener
+ * \{ */
+
+static CLG_LogRef LOG_WL_DATA_SOURCE = {"ghost.wl.handle.data_source"};
+#define LOG (&LOG_WL_DATA_SOURCE)
+
static void dnd_events(const input_t *const input, const GHOST_TEventType event)
{
- const uint64_t time = input->system->getMilliSeconds();
- GHOST_IWindow *const window = static_cast<GHOST_WindowWayland *>(
- wl_surface_get_user_data(input->focus_pointer));
- for (const std::string &type : mime_preference_order) {
- input->system->pushEvent(new GHOST_EventDragnDrop(time,
- event,
- mime_dnd.at(type),
- window,
- input->data_offer_dnd->dnd.x,
- input->data_offer_dnd->dnd.y,
- nullptr));
+ /* NOTE: `input->data_offer_dnd_mutex` must already be locked. */
+ if (wl_surface *focus_surface = input->focus_dnd) {
+ GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
+ const wl_fixed_t scale = win->scale();
+ const int event_xy[2] = {
+ wl_fixed_to_int(scale * input->data_offer_dnd->dnd.xy[0]),
+ wl_fixed_to_int(scale * input->data_offer_dnd->dnd.xy[1]),
+ };
+
+ const uint64_t time = input->system->getMilliSeconds();
+ for (const std::string &type : mime_preference_order) {
+ input->system->pushEvent(new GHOST_EventDragnDrop(
+ time, event, mime_dnd.at(type), win, UNPACK2(event_xy), nullptr));
+ }
}
}
-static std::string read_pipe(data_offer_t *data_offer, const std::string mime_receive)
+static std::string read_pipe(data_offer_t *data_offer,
+ const std::string mime_receive,
+ std::mutex *mutex)
{
int pipefd[2];
- if (pipe(pipefd) != 0) {
+ if (UNLIKELY(pipe(pipefd) != 0)) {
return {};
}
wl_data_offer_receive(data_offer->id, mime_receive.c_str(), pipefd[1]);
close(pipefd[1]);
+ data_offer->in_use.store(false);
+
+ if (mutex) {
+ mutex->unlock();
+ }
+ /* WARNING: `data_offer` may be freed from now on. */
+
std::string data;
ssize_t len;
char buffer[4096];
@@ -498,7 +969,6 @@ static std::string read_pipe(data_offer_t *data_offer, const std::string mime_re
data.insert(data.end(), buffer, buffer + len);
}
close(pipefd[0]);
- data_offer->in_use.store(false);
return data;
}
@@ -507,29 +977,35 @@ static std::string read_pipe(data_offer_t *data_offer, const std::string mime_re
* A target accepts an offered mime type.
*
* Sent when a target accepts pointer_focus or motion events. If
- * a target does not accept any of the offered types, type is NULL.
+ * a target does not accept any of the offered types, type is nullptr.
*/
-static void data_source_target(void * /*data*/,
- struct wl_data_source * /*wl_data_source*/,
- const char * /*mime_type*/)
+static void data_source_handle_target(void * /*data*/,
+ struct wl_data_source * /*wl_data_source*/,
+ const char * /*mime_type*/)
{
- /* pass */
+ CLOG_INFO(LOG, 2, "target");
}
-static void data_source_send(void *data,
- struct wl_data_source * /*wl_data_source*/,
- const char * /*mime_type*/,
- int32_t fd)
+static void data_source_handle_send(void *data,
+ struct wl_data_source * /*wl_data_source*/,
+ const char * /*mime_type*/,
+ const int32_t fd)
{
- const char *const buffer = static_cast<char *>(data);
- if (write(fd, buffer, strlen(buffer) + 1) < 0) {
+ input_t *input = static_cast<input_t *>(data);
+ std::lock_guard lock{input->data_source_mutex};
+
+ CLOG_INFO(LOG, 2, "send");
+
+ const char *const buffer = input->data_source->buffer_out;
+ if (write(fd, buffer, strlen(buffer)) < 0) {
GHOST_PRINT("error writing to clipboard: " << std::strerror(errno) << std::endl);
}
close(fd);
}
-static void data_source_cancelled(void * /*data*/, struct wl_data_source *wl_data_source)
+static void data_source_handle_cancelled(void * /*data*/, struct wl_data_source *wl_data_source)
{
+ CLOG_INFO(LOG, 2, "cancelled");
wl_data_source_destroy(wl_data_source);
}
@@ -540,10 +1016,10 @@ static void data_source_cancelled(void * /*data*/, struct wl_data_source *wl_dat
* indicate acceptance, #wl_data_source.cancelled may still be
* emitted afterwards if the drop destination does not accept any mime type.
*/
-static void data_source_dnd_drop_performed(void * /*data*/,
- struct wl_data_source * /*wl_data_source*/)
+static void data_source_handle_dnd_drop_performed(void * /*data*/,
+ struct wl_data_source * /*wl_data_source*/)
{
- /* pass */
+ CLOG_INFO(LOG, 2, "dnd_drop_performed");
}
/**
@@ -553,9 +1029,10 @@ static void data_source_dnd_drop_performed(void * /*data*/,
* source, so the client is now free to destroy this data source
* and free all associated data.
*/
-static void data_source_dnd_finished(void * /*data*/, struct wl_data_source * /*wl_data_source*/)
+static void data_source_handle_dnd_finished(void * /*data*/,
+ struct wl_data_source * /*wl_data_source*/)
{
- /* pass */
+ CLOG_INFO(LOG, 2, "dnd_finished");
}
/**
@@ -565,73 +1042,108 @@ static void data_source_dnd_finished(void * /*data*/, struct wl_data_source * /*
* after matching the source/destination side actions. Only one
* action (or none) will be offered here.
*/
-static void data_source_action(void * /*data*/,
- struct wl_data_source * /*wl_data_source*/,
- uint32_t /*dnd_action*/)
+static void data_source_handle_action(void * /*data*/,
+ struct wl_data_source * /*wl_data_source*/,
+ const uint32_t dnd_action)
{
- /* pass */
+ CLOG_INFO(LOG, 2, "handle_action (dnd_action=%u)", dnd_action);
}
static const struct wl_data_source_listener data_source_listener = {
- data_source_target,
- data_source_send,
- data_source_cancelled,
- data_source_dnd_drop_performed,
- data_source_dnd_finished,
- data_source_action,
+ data_source_handle_target,
+ data_source_handle_send,
+ data_source_handle_cancelled,
+ data_source_handle_dnd_drop_performed,
+ data_source_handle_dnd_finished,
+ data_source_handle_action,
};
-static void data_offer_offer(void *data,
- struct wl_data_offer * /*wl_data_offer*/,
- const char *mime_type)
+#undef LOG
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Listener (Data Offer), #wl_data_offer_listener
+ * \{ */
+
+static CLG_LogRef LOG_WL_DATA_OFFER = {"ghost.wl.handle.data_offer"};
+#define LOG (&LOG_WL_DATA_OFFER)
+
+static void data_offer_handle_offer(void *data,
+ struct wl_data_offer * /*wl_data_offer*/,
+ const char *mime_type)
{
+ CLOG_INFO(LOG, 2, "offer (mime_type=%s)", mime_type);
static_cast<data_offer_t *>(data)->types.insert(mime_type);
}
-static void data_offer_source_actions(void *data,
- struct wl_data_offer * /*wl_data_offer*/,
- uint32_t source_actions)
+static void data_offer_handle_source_actions(void *data,
+ struct wl_data_offer * /*wl_data_offer*/,
+ const uint32_t source_actions)
{
+ CLOG_INFO(LOG, 2, "source_actions (%u)", source_actions);
static_cast<data_offer_t *>(data)->source_actions = source_actions;
}
-static void data_offer_action(void *data,
- struct wl_data_offer * /*wl_data_offer*/,
- uint32_t dnd_action)
+static void data_offer_handle_action(void *data,
+ struct wl_data_offer * /*wl_data_offer*/,
+ const uint32_t dnd_action)
{
+ CLOG_INFO(LOG, 2, "actions (%u)", dnd_action);
static_cast<data_offer_t *>(data)->dnd_action = dnd_action;
}
static const struct wl_data_offer_listener data_offer_listener = {
- data_offer_offer,
- data_offer_source_actions,
- data_offer_action,
+ data_offer_handle_offer,
+ data_offer_handle_source_actions,
+ data_offer_handle_action,
};
-static void data_device_data_offer(void * /*data*/,
- struct wl_data_device * /*wl_data_device*/,
- struct wl_data_offer *id)
+#undef LOG
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Listener (Data Device), #wl_data_device_listener
+ * \{ */
+
+static CLG_LogRef LOG_WL_DATA_DEVICE = {"ghost.wl.handle.data_device"};
+#define LOG (&LOG_WL_DATA_DEVICE)
+
+static void data_device_handle_data_offer(void * /*data*/,
+ struct wl_data_device * /*wl_data_device*/,
+ struct wl_data_offer *id)
{
+ CLOG_INFO(LOG, 2, "data_offer");
+
data_offer_t *data_offer = new data_offer_t;
data_offer->id = id;
wl_data_offer_add_listener(id, &data_offer_listener, data_offer);
}
-static void data_device_enter(void *data,
- struct wl_data_device * /*wl_data_device*/,
- uint32_t serial,
- struct wl_surface * /*surface*/,
- wl_fixed_t x,
- wl_fixed_t y,
- struct wl_data_offer *id)
+static void data_device_handle_enter(void *data,
+ struct wl_data_device * /*wl_data_device*/,
+ const uint32_t serial,
+ struct wl_surface *surface,
+ const wl_fixed_t x,
+ const wl_fixed_t y,
+ struct wl_data_offer *id)
{
+ if (!ghost_wl_surface_own(surface)) {
+ CLOG_INFO(LOG, 2, "enter (skipped)");
+ return;
+ }
+ CLOG_INFO(LOG, 2, "enter");
+
input_t *input = static_cast<input_t *>(data);
+ std::lock_guard lock{input->data_offer_dnd_mutex};
+
input->data_offer_dnd = static_cast<data_offer_t *>(wl_data_offer_get_user_data(id));
data_offer_t *data_offer = input->data_offer_dnd;
data_offer->in_use.store(true);
- data_offer->dnd.x = wl_fixed_to_int(x);
- data_offer->dnd.y = wl_fixed_to_int(y);
+ data_offer->dnd.xy[0] = x;
+ data_offer->dnd.xy[1] = y;
wl_data_offer_set_actions(id,
WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
@@ -642,14 +1154,19 @@ static void data_device_enter(void *data,
wl_data_offer_accept(id, serial, type.c_str());
}
+ input->focus_dnd = surface;
dnd_events(input, GHOST_kEventDraggingEntered);
}
-static void data_device_leave(void *data, struct wl_data_device * /*wl_data_device*/)
+static void data_device_handle_leave(void *data, struct wl_data_device * /*wl_data_device*/)
{
input_t *input = static_cast<input_t *>(data);
+ std::lock_guard lock{input->data_offer_dnd_mutex};
+
+ CLOG_INFO(LOG, 2, "leave");
dnd_events(input, GHOST_kEventDraggingExited);
+ input->focus_dnd = nullptr;
if (input->data_offer_dnd && !input->data_offer_dnd->in_use.load()) {
wl_data_offer_destroy(input->data_offer_dnd->id);
@@ -658,21 +1175,30 @@ static void data_device_leave(void *data, struct wl_data_device * /*wl_data_devi
}
}
-static void data_device_motion(void *data,
- struct wl_data_device * /*wl_data_device*/,
- uint32_t /*time*/,
- wl_fixed_t x,
- wl_fixed_t y)
+static void data_device_handle_motion(void *data,
+ struct wl_data_device * /*wl_data_device*/,
+ const uint32_t /*time*/,
+ const wl_fixed_t x,
+ const wl_fixed_t y)
{
input_t *input = static_cast<input_t *>(data);
- input->data_offer_dnd->dnd.x = wl_fixed_to_int(x);
- input->data_offer_dnd->dnd.y = wl_fixed_to_int(y);
+ std::lock_guard lock{input->data_offer_dnd_mutex};
+
+ CLOG_INFO(LOG, 2, "motion");
+
+ input->data_offer_dnd->dnd.xy[0] = x;
+ input->data_offer_dnd->dnd.xy[1] = y;
+
dnd_events(input, GHOST_kEventDraggingUpdated);
}
-static void data_device_drop(void *data, struct wl_data_device * /*wl_data_device*/)
+static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_data_device*/)
{
input_t *input = static_cast<input_t *>(data);
+ std::lock_guard lock{input->data_offer_dnd_mutex};
+
+ CLOG_INFO(LOG, 2, "drop");
+
data_offer_t *data_offer = input->data_offer_dnd;
const std::string mime_receive = *std::find_first_of(mime_preference_order.begin(),
@@ -680,13 +1206,13 @@ static void data_device_drop(void *data, struct wl_data_device * /*wl_data_devic
data_offer->types.begin(),
data_offer->types.end());
- auto read_uris = [](input_t *const input,
- data_offer_t *data_offer,
- const std::string mime_receive) {
- const int x = data_offer->dnd.x;
- const int y = data_offer->dnd.y;
+ auto read_uris_fn = [](input_t *const input,
+ data_offer_t *data_offer,
+ wl_surface *surface,
+ const std::string mime_receive) {
+ const wl_fixed_t xy[2] = {UNPACK2(data_offer->dnd.xy)};
- const std::string data = read_pipe(data_offer, mime_receive);
+ const std::string data = read_pipe(data_offer, mime_receive, nullptr);
wl_data_offer_finish(data_offer->id);
wl_data_offer_destroy(data_offer->id);
@@ -700,6 +1226,7 @@ static void data_device_drop(void *data, struct wl_data_device * /*wl_data_devic
static constexpr const char *file_proto = "file://";
static constexpr const char *crlf = "\r\n";
+ GHOST_WindowWayland *win = ghost_wl_surface_user_data(surface);
std::vector<std::string> uris;
size_t pos = 0;
@@ -723,32 +1250,37 @@ static void data_device_drop(void *data, struct wl_data_device * /*wl_data_devic
flist->strings[i] = static_cast<uint8_t *>(malloc((uris[i].size() + 1) * sizeof(uint8_t)));
memcpy(flist->strings[i], uris[i].data(), uris[i].size() + 1);
}
- GHOST_IWindow *win = static_cast<GHOST_WindowWayland *>(
- wl_surface_get_user_data(input->focus_pointer));
+
+ const wl_fixed_t scale = win->scale();
system->pushEvent(new GHOST_EventDragnDrop(system->getMilliSeconds(),
GHOST_kEventDraggingDropDone,
GHOST_kDragnDropTypeFilenames,
win,
- x,
- y,
+ wl_fixed_to_int(scale * xy[0]),
+ wl_fixed_to_int(scale * xy[1]),
flist));
}
- else if (mime_receive == mime_text_plain || mime_receive == mime_text_utf8) {
+ else if (ELEM(mime_receive, mime_text_plain, mime_text_utf8)) {
/* TODO: enable use of internal functions 'txt_insert_buf' and
* 'text_update_edited' to behave like dropped text was pasted. */
}
wl_display_roundtrip(system->display());
};
- std::thread read_thread(read_uris, input, data_offer, mime_receive);
+ /* Pass in `input->focus_dnd` instead of accessing it from `input` since the leave callback
+ * (#data_device_leave) will clear the value once this function starts. */
+ std::thread read_thread(read_uris_fn, input, data_offer, input->focus_dnd, mime_receive);
read_thread.detach();
}
-static void data_device_selection(void *data,
- struct wl_data_device * /*wl_data_device*/,
- struct wl_data_offer *id)
+static void data_device_handle_selection(void *data,
+ struct wl_data_device * /*wl_data_device*/,
+ struct wl_data_offer *id)
{
input_t *input = static_cast<input_t *>(data);
+
+ std::lock_guard lock{input->data_offer_copy_paste_mutex};
+
data_offer_t *data_offer = input->data_offer_copy_paste;
/* Delete old data offer. */
@@ -759,206 +1291,250 @@ static void data_device_selection(void *data,
}
if (id == nullptr) {
+ CLOG_INFO(LOG, 2, "selection: (skipped)");
return;
}
+ CLOG_INFO(LOG, 2, "selection");
/* Get new data offer. */
data_offer = static_cast<data_offer_t *>(wl_data_offer_get_user_data(id));
input->data_offer_copy_paste = data_offer;
- std::string mime_receive;
- for (const std::string type : {mime_text_utf8, mime_text_plain}) {
- if (data_offer->types.count(type)) {
- mime_receive = type;
- break;
+ auto read_selection_fn = [](input_t *input) {
+ GHOST_SystemWayland *const system = input->system;
+ input->data_offer_copy_paste_mutex.lock();
+
+ data_offer_t *data_offer = input->data_offer_copy_paste;
+ std::string mime_receive;
+ for (const std::string type : {mime_text_utf8, mime_text_plain}) {
+ if (data_offer->types.count(type)) {
+ mime_receive = type;
+ break;
+ }
}
- }
+ const std::string data = read_pipe(
+ data_offer, mime_receive, &input->data_offer_copy_paste_mutex);
- auto read_selection = [](GHOST_SystemWayland *const system,
- data_offer_t *data_offer,
- const std::string mime_receive) {
- const std::string data = read_pipe(data_offer, mime_receive);
- system->setSelection(data);
+ {
+ std::lock_guard lock{system_selection_mutex};
+ system->selection_set(data);
+ }
};
- std::thread read_thread(read_selection, input->system, data_offer, mime_receive);
+ std::thread read_thread(read_selection_fn, input);
read_thread.detach();
}
static const struct wl_data_device_listener data_device_listener = {
- data_device_data_offer,
- data_device_enter,
- data_device_leave,
- data_device_motion,
- data_device_drop,
- data_device_selection,
+ data_device_handle_data_offer,
+ data_device_handle_enter,
+ data_device_handle_leave,
+ data_device_handle_motion,
+ data_device_handle_drop,
+ data_device_handle_selection,
};
-static void cursor_buffer_release(void *data, struct wl_buffer *wl_buffer)
+#undef LOG
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Listener (Buffer), #wl_buffer_listener
+ * \{ */
+
+static CLG_LogRef LOG_WL_CURSOR_BUFFER = {"ghost.wl.handle.cursor_buffer"};
+#define LOG (&LOG_WL_CURSOR_BUFFER)
+
+static void cursor_buffer_handle_release(void *data, struct wl_buffer *wl_buffer)
{
- cursor_t *cursor = static_cast<cursor_t *>(data);
+ CLOG_INFO(LOG, 2, "release");
+ cursor_t *cursor = static_cast<cursor_t *>(data);
wl_buffer_destroy(wl_buffer);
- if (wl_buffer == cursor->buffer) {
- /* the mapped buffer was from a custom cursor */
- cursor->buffer = nullptr;
+ if (wl_buffer == cursor->wl_buffer) {
+ /* The mapped buffer was from a custom cursor. */
+ cursor->wl_buffer = nullptr;
}
}
-const struct wl_buffer_listener cursor_buffer_listener = {
- cursor_buffer_release,
+static const struct wl_buffer_listener cursor_buffer_listener = {
+ cursor_buffer_handle_release,
};
-static GHOST_IWindow *get_window(struct wl_surface *surface)
-{
- if (!surface) {
- return nullptr;
- }
+#undef LOG
- for (GHOST_IWindow *win : window_manager->getWindows()) {
- if (surface == static_cast<const GHOST_WindowWayland *>(win)->surface()) {
- return win;
- }
- }
- return nullptr;
-}
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Listener (Surface), #wl_surface_listener
+ * \{ */
+
+static CLG_LogRef LOG_WL_CURSOR_SURFACE = {"ghost.wl.handle.cursor_surface"};
+#define LOG (&LOG_WL_CURSOR_SURFACE)
-static bool update_cursor_scale(cursor_t &cursor, wl_shm *shm)
+static bool update_cursor_scale(cursor_t &cursor,
+ wl_shm *shm,
+ input_state_pointer_t *input_state,
+ wl_surface *cursor_surface)
{
int scale = 0;
- for (const output_t *output : cursor.outputs) {
- if (output->scale > scale)
+ for (const output_t *output : input_state->outputs) {
+ if (output->scale > scale) {
scale = output->scale;
+ }
}
- if (scale > 0 && cursor.scale != scale) {
- cursor.scale = scale;
- wl_surface_set_buffer_scale(cursor.surface, scale);
- wl_cursor_theme_destroy(cursor.theme);
- cursor.theme = wl_cursor_theme_load(cursor.theme_name.c_str(), scale * cursor.size, shm);
+ if (scale > 0 && input_state->theme_scale != scale) {
+ input_state->theme_scale = scale;
+ if (!cursor.is_custom) {
+ wl_surface_set_buffer_scale(cursor_surface, scale);
+ }
+ wl_cursor_theme_destroy(cursor.wl_theme);
+ cursor.wl_theme = wl_cursor_theme_load(cursor.theme_name.c_str(), scale * cursor.size, shm);
return true;
}
return false;
}
-static void cursor_surface_enter(void *data,
- struct wl_surface * /*wl_surface*/,
- struct wl_output *output)
+static void cursor_surface_handle_enter(void *data,
+ struct wl_surface *wl_surface,
+ struct wl_output *output)
{
- input_t *input = static_cast<input_t *>(data);
- for (const output_t *reg_output : input->system->outputs()) {
- if (reg_output->output == output) {
- input->cursor.outputs.insert(reg_output);
- }
+ if (!ghost_wl_output_own(output)) {
+ CLOG_INFO(LOG, 2, "handle_enter (skipped)");
+ return;
}
- update_cursor_scale(input->cursor, input->system->shm());
+ CLOG_INFO(LOG, 2, "handle_enter");
+
+ input_t *input = static_cast<input_t *>(data);
+ input_state_pointer_t *input_state = input_state_pointer_from_cursor_surface(input, wl_surface);
+ const output_t *reg_output = ghost_wl_output_user_data(output);
+ input_state->outputs.insert(reg_output);
+ update_cursor_scale(input->cursor, input->system->shm(), input_state, wl_surface);
}
-static void cursor_surface_leave(void *data,
- struct wl_surface * /*wl_surface*/,
- struct wl_output *output)
+static void cursor_surface_handle_leave(void *data,
+ struct wl_surface *wl_surface,
+ struct wl_output *output)
{
- input_t *input = static_cast<input_t *>(data);
- for (const output_t *reg_output : input->system->outputs()) {
- if (reg_output->output == output) {
- input->cursor.outputs.erase(reg_output);
- }
+ if (!(output && ghost_wl_output_own(output))) {
+ CLOG_INFO(LOG, 2, "handle_leave (skipped)");
+ return;
}
- update_cursor_scale(input->cursor, input->system->shm());
+ CLOG_INFO(LOG, 2, "handle_leave");
+
+ input_t *input = static_cast<input_t *>(data);
+ input_state_pointer_t *input_state = input_state_pointer_from_cursor_surface(input, wl_surface);
+ const output_t *reg_output = ghost_wl_output_user_data(output);
+ input_state->outputs.erase(reg_output);
+ update_cursor_scale(input->cursor, input->system->shm(), input_state, wl_surface);
}
-struct wl_surface_listener cursor_surface_listener = {
- cursor_surface_enter,
- cursor_surface_leave,
+static const struct wl_surface_listener cursor_surface_listener = {
+ cursor_surface_handle_enter,
+ cursor_surface_handle_leave,
};
-static void pointer_enter(void *data,
- struct wl_pointer * /*wl_pointer*/,
- uint32_t serial,
- struct wl_surface *surface,
- wl_fixed_t surface_x,
- wl_fixed_t surface_y)
-{
- GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(get_window(surface));
+#undef LOG
- if (!win) {
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Listener (Pointer), #wl_pointer_listener
+ * \{ */
+
+static CLG_LogRef LOG_WL_POINTER = {"ghost.wl.handle.pointer"};
+#define LOG (&LOG_WL_POINTER)
+
+static void pointer_handle_enter(void *data,
+ struct wl_pointer * /*wl_pointer*/,
+ const uint32_t serial,
+ struct wl_surface *surface,
+ const wl_fixed_t surface_x,
+ const wl_fixed_t surface_y)
+{
+ if (!ghost_wl_surface_own(surface)) {
+ CLOG_INFO(LOG, 2, "enter (skipped)");
return;
}
+ CLOG_INFO(LOG, 2, "enter");
+
+ GHOST_WindowWayland *win = ghost_wl_surface_user_data(surface);
win->activate();
input_t *input = static_cast<input_t *>(data);
- input->pointer_serial = serial;
- input->x = win->scale() * wl_fixed_to_int(surface_x);
- input->y = win->scale() * wl_fixed_to_int(surface_y);
- input->focus_pointer = surface;
+ input->cursor_source_serial = serial;
+ input->pointer.serial = serial;
+ input->pointer.xy[0] = surface_x;
+ input->pointer.xy[1] = surface_y;
+ input->pointer.wl_surface = surface;
win->setCursorShape(win->getCursorShape());
+ const wl_fixed_t scale = win->scale();
input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
GHOST_kEventCursorMove,
- static_cast<GHOST_WindowWayland *>(win),
- input->x,
- input->y,
+ win,
+ wl_fixed_to_int(scale * input->pointer.xy[0]),
+ wl_fixed_to_int(scale * input->pointer.xy[1]),
GHOST_TABLET_DATA_NONE));
}
-static void pointer_leave(void *data,
- struct wl_pointer * /*wl_pointer*/,
- uint32_t /*serial*/,
- struct wl_surface *surface)
+static void pointer_handle_leave(void *data,
+ struct wl_pointer * /*wl_pointer*/,
+ const uint32_t /*serial*/,
+ struct wl_surface *surface)
{
- GHOST_IWindow *win = get_window(surface);
-
- if (!win) {
- return;
+ /* First clear the `pointer.wl_surface`, since the window won't exist when closing the window. */
+ static_cast<input_t *>(data)->pointer.wl_surface = nullptr;
+ if (surface && ghost_wl_surface_own(surface)) {
+ CLOG_INFO(LOG, 2, "leave");
+ GHOST_WindowWayland *win = ghost_wl_surface_user_data(surface);
+ win->deactivate();
+ }
+ else {
+ CLOG_INFO(LOG, 2, "leave (skipped)");
}
-
- static_cast<input_t *>(data)->focus_pointer = nullptr;
- static_cast<GHOST_WindowWayland *>(win)->deactivate();
}
-static void pointer_motion(void *data,
- struct wl_pointer * /*wl_pointer*/,
- uint32_t /*time*/,
- wl_fixed_t surface_x,
- wl_fixed_t surface_y)
+static void pointer_handle_motion(void *data,
+ struct wl_pointer * /*wl_pointer*/,
+ const uint32_t /*time*/,
+ const wl_fixed_t surface_x,
+ const wl_fixed_t surface_y)
{
input_t *input = static_cast<input_t *>(data);
-
- GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(get_window(input->focus_pointer));
-
- if (!win) {
- return;
+ input->pointer.xy[0] = surface_x;
+ input->pointer.xy[1] = surface_y;
+
+ if (wl_surface *focus_surface = input->pointer.wl_surface) {
+ CLOG_INFO(LOG, 2, "motion");
+ GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
+ const wl_fixed_t scale = win->scale();
+ input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
+ GHOST_kEventCursorMove,
+ win,
+ wl_fixed_to_int(scale * input->pointer.xy[0]),
+ wl_fixed_to_int(scale * input->pointer.xy[1]),
+ GHOST_TABLET_DATA_NONE));
+ }
+ else {
+ CLOG_INFO(LOG, 2, "motion (skipped)");
}
-
- input->x = win->scale() * wl_fixed_to_int(surface_x);
- input->y = win->scale() * wl_fixed_to_int(surface_y);
-
- input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
- GHOST_kEventCursorMove,
- win,
- input->x,
- input->y,
- GHOST_TABLET_DATA_NONE));
}
-static void pointer_button(void *data,
- struct wl_pointer * /*wl_pointer*/,
- uint32_t serial,
- uint32_t /*time*/,
- uint32_t button,
- uint32_t state)
+static void pointer_handle_button(void *data,
+ struct wl_pointer * /*wl_pointer*/,
+ const uint32_t serial,
+ const uint32_t /*time*/,
+ const uint32_t button,
+ const uint32_t state)
{
- input_t *input = static_cast<input_t *>(data);
-
- GHOST_IWindow *win = get_window(input->focus_pointer);
-
- if (!win) {
- return;
- }
+ CLOG_INFO(LOG, 2, "button (button=%u, state=%u)", button, state);
+ input_t *input = static_cast<input_t *>(data);
GHOST_TEventType etype = GHOST_kEventUnknown;
switch (state) {
case WL_POINTER_BUTTON_STATE_RELEASED:
@@ -969,7 +1545,7 @@ static void pointer_button(void *data,
break;
}
- GHOST_TButtonMask ebutton = GHOST_kButtonMaskLeft;
+ GHOST_TButton ebutton = GHOST_kButtonMaskLeft;
switch (button) {
case BTN_LEFT:
ebutton = GHOST_kButtonMaskLeft;
@@ -994,48 +1570,451 @@ static void pointer_button(void *data,
break;
}
- input->data_source->source_serial = serial;
- input->buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED);
- input->system->pushEvent(new GHOST_EventButton(
- input->system->getMilliSeconds(), etype, win, ebutton, GHOST_TABLET_DATA_NONE));
+ input->data_source_serial = serial;
+ input->pointer.buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED);
+
+ if (wl_surface *focus_surface = input->pointer.wl_surface) {
+ GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
+ input->system->pushEvent(new GHOST_EventButton(
+ input->system->getMilliSeconds(), etype, win, ebutton, GHOST_TABLET_DATA_NONE));
+ }
}
-static void pointer_axis(void *data,
- struct wl_pointer * /*wl_pointer*/,
- uint32_t /*time*/,
- uint32_t axis,
- wl_fixed_t value)
+static void pointer_handle_axis(void *data,
+ struct wl_pointer * /*wl_pointer*/,
+ const uint32_t /*time*/,
+ const uint32_t axis,
+ const wl_fixed_t value)
{
+ CLOG_INFO(LOG, 2, "axis (axis=%u, value=%d)", axis, value);
+
input_t *input = static_cast<input_t *>(data);
+ if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) {
+ return;
+ }
- GHOST_IWindow *win = get_window(input->focus_pointer);
+ if (wl_surface *focus_surface = input->pointer.wl_surface) {
+ GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
+ input->system->pushEvent(new GHOST_EventWheel(
+ input->system->getMilliSeconds(), win, std::signbit(value) ? +1 : -1));
+ }
+}
- if (!win) {
+static const struct wl_pointer_listener pointer_listener = {
+ pointer_handle_enter,
+ pointer_handle_leave,
+ pointer_handle_motion,
+ pointer_handle_button,
+ pointer_handle_axis,
+};
+
+#undef LOG
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Listener (Tablet Tool), #zwp_tablet_tool_v2_listener
+ * \{ */
+
+static CLG_LogRef LOG_WL_TABLET_TOOL = {"ghost.wl.handle.tablet_tool"};
+#define LOG (&LOG_WL_TABLET_TOOL)
+
+static void tablet_tool_handle_type(void *data,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
+ const uint32_t tool_type)
+{
+ CLOG_INFO(LOG, 2, "type (type=%u)", tool_type);
+
+ tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data);
+
+ tool_input->data.Active = tablet_tool_map_type((enum zwp_tablet_tool_v2_type)tool_type);
+}
+
+static void tablet_tool_handle_hardware_serial(void * /*data*/,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
+ const uint32_t /*hardware_serial_hi*/,
+ const uint32_t /*hardware_serial_lo*/)
+{
+ CLOG_INFO(LOG, 2, "hardware_serial");
+}
+
+static void tablet_tool_handle_hardware_id_wacom(
+ void * /*data*/,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
+ const uint32_t /*hardware_id_hi*/,
+ const uint32_t /*hardware_id_lo*/)
+{
+ CLOG_INFO(LOG, 2, "hardware_id_wacom");
+}
+
+static void tablet_tool_handle_capability(void * /*data*/,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
+ const uint32_t capability)
+{
+ CLOG_INFO(LOG,
+ 2,
+ "capability (tilt=%d, distance=%d, rotation=%d, slider=%d, wheel=%d)",
+ (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_TILT) != 0,
+ (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE) != 0,
+ (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION) != 0,
+ (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER) != 0,
+ (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL) != 0);
+}
+
+static void tablet_tool_handle_done(void * /*data*/,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/)
+{
+ CLOG_INFO(LOG, 2, "done");
+}
+static void tablet_tool_handle_removed(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2)
+{
+ CLOG_INFO(LOG, 2, "removed");
+
+ tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data);
+ input_t *input = tool_input->input;
+
+ if (tool_input->cursor_surface) {
+ wl_surface_destroy(tool_input->cursor_surface);
+ }
+ input->tablet_tools.erase(zwp_tablet_tool_v2);
+
+ delete tool_input;
+}
+static void tablet_tool_handle_proximity_in(void *data,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
+ const uint32_t serial,
+ struct zwp_tablet_v2 * /*tablet*/,
+ struct wl_surface *surface)
+{
+ if (!ghost_wl_surface_own(surface)) {
+ CLOG_INFO(LOG, 2, "proximity_in (skipped)");
return;
}
+ CLOG_INFO(LOG, 2, "proximity_in");
- if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) {
+ tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data);
+ tool_input->proximity = true;
+
+ input_t *input = tool_input->input;
+ input->cursor_source_serial = serial;
+ input->tablet.wl_surface = surface;
+ input->tablet.serial = serial;
+
+ input->data_source_serial = serial;
+
+ /* Update #GHOST_TabletData. */
+ GHOST_TabletData &td = tool_input->data;
+ /* Reset, to avoid using stale tilt/pressure. */
+ td.Xtilt = 0.0f;
+ td.Ytilt = 0.0f;
+ /* In case pressure isn't supported. */
+ td.Pressure = 1.0f;
+
+ GHOST_WindowWayland *win = ghost_wl_surface_user_data(input->tablet.wl_surface);
+
+ win->activate();
+
+ win->setCursorShape(win->getCursorShape());
+}
+static void tablet_tool_handle_proximity_out(void *data,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/)
+{
+ CLOG_INFO(LOG, 2, "proximity_out");
+ tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data);
+ /* Defer clearing the surface until the frame is handled.
+ * Without this, the frame can not access the surface. */
+ tool_input->proximity = false;
+}
+
+static void tablet_tool_handle_down(void *data,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
+ const uint32_t serial)
+{
+ CLOG_INFO(LOG, 2, "down");
+
+ tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data);
+ input_t *input = tool_input->input;
+ const GHOST_TButton ebutton = GHOST_kButtonMaskLeft;
+ const GHOST_TEventType etype = GHOST_kEventButtonDown;
+
+ input->data_source_serial = serial;
+ input->tablet.buttons.set(ebutton, true);
+
+ if (wl_surface *focus_surface = input->tablet.wl_surface) {
+ GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
+ input->system->pushEvent(new GHOST_EventButton(
+ input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data));
+ }
+}
+
+static void tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/)
+{
+ CLOG_INFO(LOG, 2, "up");
+
+ tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data);
+ input_t *input = tool_input->input;
+ const GHOST_TButton ebutton = GHOST_kButtonMaskLeft;
+ const GHOST_TEventType etype = GHOST_kEventButtonUp;
+
+ input->tablet.buttons.set(ebutton, false);
+
+ if (wl_surface *focus_surface = input->tablet.wl_surface) {
+ GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
+ input->system->pushEvent(new GHOST_EventButton(
+ input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data));
+ }
+}
+
+static void tablet_tool_handle_motion(void *data,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
+ const wl_fixed_t x,
+ const wl_fixed_t y)
+{
+ CLOG_INFO(LOG, 2, "motion");
+
+ tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data);
+ input_t *input = tool_input->input;
+
+ input->tablet.xy[0] = x;
+ input->tablet.xy[1] = y;
+
+ /* NOTE: #tablet_tool_handle_frame generates the event (with updated pressure, tilt... etc). */
+}
+
+static void tablet_tool_handle_pressure(void *data,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
+ const uint32_t pressure)
+{
+ const float pressure_unit = (float)pressure / 65535;
+ CLOG_INFO(LOG, 2, "pressure (%.4f)", pressure_unit);
+
+ tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data);
+ GHOST_TabletData &td = tool_input->data;
+ td.Pressure = pressure_unit;
+}
+static void tablet_tool_handle_distance(void * /*data*/,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
+ const uint32_t distance)
+{
+ CLOG_INFO(LOG, 2, "distance (distance=%u)", distance);
+}
+
+static void tablet_tool_handle_tilt(void *data,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
+ const wl_fixed_t tilt_x,
+ const wl_fixed_t tilt_y)
+{
+ /* Map degrees to `-1.0..1.0`. */
+ const float tilt_unit[2] = {
+ (float)(wl_fixed_to_double(tilt_x) / 90.0),
+ (float)(wl_fixed_to_double(tilt_y) / 90.0),
+ };
+ CLOG_INFO(LOG, 2, "tilt (x=%.4f, y=%.4f)", UNPACK2(tilt_unit));
+ tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data);
+ GHOST_TabletData &td = tool_input->data;
+ td.Xtilt = tilt_unit[0];
+ td.Ytilt = tilt_unit[1];
+ CLAMP(td.Xtilt, -1.0f, 1.0f);
+ CLAMP(td.Ytilt, -1.0f, 1.0f);
+}
+
+static void tablet_tool_handle_rotation(void * /*data*/,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
+ const wl_fixed_t degrees)
+{
+ CLOG_INFO(LOG, 2, "rotation (degrees=%.4f)", wl_fixed_to_double(degrees));
+}
+
+static void tablet_tool_handle_slider(void * /*data*/,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
+ const int32_t position)
+{
+ CLOG_INFO(LOG, 2, "slider (position=%d)", position);
+}
+static void tablet_tool_handle_wheel(void *data,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
+ const wl_fixed_t /*degrees*/,
+ const int32_t clicks)
+{
+ if (clicks == 0) {
return;
}
+ CLOG_INFO(LOG, 2, "wheel (clicks=%d)", clicks);
- input->system->pushEvent(
- new GHOST_EventWheel(input->system->getMilliSeconds(), win, std::signbit(value) ? +1 : -1));
+ tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data);
+ input_t *input = tool_input->input;
+ if (wl_surface *focus_surface = input->tablet.wl_surface) {
+ GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
+ input->system->pushEvent(new GHOST_EventWheel(input->system->getMilliSeconds(), win, clicks));
+ }
}
+static void tablet_tool_handle_button(void *data,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
+ const uint32_t serial,
+ const uint32_t button,
+ const uint32_t state)
+{
+ CLOG_INFO(LOG, 2, "button (button=%u, state=%u)", button, state);
-static const struct wl_pointer_listener pointer_listener = {
- pointer_enter,
- pointer_leave,
- pointer_motion,
- pointer_button,
- pointer_axis,
+ tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data);
+ input_t *input = tool_input->input;
+
+ GHOST_TEventType etype = GHOST_kEventUnknown;
+ switch (state) {
+ case WL_POINTER_BUTTON_STATE_RELEASED:
+ etype = GHOST_kEventButtonUp;
+ break;
+ case WL_POINTER_BUTTON_STATE_PRESSED:
+ etype = GHOST_kEventButtonDown;
+ break;
+ }
+
+ GHOST_TButton ebutton = GHOST_kButtonMaskLeft;
+ switch (button) {
+ case BTN_STYLUS:
+ ebutton = GHOST_kButtonMaskRight;
+ break;
+ case BTN_STYLUS2:
+ ebutton = GHOST_kButtonMaskMiddle;
+ break;
+ case BTN_STYLUS3:
+ ebutton = GHOST_kButtonMaskButton4;
+ break;
+ }
+
+ input->data_source_serial = serial;
+ input->tablet.buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED);
+
+ if (wl_surface *focus_surface = input->tablet.wl_surface) {
+ GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
+ input->system->pushEvent(new GHOST_EventButton(
+ input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data));
+ }
+}
+static void tablet_tool_handle_frame(void *data,
+ struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
+ const uint32_t /*time*/)
+{
+ CLOG_INFO(LOG, 2, "frame");
+
+ tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data);
+ input_t *input = tool_input->input;
+
+ /* No need to check the surfaces origin, it's already known to be owned by GHOST. */
+ if (wl_surface *focus_surface = input->tablet.wl_surface) {
+ GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
+ const wl_fixed_t scale = win->scale();
+ input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
+ GHOST_kEventCursorMove,
+ win,
+ wl_fixed_to_int(scale * input->tablet.xy[0]),
+ wl_fixed_to_int(scale * input->tablet.xy[1]),
+ tool_input->data));
+ if (tool_input->proximity == false) {
+ win->setCursorShape(win->getCursorShape());
+ }
+ }
+
+ if (tool_input->proximity == false) {
+ input->tablet.wl_surface = nullptr;
+ }
+}
+
+static const struct zwp_tablet_tool_v2_listener tablet_tool_listner = {
+ tablet_tool_handle_type,
+ tablet_tool_handle_hardware_serial,
+ tablet_tool_handle_hardware_id_wacom,
+ tablet_tool_handle_capability,
+ tablet_tool_handle_done,
+ tablet_tool_handle_removed,
+ tablet_tool_handle_proximity_in,
+ tablet_tool_handle_proximity_out,
+ tablet_tool_handle_down,
+ tablet_tool_handle_up,
+ tablet_tool_handle_motion,
+ tablet_tool_handle_pressure,
+ tablet_tool_handle_distance,
+ tablet_tool_handle_tilt,
+ tablet_tool_handle_rotation,
+ tablet_tool_handle_slider,
+ tablet_tool_handle_wheel,
+ tablet_tool_handle_button,
+ tablet_tool_handle_frame,
};
-static void keyboard_keymap(
- void *data, struct wl_keyboard * /*wl_keyboard*/, uint32_t format, int32_t fd, uint32_t size)
+#undef LOG
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Listener (Table Seat), #zwp_tablet_seat_v2_listener
+ * \{ */
+
+static CLG_LogRef LOG_WL_TABLET_SEAT = {"ghost.wl.handle.tablet_seat"};
+#define LOG (&LOG_WL_TABLET_SEAT)
+
+static void tablet_seat_handle_tablet_added(void * /*data*/,
+ struct zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/,
+ struct zwp_tablet_v2 *id)
+{
+ CLOG_INFO(LOG, 2, "tablet_added (id=%p)", id);
+}
+
+static void tablet_seat_handle_tool_added(void *data,
+ struct zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/,
+ struct zwp_tablet_tool_v2 *id)
+{
+ CLOG_INFO(LOG, 2, "tool_added (id=%p)", id);
+
+ input_t *input = static_cast<input_t *>(data);
+ tablet_tool_input_t *tool_input = new tablet_tool_input_t();
+ tool_input->input = input;
+
+ /* Every tool has it's own cursor surface. */
+ tool_input->cursor_surface = wl_compositor_create_surface(input->system->compositor());
+ ghost_wl_surface_tag_cursor_tablet(tool_input->cursor_surface);
+
+ wl_surface_add_listener(tool_input->cursor_surface, &cursor_surface_listener, (void *)input);
+
+ zwp_tablet_tool_v2_add_listener(id, &tablet_tool_listner, tool_input);
+
+ input->tablet_tools.insert(id);
+}
+
+static void tablet_seat_handle_pad_added(void * /*data*/,
+ struct zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/,
+ struct zwp_tablet_pad_v2 *id)
+{
+ CLOG_INFO(LOG, 2, "pad_added (id=%p)", id);
+}
+
+static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = {
+ tablet_seat_handle_tablet_added,
+ tablet_seat_handle_tool_added,
+ tablet_seat_handle_pad_added,
+};
+
+#undef LOG
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Listener (Keyboard), #wl_keyboard_listener
+ * \{ */
+
+static CLG_LogRef LOG_WL_KEYBOARD = {"ghost.wl.handle.keyboard"};
+#define LOG (&LOG_WL_KEYBOARD)
+
+static void keyboard_handle_keymap(void *data,
+ struct wl_keyboard * /*wl_keyboard*/,
+ const uint32_t format,
+ const int32_t fd,
+ const uint32_t size)
{
input_t *input = static_cast<input_t *>(data);
if ((!data) || (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)) {
+ CLOG_INFO(LOG, 2, "keymap (no data or wrong version)");
close(fd);
return;
}
@@ -1052,11 +2031,32 @@ static void keyboard_keymap(
close(fd);
if (!keymap) {
+ CLOG_INFO(LOG, 2, "keymap (not found)");
return;
}
+ CLOG_INFO(LOG, 2, "keymap");
+
+ /* In practice we can assume `xkb_state_new` always succeeds. */
+ xkb_state_unref(input->xkb_state);
input->xkb_state = xkb_state_new(keymap);
+ xkb_state_unref(input->xkb_state_empty);
+ input->xkb_state_empty = xkb_state_new(keymap);
+
+ xkb_state_unref(input->xkb_state_empty_with_numlock);
+ input->xkb_state_empty_with_numlock = nullptr;
+
+ {
+ const xkb_mod_index_t mod2 = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM);
+ const xkb_mod_index_t num = xkb_keymap_mod_get_index(keymap, "NumLock");
+ if (num != XKB_MOD_INVALID && mod2 != XKB_MOD_INVALID) {
+ input->xkb_state_empty_with_numlock = xkb_state_new(keymap);
+ xkb_state_update_mask(
+ input->xkb_state_empty_with_numlock, (1 << mod2), 0, (1 << num), 0, 0, 0);
+ }
+ }
+
xkb_keymap_unref(keymap);
}
@@ -1066,15 +2066,21 @@ static void keyboard_keymap(
* Notification that this seat's keyboard focus is on a certain
* surface.
*/
-static void keyboard_enter(void *data,
- struct wl_keyboard * /*wl_keyboard*/,
- uint32_t /*serial*/,
- struct wl_surface *surface,
- struct wl_array * /*keys*/)
-{
- if (surface != nullptr) {
- static_cast<input_t *>(data)->focus_keyboard = surface;
+static void keyboard_handle_enter(void *data,
+ struct wl_keyboard * /*wl_keyboard*/,
+ const uint32_t serial,
+ struct wl_surface *surface,
+ struct wl_array * /*keys*/)
+{
+ if (!ghost_wl_surface_own(surface)) {
+ CLOG_INFO(LOG, 2, "enter (skipped)");
+ return;
}
+ CLOG_INFO(LOG, 2, "enter");
+
+ input_t *input = static_cast<input_t *>(data);
+ input->keyboard.serial = serial;
+ input->keyboard.wl_surface = surface;
}
/**
@@ -1083,13 +2089,23 @@ static void keyboard_enter(void *data,
* Notification that this seat's keyboard focus is no longer on a
* certain surface.
*/
-static void keyboard_leave(void *data,
- struct wl_keyboard * /*wl_keyboard*/,
- uint32_t /*serial*/,
- struct wl_surface *surface)
+static void keyboard_handle_leave(void *data,
+ struct wl_keyboard * /*wl_keyboard*/,
+ const uint32_t /*serial*/,
+ struct wl_surface *surface)
{
- if (surface != nullptr) {
- static_cast<input_t *>(data)->focus_keyboard = nullptr;
+ if (!(surface && ghost_wl_surface_own(surface))) {
+ CLOG_INFO(LOG, 2, "leave (skipped)");
+ return;
+ }
+ CLOG_INFO(LOG, 2, "leave");
+
+ input_t *input = static_cast<input_t *>(data);
+ input->keyboard.wl_surface = nullptr;
+
+ /* Losing focus must stop repeating text. */
+ if (input->key_repeat.timer) {
+ keyboard_handle_key_repeat_cancel(input);
}
}
@@ -1097,36 +2113,75 @@ static void keyboard_leave(void *data,
* A version of #xkb_state_key_get_one_sym which returns the key without any modifiers pressed.
* Needed because #GHOST_TKey uses these values as key-codes.
*/
-static xkb_keysym_t xkb_state_key_get_one_sym_without_modifiers(struct xkb_state *xkb_state,
- xkb_keycode_t key)
+static xkb_keysym_t xkb_state_key_get_one_sym_without_modifiers(
+ struct xkb_state *xkb_state_empty,
+ struct xkb_state *xkb_state_empty_with_numlock,
+ const xkb_keycode_t key)
{
/* Use an empty keyboard state to access key symbol without modifiers. */
- xkb_state_get_keymap(xkb_state);
- struct xkb_keymap *keymap = xkb_state_get_keymap(xkb_state);
- struct xkb_state *xkb_state_empty = xkb_state_new(keymap);
-
- /* Enable number-lock. */
- {
- const xkb_mod_index_t mod2 = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM);
- const xkb_mod_index_t num = xkb_keymap_mod_get_index(keymap, "NumLock");
- if (num != XKB_MOD_INVALID && mod2 != XKB_MOD_INVALID) {
- xkb_state_update_mask(xkb_state_empty, (1 << mod2), 0, (1 << num), 0, 0, 0);
+ xkb_keysym_t sym = xkb_state_key_get_one_sym(xkb_state_empty, key);
+
+ /* NOTE(@campbellbarton): Only perform the number-locked lookup as a fallback
+ * when a number-pad key has been pressed. This is important as some key-maps use number lock
+ * for switching other layers (in particular `de(neo_qwertz)` turns on layer-4), see: T96170.
+ * Alternative solutions could be to inspect the layout however this could get involved
+ * and turning on the number-lock is only needed for a limited set of keys. */
+
+ /* Accounts for key-pad keys typically swapped for numbers when number-lock is enabled:
+ * `Home Left Up Right Down Prior Page_Up Next Page_Dow End Begin Insert Delete`. */
+ if (xkb_state_empty_with_numlock && (sym >= XKB_KEY_KP_Home && sym <= XKB_KEY_KP_Delete)) {
+ const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(xkb_state_empty_with_numlock, key);
+ if (sym_test != XKB_KEY_NoSymbol) {
+ sym = sym_test;
}
}
- const xkb_keysym_t sym = xkb_state_key_get_one_sym(xkb_state_empty, key);
- xkb_state_unref(xkb_state_empty);
return sym;
}
-static void keyboard_key(void *data,
- struct wl_keyboard * /*wl_keyboard*/,
- uint32_t serial,
- uint32_t /*time*/,
- uint32_t key,
- uint32_t state)
+static void keyboard_handle_key_repeat_cancel(input_t *input)
+{
+ GHOST_ASSERT(input->key_repeat.timer != nullptr, "Caller much check for timer");
+ delete static_cast<key_repeat_payload_t *>(input->key_repeat.timer->getUserData());
+ input->system->removeTimer(input->key_repeat.timer);
+ input->key_repeat.timer = nullptr;
+}
+
+/**
+ * Restart the key-repeat timer.
+ * \param use_delay: When false, use the interval
+ * (prevents pause when the setting changes while the key is held).
+ */
+static void keyboard_handle_key_repeat_reset(input_t *input, const bool use_delay)
+{
+ GHOST_ASSERT(input->key_repeat.timer != nullptr, "Caller much check for timer");
+ GHOST_SystemWayland *system = input->system;
+ GHOST_ITimerTask *timer = input->key_repeat.timer;
+ GHOST_TimerProcPtr key_repeat_fn = timer->getTimerProc();
+ GHOST_TUserDataPtr payload = input->key_repeat.timer->getUserData();
+ input->system->removeTimer(input->key_repeat.timer);
+ const uint64_t time_step = 1000 / input->key_repeat.rate;
+ const uint64_t time_start = use_delay ? input->key_repeat.delay : time_step;
+ input->key_repeat.timer = system->installTimer(time_start, time_step, key_repeat_fn, payload);
+}
+
+static void keyboard_handle_key(void *data,
+ struct wl_keyboard * /*wl_keyboard*/,
+ const uint32_t serial,
+ const uint32_t /*time*/,
+ const uint32_t key,
+ const uint32_t state)
{
input_t *input = static_cast<input_t *>(data);
+ const xkb_keycode_t key_code = key + EVDEV_OFFSET;
+
+ const xkb_keysym_t sym = xkb_state_key_get_one_sym_without_modifiers(
+ input->xkb_state_empty, input->xkb_state_empty_with_numlock, key_code);
+ if (sym == XKB_KEY_NoSymbol) {
+ CLOG_INFO(LOG, 2, "key (no symbol, skipped)");
+ return;
+ }
+ CLOG_INFO(LOG, 2, "key");
GHOST_TEventType etype = GHOST_kEventUnknown;
switch (state) {
@@ -1138,168 +2193,369 @@ static void keyboard_key(void *data,
break;
}
- const xkb_keysym_t sym = xkb_state_key_get_one_sym_without_modifiers(input->xkb_state, key + 8);
-
- if (sym == XKB_KEY_NoSymbol) {
- return;
- }
- const GHOST_TKey gkey = xkb_map_gkey(sym);
+ struct key_repeat_payload_t *key_repeat_payload = nullptr;
/* Delete previous timer. */
- if (xkb_keymap_key_repeats(xkb_state_get_keymap(input->xkb_state), key + 8) &&
- input->key_repeat.timer) {
- delete static_cast<key_repeat_payload_t *>(input->key_repeat.timer->getUserData());
- input->system->removeTimer(input->key_repeat.timer);
- input->key_repeat.timer = nullptr;
- }
+ if (input->key_repeat.timer) {
+ enum { NOP = 1, RESET, CANCEL } timer_action = NOP;
+ key_repeat_payload = static_cast<key_repeat_payload_t *>(
+ input->key_repeat.timer->getUserData());
+
+ if (input->key_repeat.rate == 0) {
+ /* Repeat was disabled (unlikely but possible). */
+ timer_action = CANCEL;
+ }
+ else if (key_code == key_repeat_payload->key_code) {
+ /* Releasing the key that was held always cancels. */
+ timer_action = CANCEL;
+ }
+ else if (xkb_keymap_key_repeats(xkb_state_get_keymap(input->xkb_state), key_code)) {
+ if (etype == GHOST_kEventKeyDown) {
+ /* Any other key-down always cancels (and may start it's own repeat timer). */
+ timer_action = CANCEL;
+ }
+ else {
+ /* Key-up from keys that were not repeating cause the repeat timer to pause.
+ *
+ * NOTE(@campbellbarton): This behavior isn't universal, some text input systems will
+ * stop the repeat entirely. Choose to pause repeat instead as this is what GTK/WIN32 do,
+ * and it fits better for keyboard input that isn't related to text entry. */
+ timer_action = RESET;
+ }
+ }
- GHOST_TEventKeyData key_data;
+ switch (timer_action) {
+ case NOP: {
+ /* Don't add a new timer, leave the existing timer owning this `key_repeat_payload`. */
+ key_repeat_payload = nullptr;
+ break;
+ }
+ case RESET: {
+ /* The payload will be added again. */
+ input->system->removeTimer(input->key_repeat.timer);
+ input->key_repeat.timer = nullptr;
+ break;
+ }
+ case CANCEL: {
+ delete key_repeat_payload;
+ key_repeat_payload = nullptr;
- if (etype == GHOST_kEventKeyDown) {
- xkb_state_key_get_utf8(
- input->xkb_state, key + 8, key_data.utf8_buf, sizeof(GHOST_TEventKeyData::utf8_buf));
- }
- else {
- key_data.utf8_buf[0] = '\0';
+ input->system->removeTimer(input->key_repeat.timer);
+ input->key_repeat.timer = nullptr;
+ break;
+ }
+ }
}
- input->data_source->source_serial = serial;
+ const GHOST_TKey gkey = xkb_map_gkey_or_scan_code(sym, key);
+ char utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'};
+ if (etype == GHOST_kEventKeyDown) {
+ xkb_state_key_get_utf8(input->xkb_state, key_code, utf8_buf, sizeof(utf8_buf));
+ }
- GHOST_IWindow *win = static_cast<GHOST_WindowWayland *>(
- wl_surface_get_user_data(input->focus_keyboard));
- input->system->pushEvent(new GHOST_EventKey(
- input->system->getMilliSeconds(), etype, win, gkey, '\0', key_data.utf8_buf, false));
+ input->data_source_serial = serial;
- /* Start timer for repeating key, if applicable. */
- if (input->key_repeat.rate > 0 &&
- xkb_keymap_key_repeats(xkb_state_get_keymap(input->xkb_state), key + 8) &&
- etype == GHOST_kEventKeyDown) {
+ if (wl_surface *focus_surface = input->keyboard.wl_surface) {
+ GHOST_IWindow *win = ghost_wl_surface_user_data(focus_surface);
+ input->system->pushEvent(
+ new GHOST_EventKey(input->system->getMilliSeconds(), etype, win, gkey, false, utf8_buf));
+ }
- key_repeat_payload_t *payload = new key_repeat_payload_t({
- .system = input->system,
- .window = win,
- .key = gkey,
- .key_data = key_data,
- });
+ /* An existing payload means the key repeat timer is reset and will be added again. */
+ if (key_repeat_payload == nullptr) {
+ /* Start timer for repeating key, if applicable. */
+ if ((input->key_repeat.rate > 0) && (etype == GHOST_kEventKeyDown) &&
+ xkb_keymap_key_repeats(xkb_state_get_keymap(input->xkb_state), key_code)) {
+ key_repeat_payload = new key_repeat_payload_t({
+ .input = input,
+ .key_code = key_code,
+ .key_data = {.gkey = gkey},
+ });
+ }
+ }
- auto cb = [](GHOST_ITimerTask *task, uint64_t /*time*/) {
+ if (key_repeat_payload) {
+ auto key_repeat_fn = [](GHOST_ITimerTask *task, uint64_t /*time*/) {
struct key_repeat_payload_t *payload = static_cast<key_repeat_payload_t *>(
task->getUserData());
- payload->system->pushEvent(new GHOST_EventKey(payload->system->getMilliSeconds(),
- GHOST_kEventKeyDown,
- payload->window,
- payload->key,
- '\0',
- payload->key_data.utf8_buf,
- true));
+
+ input_t *input = payload->input;
+ if (wl_surface *focus_surface = input->keyboard.wl_surface) {
+ GHOST_IWindow *win = ghost_wl_surface_user_data(focus_surface);
+ GHOST_SystemWayland *system = input->system;
+ /* Calculate this value every time in case modifier keys are pressed. */
+ char utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'};
+ xkb_state_key_get_utf8(input->xkb_state, payload->key_code, utf8_buf, sizeof(utf8_buf));
+ system->pushEvent(new GHOST_EventKey(system->getMilliSeconds(),
+ GHOST_kEventKeyDown,
+ win,
+ payload->key_data.gkey,
+ true,
+ utf8_buf));
+ }
};
input->key_repeat.timer = input->system->installTimer(
- input->key_repeat.delay, 1000 / input->key_repeat.rate, cb, payload);
+ input->key_repeat.delay, 1000 / input->key_repeat.rate, key_repeat_fn, key_repeat_payload);
}
}
-static void keyboard_modifiers(void *data,
- struct wl_keyboard * /*wl_keyboard*/,
- uint32_t /*serial*/,
- uint32_t mods_depressed,
- uint32_t mods_latched,
- uint32_t mods_locked,
- uint32_t group)
+static void keyboard_handle_modifiers(void *data,
+ struct wl_keyboard * /*wl_keyboard*/,
+ const uint32_t /*serial*/,
+ const uint32_t mods_depressed,
+ const uint32_t mods_latched,
+ const uint32_t mods_locked,
+ const uint32_t group)
{
- xkb_state_update_mask(static_cast<input_t *>(data)->xkb_state,
- mods_depressed,
- mods_latched,
- mods_locked,
- 0,
- 0,
- group);
+ CLOG_INFO(LOG, 2, "modifiers");
+
+ input_t *input = static_cast<input_t *>(data);
+ xkb_state_update_mask(input->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
+
+ /* A modifier changed so reset the timer,
+ * see comment in #keyboard_handle_key regarding this behavior. */
+ if (input->key_repeat.timer) {
+ keyboard_handle_key_repeat_reset(input, true);
+ }
}
-static void keyboard_repeat_info(void *data,
- struct wl_keyboard * /*wl_keyboard*/,
- int32_t rate,
- int32_t delay)
+static void keyboard_repeat_handle_info(void *data,
+ struct wl_keyboard * /*wl_keyboard*/,
+ const int32_t rate,
+ const int32_t delay)
{
- input_t *input = static_cast<input_t *>(data);
+ CLOG_INFO(LOG, 2, "info (rate=%d, delay=%d)", rate, delay);
+ input_t *input = static_cast<input_t *>(data);
input->key_repeat.rate = rate;
input->key_repeat.delay = delay;
+
+ /* Unlikely possible this setting changes while repeating. */
+ if (input->key_repeat.timer) {
+ keyboard_handle_key_repeat_reset(input, false);
+ }
}
static const struct wl_keyboard_listener keyboard_listener = {
- keyboard_keymap,
- keyboard_enter,
- keyboard_leave,
- keyboard_key,
- keyboard_modifiers,
- keyboard_repeat_info,
+ keyboard_handle_keymap,
+ keyboard_handle_enter,
+ keyboard_handle_leave,
+ keyboard_handle_key,
+ keyboard_handle_modifiers,
+ keyboard_repeat_handle_info,
};
-static void seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities)
+#undef LOG
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Listener (Seat), #wl_seat_listener
+ * \{ */
+
+static CLG_LogRef LOG_WL_SEAT = {"ghost.wl.handle.seat"};
+#define LOG (&LOG_WL_SEAT)
+
+static void seat_handle_capabilities(void *data,
+ struct wl_seat *wl_seat,
+ const uint32_t capabilities)
{
+ CLOG_INFO(LOG,
+ 2,
+ "capabilities (pointer=%d, keyboard=%d, touch=%d)",
+ (capabilities & WL_SEAT_CAPABILITY_POINTER) != 0,
+ (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) != 0,
+ (capabilities & WL_SEAT_CAPABILITY_TOUCH) != 0);
+
input_t *input = static_cast<input_t *>(data);
- input->pointer = nullptr;
- input->keyboard = nullptr;
+ input->wl_pointer = nullptr;
+ input->wl_keyboard = nullptr;
if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
- input->pointer = wl_seat_get_pointer(wl_seat);
- input->cursor.surface = wl_compositor_create_surface(input->system->compositor());
+ input->wl_pointer = wl_seat_get_pointer(wl_seat);
+ input->cursor.wl_surface = wl_compositor_create_surface(input->system->compositor());
input->cursor.visible = true;
- input->cursor.buffer = nullptr;
- input->cursor.file_buffer = new buffer_t;
+ input->cursor.wl_buffer = nullptr;
if (!get_cursor_settings(input->cursor.theme_name, input->cursor.size)) {
input->cursor.theme_name = std::string();
input->cursor.size = default_cursor_size;
}
- wl_pointer_add_listener(input->pointer, &pointer_listener, data);
- wl_surface_add_listener(input->cursor.surface, &cursor_surface_listener, data);
+ wl_pointer_add_listener(input->wl_pointer, &pointer_listener, data);
+
+ wl_surface_add_listener(input->cursor.wl_surface, &cursor_surface_listener, data);
+ ghost_wl_surface_tag_cursor_pointer(input->cursor.wl_surface);
}
if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
- input->keyboard = wl_seat_get_keyboard(wl_seat);
- wl_keyboard_add_listener(input->keyboard, &keyboard_listener, data);
+ input->wl_keyboard = wl_seat_get_keyboard(wl_seat);
+ wl_keyboard_add_listener(input->wl_keyboard, &keyboard_listener, data);
}
}
-static void seat_name(void *data, struct wl_seat * /*wl_seat*/, const char *name)
+static void seat_handle_name(void *data, struct wl_seat * /*wl_seat*/, const char *name)
{
+ CLOG_INFO(LOG, 2, "name (name=\"%s\")", name);
static_cast<input_t *>(data)->name = std::string(name);
}
static const struct wl_seat_listener seat_listener = {
- seat_capabilities,
- seat_name,
+ seat_handle_capabilities,
+ seat_handle_name,
};
-static void output_geometry(void *data,
- struct wl_output * /*wl_output*/,
- int32_t /*x*/,
- int32_t /*y*/,
- int32_t physical_width,
- int32_t physical_height,
- int32_t /*subpixel*/,
- const char *make,
- const char *model,
- int32_t transform)
+#undef LOG
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Listener (XDG Output), #zxdg_output_v1_listener
+ * \{ */
+
+static CLG_LogRef LOG_WL_XDG_OUTPUT = {"ghost.wl.handle.xdg_output"};
+#define LOG (&LOG_WL_XDG_OUTPUT)
+
+static void xdg_output_handle_logical_position(void *data,
+ struct zxdg_output_v1 * /*xdg_output*/,
+ const int32_t x,
+ const int32_t y)
+{
+ CLOG_INFO(LOG, 2, "logical_position [%d, %d]", x, y);
+
+ output_t *output = static_cast<output_t *>(data);
+ output->position_logical[0] = x;
+ output->position_logical[1] = y;
+ output->has_position_logical = true;
+}
+
+static void xdg_output_handle_logical_size(void *data,
+ struct zxdg_output_v1 * /*xdg_output*/,
+ const int32_t width,
+ const int32_t height)
+{
+ CLOG_INFO(LOG, 2, "logical_size [%d, %d]", width, height);
+
+ output_t *output = static_cast<output_t *>(data);
+ if (output->size_logical[0] != 0 && output->size_logical[1] != 0) {
+ /* Original comment from SDL. */
+ /* FIXME(@flibit): GNOME has a bug where the logical size does not account for
+ * scale, resulting in bogus viewport sizes.
+ *
+ * Until this is fixed, validate that _some_ kind of scaling is being
+ * done (we can't match exactly because fractional scaling can't be
+ * detected otherwise), then override if necessary. */
+ if ((output->size_logical[0] == width) && (output->scale_fractional == wl_fixed_from_int(1))) {
+ GHOST_PRINT("xdg_output scale did not match, overriding with wl_output scale");
+
+#ifdef USE_GNOME_CONFINE_HACK
+ /* Use a bug in GNOME to check GNOME is in use. If the bug is fixed this won't cause an issue
+ * as T98793 has been fixed up-stream too, but not in a release at time of writing. */
+ use_gnome_confine_hack = true;
+#endif
+
+ return;
+ }
+ }
+
+ output->size_logical[0] = width;
+ output->size_logical[1] = height;
+ output->has_size_logical = true;
+}
+
+static void xdg_output_handle_done(void *data, struct zxdg_output_v1 * /*xdg_output*/)
{
+ CLOG_INFO(LOG, 2, "done");
+ /* NOTE: `xdg-output.done` events are deprecated and only apply below version 3 of the protocol.
+ * `wl-output.done` event will be emitted in version 3 or higher. */
+ output_t *output = static_cast<output_t *>(data);
+ if (zxdg_output_v1_get_version(output->xdg_output) < 3) {
+ output_handle_done(data, output->wl_output);
+ }
+}
+
+static void xdg_output_handle_name(void * /*data*/,
+ struct zxdg_output_v1 * /*xdg_output*/,
+ const char *name)
+{
+ CLOG_INFO(LOG, 2, "name (name=\"%s\")", name);
+}
+
+static void xdg_output_handle_description(void * /*data*/,
+ struct zxdg_output_v1 * /*xdg_output*/,
+ const char *description)
+{
+ CLOG_INFO(LOG, 2, "description (description=\"%s\")", description);
+}
+
+static const struct zxdg_output_v1_listener xdg_output_listener = {
+ xdg_output_handle_logical_position,
+ xdg_output_handle_logical_size,
+ xdg_output_handle_done,
+ xdg_output_handle_name,
+ xdg_output_handle_description,
+};
+
+#undef LOG
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Listener (Output), #wl_output_listener
+ * \{ */
+
+static CLG_LogRef LOG_WL_OUTPUT = {"ghost.wl.handle.output"};
+#define LOG (&LOG_WL_OUTPUT)
+
+static void output_handle_geometry(void *data,
+ struct wl_output * /*wl_output*/,
+ const int32_t /*x*/,
+ const int32_t /*y*/,
+ const int32_t physical_width,
+ const int32_t physical_height,
+ const int32_t /*subpixel*/,
+ const char *make,
+ const char *model,
+ const int32_t transform)
+{
+ CLOG_INFO(LOG,
+ 2,
+ "geometry (make=\"%s\", model=\"%s\", transform=%d, size=[%d, %d])",
+ make,
+ model,
+ transform,
+ physical_width,
+ physical_height);
+
output_t *output = static_cast<output_t *>(data);
output->transform = transform;
output->make = std::string(make);
output->model = std::string(model);
- output->width_mm = physical_width;
- output->height_mm = physical_height;
+ output->size_mm[0] = physical_width;
+ output->size_mm[1] = physical_height;
}
-static void output_mode(void *data,
- struct wl_output * /*wl_output*/,
- uint32_t /*flags*/,
- int32_t width,
- int32_t height,
- int32_t /*refresh*/)
+static void output_handle_mode(void *data,
+ struct wl_output * /*wl_output*/,
+ const uint32_t flags,
+ const int32_t width,
+ const int32_t height,
+ const int32_t /*refresh*/)
{
+ if ((flags & WL_OUTPUT_MODE_CURRENT) == 0) {
+ CLOG_INFO(LOG, 2, "mode (skipped)");
+ return;
+ }
+ CLOG_INFO(LOG, 2, "mode (size=[%d, %d], flags=%u)", width, height, flags);
+
output_t *output = static_cast<output_t *>(data);
- output->width_pxl = width;
- output->height_pxl = height;
+ output->size_native[0] = width;
+ output->size_native[1] = height;
+
+ /* Don't rotate this yet, `wl-output` coordinates are transformed in
+ * handle_done and `xdg-output` coordinates are pre-transformed. */
+ if (!output->has_size_logical) {
+ output->size_logical[0] = width;
+ output->size_logical[1] = height;
+ }
}
/**
@@ -1310,42 +2566,143 @@ static void output_mode(void *data,
* changes done after that. This allows changes to the output
* properties to be seen as atomic, even if they happen via multiple events.
*/
-static void output_done(void * /*data*/, struct wl_output * /*wl_output*/)
+static void output_handle_done(void *data, struct wl_output * /*wl_output*/)
{
+ CLOG_INFO(LOG, 2, "done");
+
+ output_t *output = static_cast<output_t *>(data);
+ int32_t size_native[2];
+ if (output->transform & WL_OUTPUT_TRANSFORM_90) {
+ size_native[0] = output->size_native[1];
+ size_native[1] = output->size_native[0];
+ }
+ else {
+ size_native[0] = output->size_native[0];
+ size_native[1] = output->size_native[1];
+ }
+
+ /* If `xdg-output` is present, calculate the true scale of the desktop */
+ if (output->has_size_logical) {
+
+ /* NOTE: it's not necessary to divide these values by their greatest-common-denominator
+ * as even a 64k screen resolution doesn't approach overflowing an `int32_t`. */
+
+ GHOST_ASSERT(size_native[0] && output->size_logical[0],
+ "Screen size values were not set when they were expected to be.");
+
+ output->scale_fractional = wl_fixed_from_int(size_native[0]) / output->size_logical[0];
+ output->has_scale_fractional = true;
+ }
}
-static void output_scale(void *data, struct wl_output * /*wl_output*/, int32_t factor)
+static void output_handle_scale(void *data, struct wl_output * /*wl_output*/, const int32_t factor)
{
+ CLOG_INFO(LOG, 2, "scale");
static_cast<output_t *>(data)->scale = factor;
+
+ if (window_manager) {
+ for (GHOST_IWindow *iwin : window_manager->getWindows()) {
+ GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(iwin);
+ win->outputs_changed_update_scale();
+ /* TODO(@campbellbarton): support refreshing the UI when the DPI changes.
+ * There are glitches when resizing the monitor which would be nice to solve. */
+ }
+ }
}
static const struct wl_output_listener output_listener = {
- output_geometry,
- output_mode,
- output_done,
- output_scale,
+ output_handle_geometry,
+ output_handle_mode,
+ output_handle_done,
+ output_handle_scale,
};
-static void shell_ping(void * /*data*/, struct xdg_wm_base *xdg_wm_base, uint32_t serial)
+#undef LOG
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Listener (XDG WM Base), #xdg_wm_base_listener
+ * \{ */
+
+#ifndef WITH_GHOST_WAYLAND_LIBDECOR
+
+static CLG_LogRef LOG_WL_XDG_WM_BASE = {"ghost.wl.handle.output"};
+# define LOG (&LOG_WL_XDG_WM_BASE)
+
+static void shell_handle_ping(void * /*data*/,
+ struct xdg_wm_base *xdg_wm_base,
+ const uint32_t serial)
{
+ CLOG_INFO(LOG, 2, "ping");
xdg_wm_base_pong(xdg_wm_base, serial);
}
static const struct xdg_wm_base_listener shell_listener = {
- shell_ping,
+ shell_handle_ping,
+};
+
+# undef LOG
+
+#endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Listener (LibDecor), #libdecor_interface
+ * \{ */
+
+#ifdef WITH_GHOST_WAYLAND_LIBDECOR
+
+static CLG_LogRef LOG_WL_LIBDECOR = {"ghost.wl.handle.libdecor"};
+# define LOG (&LOG_WL_LIBDECOR)
+
+static void decor_handle_error(struct libdecor * /*context*/,
+ enum libdecor_error error,
+ const char *message)
+{
+ CLOG_INFO(LOG, 2, "error (id=%d, message=%s)", error, message);
+
+ (void)(error);
+ (void)(message);
+ GHOST_PRINT("decoration error (" << error << "): " << message << std::endl);
+ exit(EXIT_FAILURE);
+}
+
+static struct libdecor_interface libdecor_interface = {
+ decor_handle_error,
};
-static void global_add(void *data,
- struct wl_registry *wl_registry,
- uint32_t name,
- const char *interface,
- uint32_t /*version*/)
+# undef LOG
+
+#endif /* WITH_GHOST_WAYLAND_LIBDECOR. */
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Listener (Registry), #wl_registry_listener
+ * \{ */
+
+static CLG_LogRef LOG_WL_REGISTRY = {"ghost.wl.handle.registry"};
+#define LOG (&LOG_WL_REGISTRY)
+
+static void global_handle_add(void *data,
+ struct wl_registry *wl_registry,
+ const uint32_t name,
+ const char *interface,
+ const uint32_t version)
{
+ /* Log last since it can be noted if the interface was handled or not. */
+ bool found = true;
+
struct display_t *display = static_cast<struct display_t *>(data);
if (!strcmp(interface, wl_compositor_interface.name)) {
display->compositor = static_cast<wl_compositor *>(
wl_registry_bind(wl_registry, name, &wl_compositor_interface, 3));
}
+#ifdef WITH_GHOST_WAYLAND_LIBDECOR
+ /* Pass. */
+#else
else if (!strcmp(interface, xdg_wm_base_interface.name)) {
display->xdg_shell = static_cast<xdg_wm_base *>(
wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, 1));
@@ -1355,30 +2712,41 @@ static void global_add(void *data,
display->xdg_decoration_manager = static_cast<zxdg_decoration_manager_v1 *>(
wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1));
}
+#endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */
+ else if (!strcmp(interface, zxdg_output_manager_v1_interface.name)) {
+ display->xdg_output_manager = static_cast<zxdg_output_manager_v1 *>(
+ wl_registry_bind(wl_registry, name, &zxdg_output_manager_v1_interface, 2));
+ for (output_t *output : display->outputs) {
+ output->xdg_output = zxdg_output_manager_v1_get_xdg_output(display->xdg_output_manager,
+ output->wl_output);
+ zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output);
+ }
+ }
else if (!strcmp(interface, wl_output_interface.name)) {
output_t *output = new output_t;
- output->scale = 1;
- output->output = static_cast<wl_output *>(
+ output->wl_output = static_cast<wl_output *>(
wl_registry_bind(wl_registry, name, &wl_output_interface, 2));
+ ghost_wl_output_tag(output->wl_output);
+ wl_output_set_user_data(output->wl_output, output);
+
display->outputs.push_back(output);
- wl_output_add_listener(output->output, &output_listener, output);
+ wl_output_add_listener(output->wl_output, &output_listener, output);
+
+ if (display->xdg_output_manager) {
+ output->xdg_output = zxdg_output_manager_v1_get_xdg_output(display->xdg_output_manager,
+ output->wl_output);
+ zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output);
+ }
}
else if (!strcmp(interface, wl_seat_interface.name)) {
input_t *input = new input_t;
input->system = display->system;
input->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
- input->xkb_state = nullptr;
- input->data_offer_dnd = nullptr;
- input->data_offer_copy_paste = nullptr;
input->data_source = new data_source_t;
- input->data_source->data_source = nullptr;
- input->data_source->buffer_out = nullptr;
- input->relative_pointer = nullptr;
- input->locked_pointer = nullptr;
- input->seat = static_cast<wl_seat *>(
+ input->wl_seat = static_cast<wl_seat *>(
wl_registry_bind(wl_registry, name, &wl_seat_interface, 4));
display->inputs.push_back(input);
- wl_seat_add_listener(input->seat, &seat_listener, input);
+ wl_seat_add_listener(input->wl_seat, &seat_listener, input);
}
else if (!strcmp(interface, wl_shm_interface.name)) {
display->shm = static_cast<wl_shm *>(
@@ -1386,7 +2754,11 @@ static void global_add(void *data,
}
else if (!strcmp(interface, wl_data_device_manager_interface.name)) {
display->data_device_manager = static_cast<wl_data_device_manager *>(
- wl_registry_bind(wl_registry, name, &wl_data_device_manager_interface, 1));
+ wl_registry_bind(wl_registry, name, &wl_data_device_manager_interface, 3));
+ }
+ else if (!strcmp(interface, zwp_tablet_manager_v2_interface.name)) {
+ display->tablet_manager = static_cast<zwp_tablet_manager_v2 *>(
+ wl_registry_bind(wl_registry, name, &zwp_tablet_manager_v2_interface, 1));
}
else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name)) {
display->relative_pointer_manager = static_cast<zwp_relative_pointer_manager_v1 *>(
@@ -1396,6 +2768,17 @@ static void global_add(void *data,
display->pointer_constraints = static_cast<zwp_pointer_constraints_v1 *>(
wl_registry_bind(wl_registry, name, &zwp_pointer_constraints_v1_interface, 1));
}
+ else {
+ found = false;
+ }
+
+ CLOG_INFO(LOG,
+ 2,
+ "add %s(interface=%s, version=%u, name=%u)",
+ found ? "" : "(skipped), ",
+ interface,
+ version,
+ name);
}
/**
@@ -1407,25 +2790,32 @@ static void global_add(void *data,
* name is no longer available. If the client bound to the global
* using the bind request, the client should now destroy that object.
*/
-static void global_remove(void * /*data*/, struct wl_registry * /*wl_registry*/, uint32_t /*name*/)
+static void global_handle_remove(void * /*data*/,
+ struct wl_registry * /*wl_registry*/,
+ const uint32_t name)
{
+ CLOG_INFO(LOG, 2, "remove (name=%u)", name);
}
static const struct wl_registry_listener registry_listener = {
- global_add,
- global_remove,
+ global_handle_add,
+ global_handle_remove,
};
+#undef LOG
+
/** \} */
/* -------------------------------------------------------------------- */
-/** \name Ghost Implementation
+/** \name GHOST Implementation
*
- * Wayland specific implementation of the GHOST_System interface.
+ * WAYLAND specific implementation of the #GHOST_System interface.
* \{ */
GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), d(new display_t)
{
+ wl_log_set_handler_client(ghost_wayland_log_handler);
+
d->system = this;
/* Connect to the Wayland server. */
d->display = wl_display_connect(nullptr);
@@ -1443,19 +2833,35 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), d(new display_t)
wl_display_roundtrip(d->display);
wl_registry_destroy(registry);
+#ifdef WITH_GHOST_WAYLAND_LIBDECOR
+ d->decor_context = libdecor_new(d->display, &libdecor_interface);
+ if (!d->decor_context) {
+ display_destroy(d);
+ throw std::runtime_error("Wayland: unable to create window decorations!");
+ }
+#else
if (!d->xdg_shell) {
display_destroy(d);
throw std::runtime_error("Wayland: unable to access xdg_shell!");
}
+#endif
/* Register data device per seat for IPC between Wayland clients. */
if (d->data_device_manager) {
for (input_t *input : d->inputs) {
input->data_device = wl_data_device_manager_get_data_device(d->data_device_manager,
- input->seat);
+ input->wl_seat);
wl_data_device_add_listener(input->data_device, &data_device_listener, input);
}
}
+
+ if (d->tablet_manager) {
+ for (input_t *input : d->inputs) {
+ input->tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(d->tablet_manager,
+ input->wl_seat);
+ zwp_tablet_seat_v2_add_listener(input->tablet_seat, &tablet_seat_listener, input);
+ }
+ }
}
GHOST_SystemWayland::~GHOST_SystemWayland()
@@ -1484,42 +2890,52 @@ int GHOST_SystemWayland::setConsoleWindowState(GHOST_TConsoleWindowState /*actio
GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) const
{
- if (!d->inputs.empty()) {
- static const xkb_state_component mods_all = xkb_state_component(
- XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED | XKB_STATE_MODS_LOCKED |
- XKB_STATE_MODS_EFFECTIVE);
-
- keys.set(GHOST_kModifierKeyLeftShift,
- xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, XKB_MOD_NAME_SHIFT, mods_all) ==
- 1);
- keys.set(GHOST_kModifierKeyRightShift,
- xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, XKB_MOD_NAME_SHIFT, mods_all) ==
- 1);
- keys.set(GHOST_kModifierKeyLeftAlt,
- xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "LAlt", mods_all) == 1);
- keys.set(GHOST_kModifierKeyRightAlt,
- xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "RAlt", mods_all) == 1);
- keys.set(GHOST_kModifierKeyLeftControl,
- xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "LControl", mods_all) == 1);
- keys.set(GHOST_kModifierKeyRightControl,
- xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "RControl", mods_all) == 1);
- keys.set(GHOST_kModifierKeyOS,
- xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "Super", mods_all) == 1);
- keys.set(GHOST_kModifierKeyNumMasks,
- xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "NumLock", mods_all) == 1);
-
- return GHOST_kSuccess;
+ if (UNLIKELY(d->inputs.empty())) {
+ return GHOST_kFailure;
}
- return GHOST_kFailure;
+
+ static const xkb_state_component mods_all = xkb_state_component(
+ XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED | XKB_STATE_MODS_LOCKED |
+ XKB_STATE_MODS_EFFECTIVE);
+
+ bool val;
+
+ /* NOTE: XKB doesn't seem to differentiate between left/right modifiers. */
+
+ val = xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, XKB_MOD_NAME_SHIFT, mods_all) == 1;
+ keys.set(GHOST_kModifierKeyLeftShift, val);
+ keys.set(GHOST_kModifierKeyRightShift, val);
+
+ val = xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, XKB_MOD_NAME_ALT, mods_all) == 1;
+ keys.set(GHOST_kModifierKeyLeftAlt, val);
+ keys.set(GHOST_kModifierKeyRightAlt, val);
+
+ val = xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, XKB_MOD_NAME_CTRL, mods_all) == 1;
+ keys.set(GHOST_kModifierKeyLeftControl, val);
+ keys.set(GHOST_kModifierKeyRightControl, val);
+
+ val = xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, XKB_MOD_NAME_LOGO, mods_all) == 1;
+ keys.set(GHOST_kModifierKeyOS, val);
+
+ val = xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, XKB_MOD_NAME_NUM, mods_all) == 1;
+ keys.set(GHOST_kModifierKeyNum, val);
+
+ return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_SystemWayland::getButtons(GHOST_Buttons &buttons) const
{
- if (!d->inputs.empty()) {
- buttons = d->inputs[0]->buttons;
- return GHOST_kSuccess;
+ if (UNLIKELY(d->inputs.empty())) {
+ return GHOST_kFailure;
}
- return GHOST_kFailure;
+ input_t *input = d->inputs[0];
+ input_state_pointer_t *input_state = input_state_pointer_active(input);
+ if (!input_state) {
+ return GHOST_kFailure;
+ }
+
+ buttons = input_state->buttons;
+ return GHOST_kSuccess;
}
char *GHOST_SystemWayland::getClipboard(bool /*selection*/) const
@@ -1531,28 +2947,33 @@ char *GHOST_SystemWayland::getClipboard(bool /*selection*/) const
void GHOST_SystemWayland::putClipboard(const char *buffer, bool /*selection*/) const
{
- if (!d->data_device_manager || d->inputs.empty()) {
+ if (UNLIKELY(!d->data_device_manager || d->inputs.empty())) {
return;
}
- data_source_t *data_source = d->inputs[0]->data_source;
+ input_t *input = d->inputs[0];
+
+ std::lock_guard lock{input->data_source_mutex};
+
+ data_source_t *data_source = input->data_source;
/* Copy buffer. */
- data_source->buffer_out = static_cast<char *>(malloc(strlen(buffer) + 1));
- std::strcpy(data_source->buffer_out, buffer);
+ free(data_source->buffer_out);
+ const size_t buffer_size = strlen(buffer) + 1;
+ data_source->buffer_out = static_cast<char *>(malloc(buffer_size));
+ std::memcpy(data_source->buffer_out, buffer, buffer_size);
data_source->data_source = wl_data_device_manager_create_data_source(d->data_device_manager);
- wl_data_source_add_listener(
- data_source->data_source, &data_source_listener, data_source->buffer_out);
+ wl_data_source_add_listener(data_source->data_source, &data_source_listener, input);
for (const std::string &type : mime_send) {
wl_data_source_offer(data_source->data_source, type.c_str());
}
- if (!d->inputs.empty() && d->inputs[0]->data_device) {
+ if (input->data_device) {
wl_data_device_set_selection(
- d->inputs[0]->data_device, data_source->data_source, data_source->source_serial);
+ input->data_device, data_source->data_source, input->data_source_serial);
}
}
@@ -1561,53 +2982,145 @@ uint8_t GHOST_SystemWayland::getNumDisplays() const
return d ? uint8_t(d->outputs.size()) : 0;
}
+static GHOST_TSuccess getCursorPositionClientRelative_impl(
+ const input_state_pointer_t *input_state,
+ const GHOST_WindowWayland *win,
+ int32_t &x,
+ int32_t &y)
+{
+ const wl_fixed_t scale = win->scale();
+ x = wl_fixed_to_int(scale * input_state->xy[0]);
+ y = wl_fixed_to_int(scale * input_state->xy[1]);
+ return GHOST_kSuccess;
+}
+
+static GHOST_TSuccess setCursorPositionClientRelative_impl(input_t *input,
+ GHOST_WindowWayland *win,
+ const int32_t x,
+ const int32_t y)
+{
+ /* NOTE: WAYLAND doesn't support warping the cursor.
+ * However when grab is enabled, we already simulate a cursor location
+ * so that can be set to a new location. */
+ if (!input->relative_pointer) {
+ return GHOST_kFailure;
+ }
+ const wl_fixed_t scale = win->scale();
+ const wl_fixed_t xy_next[2] = {
+ wl_fixed_from_int(x) / scale,
+ wl_fixed_from_int(y) / scale,
+ };
+
+ /* As the cursor was "warped" generate an event at the new location. */
+ relative_pointer_handle_relative_motion_impl(input, win, xy_next);
+
+ return GHOST_kSuccess;
+}
+
+GHOST_TSuccess GHOST_SystemWayland::getCursorPositionClientRelative(const GHOST_IWindow *window,
+ int32_t &x,
+ int32_t &y) const
+{
+ if (UNLIKELY(d->inputs.empty())) {
+ return GHOST_kFailure;
+ }
+ input_t *input = d->inputs[0];
+ input_state_pointer_t *input_state = input_state_pointer_active(input);
+ if (!input_state || !input_state->wl_surface) {
+ return GHOST_kFailure;
+ }
+ const GHOST_WindowWayland *win = static_cast<const GHOST_WindowWayland *>(window);
+ return getCursorPositionClientRelative_impl(input_state, win, x, y);
+}
+
+GHOST_TSuccess GHOST_SystemWayland::setCursorPositionClientRelative(GHOST_IWindow *window,
+ const int32_t x,
+ const int32_t y)
+{
+ if (UNLIKELY(d->inputs.empty())) {
+ return GHOST_kFailure;
+ }
+ input_t *input = d->inputs[0];
+ GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(window);
+ return setCursorPositionClientRelative_impl(input, win, x, y);
+}
+
GHOST_TSuccess GHOST_SystemWayland::getCursorPosition(int32_t &x, int32_t &y) const
{
- if (!d->inputs.empty() && (d->inputs[0]->focus_pointer != nullptr)) {
- x = d->inputs[0]->x;
- y = d->inputs[0]->y;
- return GHOST_kSuccess;
+ if (UNLIKELY(d->inputs.empty())) {
+ return GHOST_kFailure;
}
- else {
+ input_t *input = d->inputs[0];
+ input_state_pointer_t *input_state = input_state_pointer_active(input);
+ if (!input_state) {
return GHOST_kFailure;
}
+
+ if (wl_surface *focus_surface = input_state->wl_surface) {
+ GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
+ return getCursorPositionClientRelative_impl(input_state, win, x, y);
+ }
+ return GHOST_kFailure;
}
-GHOST_TSuccess GHOST_SystemWayland::setCursorPosition(int32_t /*x*/, int32_t /*y*/)
+GHOST_TSuccess GHOST_SystemWayland::setCursorPosition(const int32_t x, const int32_t y)
{
+ if (UNLIKELY(d->inputs.empty())) {
+ return GHOST_kFailure;
+ }
+ input_t *input = d->inputs[0];
+
+ /* Intentionally different from `getCursorPosition` which supports both tablet & pointer.
+ * In the case of setting the cursor location, tablets don't support this. */
+ if (wl_surface *focus_surface = input->pointer.wl_surface) {
+ GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
+ return setCursorPositionClientRelative_impl(input, win, x, y);
+ }
return GHOST_kFailure;
}
void GHOST_SystemWayland::getMainDisplayDimensions(uint32_t &width, uint32_t &height) const
{
- if (getNumDisplays() > 0) {
- /* We assume first output as main. */
- width = uint32_t(d->outputs[0]->width_pxl) / d->outputs[0]->scale;
- height = uint32_t(d->outputs[0]->height_pxl) / d->outputs[0]->scale;
+ if (getNumDisplays() == 0) {
+ return;
}
+ /* We assume first output as main. */
+ width = uint32_t(d->outputs[0]->size_native[0]);
+ height = uint32_t(d->outputs[0]->size_native[1]);
}
void GHOST_SystemWayland::getAllDisplayDimensions(uint32_t &width, uint32_t &height) const
{
- getMainDisplayDimensions(width, height);
-}
+ int32_t xy_min[2] = {INT32_MAX, INT32_MAX};
+ int32_t xy_max[2] = {INT32_MIN, INT32_MIN};
-GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings /*glSettings*/)
-{
- /* Create new off-screen window. */
- wl_surface *os_surface = wl_compositor_create_surface(compositor());
- wl_egl_window *os_egl_window = wl_egl_window_create(os_surface, int(1), int(1));
+ for (const output_t *output : d->outputs) {
+ int32_t xy[2] = {0, 0};
+ if (output->has_position_logical) {
+ xy[0] = output->position_logical[0];
+ xy[1] = output->position_logical[1];
+ }
+ xy_min[0] = std::min(xy_min[0], xy[0]);
+ xy_min[1] = std::min(xy_min[1], xy[1]);
+ xy_max[0] = std::max(xy_max[0], xy[0] + output->size_native[0]);
+ xy_max[1] = std::max(xy_max[1], xy[1] + output->size_native[1]);
+ }
- d->os_surfaces.push_back(os_surface);
- d->os_egl_windows.push_back(os_egl_window);
+ width = xy_max[0] - xy_min[0];
+ height = xy_max[1] - xy_min[1];
+}
+static GHOST_Context *createOffscreenContext_impl(GHOST_SystemWayland *system,
+ struct wl_display *wl_display,
+ wl_egl_window *egl_window)
+{
GHOST_Context *context;
for (int minor = 6; minor >= 0; --minor) {
- context = new GHOST_ContextEGL(this,
+ context = new GHOST_ContextEGL(system,
false,
- EGLNativeWindowType(os_egl_window),
- EGLNativeDisplayType(d->display),
+ EGLNativeWindowType(egl_window),
+ EGLNativeDisplayType(wl_display),
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
4,
minor,
@@ -1615,16 +3128,16 @@ GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings /*g
GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY,
EGL_OPENGL_API);
- if (context->initializeDrawingContext())
+ if (context->initializeDrawingContext()) {
return context;
- else
- delete context;
+ }
+ delete context;
}
- context = new GHOST_ContextEGL(this,
+ context = new GHOST_ContextEGL(system,
false,
- EGLNativeWindowType(os_egl_window),
- EGLNativeDisplayType(d->display),
+ EGLNativeWindowType(egl_window),
+ EGLNativeDisplayType(wl_display),
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
3,
3,
@@ -1635,34 +3148,60 @@ GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings /*g
if (context->initializeDrawingContext()) {
return context;
}
- else {
- delete context;
+ delete context;
+ return nullptr;
+}
+
+GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings /*glSettings*/)
+{
+ /* Create new off-screen window. */
+ wl_surface *wl_surface = wl_compositor_create_surface(compositor());
+ wl_egl_window *egl_window = wl_surface ? wl_egl_window_create(wl_surface, 1, 1) : nullptr;
+
+ GHOST_Context *context = createOffscreenContext_impl(this, d->display, egl_window);
+
+ if (!context) {
+ GHOST_PRINT("Cannot create off-screen EGL context" << std::endl);
+ if (wl_surface) {
+ wl_surface_destroy(wl_surface);
+ }
+ if (egl_window) {
+ wl_egl_window_destroy(egl_window);
+ }
+ return nullptr;
}
- GHOST_PRINT("Cannot create off-screen EGL context" << std::endl);
+ wl_surface_set_user_data(wl_surface, egl_window);
+ context->setUserData(wl_surface);
- return nullptr;
+ return context;
}
GHOST_TSuccess GHOST_SystemWayland::disposeContext(GHOST_IContext *context)
{
+ struct wl_surface *wl_surface = (struct wl_surface *)((GHOST_Context *)context)->getUserData();
+ wl_egl_window *egl_window = (wl_egl_window *)wl_surface_get_user_data(wl_surface);
+ wl_egl_window_destroy(egl_window);
+ wl_surface_destroy(wl_surface);
+
delete context;
+
return GHOST_kSuccess;
}
GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title,
- int32_t left,
- int32_t top,
- uint32_t width,
- uint32_t height,
- GHOST_TWindowState state,
- GHOST_TDrawingContextType type,
- GHOST_GLSettings glSettings,
+ const int32_t left,
+ const int32_t top,
+ const uint32_t width,
+ const uint32_t height,
+ const GHOST_TWindowState state,
+ const GHOST_TDrawingContextType type,
+ const GHOST_GLSettings glSettings,
const bool exclusive,
const bool is_dialog,
const GHOST_IWindow *parentWindow)
{
- /* globally store pointer to window manager */
+ /* Globally store pointer to window manager. */
if (!window_manager) {
window_manager = getWindowManager();
}
@@ -1696,76 +3235,210 @@ GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title,
return window;
}
-wl_display *GHOST_SystemWayland::display()
-{
- return d->display;
-}
-
-wl_compositor *GHOST_SystemWayland::compositor()
+/**
+ * Show the buffer defined by #cursor_buffer_set without changing anything else,
+ * so #cursor_buffer_hide can be used to display it again.
+ *
+ * The caller is responsible for setting `input->cursor.visible`.
+ */
+static void cursor_buffer_show(const input_t *input)
{
- return d->compositor;
-}
+ const cursor_t *c = &input->cursor;
-xdg_wm_base *GHOST_SystemWayland::shell()
-{
- return d->xdg_shell;
-}
+ if (input->wl_pointer) {
+ const int scale = c->is_custom ? c->custom_scale : input->pointer.theme_scale;
+ const int32_t hotspot_x = int32_t(c->wl_image.hotspot_x) / scale;
+ const int32_t hotspot_y = int32_t(c->wl_image.hotspot_y) / scale;
+ if (input->wl_pointer) {
+ wl_pointer_set_cursor(
+ input->wl_pointer, input->pointer.serial, c->wl_surface, hotspot_x, hotspot_y);
+ }
+ }
-zxdg_decoration_manager_v1 *GHOST_SystemWayland::decoration_manager()
-{
- return d->xdg_decoration_manager;
+ if (!input->tablet_tools.empty()) {
+ const int scale = c->is_custom ? c->custom_scale : input->tablet.theme_scale;
+ const int32_t hotspot_x = int32_t(c->wl_image.hotspot_x) / scale;
+ const int32_t hotspot_y = int32_t(c->wl_image.hotspot_y) / scale;
+ for (struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : input->tablet_tools) {
+ tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(
+ zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
+ zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2,
+ input->tablet.serial,
+ tool_input->cursor_surface,
+ hotspot_x,
+ hotspot_y);
+ }
+ }
}
-const std::vector<output_t *> &GHOST_SystemWayland::outputs() const
+/**
+ * Hide the buffer defined by #cursor_buffer_set without changing anything else,
+ * so #cursor_buffer_show can be used to display it again.
+ *
+ * The caller is responsible for setting `input->cursor.visible`.
+ */
+static void cursor_buffer_hide(const input_t *input)
{
- return d->outputs;
+ wl_pointer_set_cursor(input->wl_pointer, input->pointer.serial, nullptr, 0, 0);
+ for (struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : input->tablet_tools) {
+ zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2, input->tablet.serial, nullptr, 0, 0);
+ }
}
-wl_shm *GHOST_SystemWayland::shm() const
+/**
+ * Needed to ensure the cursor size is always a multiple of scale.
+ */
+static int cursor_buffer_compatible_scale_from_image(const struct wl_cursor_image *wl_image,
+ int scale)
{
- return d->shm;
+ const int32_t image_size_x = int32_t(wl_image->width);
+ const int32_t image_size_y = int32_t(wl_image->height);
+ while (scale > 1) {
+ if ((image_size_x % scale) == 0 && (image_size_y % scale) == 0) {
+ break;
+ }
+ scale -= 1;
+ }
+ return scale;
+}
+
+static void cursor_buffer_set_surface_impl(const input_t *input,
+ wl_buffer *buffer,
+ struct wl_surface *wl_surface,
+ const int scale)
+{
+ const wl_cursor_image *wl_image = &input->cursor.wl_image;
+ const int32_t image_size_x = int32_t(wl_image->width);
+ const int32_t image_size_y = int32_t(wl_image->height);
+ GHOST_ASSERT((image_size_x % scale) == 0 && (image_size_y % scale) == 0,
+ "The size must be a multiple of the scale!");
+
+ wl_surface_set_buffer_scale(wl_surface, scale);
+ wl_surface_attach(wl_surface, buffer, 0, 0);
+ wl_surface_damage(wl_surface, 0, 0, image_size_x, image_size_y);
+ wl_surface_commit(wl_surface);
+}
+
+static void cursor_buffer_set(const input_t *input, wl_buffer *buffer)
+{
+ const cursor_t *c = &input->cursor;
+ const wl_cursor_image *wl_image = &input->cursor.wl_image;
+ const bool visible = (c->visible && c->is_hardware);
+
+ /* This is a requirement of WAYLAND, when this isn't the case,
+ * it causes Blender's window to close intermittently. */
+ if (input->wl_pointer) {
+ const int scale = cursor_buffer_compatible_scale_from_image(
+ wl_image, c->is_custom ? c->custom_scale : input->pointer.theme_scale);
+ const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale;
+ const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale;
+ cursor_buffer_set_surface_impl(input, buffer, c->wl_surface, scale);
+ wl_pointer_set_cursor(input->wl_pointer,
+ input->pointer.serial,
+ visible ? c->wl_surface : nullptr,
+ hotspot_x,
+ hotspot_y);
+ }
+
+ /* Set the cursor for all tablet tools as well. */
+ if (!input->tablet_tools.empty()) {
+ const int scale = cursor_buffer_compatible_scale_from_image(
+ wl_image, c->is_custom ? c->custom_scale : input->tablet.theme_scale);
+ const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale;
+ const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale;
+ for (struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : input->tablet_tools) {
+ tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(
+ zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
+ cursor_buffer_set_surface_impl(input, buffer, tool_input->cursor_surface, scale);
+ zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2,
+ input->tablet.serial,
+ visible ? tool_input->cursor_surface : nullptr,
+ hotspot_x,
+ hotspot_y);
+ }
+ }
}
-void GHOST_SystemWayland::setSelection(const std::string &selection)
-{
- this->selection = selection;
-}
+enum eCursorSetMode {
+ CURSOR_VISIBLE_ALWAYS_SET = 1,
+ CURSOR_VISIBLE_ONLY_HIDE,
+ CURSOR_VISIBLE_ONLY_SHOW,
+};
-static void set_cursor_buffer(input_t *input, wl_buffer *buffer)
+static void cursor_visible_set(input_t *input,
+ const bool visible,
+ const bool is_hardware,
+ const enum eCursorSetMode set_mode)
{
- cursor_t *c = &input->cursor;
-
- c->visible = (buffer != nullptr);
+ cursor_t *cursor = &input->cursor;
+ const bool was_visible = cursor->is_hardware && cursor->visible;
+ const bool use_visible = is_hardware && visible;
- wl_surface_attach(c->surface, buffer, 0, 0);
+ if (set_mode == CURSOR_VISIBLE_ALWAYS_SET) {
+ /* Pass. */
+ }
+ else if ((set_mode == CURSOR_VISIBLE_ONLY_SHOW)) {
+ if (!use_visible) {
+ return;
+ }
+ }
+ else if ((set_mode == CURSOR_VISIBLE_ONLY_HIDE)) {
+ if (use_visible) {
+ return;
+ }
+ }
- wl_surface_damage(c->surface, 0, 0, int32_t(c->image.width), int32_t(c->image.height));
- wl_pointer_set_cursor(input->pointer,
- input->pointer_serial,
- c->visible ? c->surface : nullptr,
- int32_t(c->image.hotspot_x) / c->scale,
- int32_t(c->image.hotspot_y) / c->scale);
+ if (use_visible) {
+ if (!was_visible) {
+ cursor_buffer_show(input);
+ }
+ }
+ else {
+ if (was_visible) {
+ cursor_buffer_hide(input);
+ }
+ }
+ cursor->visible = visible;
+ cursor->is_hardware = is_hardware;
+}
- wl_surface_commit(c->surface);
+static bool cursor_is_software(const GHOST_TGrabCursorMode mode, const bool use_software_confine)
+{
+ if (mode == GHOST_kGrabWrap) {
+ return true;
+ }
+#ifdef USE_GNOME_CONFINE_HACK
+ if (mode == GHOST_kGrabNormal) {
+ if (use_software_confine) {
+ return true;
+ }
+ }
+#else
+ (void)use_software_confine;
+#endif
+ return false;
}
-GHOST_TSuccess GHOST_SystemWayland::setCursorShape(GHOST_TStandardCursor shape)
+GHOST_TSuccess GHOST_SystemWayland::setCursorShape(const GHOST_TStandardCursor shape)
{
- if (d->inputs.empty()) {
+ if (UNLIKELY(d->inputs.empty())) {
return GHOST_kFailure;
}
- const std::string cursor_name = cursors.count(shape) ? cursors.at(shape) :
- cursors.at(GHOST_kStandardCursorDefault);
+ auto cursor_find = cursors.find(shape);
+ const char *cursor_name = (cursor_find == cursors.end()) ?
+ cursors.at(GHOST_kStandardCursorDefault) :
+ (*cursor_find).second;
input_t *input = d->inputs[0];
cursor_t *c = &input->cursor;
- if (!c->theme) {
+ if (!c->wl_theme) {
/* The cursor surface hasn't entered an output yet. Initialize theme with scale 1. */
- c->theme = wl_cursor_theme_load(c->theme_name.c_str(), c->size, d->inputs[0]->system->shm());
+ c->wl_theme = wl_cursor_theme_load(
+ c->theme_name.c_str(), c->size, d->inputs[0]->system->shm());
}
- wl_cursor *cursor = wl_cursor_theme_get_cursor(c->theme, cursor_name.c_str());
+ wl_cursor *cursor = wl_cursor_theme_get_cursor(c->wl_theme, cursor_name);
if (!cursor) {
GHOST_PRINT("cursor '" << cursor_name << "' does not exist" << std::endl);
@@ -1778,81 +3451,56 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(GHOST_TStandardCursor shape)
return GHOST_kFailure;
}
- c->buffer = buffer;
- c->image = *image;
+ c->visible = true;
+ c->is_custom = false;
+ c->wl_buffer = buffer;
+ c->wl_image = *image;
- set_cursor_buffer(input, buffer);
+ cursor_buffer_set(input, buffer);
return GHOST_kSuccess;
}
-GHOST_TSuccess GHOST_SystemWayland::hasCursorShape(GHOST_TStandardCursor cursorShape)
+GHOST_TSuccess GHOST_SystemWayland::hasCursorShape(const GHOST_TStandardCursor cursorShape)
{
- return GHOST_TSuccess(cursors.count(cursorShape) && !cursors.at(cursorShape).empty());
+ auto cursor_find = cursors.find(cursorShape);
+ if (cursor_find == cursors.end()) {
+ return GHOST_kFailure;
+ }
+ const char *value = (*cursor_find).second;
+ if (*value == '\0') {
+ return GHOST_kFailure;
+ }
+ return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap,
uint8_t *mask,
- int sizex,
- int sizey,
- int hotX,
- int hotY,
- bool /*canInvertColor*/)
+ const int sizex,
+ const int sizey,
+ const int hotX,
+ const int hotY,
+ const bool /*canInvertColor*/)
{
- if (d->inputs.empty()) {
+ if (UNLIKELY(d->inputs.empty())) {
return GHOST_kFailure;
}
cursor_t *cursor = &d->inputs[0]->cursor;
- static const int32_t stride = sizex * 4; /* ARGB */
- cursor->file_buffer->size = size_t(stride * sizey);
-
-#ifdef HAVE_MEMFD_CREATE
- const int fd = memfd_create("blender-cursor-custom", MFD_CLOEXEC | MFD_ALLOW_SEALING);
- if (fd >= 0) {
- fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
- }
-#else
- char *path = getenv("XDG_RUNTIME_DIR");
- if (!path) {
- errno = ENOENT;
- return GHOST_kFailure;
+ if (cursor->custom_data) {
+ munmap(cursor->custom_data, cursor->custom_data_size);
+ cursor->custom_data = nullptr;
+ cursor->custom_data_size = 0; /* Not needed, but the value is no longer meaningful. */
}
- char *tmpname;
- asprintf(&tmpname, "%s/%s", path, "blender-XXXXXX");
- const int fd = mkostemp(tmpname, O_CLOEXEC);
- if (fd >= 0) {
- unlink(tmpname);
- }
- free(tmpname);
-#endif
-
- if (fd < 0) {
+ const int32_t size_xy[2] = {sizex, sizey};
+ wl_buffer *buffer = ghost_wl_buffer_create_for_image(
+ d->shm, size_xy, WL_SHM_FORMAT_ARGB8888, &cursor->custom_data, &cursor->custom_data_size);
+ if (buffer == nullptr) {
return GHOST_kFailure;
}
- if (posix_fallocate(fd, 0, int32_t(cursor->file_buffer->size)) != 0) {
- return GHOST_kFailure;
- }
-
- cursor->file_buffer->data = mmap(
- nullptr, cursor->file_buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
-
- if (cursor->file_buffer->data == MAP_FAILED) {
- close(fd);
- return GHOST_kFailure;
- }
-
- struct wl_shm_pool *pool = wl_shm_create_pool(d->shm, fd, int32_t(cursor->file_buffer->size));
-
- wl_buffer *buffer = wl_shm_pool_create_buffer(
- pool, 0, sizex, sizey, stride, WL_SHM_FORMAT_ARGB8888);
-
- wl_shm_pool_destroy(pool);
- close(fd);
-
wl_buffer_add_listener(buffer, &cursor_buffer_listener, cursor);
static constexpr uint32_t black = 0xFF000000;
@@ -1863,7 +3511,7 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap,
uint32_t *pixel;
for (int y = 0; y < sizey; ++y) {
- pixel = &static_cast<uint32_t *>(cursor->file_buffer->data)[y * sizex];
+ pixel = &static_cast<uint32_t *>(cursor->custom_data)[y * sizex];
for (int x = 0; x < sizex; ++x) {
if ((x % 8) == 0) {
datab = *bitmap++;
@@ -1885,90 +3533,466 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap,
}
}
- cursor->buffer = buffer;
- cursor->image.width = uint32_t(sizex);
- cursor->image.height = uint32_t(sizey);
- cursor->image.hotspot_x = uint32_t(hotX);
- cursor->image.hotspot_y = uint32_t(hotY);
+ cursor->visible = true;
+ cursor->is_custom = true;
+ cursor->custom_scale = 1; /* TODO: support Hi-DPI custom cursors. */
+ cursor->wl_buffer = buffer;
+ cursor->wl_image.width = uint32_t(sizex);
+ cursor->wl_image.height = uint32_t(sizey);
+ cursor->wl_image.hotspot_x = uint32_t(hotX);
+ cursor->wl_image.hotspot_y = uint32_t(hotY);
- set_cursor_buffer(d->inputs[0], buffer);
+ cursor_buffer_set(d->inputs[0], buffer);
return GHOST_kSuccess;
}
-GHOST_TSuccess GHOST_SystemWayland::setCursorVisibility(bool visible)
+GHOST_TSuccess GHOST_SystemWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitmap)
{
- if (d->inputs.empty()) {
+ cursor_t *cursor = &d->inputs[0]->cursor;
+ if (cursor->custom_data == nullptr) {
+ return GHOST_kFailure;
+ }
+ if (!cursor->is_custom) {
+ return GHOST_kFailure;
+ }
+
+ bitmap->data_size[0] = cursor->wl_image.width;
+ bitmap->data_size[1] = cursor->wl_image.height;
+
+ bitmap->hot_spot[0] = cursor->wl_image.hotspot_x;
+ bitmap->hot_spot[1] = cursor->wl_image.hotspot_y;
+
+ bitmap->data = (uint8_t *)static_cast<void *>(cursor->custom_data);
+
+ return GHOST_kSuccess;
+}
+
+GHOST_TSuccess GHOST_SystemWayland::setCursorVisibility(const bool visible)
+{
+ if (UNLIKELY(d->inputs.empty())) {
return GHOST_kFailure;
}
input_t *input = d->inputs[0];
+ cursor_visible_set(input, visible, input->cursor.is_hardware, CURSOR_VISIBLE_ALWAYS_SET);
+ return GHOST_kSuccess;
+}
- cursor_t *cursor = &input->cursor;
- if (visible) {
- if (!cursor->visible) {
- set_cursor_buffer(input, cursor->buffer);
- }
+bool GHOST_SystemWayland::supportsCursorWarp()
+{
+ /* WAYLAND doesn't support setting the cursor position directly,
+ * this is an intentional choice, forcing us to use a software cursor in this case. */
+ return false;
+}
+
+bool GHOST_SystemWayland::supportsWindowPosition()
+{
+ /* WAYLAND doesn't support accessing the window position. */
+ return false;
+}
+
+bool GHOST_SystemWayland::getCursorGrabUseSoftwareDisplay(const GHOST_TGrabCursorMode mode)
+{
+ if (UNLIKELY(d->inputs.empty())) {
+ return false;
}
- else {
- if (cursor->visible) {
- set_cursor_buffer(input, nullptr);
- }
+
+#ifdef USE_GNOME_CONFINE_HACK
+ input_t *input = d->inputs[0];
+ const bool use_software_confine = input->use_pointer_software_confine;
+#else
+ const bool use_software_confine = false;
+#endif
+
+ return cursor_is_software(mode, use_software_confine);
+}
+
+#ifdef USE_GNOME_CONFINE_HACK
+static bool setCursorGrab_use_software_confine(const GHOST_TGrabCursorMode mode,
+ wl_surface *surface)
+{
+# ifndef USE_GNOME_CONFINE_HACK_ALWAYS_ON
+ if (use_gnome_confine_hack == false) {
+ return false;
+ }
+# endif
+ if (mode != GHOST_kGrabNormal) {
+ return false;
+ }
+ const GHOST_WindowWayland *win = ghost_wl_surface_user_data(surface);
+ if (!win) {
+ return false;
}
- return GHOST_kSuccess;
+# ifndef USE_GNOME_CONFINE_HACK_ALWAYS_ON
+ if (win->scale() <= 1) {
+ return false;
+ }
+# endif
+ return true;
+}
+#endif
+
+static input_grab_state_t input_grab_state_from_mode(const GHOST_TGrabCursorMode mode,
+ const bool use_software_confine)
+{
+ /* Initialize all members. */
+ const struct input_grab_state_t grab_state = {
+ /* Warping happens to require software cursor which also hides. */
+ .use_lock = ELEM(mode, GHOST_kGrabWrap, GHOST_kGrabHide) || use_software_confine,
+ .use_confine = (mode == GHOST_kGrabNormal) && (use_software_confine == false),
+ };
+ return grab_state;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public WAYLAND Proxy Ownership API
+ * \{ */
+
+static const char *ghost_wl_output_tag_id = "GHOST-output";
+static const char *ghost_wl_surface_tag_id = "GHOST-window";
+static const char *ghost_wl_surface_cursor_pointer_tag_id = "GHOST-cursor-pointer";
+static const char *ghost_wl_surface_cursor_tablet_tag_id = "GHOST-cursor-tablet";
+
+bool ghost_wl_output_own(const struct wl_output *output)
+{
+ return wl_proxy_get_tag((struct wl_proxy *)output) == &ghost_wl_output_tag_id;
+}
+
+bool ghost_wl_surface_own(const struct wl_surface *surface)
+{
+ return wl_proxy_get_tag((struct wl_proxy *)surface) == &ghost_wl_surface_tag_id;
+}
+
+bool ghost_wl_surface_own_cursor_pointer(const struct wl_surface *surface)
+{
+ return wl_proxy_get_tag((struct wl_proxy *)surface) == &ghost_wl_surface_cursor_pointer_tag_id;
+}
+
+bool ghost_wl_surface_own_cursor_tablet(const struct wl_surface *surface)
+{
+ return wl_proxy_get_tag((struct wl_proxy *)surface) == &ghost_wl_surface_cursor_tablet_tag_id;
+}
+
+void ghost_wl_output_tag(struct wl_output *output)
+{
+ wl_proxy_set_tag((struct wl_proxy *)output, &ghost_wl_output_tag_id);
+}
+
+void ghost_wl_surface_tag(struct wl_surface *surface)
+{
+ wl_proxy_set_tag((struct wl_proxy *)surface, &ghost_wl_surface_tag_id);
+}
+
+void ghost_wl_surface_tag_cursor_pointer(struct wl_surface *surface)
+{
+ wl_proxy_set_tag((struct wl_proxy *)surface, &ghost_wl_surface_cursor_pointer_tag_id);
+}
+
+void ghost_wl_surface_tag_cursor_tablet(struct wl_surface *surface)
+{
+ wl_proxy_set_tag((struct wl_proxy *)surface, &ghost_wl_surface_cursor_tablet_tag_id);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public WAYLAND Direct Data Access
+ *
+ * Expose some members via methods.
+ * \{ */
+
+wl_display *GHOST_SystemWayland::display()
+{
+ return d->display;
+}
+
+wl_compositor *GHOST_SystemWayland::compositor()
+{
+ return d->compositor;
}
-GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mode,
- const GHOST_TGrabCursorMode mode_current,
+#ifdef WITH_GHOST_WAYLAND_LIBDECOR
+
+libdecor *GHOST_SystemWayland::decor_context()
+{
+ return d->decor_context;
+}
+
+#else /* WITH_GHOST_WAYLAND_LIBDECOR */
+
+xdg_wm_base *GHOST_SystemWayland::xdg_shell()
+{
+ return d->xdg_shell;
+}
- wl_surface *surface)
+zxdg_decoration_manager_v1 *GHOST_SystemWayland::xdg_decoration_manager()
{
- /* ignore, if the required protocols are not supported */
- if (!d->relative_pointer_manager || !d->pointer_constraints) {
+ return d->xdg_decoration_manager;
+}
+
+#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */
+
+const std::vector<output_t *> &GHOST_SystemWayland::outputs() const
+{
+ return d->outputs;
+}
+
+wl_shm *GHOST_SystemWayland::shm() const
+{
+ return d->shm;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public WAYLAND Query Access
+ * \{ */
+
+struct output_t *ghost_wl_output_user_data(struct wl_output *wl_output)
+{
+ GHOST_ASSERT(wl_output, "output must not be NULL");
+ GHOST_ASSERT(ghost_wl_output_own(wl_output), "output is not owned by GHOST");
+ output_t *output = static_cast<output_t *>(wl_output_get_user_data(wl_output));
+ return output;
+}
+
+GHOST_WindowWayland *ghost_wl_surface_user_data(struct wl_surface *surface)
+{
+ GHOST_ASSERT(surface, "surface must not be NULL");
+ GHOST_ASSERT(ghost_wl_surface_own(surface), "surface is not owned by GHOST");
+ GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(wl_surface_get_user_data(surface));
+ return win;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public WAYLAND Utility Functions
+ *
+ * Functionality only used for the WAYLAND implementation.
+ * \{ */
+
+void GHOST_SystemWayland::selection_set(const std::string &selection)
+{
+ this->selection = selection;
+}
+
+void GHOST_SystemWayland::window_surface_unref(const wl_surface *surface)
+{
+#define SURFACE_CLEAR_PTR(surface_test) \
+ if (surface_test == surface) { \
+ surface_test = nullptr; \
+ } \
+ ((void)0);
+
+ /* Only clear window surfaces (not cursors, off-screen surfaces etc). */
+ for (input_t *input : d->inputs) {
+ SURFACE_CLEAR_PTR(input->pointer.wl_surface);
+ SURFACE_CLEAR_PTR(input->tablet.wl_surface);
+ SURFACE_CLEAR_PTR(input->keyboard.wl_surface);
+ SURFACE_CLEAR_PTR(input->focus_dnd);
+ }
+#undef SURFACE_CLEAR_PTR
+}
+
+bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mode,
+ const GHOST_TGrabCursorMode mode_current,
+ int32_t init_grab_xy[2],
+ const GHOST_Rect *wrap_bounds,
+ const GHOST_TAxisFlag wrap_axis,
+ wl_surface *surface,
+ const int scale)
+{
+ /* Ignore, if the required protocols are not supported. */
+ if (UNLIKELY(!d->relative_pointer_manager || !d->pointer_constraints)) {
return GHOST_kFailure;
}
- if (d->inputs.empty()) {
+ if (UNLIKELY(d->inputs.empty())) {
return GHOST_kFailure;
}
+ /* No change, success. */
+ if (mode == mode_current) {
+ return GHOST_kSuccess;
+ }
input_t *input = d->inputs[0];
- if (mode != GHOST_kGrabDisable) {
- /* TODO(@campbellbarton): Support #GHOST_kGrabWrap,
- * where the cursor moves but is constrained to a region. */
- input->relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(
- d->relative_pointer_manager, input->pointer);
- zwp_relative_pointer_v1_add_listener(
- input->relative_pointer, &relative_pointer_listener, input);
- input->locked_pointer = zwp_pointer_constraints_v1_lock_pointer(
- d->pointer_constraints,
- surface,
- input->pointer,
- nullptr,
- ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
-
- if (mode == GHOST_kGrabHide) {
- setCursorVisibility(false);
- }
- }
- else {
+#ifdef USE_GNOME_CONFINE_HACK
+ const bool was_software_confine = input->use_pointer_software_confine;
+ const bool use_software_confine = setCursorGrab_use_software_confine(mode, surface);
+#else
+ const bool was_software_confine = false;
+ const bool use_software_confine = false;
+#endif
+
+ const struct input_grab_state_t grab_state_prev = input_grab_state_from_mode(
+ mode_current, was_software_confine);
+ const struct input_grab_state_t grab_state_next = input_grab_state_from_mode(
+ mode, use_software_confine);
+
+ /* Check for wrap as #supportsCursorWarp isn't supported. */
+ const bool use_visible = !(ELEM(mode, GHOST_kGrabHide, GHOST_kGrabWrap) || use_software_confine);
+ const bool is_hardware_cursor = !cursor_is_software(mode, use_software_confine);
+
+ /* Only hide so the cursor is not made visible before it's location is restored.
+ * This function is called again at the end of this function which only shows. */
+ cursor_visible_set(input, use_visible, is_hardware_cursor, CURSOR_VISIBLE_ONLY_HIDE);
+
+ /* Switching from one grab mode to another,
+ * in this case disable the current locks as it makes logic confusing,
+ * postpone changing the cursor to avoid flickering. */
+ if (!grab_state_next.use_lock) {
if (input->relative_pointer) {
zwp_relative_pointer_v1_destroy(input->relative_pointer);
input->relative_pointer = nullptr;
}
if (input->locked_pointer) {
+ /* Request location to restore to. */
+ if (mode_current == GHOST_kGrabWrap) {
+ /* Since this call is initiated by Blender, we can be sure the window wasn't closed
+ * by logic outside this function - as the window was needed to make this call. */
+ int32_t xy_new[2] = {UNPACK2(input->pointer.xy)};
+
+ GHOST_Rect bounds_scale;
+
+ bounds_scale.m_l = wl_fixed_from_int(wrap_bounds->m_l) / scale;
+ bounds_scale.m_t = wl_fixed_from_int(wrap_bounds->m_t) / scale;
+ bounds_scale.m_r = wl_fixed_from_int(wrap_bounds->m_r) / scale;
+ bounds_scale.m_b = wl_fixed_from_int(wrap_bounds->m_b) / scale;
+
+ bounds_scale.wrapPoint(UNPACK2(xy_new), 0, wrap_axis);
+
+ /* Push an event so the new location is registered. */
+ if ((xy_new[0] != input->pointer.xy[0]) || (xy_new[1] != input->pointer.xy[1])) {
+ input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
+ GHOST_kEventCursorMove,
+ ghost_wl_surface_user_data(surface),
+ wl_fixed_to_int(scale * xy_new[0]),
+ wl_fixed_to_int(scale * xy_new[1]),
+ GHOST_TABLET_DATA_NONE));
+ }
+ input->pointer.xy[0] = xy_new[0];
+ input->pointer.xy[1] = xy_new[1];
+
+ zwp_locked_pointer_v1_set_cursor_position_hint(input->locked_pointer, UNPACK2(xy_new));
+ wl_surface_commit(surface);
+ }
+ else if (mode_current == GHOST_kGrabHide) {
+ if ((init_grab_xy[0] != input->grab_lock_xy[0]) ||
+ (init_grab_xy[1] != input->grab_lock_xy[1])) {
+ const wl_fixed_t xy_next[2] = {
+ wl_fixed_from_int(init_grab_xy[0]) / scale,
+ wl_fixed_from_int(init_grab_xy[1]) / scale,
+ };
+ zwp_locked_pointer_v1_set_cursor_position_hint(input->locked_pointer, UNPACK2(xy_next));
+ wl_surface_commit(surface);
+ }
+ }
+#ifdef USE_GNOME_CONFINE_HACK
+ else if (mode_current == GHOST_kGrabNormal) {
+ if (was_software_confine) {
+ zwp_locked_pointer_v1_set_cursor_position_hint(input->locked_pointer,
+ UNPACK2(input->pointer.xy));
+ wl_surface_commit(surface);
+ }
+ }
+#endif
+
zwp_locked_pointer_v1_destroy(input->locked_pointer);
input->locked_pointer = nullptr;
}
+ }
+
+ if (!grab_state_next.use_confine) {
+ if (input->confined_pointer) {
+ zwp_confined_pointer_v1_destroy(input->confined_pointer);
+ input->confined_pointer = nullptr;
+ }
+ }
- if (mode_current == GHOST_kGrabHide) {
- setCursorVisibility(false);
+ if (mode != GHOST_kGrabDisable) {
+ if (grab_state_next.use_lock) {
+ if (!grab_state_prev.use_lock) {
+ /* TODO(@campbellbarton): As WAYLAND does not support warping the pointer it may not be
+ * possible to support #GHOST_kGrabWrap by pragmatically settings it's coordinates.
+ * An alternative could be to draw the cursor in software (and hide the real cursor),
+ * or just accept a locked cursor on WAYLAND. */
+ input->relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(
+ d->relative_pointer_manager, input->wl_pointer);
+ zwp_relative_pointer_v1_add_listener(
+ input->relative_pointer, &relative_pointer_listener, input);
+ input->locked_pointer = zwp_pointer_constraints_v1_lock_pointer(
+ d->pointer_constraints,
+ surface,
+ input->wl_pointer,
+ nullptr,
+ ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
+ }
+ if (mode == GHOST_kGrabHide) {
+ /* Set the initial position to detect any changes when un-grabbing,
+ * otherwise the unlocked cursor defaults to un-locking in-place. */
+ init_grab_xy[0] = wl_fixed_to_int(scale * input->pointer.xy[0]);
+ init_grab_xy[1] = wl_fixed_to_int(scale * input->pointer.xy[1]);
+ input->grab_lock_xy[0] = init_grab_xy[0];
+ input->grab_lock_xy[1] = init_grab_xy[1];
+ }
+ }
+ else if (grab_state_next.use_confine) {
+ if (!grab_state_prev.use_confine) {
+ input->confined_pointer = zwp_pointer_constraints_v1_confine_pointer(
+ d->pointer_constraints,
+ surface,
+ input->wl_pointer,
+ nullptr,
+ ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
+ }
}
}
+ /* Only show so the cursor is made visible as the last step. */
+ cursor_visible_set(input, use_visible, is_hardware_cursor, CURSOR_VISIBLE_ONLY_SHOW);
+
+#ifdef USE_GNOME_CONFINE_HACK
+ input->use_pointer_software_confine = use_software_confine;
+#endif
+
return GHOST_kSuccess;
}
+#ifdef WITH_GHOST_WAYLAND_DYNLOAD
+bool ghost_wl_dynload_libraries()
+{
+ /* Only report when `libwayland-client` is not found when building without X11,
+ * which will be used as a fallback. */
+# ifdef WITH_GHOST_X11
+ bool verbose = false;
+# else
+ bool verbose = true;
+# endif
+
+ if (wayland_dynload_client_init(verbose) && /* `libwayland-client`. */
+ wayland_dynload_cursor_init(verbose) && /* `libwayland-cursor`. */
+ wayland_dynload_egl_init(verbose) && /* `libwayland-egl`. */
+# ifdef WITH_GHOST_WAYLAND_LIBDECOR
+ wayland_dynload_libdecor_init(verbose) && /* `libdecor-0`. */
+# endif
+ true) {
+ return true;
+ }
+# ifdef WITH_GHOST_WAYLAND_LIBDECOR
+ wayland_dynload_libdecor_exit();
+# endif
+ wayland_dynload_client_exit();
+ wayland_dynload_cursor_exit();
+ wayland_dynload_egl_exit();
+
+ return false;
+}
+#endif /* WITH_GHOST_WAYLAND_DYNLOAD */
+
/** \} */