diff options
Diffstat (limited to 'intern/ghost/intern/GHOST_SystemWayland.cpp')
-rw-r--r-- | intern/ghost/intern/GHOST_SystemWayland.cpp | 2131 |
1 files changed, 1648 insertions, 483 deletions
diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 6f694bfd9a6..33afb5f3155 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -26,19 +26,51 @@ #include <unordered_map> #include <unordered_set> -#include "GHOST_WaylandCursorSettings.h" -#include <pointer-constraints-client-protocol.h> -#include <relative-pointer-client-protocol.h> #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> + +static GHOST_WindowWayland *window_from_surface(struct wl_surface *surface); + +/** + * 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 -/* selected input event code defines from 'linux/input-event-codes.h' +#ifdef USE_GNOME_CONFINE_HACK +static bool use_gnome_confine_hack = false; +#endif + +/* -------------------------------------------------------------------- */ +/** \name Private Types & Defines + * \{ */ + +/** + * 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 * to differentiate from which mouse button an event comes from. @@ -46,103 +78,182 @@ #define BTN_LEFT 0x110 #define BTN_RIGHT 0x111 #define BTN_MIDDLE 0x112 +#define BTN_SIDE 0x113 +#define BTN_EXTRA 0x114 +#define BTN_FORWARD 0x115 +#define BTN_BACK 0x116 +// #define BTN_TASK 0x117 /* UNUSED. */ + +/** + * Tablet events, also from `linux/input-event-codes.h`. + */ +#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 struct buffer_t { - void *data; - size_t size; + void *data = nullptr; + size_t size = 0; }; struct cursor_t { - bool visible; - struct wl_surface *surface = nullptr; - struct wl_buffer *buffer; - struct wl_cursor_image image; + 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; struct buffer_t *file_buffer = nullptr; - struct wl_cursor_theme *theme = nullptr; - int size; + int size = 0; std::string theme_name; - // outputs on which the cursor is visible + /** Outputs on which the cursor is visible. */ std::unordered_set<const output_t *> outputs; - int scale = 1; + + int theme_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; + + 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; }; struct key_repeat_payload_t { - GHOST_SystemWayland *system; - GHOST_IWindow *window; - GHOST_TKey key; - GHOST_TEventKeyData key_data; + GHOST_SystemWayland *system = nullptr; + GHOST_IWindow *window = nullptr; + GHOST_TEventKeyData key_data = {GHOST_kKeyUnknown}; +}; + +/** Internal variables used to track grab-state. */ +struct input_grab_state_t { + bool use_lock = false; + bool use_confine = false; }; 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; + + uint32_t pointer_serial = 0; + uint32_t tablet_serial = 0; + + /** Use to check if the last cursor input was tablet or pointer. */ + uint32_t cursor_serial = 0; + + /** + * High precision mouse coordinates (pointer or tablet). + * + * 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->xy[0]), + * wl_fixed_to_int(scale * input->xy[1]), + * }; + * \endcode + */ + wl_fixed_t xy[2] = {0, 0}; + +#ifdef USE_GNOME_CONFINE_HACK + bool xy_software_confine = false; +#endif - uint32_t pointer_serial; - int x, y; - GHOST_Buttons buttons; + GHOST_Buttons buttons = GHOST_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; - struct xkb_state *xkb_state; + struct xkb_context *xkb_context = nullptr; + struct xkb_state *xkb_state = nullptr; 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_tablet = nullptr; 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; - struct data_source_t *data_source; + /** Copy & Paste. */ + struct data_offer_t *data_offer_copy_paste = nullptr; + std::mutex data_offer_copy_paste_mutex; + + 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; struct xdg_wm_base *xdg_shell = nullptr; struct zxdg_decoration_manager_v1 *xdg_decoration_manager = nullptr; + 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; + int size = 0; } 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; @@ -150,56 +261,105 @@ struct display_t { std::vector<struct wl_egl_window *> os_egl_windows; }; +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \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 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; } - 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_dnd_mutex}; + if (input->data_offer_dnd) { + wl_data_offer_destroy(input->data_offer_dnd->id); + delete input->data_offer_dnd; + } } + + { + 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->wl_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.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; } - wl_keyboard_destroy(input->keyboard); + wl_keyboard_destroy(input->wl_keyboard); } if (input->xkb_state) { xkb_state_unref(input->xkb_state); @@ -207,7 +367,7 @@ static void display_destroy(display_t *d) if (input->xkb_context) { xkb_context_unref(input->xkb_context); } - wl_seat_destroy(input->seat); + wl_seat_destroy(input->wl_seat); delete input; } @@ -355,9 +515,30 @@ static GHOST_TKey xkb_map_gkey(const xkb_keysym_t &sym) 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"}, @@ -423,14 +604,16 @@ static const std::vector<std::string> mime_send = { "text/plain", }; +/** \} */ + /* -------------------------------------------------------------------- */ -/** \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( +static void relative_pointer_handle_relative_motion( void *data, struct zwp_relative_pointer_v1 * /*zwp_relative_pointer_v1*/, uint32_t /*utime_hi*/, @@ -441,42 +624,71 @@ static void relative_pointer_relative_motion( wl_fixed_t /*dy_unaccel*/) { input_t *input = static_cast<input_t *>(data); + GHOST_WindowWayland *win = window_from_surface(input->focus_pointer); + if (!win) { + return; + } + const wl_fixed_t scale = win->scale(); + input->xy[0] += dx / scale; + input->xy[1] += dy / scale; - input->x += wl_fixed_to_int(dx); - input->y += wl_fixed_to_int(dy); - - GHOST_IWindow *win = static_cast<GHOST_WindowWayland *>( - wl_surface_get_user_data(input->focus_pointer)); +#ifdef USE_GNOME_CONFINE_HACK + if (input->xy_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(input->xy[0], input->xy[1]); + } +#endif input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), GHOST_kEventCursorMove, win, - input->x, - input->y, + wl_fixed_to_int(scale * input->xy[0]), + wl_fixed_to_int(scale * input->xy[1]), GHOST_TABLET_DATA_NONE)); } static const zwp_relative_pointer_v1_listener relative_pointer_listener = { - relative_pointer_relative_motion, + relative_pointer_handle_relative_motion, }; +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Data Source), #wl_data_source_listener + * \{ */ + static void dnd_events(const input_t *const input, const GHOST_TEventType event) { + /* NOTE: `input->data_offer_dnd_mutex` must already be locked. */ const uint64_t time = input->system->getMilliSeconds(); - GHOST_IWindow *const window = static_cast<GHOST_WindowWayland *>( - wl_surface_get_user_data(input->focus_pointer)); + GHOST_WindowWayland *const win = static_cast<GHOST_WindowWayland *>( + wl_surface_get_user_data(input->focus_dnd)); + if (!win) { + return; + } + 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]), + }; + 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)); + input->system->pushEvent(new GHOST_EventDragnDrop( + time, event, mime_dnd.at(type), win, event_xy[0], event_xy[1], 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) { @@ -485,6 +697,13 @@ static std::string read_pipe(data_offer_t *data_offer, const std::string mime_re 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]; @@ -492,7 +711,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; } @@ -501,28 +719,31 @@ 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 */ } -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*/, + 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}; + + 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) { wl_data_source_destroy(wl_data_source); } @@ -534,8 +755,8 @@ 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 */ } @@ -547,7 +768,8 @@ 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 */ } @@ -559,73 +781,87 @@ 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*/, + uint32_t /*dnd_action*/) { /* pass */ } 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) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Data Offer), #wl_data_offer_listener + * \{ */ + +static void data_offer_handle_offer(void *data, + struct wl_data_offer * /*wl_data_offer*/, + const char *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*/, + uint32_t 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*/, + uint32_t 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) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Data Device), #wl_data_device_listener + * \{ */ + +static void data_device_handle_data_offer(void * /*data*/, + struct wl_data_device * /*wl_data_device*/, + struct wl_data_offer *id) { 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*/, + uint32_t serial, + struct wl_surface *surface, + wl_fixed_t x, + wl_fixed_t y, + struct wl_data_offer *id) { 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 | @@ -636,14 +872,17 @@ 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}; 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); @@ -652,21 +891,26 @@ 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*/, + uint32_t /*time*/, + wl_fixed_t x, + 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}; + + 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}; + data_offer_t *data_offer = input->data_offer_dnd; const std::string mime_receive = *std::find_first_of(mime_preference_order.begin(), @@ -674,13 +918,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] = {data_offer->dnd.xy[0], data_offer->dnd.xy[1]}; - 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); @@ -694,6 +938,9 @@ 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 = window_from_surface(surface); + GHOST_ASSERT(win != nullptr, "Unable to find window for drop event from surface"); + std::vector<std::string> uris; size_t pos = 0; @@ -717,14 +964,14 @@ 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) { @@ -734,15 +981,20 @@ static void data_device_drop(void *data, struct wl_data_device * /*wl_data_devic 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. */ @@ -760,59 +1012,76 @@ static void data_device_selection(void *data, 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->setSelection(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) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Buffer), #wl_buffer_listener + * \{ */ + +static void cursor_buffer_handle_release(void *data, struct wl_buffer *wl_buffer) { cursor_t *cursor = static_cast<cursor_t *>(data); wl_buffer_destroy(wl_buffer); - if (wl_buffer == cursor->buffer) { + if (wl_buffer == cursor->wl_buffer) { /* the mapped buffer was from a custom cursor */ - cursor->buffer = nullptr; + cursor->wl_buffer = nullptr; } } const struct wl_buffer_listener cursor_buffer_listener = { - cursor_buffer_release, + cursor_buffer_handle_release, }; -static GHOST_IWindow *get_window(struct wl_surface *surface) -{ - if (!surface) { - return nullptr; - } +/** \} */ - for (GHOST_IWindow *win : window_manager->getWindows()) { - if (surface == static_cast<const GHOST_WindowWayland *>(win)->surface()) { - return win; +/* -------------------------------------------------------------------- */ +/** \name Listener (Surface), #wl_surface_listener + * \{ */ + +static GHOST_WindowWayland *window_from_surface(struct wl_surface *surface) +{ + if (surface) { + for (GHOST_IWindow *iwin : window_manager->getWindows()) { + GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(iwin); + if (surface == win->surface()) { + return win; + } } } return nullptr; @@ -822,40 +1091,43 @@ static bool update_cursor_scale(cursor_t &cursor, wl_shm *shm) { int scale = 0; for (const output_t *output : cursor.outputs) { - if (output->scale > scale) + 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 && cursor.theme_scale != scale) { + cursor.theme_scale = scale; + if (!cursor.is_custom) { + wl_surface_set_buffer_scale(cursor.wl_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) { + if (reg_output->wl_output == output) { input->cursor.outputs.insert(reg_output); } } update_cursor_scale(input->cursor, input->system->shm()); } -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) { + if (reg_output->wl_output == output) { input->cursor.outputs.erase(reg_output); } } @@ -863,19 +1135,24 @@ static void cursor_surface_leave(void *data, } struct wl_surface_listener cursor_surface_listener = { - cursor_surface_enter, - cursor_surface_leave, + 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)); +/** \} */ +/* -------------------------------------------------------------------- */ +/** \name Listener (Pointer), #wl_pointer_listener + * \{ */ + +static void pointer_handle_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 = window_from_surface(surface); if (!win) { return; } @@ -884,71 +1161,70 @@ static void pointer_enter(void *data, 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->cursor_serial = serial; + input->xy[0] = surface_x; + input->xy[1] = surface_y; input->focus_pointer = 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, + wl_fixed_to_int(scale * input->xy[0]), + wl_fixed_to_int(scale * input->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*/, + uint32_t /*serial*/, + struct wl_surface *surface) { - GHOST_IWindow *win = get_window(surface); + /* First clear the `focus_pointer`, since the window won't exist when closing the window. */ + static_cast<input_t *>(data)->focus_pointer = nullptr; + GHOST_IWindow *win = window_from_surface(surface); if (!win) { return; } - - 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*/, + uint32_t /*time*/, + wl_fixed_t surface_x, + 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)); - + GHOST_WindowWayland *win = window_from_surface(input->focus_pointer); if (!win) { return; } - input->x = win->scale() * wl_fixed_to_int(surface_x); - input->y = win->scale() * wl_fixed_to_int(surface_y); + input->xy[0] = surface_x; + input->xy[1] = surface_y; + const wl_fixed_t scale = win->scale(); input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), GHOST_kEventCursorMove, win, - input->x, - input->y, + wl_fixed_to_int(scale * input->xy[0]), + wl_fixed_to_int(scale * input->xy[1]), 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*/, + uint32_t serial, + uint32_t /*time*/, + uint32_t button, + uint32_t state) { input_t *input = static_cast<input_t *>(data); - - GHOST_IWindow *win = get_window(input->focus_pointer); - + GHOST_IWindow *win = window_from_surface(input->focus_pointer); if (!win) { return; } @@ -974,24 +1250,34 @@ static void pointer_button(void *data, case BTN_RIGHT: ebutton = GHOST_kButtonMaskRight; break; + case BTN_SIDE: + ebutton = GHOST_kButtonMaskButton4; + break; + case BTN_EXTRA: + ebutton = GHOST_kButtonMaskButton5; + break; + case BTN_FORWARD: + ebutton = GHOST_kButtonMaskButton6; + break; + case BTN_BACK: + ebutton = GHOST_kButtonMaskButton7; + break; } - input->data_source->source_serial = serial; + input->data_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)); } -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*/, + uint32_t /*time*/, + uint32_t axis, + wl_fixed_t value) { input_t *input = static_cast<input_t *>(data); - - GHOST_IWindow *win = get_window(input->focus_pointer); - + GHOST_IWindow *win = window_from_surface(input->focus_pointer); if (!win) { return; } @@ -1005,14 +1291,358 @@ static void pointer_axis(void *data, } static const struct wl_pointer_listener pointer_listener = { - pointer_enter, - pointer_leave, - pointer_motion, - pointer_button, - pointer_axis, + pointer_handle_enter, + pointer_handle_leave, + pointer_handle_motion, + pointer_handle_button, + pointer_handle_axis, +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Tablet Tool), #zwp_tablet_tool_v2_listener + * \{ */ + +static void tablet_tool_handle_type(void *data, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + uint32_t 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*/, + uint32_t /*hardware_serial_hi*/, + uint32_t /*hardware_serial_lo*/) +{ +} + +static void tablet_tool_handle_hardware_id_wacom( + void * /*data*/, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + uint32_t /*hardware_id_hi*/, + uint32_t /*hardware_id_lo*/) +{ +} + +static void tablet_tool_handle_capability(void * /*data*/, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + uint32_t /*capability*/) +{ +} + +static void tablet_tool_handle_done(void * /*data*/, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/) +{ +} +static void tablet_tool_handle_removed(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) +{ + 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*/, + uint32_t serial, + struct zwp_tablet_v2 * /*tablet*/, + struct wl_surface *surface) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + + input->focus_tablet = surface; + input->tablet_serial = serial; + input->cursor_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 = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + win->activate(); + + win->setCursorShape(win->getCursorShape()); +} +static void tablet_tool_handle_proximity_out(void *data, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + input->focus_tablet = nullptr; + + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + win->setCursorShape(win->getCursorShape()); +} + +static void tablet_tool_handle_down(void *data, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + uint32_t serial) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + + const GHOST_TEventType etype = GHOST_kEventButtonDown; + const GHOST_TButtonMask ebutton = GHOST_kButtonMaskLeft; + input->data_source_serial = serial; + input->buttons.set(ebutton, true); + 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*/) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + + const GHOST_TEventType etype = GHOST_kEventButtonUp; + const GHOST_TButtonMask ebutton = GHOST_kButtonMaskLeft; + input->buttons.set(ebutton, false); + 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*/, + wl_fixed_t x, + wl_fixed_t y) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + + input->xy[0] = x; + input->xy[1] = y; + + 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->xy[0]), + wl_fixed_to_int(scale * input->xy[1]), + tool_input->data)); +} + +static void tablet_tool_handle_pressure(void *data, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + uint32_t pressure) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + + GHOST_TabletData &td = tool_input->data; + td.Pressure = (float)pressure / 65535; +} +static void tablet_tool_handle_distance(void * /*data*/, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + uint32_t /*distance*/) +{ +} +static void tablet_tool_handle_tilt(void *data, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + wl_fixed_t tilt_x, + wl_fixed_t tilt_y) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + + GHOST_TabletData &td = tool_input->data; + /* Map degrees to `-1.0..1.0`. */ + td.Xtilt = wl_fixed_to_double(tilt_x) / 90.0f; + td.Ytilt = wl_fixed_to_double(tilt_y) / 90.0f; + td.Xtilt = td.Xtilt < -1.0f ? -1.0f : (td.Xtilt > 1.0f ? 1.0f : td.Xtilt); + td.Ytilt = td.Ytilt < -1.0f ? -1.0f : (td.Ytilt > 1.0f ? 1.0f : td.Ytilt); +} + +static void tablet_tool_handle_rotation(void * /*data*/, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + wl_fixed_t /*degrees*/) +{ + /* Pass. */ +} + +static void tablet_tool_handle_slider(void * /*data*/, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + int32_t /*position*/) +{ + /* Pass. */ +} +static void tablet_tool_handle_wheel(void *data, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + wl_fixed_t /*degrees*/, + int32_t clicks) +{ + if (clicks == 0) { + return; + } + + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + + 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*/, + uint32_t serial, + uint32_t button, + uint32_t state) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + + 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_TButtonMask 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->buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED); + 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*/, + uint32_t /*time*/) +{ +} + +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( +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Table Seat), #zwp_tablet_seat_v2_listener + * \{ */ + +static void tablet_seat_handle_tablet_added(void * /*data*/, + struct zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/, + struct zwp_tablet_v2 * /*id*/) +{ + /* Pass. */ +} + +static void tablet_seat_handle_tool_added(void *data, + struct zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/, + struct zwp_tablet_tool_v2 *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()); + 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*/) +{ + /* Pass. */ +} + +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, +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Keyboard), #wl_keyboard_listener + * \{ */ + +static void keyboard_handle_keymap( void *data, struct wl_keyboard * /*wl_keyboard*/, uint32_t format, int32_t fd, uint32_t size) { input_t *input = static_cast<input_t *>(data); @@ -1037,8 +1667,13 @@ static void keyboard_keymap( return; } - input->xkb_state = xkb_state_new(keymap); - + struct xkb_state *xkb_state_next = xkb_state_new(keymap); + if (xkb_state_next) { + if (input->xkb_state) { + xkb_state_unref(input->xkb_state); + } + input->xkb_state = xkb_state_next; + } xkb_keymap_unref(keymap); } @@ -1048,11 +1683,11 @@ 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*/) +static void keyboard_handle_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; @@ -1065,10 +1700,10 @@ 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*/, + uint32_t /*serial*/, + struct wl_surface *surface) { if (surface != nullptr) { static_cast<input_t *>(data)->focus_keyboard = nullptr; @@ -1101,12 +1736,12 @@ static xkb_keysym_t xkb_state_key_get_one_sym_without_modifiers(struct xkb_state 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(void *data, + struct wl_keyboard * /*wl_keyboard*/, + uint32_t serial, + uint32_t /*time*/, + uint32_t key, + uint32_t state) { input_t *input = static_cast<input_t *>(data); @@ -1125,7 +1760,6 @@ static void keyboard_key(void *data, if (sym == XKB_KEY_NoSymbol) { return; } - const GHOST_TKey gkey = xkb_map_gkey(sym); /* Delete previous timer. */ if (xkb_keymap_key_repeats(xkb_state_get_keymap(input->xkb_state), key + 8) && @@ -1135,7 +1769,9 @@ static void keyboard_key(void *data, input->key_repeat.timer = nullptr; } - GHOST_TEventKeyData key_data; + GHOST_TEventKeyData key_data = { + .key = xkb_map_gkey(sym), + }; if (etype == GHOST_kEventKeyDown) { xkb_state_key_get_utf8( @@ -1145,12 +1781,12 @@ static void keyboard_key(void *data, key_data.utf8_buf[0] = '\0'; } - input->data_source->source_serial = serial; + input->data_source_serial = serial; 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->system->getMilliSeconds(), etype, win, key_data.key, '\0', key_data.utf8_buf, false)); /* Start timer for repeating key, if applicable. */ if (input->key_repeat.rate > 0 && @@ -1160,33 +1796,32 @@ static void keyboard_key(void *data, key_repeat_payload_t *payload = new key_repeat_payload_t({ .system = input->system, .window = win, - .key = gkey, .key_data = key_data, }); - auto cb = [](GHOST_ITimerTask *task, uint64_t /*time*/) { + 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, + payload->key_data.key, '\0', payload->key_data.utf8_buf, true)); }; 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, 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*/, + uint32_t /*serial*/, + uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, + uint32_t group) { xkb_state_update_mask(static_cast<input_t *>(data)->xkb_state, mods_depressed, @@ -1197,10 +1832,10 @@ static void keyboard_modifiers(void *data, group); } -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*/, + int32_t rate, + int32_t delay) { input_t *input = static_cast<input_t *>(data); @@ -1209,79 +1844,179 @@ static void keyboard_repeat_info(void *data, } 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) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Seat), #wl_seat_listener + * \{ */ + +static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) { 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.wl_buffer = nullptr; input->cursor.file_buffer = new buffer_t; 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); } 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) { 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, +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (XDG Output), #zxdg_output_v1_listener + * \{ */ + +static void xdg_output_handle_logical_position(void *data, + struct zxdg_output_v1 * /*xdg_output*/, + int32_t x, + int32_t 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*/, + int32_t width, + int32_t 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*/) +{ + /* 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. */ +} + +static void xdg_output_handle_name(void * /*data*/, + struct zxdg_output_v1 * /*xdg_output*/, + const char * /*name*/) +{ + /* Pass. */ +} + +static void xdg_output_handle_description(void * /*data*/, + struct zxdg_output_v1 * /*xdg_output*/, + const char * /*description*/) +{ + /* Pass. */ +} + +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, }; -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) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Output), #wl_output_listener + * \{ */ + +static void output_handle_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) { 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*/, + uint32_t flags, + int32_t width, + int32_t height, + int32_t /*refresh*/) { output_t *output = static_cast<output_t *>(data); - output->width_pxl = width; - output->height_pxl = height; + + if (flags & WL_OUTPUT_MODE_CURRENT) { + 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; + } + } } /** @@ -1292,36 +2027,71 @@ 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*/) { + 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*/, int32_t factor) { static_cast<output_t *>(data)->scale = factor; } 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) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (XDG WM Base), #xdg_wm_base_listener + * \{ */ + +static void shell_handle_ping(void * /*data*/, struct xdg_wm_base *xdg_wm_base, uint32_t serial) { xdg_wm_base_pong(xdg_wm_base, serial); } static const struct xdg_wm_base_listener shell_listener = { - shell_ping, + shell_handle_ping, }; -static void global_add(void *data, - struct wl_registry *wl_registry, - uint32_t name, - const char *interface, - uint32_t /*version*/) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Registry), #wl_registry_listener + * \{ */ + +static void global_handle_add(void *data, + struct wl_registry *wl_registry, + uint32_t name, + const char *interface, + uint32_t /*version*/) { struct display_t *display = static_cast<struct display_t *>(data); if (!strcmp(interface, wl_compositor_interface.name)) { @@ -1337,30 +2107,37 @@ 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)); } + 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, 3)); + 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)); 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 *>( @@ -1368,7 +2145,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 *>( @@ -1389,13 +2170,15 @@ 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*/, + uint32_t /*name*/) { } static const struct wl_registry_listener registry_listener = { - global_add, - global_remove, + global_handle_add, + global_handle_remove, }; /** \} */ @@ -1408,6 +2191,8 @@ static const struct wl_registry_listener registry_listener = { 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); @@ -1434,10 +2219,18 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), d(new display_t) 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() @@ -1466,42 +2259,47 @@ 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 (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_kModifierKeyNumMasks, 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 (d->inputs.empty()) { + return GHOST_kFailure; } - return GHOST_kFailure; + + buttons = d->inputs[0]->buttons; + return GHOST_kSuccess; } char *GHOST_SystemWayland::getClipboard(bool /*selection*/) const @@ -1517,24 +2315,29 @@ void GHOST_SystemWayland::putClipboard(const char *buffer, bool /*selection*/) c 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); } } @@ -1545,14 +2348,31 @@ uint8_t GHOST_SystemWayland::getNumDisplays() const 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 (d->inputs.empty()) { + return GHOST_kFailure; } - else { + + input_t *input = d->inputs[0]; + struct wl_surface *surface = nullptr; + if (input->pointer_serial == input->cursor_serial) { + surface = input->focus_pointer; + } + else if (input->tablet_serial == input->cursor_serial) { + surface = input->focus_tablet; + } + if (!surface) { return GHOST_kFailure; } + + GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(wl_surface_get_user_data(surface)); + if (!win) { + return GHOST_kFailure; + } + + const wl_fixed_t scale = win->scale(); + x = wl_fixed_to_int(scale * input->xy[0]); + y = wl_fixed_to_int(scale * input->xy[1]); + return GHOST_kSuccess; } GHOST_TSuccess GHOST_SystemWayland::setCursorPosition(int32_t /*x*/, int32_t /*y*/) @@ -1562,16 +2382,33 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorPosition(int32_t /*x*/, int32_t /*y 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}; + + 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]); + } + + width = xy_max[0] - xy_min[0]; + height = xy_max[1] - xy_min[1]; } GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings /*glSettings*/) @@ -1597,10 +2434,10 @@ 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, @@ -1617,9 +2454,7 @@ GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings /*g if (context->initializeDrawingContext()) { return context; } - else { - delete context; - } + delete context; GHOST_PRINT("Cannot create off-screen EGL context" << std::endl); @@ -1688,12 +2523,12 @@ wl_compositor *GHOST_SystemWayland::compositor() return d->compositor; } -xdg_wm_base *GHOST_SystemWayland::shell() +xdg_wm_base *GHOST_SystemWayland::xdg_shell() { return d->xdg_shell; } -zxdg_decoration_manager_v1 *GHOST_SystemWayland::decoration_manager() +zxdg_decoration_manager_v1 *GHOST_SystemWayland::xdg_decoration_manager() { return d->xdg_decoration_manager; } @@ -1713,22 +2548,152 @@ void GHOST_SystemWayland::setSelection(const std::string &selection) this->selection = selection; } -static void set_cursor_buffer(input_t *input, wl_buffer *buffer) +/** + * 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) { - cursor_t *c = &input->cursor; + const cursor_t *c = &input->cursor; + const int scale = c->is_custom ? c->custom_scale : c->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; + wl_pointer_set_cursor( + input->wl_pointer, input->pointer_serial, c->wl_surface, hotspot_x, hotspot_y); + 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); + } +} - c->visible = (buffer != nullptr); +/** + * 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) +{ + 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_surface_attach(c->surface, buffer, 0, 0); +static void cursor_buffer_set(const input_t *input, wl_buffer *buffer) +{ + const cursor_t *c = &input->cursor; + const int scale = c->is_custom ? c->custom_scale : c->theme_scale; - wl_surface_damage(c->surface, 0, 0, int32_t(c->image.width), int32_t(c->image.height)); - wl_pointer_set_cursor(input->pointer, + const bool visible = (c->visible && c->is_hardware); + + const int32_t image_size_x = int32_t(c->wl_image.width); + const int32_t image_size_y = int32_t(c->wl_image.height); + + /* This is a requirement of WAYLAND, when this isn't the case, + * it causes Blender's window to close intermittently. */ + GHOST_ASSERT((image_size_x % scale) == 0 && (image_size_y % scale) == 0, + "The size must be a multiple of the 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; + + wl_surface_set_buffer_scale(c->wl_surface, scale); + wl_surface_attach(c->wl_surface, buffer, 0, 0); + wl_surface_damage(c->wl_surface, 0, 0, image_size_x, image_size_y); + wl_surface_commit(c->wl_surface); + + wl_pointer_set_cursor(input->wl_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); + visible ? c->wl_surface : nullptr, + hotspot_x, + hotspot_y); + + /* Set the cursor for all tablet tools as well. */ + 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)); + + /* FIXME: for some reason cursor scale is applied twice (when the scale isn't 1x), + * this happens both in gnome-shell & KDE. Setting the surface scale here doesn't help. */ + wl_surface_set_buffer_scale(tool_input->cursor_surface, scale); + wl_surface_attach(tool_input->cursor_surface, buffer, 0, 0); + wl_surface_damage(tool_input->cursor_surface, 0, 0, image_size_x, image_size_y); + wl_surface_commit(tool_input->cursor_surface); + + zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2, + input->tablet_serial, + visible ? tool_input->cursor_surface : nullptr, + hotspot_x, + hotspot_y); + } +} + +enum eCursorSetMode { + CURSOR_VISIBLE_ALWAYS_SET = 1, + CURSOR_VISIBLE_ONLY_HIDE, + CURSOR_VISIBLE_ONLY_SHOW, +}; + +static void cursor_visible_set(input_t *input, + const bool visible, + const bool is_hardware, + const enum eCursorSetMode set_mode) +{ + cursor_t *cursor = &input->cursor; + const bool was_visible = cursor->is_hardware && cursor->visible; + const bool use_visible = is_hardware && visible; - wl_surface_commit(c->surface); + 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; + } + } + + 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; +} + +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) @@ -1736,18 +2701,21 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(GHOST_TStandardCursor shape) if (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); @@ -1760,17 +2728,27 @@ 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) { - 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, @@ -1788,7 +2766,7 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, cursor_t *cursor = &d->inputs[0]->cursor; static const int32_t stride = sizex * 4; /* ARGB */ - cursor->file_buffer->size = size_t(stride * sizey); + 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); @@ -1823,6 +2801,7 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, nullptr, cursor->file_buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (cursor->file_buffer->data == MAP_FAILED) { + cursor->file_buffer->data = nullptr; close(fd); return GHOST_kFailure; } @@ -1867,13 +2846,37 @@ 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::getCursorBitmap(GHOST_CursorBitmapRef *bitmap) +{ + cursor_t *cursor = &d->inputs[0]->cursor; + if (cursor->file_buffer->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->file_buffer->data); return GHOST_kSuccess; } @@ -1885,23 +2888,79 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorVisibility(bool visible) } 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 (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->xy_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; + } + GHOST_WindowWayland *win = window_from_surface(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 = (mode == GHOST_kGrabWrap || mode == GHOST_kGrabHide) || use_software_confine, + .use_confine = (mode == GHOST_kGrabNormal) && (use_software_confine == false), + }; + return grab_state; } GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mode, + const GHOST_TGrabCursorMode mode_current, wl_surface *surface) { /* ignore, if the required protocols are not supported */ @@ -1912,41 +2971,147 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mo if (d->inputs.empty()) { return GHOST_kFailure; } + /* No change, success. */ + if (mode == mode_current) { + return GHOST_kSuccess; + } input_t *input = d->inputs[0]; - switch (mode) { - case GHOST_kGrabDisable: - if (input->relative_pointer) { - zwp_relative_pointer_v1_destroy(input->relative_pointer); - input->relative_pointer = nullptr; +#ifdef USE_GNOME_CONFINE_HACK + const bool was_software_confine = input->xy_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 = !(((mode == GHOST_kGrabHide) || (mode == 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) { + /* The chance this fails is _very_ low. */ + GHOST_WindowWayland *win = window_from_surface(surface); + if (!win) { + GHOST_PRINT("could not find window from surface when un-grabbing!" << std::endl); + } + else { + GHOST_Rect bounds; + int32_t xy_new[2] = {input->xy[0], input->xy[1]}; + + /* Fallback to window bounds. */ + if (win->getCursorGrabBounds(bounds) == GHOST_kFailure) { + win->getClientBounds(bounds); + } + + const int scale = win->scale(); + + 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.wrapPoint(xy_new[0], xy_new[1], 0, win->getCursorGrabAxis()); + + /* Push an event so the new location is registered. */ + if ((xy_new[0] != input->xy[0]) || (xy_new[1] != input->xy[1])) { + input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), + GHOST_kEventCursorMove, + win, + wl_fixed_to_int(scale * xy_new[0]), + wl_fixed_to_int(scale * xy_new[1]), + GHOST_TABLET_DATA_NONE)); + } + input->xy[0] = xy_new[0]; + input->xy[1] = xy_new[1]; + + zwp_locked_pointer_v1_set_cursor_position_hint( + input->locked_pointer, xy_new[0], xy_new[1]); + wl_surface_commit(surface); + } } - if (input->locked_pointer) { - zwp_locked_pointer_v1_destroy(input->locked_pointer); - input->locked_pointer = nullptr; +#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, input->xy[0], input->xy[1]); + wl_surface_commit(surface); + } } - break; +#endif - case GHOST_kGrabNormal: - break; - case GHOST_kGrabWrap: - 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); - break; + zwp_locked_pointer_v1_destroy(input->locked_pointer); + input->locked_pointer = nullptr; + } + } - case GHOST_kGrabHide: - setCursorVisibility(false); - break; + if (!grab_state_next.use_confine) { + if (input->confined_pointer) { + zwp_confined_pointer_v1_destroy(input->confined_pointer); + input->confined_pointer = nullptr; + } } + 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); + } + } + 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->xy_software_confine = use_software_confine; +#endif + return GHOST_kSuccess; } |