diff options
Diffstat (limited to 'intern/ghost/intern/GHOST_SystemWayland.cpp')
-rw-r--r-- | intern/ghost/intern/GHOST_SystemWayland.cpp | 2076 |
1 files changed, 1512 insertions, 564 deletions
diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 442e51d4f7c..3a0ba5cd21a 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -89,10 +89,19 @@ static void gwl_seat_capability_pointer_disable(GWL_Seat *seat); static void gwl_seat_capability_keyboard_disable(GWL_Seat *seat); static void gwl_seat_capability_touch_disable(GWL_Seat *seat); +static bool gwl_registry_entry_remove_by_name(GWL_Display *display, + uint32_t name, + int *r_interface_slot); +static void gwl_registry_entry_remove_all(GWL_Display *display); + +struct GWL_RegistryHandler; +static int gwl_registry_handler_interface_slot_max(); +static int gwl_registry_handler_interface_slot_from_string(const char *interface); +static const struct GWL_RegistryHandler *gwl_registry_handler_from_interface_slot( + int interface_slot); + /* -------------------------------------------------------------------- */ -/** \name Local Defines - * - * Control local functionality, compositors specific workarounds. +/** \name Workaround Compositor Specific Bugs * \{ */ /** @@ -130,6 +139,13 @@ static bool use_gnome_confine_hack = false; #define USE_GNOME_KEYBOARD_SUPPRESS_WARNING /** + * KDE (plasma 5.26.1) has a bug where the cursor surface needs to be committed + * (via `wl_surface_commit`) when it was hidden and is being set to visible again, see: T102048. + * See: https://bugs.kde.org/show_bug.cgi?id=461001 + */ +#define USE_KDE_TABLET_HIDDEN_CURSOR_HACK + +/** * When GNOME is found, require `libdecor`. * This is a hack because it seems there is no way to check if the compositor supports * server side decorations when initializing WAYLAND. @@ -138,6 +154,20 @@ static bool use_gnome_confine_hack = false; # define USE_GNOME_NEEDS_LIBDECOR_HACK #endif +/* -------------------------------------------------------------------- */ +/** \name Local Defines + * + * Control local functionality, compositors specific workarounds. + * \{ */ + +/** + * Fix short-cut part of keyboard reading code not properly handling some keys, see: T102194. + * \note This is similar to X11 workaround by the same name, see: T47228. + */ +#define USE_NON_LATIN_KB_WORKAROUND + +#define WL_NAME_UNSET uint32_t(-1) + /** \} */ /* -------------------------------------------------------------------- */ @@ -162,9 +192,14 @@ static bool use_gnome_confine_hack = false; /** * Tablet events. + * + * \note Gnome/GTK swap middle/right, where the same application in X11 will swap the middle/right + * mouse button when running under WAYLAND. KDE doesn't do this, and according to artists + * at the Blender studio, having the button closest to the nib be MMB is preferable, + * so use this as a default. If needs be - swapping these could be a preference. */ -#define BTN_STYLUS 0x14b /* Use as right-mouse. */ -#define BTN_STYLUS2 0x14c /* Use as middle-mouse. */ +#define BTN_STYLUS 0x14b /* Use as middle-mouse. */ +#define BTN_STYLUS2 0x14c /* Use as right-mouse. */ /* NOTE(@campbellbarton): Map to an additional button (not sure which hardware uses this). */ #define BTN_STYLUS3 0x149 @@ -173,6 +208,19 @@ static bool use_gnome_confine_hack = false; */ #define KEY_GRAVE 41 +#ifdef USE_NON_LATIN_KB_WORKAROUND +# define KEY_1 2 +# define KEY_2 3 +# define KEY_3 4 +# define KEY_4 5 +# define KEY_5 6 +# define KEY_6 7 +# define KEY_7 8 +# define KEY_8 9 +# define KEY_9 10 +# define KEY_0 11 +#endif + /** \} */ /* -------------------------------------------------------------------- */ @@ -255,15 +303,6 @@ static void gwl_simple_buffer_free_data(GWL_SimpleBuffer *buffer) buffer->data_size = 0; } -static void gwl_simple_buffer_set_and_take_ownership(GWL_SimpleBuffer *buffer, - const char *data, - size_t data_size) -{ - free(const_cast<char *>(buffer->data)); - buffer->data = data; - buffer->data_size = data_size; -} - static void gwl_simple_buffer_set_from_string(GWL_SimpleBuffer *buffer, const char *str) { free(const_cast<char *>(buffer->data)); @@ -273,14 +312,6 @@ static void gwl_simple_buffer_set_from_string(GWL_SimpleBuffer *buffer, const ch buffer->data = data; } -static char *gwl_simple_buffer_as_string(const GWL_SimpleBuffer *buffer) -{ - char *buffer_str = static_cast<char *>(malloc(buffer->data_size + 1)); - memcpy(buffer_str, buffer->data, buffer->data_size); - buffer_str[buffer->data_size] = '\0'; - return buffer_str; -} - /** \} */ /* -------------------------------------------------------------------- */ @@ -304,7 +335,7 @@ struct GWL_Cursor { bool is_hardware = true; /** When true, a custom image is used to display the cursor (stored in `wl_image`). */ bool is_custom = false; - struct wl_surface *wl_surface = nullptr; + struct wl_surface *wl_surface_cursor = nullptr; struct wl_buffer *wl_buffer = nullptr; struct wl_cursor_image wl_image = {0}; struct wl_cursor_theme *wl_theme = nullptr; @@ -352,10 +383,14 @@ struct GWL_TabletTool { struct GWL_DataOffer { struct wl_data_offer *id = nullptr; std::unordered_set<std::string> types; - std::atomic<bool> in_use = false; struct { /** + * Prevents freeing after #wl_data_device_listener.leave, + * before #wl_data_device_listener.drop. + */ + bool in_use = false; + /** * Bit-mask with available drop options. * #WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY, #WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE.. etc. * The application that initializes the drag may set these depending on modifiers held @@ -444,7 +479,7 @@ struct GWL_SeatStatePointer { * The wl_surface last used with this pointing device * (events with this pointing device will be sent here). */ - struct wl_surface *wl_surface = nullptr; + struct wl_surface *wl_surface_window = nullptr; GHOST_Buttons buttons = GHOST_Buttons(); }; @@ -506,7 +541,7 @@ struct GWL_SeatStateKeyboard { * The wl_surface last used with this pointing device * (events with this pointing device will be sent here). */ - struct wl_surface *wl_surface = nullptr; + struct wl_surface *wl_surface_window = nullptr; }; /** @@ -527,6 +562,7 @@ static void gwl_libdecor_system_destroy(GWL_LibDecor_System *decor) { if (decor->context) { libdecor_unref(decor->context); + decor->context = nullptr; } delete decor; } @@ -534,23 +570,27 @@ static void gwl_libdecor_system_destroy(GWL_LibDecor_System *decor) struct GWL_XDG_Decor_System { struct xdg_wm_base *shell = nullptr; + uint32_t shell_name = WL_NAME_UNSET; + struct zxdg_decoration_manager_v1 *manager = nullptr; + uint32_t manager_name = WL_NAME_UNSET; }; -static void gwl_xdg_decor_system_destroy(GWL_XDG_Decor_System *decor) +static void gwl_xdg_decor_system_destroy(struct GWL_Display *display, GWL_XDG_Decor_System *decor) { if (decor->manager) { - zxdg_decoration_manager_v1_destroy(decor->manager); + gwl_registry_entry_remove_by_name(display, decor->manager_name, nullptr); + GHOST_ASSERT(decor->manager == nullptr, "Internal registry error"); } if (decor->shell) { - xdg_wm_base_destroy(decor->shell); + gwl_registry_entry_remove_by_name(display, decor->shell_name, nullptr); + GHOST_ASSERT(decor->shell == nullptr, "Internal registry error"); } delete decor; } struct GWL_PrimarySelection_DataOffer { struct zwp_primary_selection_offer_v1 *id = nullptr; - std::atomic<bool> in_use = false; std::unordered_set<std::string> types; }; @@ -604,9 +644,15 @@ struct GWL_Seat { struct wl_keyboard *wl_keyboard = nullptr; struct zwp_tablet_seat_v2 *wp_tablet_seat = nullptr; +#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE struct zwp_pointer_gesture_hold_v1 *wp_pointer_gesture_hold = nullptr; +#endif +#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE struct zwp_pointer_gesture_pinch_v1 *wp_pointer_gesture_pinch = nullptr; +#endif +#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE struct zwp_pointer_gesture_swipe_v1 *wp_pointer_gesture_swipe = nullptr; +#endif /** All currently active tablet tools (needed for changing the cursor). */ std::unordered_set<zwp_tablet_tool_v2 *> tablet_tools; @@ -642,12 +688,22 @@ struct GWL_Seat { * Keep a state with no modifiers active, use for symbol lookups. */ struct xkb_state *xkb_state_empty = nullptr; + + /** + * Keep a state with shift enabled, use to access predictable number access for AZERTY keymaps. + * If shift is not supported by the key-map, this is set to NULL. + */ + struct xkb_state *xkb_state_empty_with_shift = nullptr; /** * Keep a state with number-lock enabled, use to access predictable key-pad symbols. * If number-lock is not supported by the key-map, this is set to NULL. */ struct xkb_state *xkb_state_empty_with_numlock = nullptr; +#ifdef USE_NON_LATIN_KB_WORKAROUND + bool xkb_use_non_latin_workaround = false; +#endif + /** Keys held matching `xkb_state`. */ struct GWL_KeyboardDepressedState key_depressed; @@ -674,7 +730,7 @@ struct GWL_Seat { GHOST_ITimerTask *timer = nullptr; } key_repeat; - struct wl_surface *wl_surface_focus_dnd = nullptr; + struct wl_surface *wl_surface_window_focus_dnd = nullptr; struct wl_data_device *wl_data_device = nullptr; /** Drag & Drop. */ @@ -695,15 +751,51 @@ struct GWL_Seat { uint32_t data_source_serial = 0; }; +static GWL_SeatStatePointer *gwl_seat_state_pointer_active(GWL_Seat *seat) +{ + if (seat->pointer.serial == seat->cursor_source_serial) { + return &seat->pointer; + } + if (seat->tablet.serial == seat->cursor_source_serial) { + return &seat->tablet; + } + return nullptr; +} + +static GWL_SeatStatePointer *gwl_seat_state_pointer_from_cursor_surface( + GWL_Seat *seat, const wl_surface *wl_surface) +{ + if (ghost_wl_surface_own_cursor_pointer(wl_surface)) { + return &seat->pointer; + } + if (ghost_wl_surface_own_cursor_tablet(wl_surface)) { + return &seat->tablet; + } + GHOST_ASSERT(0, "Surface found without pointer/tablet tag"); + return nullptr; +} + /** \} */ /* -------------------------------------------------------------------- */ /** \name Internal #GWL_Display Type (#wl_display & #wl_compositor wrapper) * \{ */ +struct GWL_RegistryEntry; + struct GWL_Display { GHOST_SystemWayland *system = nullptr; + /** + * True when initializing registration, while updating all other entries wont cause problems, + * it will preform many redundant update calls. + */ + bool registry_skip_update_all = false; + + /** Registry entries, kept to allow updating & removal at run-time. */ + struct GWL_RegistryEntry *registry_entry = nullptr; + + struct wl_registry *wl_registry = nullptr; struct wl_display *wl_display = nullptr; struct wl_compositor *wl_compositor = nullptr; @@ -717,210 +809,369 @@ struct GWL_Display { struct wl_shm *wl_shm = nullptr; std::vector<GWL_Output *> outputs; std::vector<GWL_Seat *> seats; + /** + * Support a single active seat at once, this isn't an exact or correct mapping from WAYLAND. + * Only allow input from different seats, not full concurrent multi-seat support. + * + * The main purpose of having an active seat is an alternative from always using the first + * seat which prevents events from any other seat. + * + * NOTE(@campbellbarton): This could be extended and developed further extended to support + * an active seat per window (for e.g.), basic support is sufficient for now as currently isn't + * a widely used feature. + */ + int seats_active_index = 0; + /* Managers. */ struct wl_data_device_manager *wl_data_device_manager = nullptr; struct zwp_tablet_manager_v2 *wp_tablet_manager = nullptr; struct zwp_relative_pointer_manager_v1 *wp_relative_pointer_manager = nullptr; - struct zwp_pointer_constraints_v1 *wp_pointer_constraints = nullptr; - struct zwp_pointer_gestures_v1 *wp_pointer_gestures = nullptr; - struct zwp_primary_selection_device_manager_v1 *wp_primary_selection_device_manager = nullptr; - GWL_SimpleBuffer clipboard; - GWL_SimpleBuffer clipboard_primary; + struct zwp_pointer_constraints_v1 *wp_pointer_constraints = nullptr; + struct zwp_pointer_gestures_v1 *wp_pointer_gestures = nullptr; }; -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Private Utility Functions - * \{ */ - -static GHOST_WindowManager *window_manager = nullptr; - -/** Check this lock before accessing #GHOST_SystemWayland::clipboard_ from a thread. */ -static std::mutex system_clipboard_mutex; - /** - * Callback for WAYLAND to run when there is an error. + * Free the #GWL_Display and it's related members. * - * \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. + * \note This may run on a partially initialized struct, + * so it can't be assumed all members are set. */ -static void ghost_wayland_log_handler(const char *msg, va_list arg) +static void gwl_display_destroy(GWL_Display *display) { - fprintf(stderr, "GHOST/Wayland: "); - vfprintf(stderr, msg, arg); /* Includes newline. */ + /* For typical WAYLAND use this will always be set. + * However when WAYLAND isn't running, this will early-exit and be null. */ + if (display->wl_registry) { + wl_registry_destroy(display->wl_registry); + display->wl_registry = nullptr; + } - GHOST_TBacktraceFn backtrace_fn = GHOST_ISystem::getBacktraceFn(); - if (backtrace_fn) { - backtrace_fn(stderr); /* Includes newline. */ + /* Unregister items in reverse order. */ + gwl_registry_entry_remove_all(display); + +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + if (use_libdecor) { + if (display->libdecor) { + gwl_libdecor_system_destroy(display->libdecor); + display->libdecor = nullptr; + } + } + else +#endif + { + if (display->xdg_decor) { + gwl_xdg_decor_system_destroy(display, display->xdg_decor); + display->xdg_decor = nullptr; + } } -} -static GWL_SeatStatePointer *seat_state_pointer_active(GWL_Seat *seat) -{ - if (seat->pointer.serial == seat->cursor_source_serial) { - return &seat->pointer; + if (eglGetDisplay) { + ::eglTerminate(eglGetDisplay(EGLNativeDisplayType(display->wl_display))); } - if (seat->tablet.serial == seat->cursor_source_serial) { - return &seat->tablet; + + if (display->wl_display) { + wl_display_disconnect(display->wl_display); } - return nullptr; + + delete display; } -static GWL_SeatStatePointer *seat_state_pointer_from_cursor_surface(GWL_Seat *seat, - const wl_surface *wl_surface) +static int gwl_display_seat_index(GWL_Display *display, const GWL_Seat *seat) { - if (ghost_wl_surface_own_cursor_pointer(wl_surface)) { - return &seat->pointer; - } - if (ghost_wl_surface_own_cursor_tablet(wl_surface)) { - return &seat->tablet; - } - GHOST_ASSERT(0, "Surface found without pointer/tablet tag"); - return nullptr; + std::vector<GWL_Seat *>::iterator iter = std::find( + display->seats.begin(), display->seats.end(), seat); + const int index = (iter != display->seats.cend()) ? std::distance(display->seats.begin(), iter) : + -1; + GHOST_ASSERT(index != -1, "invalid internal state"); + return index; } -static void display_destroy(GWL_Display *display) +static GWL_Seat *gwl_display_seat_active_get(const GWL_Display *display) { - if (display->wl_data_device_manager) { - wl_data_device_manager_destroy(display->wl_data_device_manager); + if (UNLIKELY(display->seats.empty())) { + return nullptr; } + return display->seats[display->seats_active_index]; +} - if (display->wp_tablet_manager) { - zwp_tablet_manager_v2_destroy(display->wp_tablet_manager); +static bool gwl_display_seat_active_set(GWL_Display *display, const GWL_Seat *seat) +{ + if (UNLIKELY(display->seats.empty())) { + return false; } - - for (GWL_Output *output : display->outputs) { - wl_output_destroy(output->wl_output); - delete output; + const int index = gwl_display_seat_index(display, seat); + if (index == display->seats_active_index) { + return false; } + display->seats_active_index = index; + return true; +} - for (GWL_Seat *seat : display->seats) { +/** \} */ - /* First handle members that require locking. - * While highly unlikely, it's possible they are being used while this function runs. */ - { - std::lock_guard lock{seat->data_source_mutex}; - if (seat->data_source) { - gwl_simple_buffer_free_data(&seat->data_source->buffer_out); - if (seat->data_source->wl_source) { - wl_data_source_destroy(seat->data_source->wl_source); - } - delete seat->data_source; - } - } +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_RegistryHandler + * \{ */ - { - std::lock_guard lock{seat->data_offer_dnd_mutex}; - if (seat->data_offer_dnd) { - wl_data_offer_destroy(seat->data_offer_dnd->id); - delete seat->data_offer_dnd; - } - } +struct GWL_RegisteryAdd_Params { + uint32_t name = 0; + /** Index within `gwl_registry_handlers`. */ + int interface_slot = 0; + uint32_t version = 0; +}; - { - std::lock_guard lock{seat->data_offer_copy_paste_mutex}; - if (seat->data_offer_copy_paste) { - wl_data_offer_destroy(seat->data_offer_copy_paste->id); - delete seat->data_offer_copy_paste; - } - } +/** + * Add callback for object registry. + * \note Any operations that depend on other interfaces being registered must be performed in the + * #GWL_RegistryHandler_UpdateFn callback as the order interfaces are added is out of our control. + * + * \param display: The display which holes a reference to the global object. + * \param params: Various arguments needed for registration. + */ +using GWL_RegistryHandler_AddFn = void (*)(GWL_Display *display, + const GWL_RegisteryAdd_Params *params); - { - GWL_PrimarySelection *primary = &seat->primary_selection; - std::lock_guard lock{primary->data_offer_mutex}; - gwl_primary_selection_discard_offer(primary); - } +struct GWL_RegisteryUpdate_Params { + uint32_t name = 0; + /** Index within `gwl_registry_handlers`. */ + int interface_slot = 0; + uint32_t version = 0; - { - GWL_PrimarySelection *primary = &seat->primary_selection; - std::lock_guard lock{primary->data_source_mutex}; - gwl_primary_selection_discard_source(primary); - } + /** Set to #GWL_RegistryEntry.user_data. */ + void *user_data = nullptr; +}; - if (seat->wp_primary_selection_device) { - zwp_primary_selection_device_v1_destroy(seat->wp_primary_selection_device); - } +/** + * Optional update callback to refresh internal data when another interface has been added/removed. + * + * \param display: The display which holes a reference to the global object. + * \param params: Various arguments needed for updating. + */ +using GWL_RegistryHandler_UpdateFn = void (*)(GWL_Display *display, + const GWL_RegisteryUpdate_Params *params); - if (seat->wl_data_device) { - wl_data_device_release(seat->wl_data_device); - } +/** + * Remove callback for object registry. + * \param display: The display which holes a reference to the global object. + * \param user_data: Optional reference to a sub element of `display`, + * use for outputs or seats for e.g. when the display may hold multiple references. + * \param on_exit: Enabled when freeing on exit. + * When true the consistency of references between objects should be kept valid. + * Otherwise it can be assumed that all objects will be freed and none will be used again, + * so there is no need to ensure a valid state. + */ +using GWL_RegistryEntry_RemoveFn = void (*)(GWL_Display *display, void *user_data, bool on_exit); + +struct GWL_RegistryHandler { + /** Pointer to the name (not the name it's self), needed as the values aren't set on startup. */ + const char *const *interface_p = nullptr; + + /** Add the interface. */ + GWL_RegistryHandler_AddFn add_fn = nullptr; + /** Optional update the interface (when other interfaces have been added/removed). */ + GWL_RegistryHandler_UpdateFn update_fn = nullptr; + /** Remove the interface. */ + GWL_RegistryEntry_RemoveFn remove_fn = nullptr; +}; - if (seat->cursor.custom_data) { - munmap(seat->cursor.custom_data, seat->cursor.custom_data_size); - } +/** \} */ - /* Disable all capabilities as a way to free: - * - `seat.wl_pointer` (and related cursor variables). - * - `seat.wl_touch`. - * - `seat.wl_keyboard`. - */ - gwl_seat_capability_pointer_disable(seat); - gwl_seat_capability_keyboard_disable(seat); - gwl_seat_capability_touch_disable(seat); +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_RegistryEntry + * \{ */ + +/** + * Registered global objects can be removed by the compositor, + * these entries are a registry of objects and callbacks to properly remove them. + * These are also used to remove all registered objects before exiting. + */ +struct GWL_RegistryEntry { + GWL_RegistryEntry *next = nullptr; + /** + * Optional pointer passed to `remove_fn`, typically the container in #GWL_Display + * in cases multiple instances of the same interface are supported. + */ + void *user_data = nullptr; + /** + * A unique identifier used as a handle by `wl_registry_listener.global_remove`. + */ + uint32_t name = WL_NAME_UNSET; + /** + * Version passed by the add callback. + */ + uint32_t version; + /** + * The index in `gwl_registry_handlers`, + * useful for accessing the interface name (for logging for example). + */ + int interface_slot = 0; +}; - /* Un-referencing checks for NULL case. */ - xkb_state_unref(seat->xkb_state); - xkb_state_unref(seat->xkb_state_empty); - xkb_state_unref(seat->xkb_state_empty_with_numlock); +static void gwl_registry_entry_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params, + void *user_data) +{ + GWL_RegistryEntry *reg = new GWL_RegistryEntry; - xkb_context_unref(seat->xkb_context); + reg->interface_slot = params->interface_slot; + reg->name = params->name; + reg->version = params->version; + reg->user_data = user_data; - wl_seat_destroy(seat->wl_seat); - delete seat; - } + reg->next = display->registry_entry; + display->registry_entry = reg; +} - if (display->wl_shm) { - wl_shm_destroy(display->wl_shm); - } +static bool gwl_registry_entry_remove_by_name(GWL_Display *display, + uint32_t name, + int *r_interface_slot) +{ + GWL_RegistryEntry *reg = display->registry_entry; + GWL_RegistryEntry **reg_link_p = &display->registry_entry; + bool found = false; - if (display->wp_relative_pointer_manager) { - zwp_relative_pointer_manager_v1_destroy(display->wp_relative_pointer_manager); + if (r_interface_slot) { + *r_interface_slot = -1; } - if (display->wp_pointer_constraints) { - zwp_pointer_constraints_v1_destroy(display->wp_pointer_constraints); + while (reg) { + if (reg->name == name) { + GWL_RegistryEntry *reg_next = reg->next; + const GWL_RegistryHandler *handler = gwl_registry_handler_from_interface_slot( + reg->interface_slot); + handler->remove_fn(display, reg->user_data, false); + if (r_interface_slot) { + *r_interface_slot = reg->interface_slot; + } + delete reg; + *reg_link_p = reg_next; + found = true; + break; + } + reg_link_p = ®->next; + reg = reg->next; + } + return found; +} + +static bool gwl_registry_entry_remove_by_interface_slot(GWL_Display *display, + int interface_slot, + bool on_exit) +{ + GWL_RegistryEntry *reg = display->registry_entry; + GWL_RegistryEntry **reg_link_p = &display->registry_entry; + bool found = false; + + while (reg) { + if (reg->interface_slot == interface_slot) { + GWL_RegistryEntry *reg_next = reg->next; + const GWL_RegistryHandler *handler = gwl_registry_handler_from_interface_slot( + interface_slot); + handler->remove_fn(display, reg->user_data, on_exit); + delete reg; + *reg_link_p = reg_next; + reg = reg_next; + found = true; + continue; + } + reg_link_p = ®->next; + reg = reg->next; } + return found; +} - if (display->wp_pointer_gestures) { - zwp_pointer_gestures_v1_destroy(display->wp_pointer_gestures); - } +/** + * Remove all global objects (on exit). + */ +static void gwl_registry_entry_remove_all(GWL_Display *display) +{ + const bool on_exit = true; - if (display->wl_compositor) { - wl_compositor_destroy(display->wl_compositor); + /* NOTE(@campbellbarton): Free by slot instead of simply looping over + * `display->registry_entry` so the order of freeing is always predictable. + * Otherwise global objects would be feed in the order they are registered. + * While this works in my tests, it could cause difficult to reproduce bugs + * where lesser used compositors or changes to existing compositors could + * crash on exit based on the order of freeing objects is out of our control. + * + * To give a concrete example of how this could fail, it's possible removing + * a tablet interface could reference the pointer interface, or the output interface. + * Even though references between interfaces shouldn't be necessary in most cases + * when `on_exit` is enabled. */ + int interface_slot = gwl_registry_handler_interface_slot_max(); + while (interface_slot--) { + gwl_registry_entry_remove_by_interface_slot(display, interface_slot, on_exit); } -#ifdef WITH_GHOST_WAYLAND_LIBDECOR - if (use_libdecor) { - if (display->libdecor) { - gwl_libdecor_system_destroy(display->libdecor); + GHOST_ASSERT(display->registry_entry == nullptr, "Failed to remove all entries!"); + display->registry_entry = nullptr; +} + +/** + * Run GWL_RegistryHandler.update_fn an all registered interface instances. + * This is needed to refresh the state of interfaces that may reference other interfaces. + * Called when interfaces are added/removed. + * + * \param interface_slot_exclude: Skip updating slots of this type. + * Note that while harmless dependencies only exist between different types, + * so there is no reason to update all other outputs that an output was removed (for e.g.). + * Pass as -1 to update all slots. + * + * NOTE(@campbellbarton): Updating all other items on a single change is typically worth avoiding. + * In practice this isn't a problem as so there are so few elements in `display->registry_entry`, + * so few use update functions and adding/removal at runtime is rarely called (plugging/unplugging) + * hardware for e.g. So while it's possible to store dependency links to avoid unnecessary + * looping over data - it ends up being a non issue. + */ +static void gwl_registry_entry_update_all(GWL_Display *display, const int interface_slot_exclude) +{ + GHOST_ASSERT(interface_slot_exclude == -1 || (uint(interface_slot_exclude) < + uint(gwl_registry_handler_interface_slot_max())), + "Invalid exclude slot"); + + for (GWL_RegistryEntry *reg = display->registry_entry; reg; reg = reg->next) { + if (reg->interface_slot == interface_slot_exclude) { + continue; } - } - else -#endif - { - if (display->xdg_decor) { - gwl_xdg_decor_system_destroy(display->xdg_decor); + const GWL_RegistryHandler *handler = gwl_registry_handler_from_interface_slot( + reg->interface_slot); + if (handler->update_fn == nullptr) { + continue; } - } - if (eglGetDisplay) { - ::eglTerminate(eglGetDisplay(EGLNativeDisplayType(display->wl_display))); - } + GWL_RegisteryUpdate_Params params = { + .name = reg->name, + .interface_slot = reg->interface_slot, + .version = reg->version, - if (display->wl_display) { - wl_display_disconnect(display->wl_display); + .user_data = reg->user_data, + }; + handler->update_fn(display, ¶ms); } +} - { - std::lock_guard lock{system_clipboard_mutex}; - gwl_simple_buffer_free_data(&display->clipboard); - gwl_simple_buffer_free_data(&display->clipboard_primary); - } +/** \} */ - delete display; +/* -------------------------------------------------------------------- */ +/** \name Private Utility Functions + * \{ */ + +/** + * 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 GHOST_TKey xkb_map_gkey(const xkb_keysym_t sym) @@ -1090,13 +1341,13 @@ static GHOST_TTabletMode tablet_tool_map_type(enum zwp_tablet_tool_v2_type wp_ta } } - GHOST_PRINT("unknown tablet tool: " << wl_tablet_tool_type << std::endl); + GHOST_PRINT("unknown tablet tool: " << wp_tablet_tool_type << std::endl); return GHOST_kTabletModeStylus; } static const int default_cursor_size = 24; -static const std::unordered_map<GHOST_TStandardCursor, const char *> cursors = { +static const std::unordered_map<GHOST_TStandardCursor, const char *> ghost_wl_cursors = { {GHOST_kStandardCursorDefault, "left_ptr"}, {GHOST_kStandardCursorRightArrow, "right_ptr"}, {GHOST_kStandardCursorLeftArrow, "left_ptr"}, @@ -1137,23 +1388,23 @@ static const std::unordered_map<GHOST_TStandardCursor, const char *> cursors = { {GHOST_kStandardCursorCopy, "copy"}, }; -static constexpr const char *mime_text_plain = "text/plain"; -static constexpr const char *mime_text_utf8 = "text/plain;charset=utf-8"; -static constexpr const char *mime_text_uri = "text/uri-list"; +static constexpr const char *ghost_wl_mime_text_plain = "text/plain"; +static constexpr const char *ghost_wl_mime_text_utf8 = "text/plain;charset=utf-8"; +static constexpr const char *ghost_wl_mime_text_uri = "text/uri-list"; -static const std::unordered_map<std::string, GHOST_TDragnDropTypes> mime_dnd = { - {mime_text_plain, GHOST_kDragnDropTypeString}, - {mime_text_utf8, GHOST_kDragnDropTypeString}, - {mime_text_uri, GHOST_kDragnDropTypeFilenames}, +static const char *ghost_wl_mime_preference_order[] = { + ghost_wl_mime_text_uri, + ghost_wl_mime_text_utf8, + ghost_wl_mime_text_plain, }; - -static const std::vector<std::string> mime_preference_order = { - mime_text_uri, - mime_text_utf8, - mime_text_plain, +/* Aligned to `ghost_wl_mime_preference_order`. */ +static const GHOST_TDragnDropTypes ghost_wl_mime_preference_order_type[] = { + GHOST_kDragnDropTypeString, + GHOST_kDragnDropTypeString, + GHOST_kDragnDropTypeFilenames, }; -static const std::vector<std::string> mime_send = { +static const char *ghost_wl_mime_send[] = { "UTF8_STRING", "COMPOUND_TEXT", "TEXT", @@ -1283,7 +1534,7 @@ static void keyboard_depressed_state_key_event(GWL_Seat *seat, static void keyboard_depressed_state_push_events_from_change( GWL_Seat *seat, const GWL_KeyboardDepressedState &key_depressed_prev) { - GHOST_IWindow *win = ghost_wl_surface_user_data(seat->keyboard.wl_surface); + GHOST_IWindow *win = ghost_wl_surface_user_data(seat->keyboard.wl_surface_window); GHOST_SystemWayland *system = seat->system; /* Separate key up and down into separate passes so key down events always come after key up. @@ -1370,7 +1621,7 @@ static void relative_pointer_handle_relative_motion( const wl_fixed_t /*dy_unaccel*/) { GWL_Seat *seat = static_cast<GWL_Seat *>(data); - if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) { CLOG_INFO(LOG, 2, "relative_motion"); GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); const wl_fixed_t scale = win->scale(); @@ -1403,7 +1654,7 @@ static CLG_LogRef LOG_WL_DATA_SOURCE = {"ghost.wl.handle.data_source"}; static void dnd_events(const GWL_Seat *const seat, const GHOST_TEventType event) { /* NOTE: `seat->data_offer_dnd_mutex` must already be locked. */ - if (wl_surface *wl_surface_focus = seat->wl_surface_focus_dnd) { + if (wl_surface *wl_surface_focus = seat->wl_surface_window_focus_dnd) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); const wl_fixed_t scale = win->scale(); const int event_xy[2] = { @@ -1412,9 +1663,10 @@ static void dnd_events(const GWL_Seat *const seat, const GHOST_TEventType event) }; const uint64_t time = seat->system->getMilliSeconds(); - for (const std::string &type : mime_preference_order) { - seat->system->pushEvent(new GHOST_EventDragnDrop( - time, event, mime_dnd.at(type), win, UNPACK2(event_xy), nullptr)); + for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_preference_order_type); i++) { + const GHOST_TDragnDropTypes type = ghost_wl_mime_preference_order_type[i]; + seat->system->pushEvent( + new GHOST_EventDragnDrop(time, event, type, win, UNPACK2(event_xy), nullptr)); } } } @@ -1423,7 +1675,7 @@ static void dnd_events(const GWL_Seat *const seat, const GHOST_TEventType event) * Read from `fd` into a buffer which is returned. * \return the buffer or null on failure. */ -static const char *read_file_as_buffer(const int fd, size_t *r_len) +static char *read_file_as_buffer(const int fd, const bool nil_terminate, size_t *r_len) { struct ByteChunk { ByteChunk *next; @@ -1459,14 +1711,23 @@ static const char *read_file_as_buffer(const int fd, size_t *r_len) char *buf = nullptr; if (ok) { - buf = static_cast<char *>(malloc(len)); + buf = static_cast<char *>(malloc(len + (nil_terminate ? 1 : 0))); if (UNLIKELY(buf == nullptr)) { CLOG_WARN(LOG, "unable to allocate file buffer: %zu bytes", len); ok = false; } } - *r_len = ok ? len : 0; + if (ok) { + *r_len = len; + if (nil_terminate) { + buf[len] = '\0'; + } + } + else { + *r_len = 0; + } + char *buf_stride = buf; while (chunk_first) { if (ok) { @@ -1483,53 +1744,62 @@ static const char *read_file_as_buffer(const int fd, size_t *r_len) return buf; } -static const char *read_buffer_from_data_offer(GWL_DataOffer *data_offer, - const char *mime_receive, - std::mutex *mutex, - size_t *r_len) +static char *read_buffer_from_data_offer(GWL_DataOffer *data_offer, + const char *mime_receive, + std::mutex *mutex, + const bool nil_terminate, + size_t *r_len) { int pipefd[2]; - if (UNLIKELY(pipe(pipefd) != 0)) { + const bool pipefd_ok = pipe(pipefd) == 0; + if (pipefd_ok) { + wl_data_offer_receive(data_offer->id, mime_receive, pipefd[1]); + close(pipefd[1]); + } + else { CLOG_WARN(LOG, "error creating pipe: %s", std::strerror(errno)); - return nullptr; } - wl_data_offer_receive(data_offer->id, mime_receive, pipefd[1]); - close(pipefd[1]); - data_offer->in_use.store(false); + /* Only for DND (A no-op to disable for clipboard data-offer). */ + data_offer->dnd.in_use = false; if (mutex) { mutex->unlock(); } /* WARNING: `data_offer` may be freed from now on. */ - - const char *buf = read_file_as_buffer(pipefd[0], r_len); - close(pipefd[0]); + char *buf = nullptr; + if (pipefd_ok) { + buf = read_file_as_buffer(pipefd[0], nil_terminate, r_len); + close(pipefd[0]); + } return buf; } -static const char *read_buffer_from_primary_selection_offer( - GWL_PrimarySelection_DataOffer *data_offer, - const char *mime_receive, - std::mutex *mutex, - size_t *r_len) +static char *read_buffer_from_primary_selection_offer(GWL_PrimarySelection_DataOffer *data_offer, + const char *mime_receive, + std::mutex *mutex, + const bool nil_terminate, + size_t *r_len) { int pipefd[2]; - if (UNLIKELY(pipe(pipefd) != 0)) { + const bool pipefd_ok = pipe(pipefd) == 0; + if (pipefd_ok) { + zwp_primary_selection_offer_v1_receive(data_offer->id, mime_receive, pipefd[1]); + close(pipefd[1]); + } + else { CLOG_WARN(LOG, "error creating pipe: %s", std::strerror(errno)); - return nullptr; } - zwp_primary_selection_offer_v1_receive(data_offer->id, mime_receive, pipefd[1]); - close(pipefd[1]); - - data_offer->in_use.store(false); if (mutex) { mutex->unlock(); } /* WARNING: `data_offer` may be freed from now on. */ - const char *buf = read_file_as_buffer(pipefd[0], r_len); - close(pipefd[0]); + char *buf = nullptr; + if (pipefd_ok) { + buf = read_file_as_buffer(pipefd[0], nil_terminate, r_len); + close(pipefd[0]); + } return buf; } @@ -1552,15 +1822,22 @@ static void data_source_handle_send(void *data, const int32_t fd) { GWL_Seat *seat = static_cast<GWL_Seat *>(data); - std::lock_guard lock{seat->data_source_mutex}; CLOG_INFO(LOG, 2, "send"); - const char *const buffer = seat->data_source->buffer_out.data; - if (UNLIKELY(write(fd, buffer, seat->data_source->buffer_out.data_size) < 0)) { - CLOG_WARN(LOG, "error writing to clipboard: %s", std::strerror(errno)); - } - close(fd); + auto write_file_fn = [](GWL_Seat *seat, const int fd) { + if (UNLIKELY(write(fd, + seat->data_source->buffer_out.data, + seat->data_source->buffer_out.data_size) < 0)) { + CLOG_WARN(LOG, "error writing to clipboard: %s", std::strerror(errno)); + } + close(fd); + seat->data_source_mutex.unlock(); + }; + + seat->data_source_mutex.lock(); + std::thread write_thread(write_file_fn, seat, fd); + write_thread.detach(); } static void data_source_handle_cancelled(void *data, struct wl_data_source *wl_data_source) @@ -1711,7 +1988,7 @@ static void data_device_handle_enter(void *data, seat->data_offer_dnd = static_cast<GWL_DataOffer *>(wl_data_offer_get_user_data(id)); GWL_DataOffer *data_offer = seat->data_offer_dnd; - data_offer->in_use.store(true); + data_offer->dnd.in_use = true; data_offer->dnd.xy[0] = x; data_offer->dnd.xy[1] = y; @@ -1720,11 +1997,15 @@ static void data_device_handle_enter(void *data, WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY); - for (const std::string &type : mime_preference_order) { - wl_data_offer_accept(id, serial, type.c_str()); + for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_preference_order); i++) { + const char *type = ghost_wl_mime_preference_order[i]; + wl_data_offer_accept(id, serial, type); } - seat->wl_surface_focus_dnd = wl_surface; + seat->wl_surface_window_focus_dnd = wl_surface; + + seat->system->seat_active_set(seat); + dnd_events(seat, GHOST_kEventDraggingEntered); } @@ -1736,9 +2017,9 @@ static void data_device_handle_leave(void *data, struct wl_data_device * /*wl_da CLOG_INFO(LOG, 2, "leave"); dnd_events(seat, GHOST_kEventDraggingExited); - seat->wl_surface_focus_dnd = nullptr; + seat->wl_surface_window_focus_dnd = nullptr; - if (seat->data_offer_dnd && !seat->data_offer_dnd->in_use.load()) { + if (seat->data_offer_dnd && !seat->data_offer_dnd->dnd.in_use) { wl_data_offer_destroy(seat->data_offer_dnd->id); delete seat->data_offer_dnd; seat->data_offer_dnd = nullptr; @@ -1769,27 +2050,35 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat GWL_DataOffer *data_offer = seat->data_offer_dnd; - const std::string mime_receive = *std::find_first_of(mime_preference_order.begin(), - mime_preference_order.end(), - data_offer->types.begin(), - data_offer->types.end()); + /* Use a blank string for `mime_receive` to prevent crashes, although could also be `nullptr`. + * Failure to set this to a known type just means the file won't have any special handling. + * GHOST still generates a dropped file event. + * NOTE: this string can be compared with `mime_text_plain`, `mime_text_uri` etc... + * as the this always points to the same values. */ + const char *mime_receive = ""; + for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_preference_order); i++) { + const char *type = ghost_wl_mime_preference_order[i]; + if (data_offer->types.count(type)) { + mime_receive = type; + break; + } + } - CLOG_INFO(LOG, 2, "drop mime_recieve=%s", mime_receive.c_str()); + CLOG_INFO(LOG, 2, "drop mime_recieve=%s", mime_receive); auto read_uris_fn = [](GWL_Seat *const seat, GWL_DataOffer *data_offer, - wl_surface *wl_surface, - const std::string mime_receive) { + wl_surface *wl_surface_window, + const char *mime_receive) { const wl_fixed_t xy[2] = {UNPACK2(data_offer->dnd.xy)}; size_t data_buf_len = 0; const char *data_buf = read_buffer_from_data_offer( - data_offer, mime_receive.c_str(), nullptr, &data_buf_len); + data_offer, mime_receive, nullptr, false, &data_buf_len); std::string data = data_buf ? std::string(data_buf, data_buf_len) : ""; free(const_cast<char *>(data_buf)); - CLOG_INFO( - LOG, 2, "drop_read_uris mime_receive=%s, data=%s", mime_receive.c_str(), data.c_str()); + CLOG_INFO(LOG, 2, "drop_read_uris mime_receive=%s, data=%s", mime_receive, data.c_str()); wl_data_offer_finish(data_offer->id); wl_data_offer_destroy(data_offer->id); @@ -1802,13 +2091,13 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat GHOST_SystemWayland *const system = seat->system; - if (mime_receive == mime_text_uri) { + if (mime_receive == ghost_wl_mime_text_uri) { static constexpr const char *file_proto = "file://"; /* NOTE: some applications CRLF (`\r\n`) GTK3 for e.g. & others don't `pcmanfm-qt`. * So support both, once `\n` is found, strip the preceding `\r` if found. */ static constexpr const char *lf = "\n"; - GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface); + GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_window); std::vector<std::string> uris; size_t pos = 0; @@ -1847,7 +2136,7 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat wl_fixed_to_int(scale * xy[1]), flist)); } - else if (ELEM(mime_receive, mime_text_plain, mime_text_utf8)) { + else if (ELEM(mime_receive, ghost_wl_mime_text_plain, ghost_wl_mime_text_utf8)) { /* TODO: enable use of internal functions 'txt_insert_buf' and * 'text_update_edited' to behave like dropped text was pasted. */ CLOG_INFO(LOG, 2, "drop_read_uris_fn (text_plain, text_utf8), unhandled!"); @@ -1855,10 +2144,10 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat wl_display_roundtrip(system->wl_display()); }; - /* Pass in `seat->wl_surface_focus_dnd` instead of accessing it from `seat` since the leave - * callback (#data_device_handle_leave) will clear the value once this function starts. */ + /* Pass in `seat->wl_surface_window_focus_dnd` instead of accessing it from `seat` since the + * leave callback (#data_device_handle_leave) will clear the value once this function starts. */ std::thread read_thread( - read_uris_fn, seat, data_offer, seat->wl_surface_focus_dnd, mime_receive); + read_uris_fn, seat, data_offer, seat->wl_surface_window_focus_dnd, mime_receive); read_thread.detach(); } @@ -1888,33 +2177,6 @@ static void data_device_handle_selection(void *data, /* Get new data offer. */ data_offer = static_cast<GWL_DataOffer *>(wl_data_offer_get_user_data(id)); seat->data_offer_copy_paste = data_offer; - - auto read_selection_fn = [](GWL_Seat *seat) { - GHOST_SystemWayland *const system = seat->system; - seat->data_offer_copy_paste_mutex.lock(); - - GWL_DataOffer *data_offer = seat->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; - } - } - - size_t data_len = 0; - const char *data = read_buffer_from_data_offer( - data_offer, mime_receive.c_str(), &seat->data_offer_copy_paste_mutex, &data_len); - - { - std::lock_guard lock{system_clipboard_mutex}; - GWL_SimpleBuffer *buf = system->clipboard_data(false); - gwl_simple_buffer_set_and_take_ownership(buf, data, data_len); - } - }; - - std::thread read_thread(read_selection_fn, seat); - read_thread.detach(); } static const struct wl_data_device_listener data_device_listener = { @@ -1968,7 +2230,7 @@ static CLG_LogRef LOG_WL_CURSOR_SURFACE = {"ghost.wl.handle.cursor_surface"}; static bool update_cursor_scale(GWL_Cursor &cursor, wl_shm *shm, GWL_SeatStatePointer *seat_state_pointer, - wl_surface *wl_cursor_surface) + wl_surface *wl_surface_cursor) { int scale = 0; for (const GWL_Output *output : seat_state_pointer->outputs) { @@ -1980,7 +2242,7 @@ static bool update_cursor_scale(GWL_Cursor &cursor, if (scale > 0 && seat_state_pointer->theme_scale != scale) { seat_state_pointer->theme_scale = scale; if (!cursor.is_custom) { - wl_surface_set_buffer_scale(wl_cursor_surface, scale); + wl_surface_set_buffer_scale(wl_surface_cursor, scale); } wl_cursor_theme_destroy(cursor.wl_theme); cursor.wl_theme = wl_cursor_theme_load( @@ -2001,8 +2263,8 @@ static void cursor_surface_handle_enter(void *data, CLOG_INFO(LOG, 2, "handle_enter"); GWL_Seat *seat = static_cast<GWL_Seat *>(data); - GWL_SeatStatePointer *seat_state_pointer = seat_state_pointer_from_cursor_surface(seat, - wl_surface); + GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_from_cursor_surface( + seat, wl_surface); const GWL_Output *reg_output = ghost_wl_output_user_data(wl_output); seat_state_pointer->outputs.insert(reg_output); update_cursor_scale(seat->cursor, seat->system->wl_shm(), seat_state_pointer, wl_surface); @@ -2019,8 +2281,8 @@ static void cursor_surface_handle_leave(void *data, CLOG_INFO(LOG, 2, "handle_leave"); GWL_Seat *seat = static_cast<GWL_Seat *>(data); - GWL_SeatStatePointer *seat_state_pointer = seat_state_pointer_from_cursor_surface(seat, - wl_surface); + GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_from_cursor_surface( + seat, wl_surface); const GWL_Output *reg_output = ghost_wl_output_user_data(wl_output); seat_state_pointer->outputs.erase(reg_output); update_cursor_scale(seat->cursor, seat->system->wl_shm(), seat_state_pointer, wl_surface); @@ -2073,7 +2335,9 @@ static void pointer_handle_enter(void *data, seat->pointer_scroll.discrete_xy[1] = 0; seat->pointer_scroll.axis_source = WL_POINTER_AXIS_SOURCE_WHEEL; - seat->pointer.wl_surface = wl_surface; + seat->pointer.wl_surface_window = wl_surface; + + seat->system->seat_active_set(seat); win->setCursorShape(win->getCursorShape()); @@ -2092,7 +2356,7 @@ static void pointer_handle_leave(void *data, struct wl_surface *wl_surface) { /* First clear the `pointer.wl_surface`, since the window won't exist when closing the window. */ - static_cast<GWL_Seat *>(data)->pointer.wl_surface = nullptr; + static_cast<GWL_Seat *>(data)->pointer.wl_surface_window = nullptr; if (wl_surface && ghost_wl_surface_own(wl_surface)) { CLOG_INFO(LOG, 2, "leave"); GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface); @@ -2113,7 +2377,7 @@ static void pointer_handle_motion(void *data, seat->pointer.xy[0] = surface_x; seat->pointer.xy[1] = surface_y; - if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) { CLOG_INFO(LOG, 2, "motion"); GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); const wl_fixed_t scale = win->scale(); @@ -2177,7 +2441,7 @@ static void pointer_handle_button(void *data, seat->data_source_serial = serial; seat->pointer.buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED); - if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); seat->system->pushEvent(new GHOST_EventButton( seat->system->getMilliSeconds(), etype, win, ebutton, GHOST_TABLET_DATA_NONE)); @@ -2228,7 +2492,7 @@ static void pointer_handle_frame(void *data, struct wl_pointer * /*wl_pointer*/) /* Discrete X axis currently unsupported. */ if (seat->pointer_scroll.discrete_xy[1]) { - if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); const int32_t discrete = seat->pointer_scroll.discrete_xy[1]; seat->system->pushEvent(new GHOST_EventWheel( @@ -2239,7 +2503,7 @@ static void pointer_handle_frame(void *data, struct wl_pointer * /*wl_pointer*/) } if (seat->pointer_scroll.smooth_xy[0] || seat->pointer_scroll.smooth_xy[1]) { - if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); const wl_fixed_t scale = win->scale(); seat->system->pushEvent(new GHOST_EventTrackpad( @@ -2249,10 +2513,11 @@ static void pointer_handle_frame(void *data, struct wl_pointer * /*wl_pointer*/) wl_fixed_to_int(scale * seat->pointer.xy[0]), wl_fixed_to_int(scale * seat->pointer.xy[1]), /* NOTE: scaling the delta doesn't seem necessary. - * NOTE: inverting delta gives correct results, see: QTBUG-85767. */ + * NOTE: inverting delta gives correct results, see: QTBUG-85767. + * NOTE: the preference to invert scrolling (in GNOME at least) + * has already been applied so there is no need to read this preference. */ -wl_fixed_to_int(seat->pointer_scroll.smooth_xy[0]), -wl_fixed_to_int(seat->pointer_scroll.smooth_xy[1]), - /* TODO: investigate a way to request this configuration from the system. */ false)); } @@ -2313,8 +2578,9 @@ static const struct wl_pointer_listener pointer_listener = { /** \name Listener (Pointer Gesture: Hold), #zwp_pointer_gesture_hold_v1_listener * \{ */ +#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE static CLG_LogRef LOG_WL_POINTER_GESTURE_HOLD = {"ghost.wl.handle.pointer_gesture.hold"}; -#define LOG (&LOG_WL_POINTER_GESTURE_HOLD) +# define LOG (&LOG_WL_POINTER_GESTURE_HOLD) static void gesture_hold_handle_begin( void * /*data*/, @@ -2342,7 +2608,8 @@ static const struct zwp_pointer_gesture_hold_v1_listener gesture_hold_listener = gesture_hold_handle_end, }; -#undef LOG +# undef LOG +#endif /* ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE */ /** \} */ @@ -2350,8 +2617,9 @@ static const struct zwp_pointer_gesture_hold_v1_listener gesture_hold_listener = /** \name Listener (Pointer Gesture: Pinch), #zwp_pointer_gesture_pinch_v1_listener * \{ */ +#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE static CLG_LogRef LOG_WL_POINTER_GESTURE_PINCH = {"ghost.wl.handle.pointer_gesture.pinch"}; -#define LOG (&LOG_WL_POINTER_GESTURE_PINCH) +# define LOG (&LOG_WL_POINTER_GESTURE_PINCH) static void gesture_pinch_handle_begin(void *data, struct zwp_pointer_gesture_pinch_v1 * /*pinch*/, @@ -2366,9 +2634,21 @@ static void gesture_pinch_handle_begin(void *data, seat->pointer_gesture_pinch = GWL_SeatStatePointerGesture_Pinch{}; GHOST_WindowWayland *win = nullptr; - if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) { win = ghost_wl_surface_user_data(wl_surface_focus); } + /* NOTE(@campbellbarton): Blender's use of track-pad coordinates is inconsistent and needs work. + * This isn't specific to WAYLAND, in practice they tend to work well enough in most cases. + * Some operators scale by the UI scale, some don't. + * Even this window scale is not correct because it doesn't account for: + * 1) Fractional window scale. + * 2) Blender's UI scale preference (which GHOST doesn't know about). + * + * If support for this were all that was needed it could be handled in GHOST, + * however as the operators are not even using coordinates compatible with each other, + * it would be better to resolve this by passing rotation & zoom levels directly, + * instead of attempting to handle them as cursor coordinates. + */ const wl_fixed_t win_scale = win ? win->scale() : 1; /* NOTE(@campbellbarton): Scale factors match Blender's operators & default preferences. @@ -2410,7 +2690,7 @@ static void gesture_pinch_handle_update(void *data, GHOST_WindowWayland *win = nullptr; - if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) { win = ghost_wl_surface_user_data(wl_surface_focus); } @@ -2469,7 +2749,8 @@ static const struct zwp_pointer_gesture_pinch_v1_listener gesture_pinch_listener gesture_pinch_handle_end, }; -#undef LOG +# undef LOG +#endif /* ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE */ /** \} */ @@ -2482,8 +2763,9 @@ static const struct zwp_pointer_gesture_pinch_v1_listener gesture_pinch_listener * (swiping with 3+ fingers, for e.g.). So keep this to allow logging & testing gestures. * \{ */ +#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE static CLG_LogRef LOG_WL_POINTER_GESTURE_SWIPE = {"ghost.wl.handle.pointer_gesture.swipe"}; -#define LOG (&LOG_WL_POINTER_GESTURE_SWIPE) +# define LOG (&LOG_WL_POINTER_GESTURE_SWIPE) static void gesture_swipe_handle_begin( void * /*data*/, @@ -2522,15 +2804,17 @@ static const struct zwp_pointer_gesture_swipe_v1_listener gesture_swipe_listener gesture_swipe_handle_end, }; -#undef LOG +# undef LOG +#endif /* ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE */ /** \} */ /* -------------------------------------------------------------------- */ /** \name Listener (Touch Seat), #wl_touch_listener * - * TODO(@campbellbarton): Only setup the callbacks for now as I don't have - * hardware that generates touch events. + * NOTE(@campbellbarton): It's not clear if this interface is used by popular compositors. + * It looks like GNOME/KDE only support `zwp_pointer_gestures_v1_interface`. + * If this isn't used anywhere, it could be removed. * \{ */ static CLG_LogRef LOG_WL_TOUCH = {"ghost.wl.handle.touch"}; @@ -2694,11 +2978,13 @@ static void tablet_tool_handle_proximity_in(void *data, GWL_Seat *seat = tablet_tool->seat; seat->cursor_source_serial = serial; - seat->tablet.wl_surface = wl_surface; + seat->tablet.wl_surface_window = wl_surface; seat->tablet.serial = serial; seat->data_source_serial = serial; + seat->system->seat_active_set(seat); + /* Update #GHOST_TabletData. */ GHOST_TabletData &td = tablet_tool->data; /* Reset, to avoid using stale tilt/pressure. */ @@ -2707,7 +2993,7 @@ static void tablet_tool_handle_proximity_in(void *data, /* In case pressure isn't supported. */ td.Pressure = 1.0f; - GHOST_WindowWayland *win = ghost_wl_surface_user_data(seat->tablet.wl_surface); + GHOST_WindowWayland *win = ghost_wl_surface_user_data(seat->tablet.wl_surface_window); win->activate(); @@ -2737,7 +3023,7 @@ static void tablet_tool_handle_down(void *data, seat->data_source_serial = serial; seat->tablet.buttons.set(ebutton, true); - if (wl_surface *wl_surface_focus = seat->tablet.wl_surface) { + if (wl_surface *wl_surface_focus = seat->tablet.wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); seat->system->pushEvent(new GHOST_EventButton( seat->system->getMilliSeconds(), etype, win, ebutton, tablet_tool->data)); @@ -2755,7 +3041,7 @@ static void tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 * /*zwp_ seat->tablet.buttons.set(ebutton, false); - if (wl_surface *wl_surface_focus = seat->tablet.wl_surface) { + if (wl_surface *wl_surface_focus = seat->tablet.wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); seat->system->pushEvent(new GHOST_EventButton( seat->system->getMilliSeconds(), etype, win, ebutton, tablet_tool->data)); @@ -2840,7 +3126,7 @@ static void tablet_tool_handle_wheel(void *data, GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data); GWL_Seat *seat = tablet_tool->seat; - if (wl_surface *wl_surface_focus = seat->tablet.wl_surface) { + if (wl_surface *wl_surface_focus = seat->tablet.wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); seat->system->pushEvent(new GHOST_EventWheel(seat->system->getMilliSeconds(), win, clicks)); } @@ -2869,10 +3155,10 @@ static void tablet_tool_handle_button(void *data, GHOST_TButton ebutton = GHOST_kButtonMaskLeft; switch (button) { case BTN_STYLUS: - ebutton = GHOST_kButtonMaskRight; + ebutton = GHOST_kButtonMaskMiddle; break; case BTN_STYLUS2: - ebutton = GHOST_kButtonMaskMiddle; + ebutton = GHOST_kButtonMaskRight; break; case BTN_STYLUS3: ebutton = GHOST_kButtonMaskButton4; @@ -2882,7 +3168,7 @@ static void tablet_tool_handle_button(void *data, seat->data_source_serial = serial; seat->tablet.buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED); - if (wl_surface *wl_surface_focus = seat->tablet.wl_surface) { + if (wl_surface *wl_surface_focus = seat->tablet.wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); seat->system->pushEvent(new GHOST_EventButton( seat->system->getMilliSeconds(), etype, win, ebutton, tablet_tool->data)); @@ -2898,7 +3184,7 @@ static void tablet_tool_handle_frame(void *data, GWL_Seat *seat = tablet_tool->seat; /* No need to check the surfaces origin, it's already known to be owned by GHOST. */ - if (wl_surface *wl_surface_focus = seat->tablet.wl_surface) { + if (wl_surface *wl_surface_focus = seat->tablet.wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); const wl_fixed_t scale = win->scale(); seat->system->pushEvent(new GHOST_EventCursor(seat->system->getMilliSeconds(), @@ -2913,7 +3199,7 @@ static void tablet_tool_handle_frame(void *data, } if (tablet_tool->proximity == false) { - seat->tablet.wl_surface = nullptr; + seat->tablet.wl_surface_window = nullptr; } } @@ -3041,9 +3327,23 @@ static void keyboard_handle_keymap(void *data, xkb_state_unref(seat->xkb_state_empty); seat->xkb_state_empty = xkb_state_new(keymap); + for (int i = 0; i < MOD_INDEX_NUM; i++) { + const GWL_ModifierInfo &mod_info = g_modifier_info_table[i]; + seat->xkb_keymap_mod_index[i] = xkb_keymap_mod_get_index(keymap, mod_info.xkb_id); + } + + xkb_state_unref(seat->xkb_state_empty_with_shift); + seat->xkb_state_empty_with_shift = nullptr; + { + const xkb_mod_index_t mod_shift = seat->xkb_keymap_mod_index[MOD_INDEX_SHIFT]; + if (mod_shift != XKB_MOD_INVALID) { + seat->xkb_state_empty_with_shift = xkb_state_new(keymap); + xkb_state_update_mask(seat->xkb_state_empty_with_shift, (1 << mod_shift), 0, 0, 0, 0, 0); + } + } + xkb_state_unref(seat->xkb_state_empty_with_numlock); seat->xkb_state_empty_with_numlock = nullptr; - { const xkb_mod_index_t mod2 = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM); const xkb_mod_index_t num = xkb_keymap_mod_get_index(keymap, "NumLock"); @@ -3054,10 +3354,21 @@ static void keyboard_handle_keymap(void *data, } } - for (int i = 0; i < MOD_INDEX_NUM; i++) { - const GWL_ModifierInfo &mod_info = g_modifier_info_table[i]; - seat->xkb_keymap_mod_index[i] = xkb_keymap_mod_get_index(keymap, mod_info.xkb_id); +#ifdef USE_NON_LATIN_KB_WORKAROUND + seat->xkb_use_non_latin_workaround = false; + if (seat->xkb_state_empty_with_shift) { + seat->xkb_use_non_latin_workaround = true; + for (xkb_keycode_t key_code = KEY_1 + EVDEV_OFFSET; key_code <= KEY_0 + EVDEV_OFFSET; + key_code++) { + const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(seat->xkb_state_empty_with_shift, + key_code); + if (!(sym_test >= XKB_KEY_0 && sym_test <= XKB_KEY_9)) { + seat->xkb_use_non_latin_workaround = false; + break; + } + } } +#endif keyboard_depressed_state_reset(seat); @@ -3083,7 +3394,9 @@ static void keyboard_handle_enter(void *data, GWL_Seat *seat = static_cast<GWL_Seat *>(data); seat->keyboard.serial = serial; - seat->keyboard.wl_surface = wl_surface; + seat->keyboard.wl_surface_window = wl_surface; + + seat->system->seat_active_set(seat); /* If there are any keys held when activating the window, * modifiers will be compared against the seat state, @@ -3126,7 +3439,7 @@ static void keyboard_handle_leave(void *data, CLOG_INFO(LOG, 2, "leave"); GWL_Seat *seat = static_cast<GWL_Seat *>(data); - seat->keyboard.wl_surface = nullptr; + seat->keyboard.wl_surface_window = nullptr; /* Losing focus must stop repeating text. */ if (seat->key_repeat.timer) { @@ -3146,6 +3459,8 @@ static void keyboard_handle_leave(void *data, static xkb_keysym_t xkb_state_key_get_one_sym_without_modifiers( struct xkb_state *xkb_state_empty, struct xkb_state *xkb_state_empty_with_numlock, + struct xkb_state *xkb_state_empty_with_shift, + const bool xkb_use_non_latin_workaround, const xkb_keycode_t key) { /* Use an empty keyboard state to access key symbol without modifiers. */ @@ -3159,11 +3474,30 @@ static xkb_keysym_t xkb_state_key_get_one_sym_without_modifiers( /* Accounts for key-pad keys typically swapped for numbers when number-lock is enabled: * `Home Left Up Right Down Prior Page_Up Next Page_Dow End Begin Insert Delete`. */ - if (xkb_state_empty_with_numlock && (sym >= XKB_KEY_KP_Home && sym <= XKB_KEY_KP_Delete)) { - const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(xkb_state_empty_with_numlock, key); - if (sym_test != XKB_KEY_NoSymbol) { - sym = sym_test; + if (sym >= XKB_KEY_KP_Home && sym <= XKB_KEY_KP_Delete) { + if (xkb_state_empty_with_numlock) { + const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(xkb_state_empty_with_numlock, key); + if (sym_test != XKB_KEY_NoSymbol) { + sym = sym_test; + } + } + } + else { +#ifdef USE_NON_LATIN_KB_WORKAROUND + if (key >= (KEY_1 + EVDEV_OFFSET) && key <= (KEY_0 + EVDEV_OFFSET)) { + if (xkb_state_empty_with_shift && xkb_use_non_latin_workaround) { + const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(xkb_state_empty_with_shift, key); + if (sym_test != XKB_KEY_NoSymbol) { + /* Should never happen as enabling `xkb_use_non_latin_workaround` checks this. */ + GHOST_ASSERT(sym_test >= XKB_KEY_0 && sym_test <= XKB_KEY_9, "Unexpected key"); + sym = sym_test; + } + } } +#else + (void)xkb_state_empty_with_shift; + (void)xkb_use_non_latin_workaround; +#endif } return sym; @@ -3206,7 +3540,15 @@ static void keyboard_handle_key(void *data, const xkb_keycode_t key_code = key + EVDEV_OFFSET; const xkb_keysym_t sym = xkb_state_key_get_one_sym_without_modifiers( - seat->xkb_state_empty, seat->xkb_state_empty_with_numlock, key_code); + seat->xkb_state_empty, + seat->xkb_state_empty_with_numlock, + seat->xkb_state_empty_with_shift, +#ifdef USE_NON_LATIN_KB_WORKAROUND + seat->xkb_use_non_latin_workaround, +#else + false, +#endif + key_code); if (sym == XKB_KEY_NoSymbol) { CLOG_INFO(LOG, 2, "key (code=%d, state=%u, no symbol, skipped)", int(key_code), state); return; @@ -3287,7 +3629,7 @@ static void keyboard_handle_key(void *data, keyboard_depressed_state_key_event(seat, gkey, etype); - if (wl_surface *wl_surface_focus = seat->keyboard.wl_surface) { + if (wl_surface *wl_surface_focus = seat->keyboard.wl_surface_window) { GHOST_IWindow *win = ghost_wl_surface_user_data(wl_surface_focus); seat->system->pushEvent( new GHOST_EventKey(seat->system->getMilliSeconds(), etype, win, gkey, false, utf8_buf)); @@ -3311,7 +3653,7 @@ static void keyboard_handle_key(void *data, task->getUserData()); GWL_Seat *seat = payload->seat; - if (wl_surface *wl_surface_focus = seat->keyboard.wl_surface) { + if (wl_surface *wl_surface_focus = seat->keyboard.wl_surface_window) { GHOST_IWindow *win = ghost_wl_surface_user_data(wl_surface_focus); GHOST_SystemWayland *system = seat->system; /* Calculate this value every time in case modifier keys are pressed. */ @@ -3460,32 +3802,6 @@ static void primary_selection_device_handle_selection( GWL_PrimarySelection_DataOffer *data_offer = static_cast<GWL_PrimarySelection_DataOffer *>( zwp_primary_selection_offer_v1_get_user_data(id)); primary->data_offer = data_offer; - - auto read_selection_fn = [](GWL_PrimarySelection *primary) { - GHOST_SystemWayland *system = static_cast<GHOST_SystemWayland *>(GHOST_ISystem::getSystem()); - primary->data_offer_mutex.lock(); - - GWL_PrimarySelection_DataOffer *data_offer = primary->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; - } - } - size_t data_len = 0; - const char *data = read_buffer_from_primary_selection_offer( - data_offer, mime_receive.c_str(), &primary->data_offer_mutex, &data_len); - - { - std::lock_guard lock{system_clipboard_mutex}; - GWL_SimpleBuffer *buf = system->clipboard_data(true); - gwl_simple_buffer_set_and_take_ownership(buf, data, data_len); - } - }; - - std::thread read_thread(read_selection_fn, primary); - read_thread.detach(); } static const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = { @@ -3513,14 +3829,19 @@ static void primary_selection_source_send(void *data, GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data); - std::lock_guard lock{primary->data_source_mutex}; - GWL_PrimarySelection_DataSource *data_source = primary->data_source; + auto write_file_fn = [](GWL_PrimarySelection *primary, const int fd) { + if (UNLIKELY(write(fd, + primary->data_source->buffer_out.data, + primary->data_source->buffer_out.data_size) < 0)) { + CLOG_WARN(LOG, "error writing to primary clipboard: %s", std::strerror(errno)); + } + close(fd); + primary->data_source_mutex.unlock(); + }; - const char *const buffer = data_source->buffer_out.data; - if (UNLIKELY(write(fd, buffer, data_source->buffer_out.data_size) < 0)) { - CLOG_WARN(LOG, "error writing to primary clipboard: %s", std::strerror(errno)); - } - close(fd); + primary->data_source_mutex.lock(); + std::thread write_thread(write_file_fn, primary, fd); + write_thread.detach(); } static void primary_selection_source_cancelled(void *data, @@ -3557,7 +3878,7 @@ static void gwl_seat_capability_pointer_enable(GWL_Seat *seat) return; } seat->wl_pointer = wl_seat_get_pointer(seat->wl_seat); - seat->cursor.wl_surface = wl_compositor_create_surface(seat->system->wl_compositor()); + seat->cursor.wl_surface_cursor = wl_compositor_create_surface(seat->system->wl_compositor()); seat->cursor.visible = true; seat->cursor.wl_buffer = nullptr; if (!get_cursor_settings(seat->cursor.theme_name, seat->cursor.theme_size)) { @@ -3566,11 +3887,12 @@ static void gwl_seat_capability_pointer_enable(GWL_Seat *seat) } wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat); - wl_surface_add_listener(seat->cursor.wl_surface, &cursor_surface_listener, seat); - ghost_wl_surface_tag_cursor_pointer(seat->cursor.wl_surface); + wl_surface_add_listener(seat->cursor.wl_surface_cursor, &cursor_surface_listener, seat); + ghost_wl_surface_tag_cursor_pointer(seat->cursor.wl_surface_cursor); zwp_pointer_gestures_v1 *pointer_gestures = seat->system->wp_pointer_gestures(); if (pointer_gestures) { +#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE { /* Hold gesture. */ struct zwp_pointer_gesture_hold_v1 *gesture = zwp_pointer_gestures_v1_get_hold_gesture( pointer_gestures, seat->wl_pointer); @@ -3578,6 +3900,8 @@ static void gwl_seat_capability_pointer_enable(GWL_Seat *seat) zwp_pointer_gesture_hold_v1_add_listener(gesture, &gesture_hold_listener, seat); seat->wp_pointer_gesture_hold = gesture; } +#endif +#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE { /* Pinch gesture. */ struct zwp_pointer_gesture_pinch_v1 *gesture = zwp_pointer_gestures_v1_get_pinch_gesture( pointer_gestures, seat->wl_pointer); @@ -3585,6 +3909,8 @@ static void gwl_seat_capability_pointer_enable(GWL_Seat *seat) zwp_pointer_gesture_pinch_v1_add_listener(gesture, &gesture_pinch_listener, seat); seat->wp_pointer_gesture_pinch = gesture; } +#endif +#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE { /* Swipe gesture. */ struct zwp_pointer_gesture_swipe_v1 *gesture = zwp_pointer_gestures_v1_get_swipe_gesture( pointer_gestures, seat->wl_pointer); @@ -3592,6 +3918,7 @@ static void gwl_seat_capability_pointer_enable(GWL_Seat *seat) zwp_pointer_gesture_swipe_v1_add_listener(gesture, &gesture_swipe_listener, seat); seat->wp_pointer_gesture_swipe = gesture; } +#endif } } @@ -3603,6 +3930,7 @@ static void gwl_seat_capability_pointer_disable(GWL_Seat *seat) zwp_pointer_gestures_v1 *pointer_gestures = seat->system->wp_pointer_gestures(); if (pointer_gestures) { +#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE { /* Hold gesture. */ struct zwp_pointer_gesture_hold_v1 **gesture_p = &seat->wp_pointer_gesture_hold; if (*gesture_p) { @@ -3610,6 +3938,8 @@ static void gwl_seat_capability_pointer_disable(GWL_Seat *seat) *gesture_p = nullptr; } } +#endif +#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE { /* Pinch gesture. */ struct zwp_pointer_gesture_pinch_v1 **gesture_p = &seat->wp_pointer_gesture_pinch; if (*gesture_p) { @@ -3617,6 +3947,8 @@ static void gwl_seat_capability_pointer_disable(GWL_Seat *seat) *gesture_p = nullptr; } } +#endif +#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE { /* Swipe gesture. */ struct zwp_pointer_gesture_swipe_v1 **gesture_p = &seat->wp_pointer_gesture_swipe; if (*gesture_p) { @@ -3624,11 +3956,12 @@ static void gwl_seat_capability_pointer_disable(GWL_Seat *seat) *gesture_p = nullptr; } } +#endif } - if (seat->cursor.wl_surface) { - wl_surface_destroy(seat->cursor.wl_surface); - seat->cursor.wl_surface = nullptr; + if (seat->cursor.wl_surface_cursor) { + wl_surface_destroy(seat->cursor.wl_surface_cursor); + seat->cursor.wl_surface_cursor = nullptr; } if (seat->cursor.wl_theme) { wl_cursor_theme_destroy(seat->cursor.wl_theme); @@ -3680,7 +4013,8 @@ static void gwl_seat_capability_touch_disable(GWL_Seat *seat) } static void seat_handle_capabilities(void *data, - struct wl_seat *wl_seat, + /* Only used in an assert. */ + [[maybe_unused]] struct wl_seat *wl_seat, const uint32_t capabilities) { CLOG_INFO(LOG, @@ -3713,22 +4047,6 @@ static void seat_handle_capabilities(void *data, else { gwl_seat_capability_touch_disable(seat); } - - /* TODO(@campbellbarton): this could be moved out elsewhere. */ - if (seat->system) { - zwp_primary_selection_device_manager_v1 *primary_selection_device_manager = - seat->system->wp_primary_selection_manager(); - if (primary_selection_device_manager) { - if (seat->wp_primary_selection_device == nullptr) { - seat->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device( - primary_selection_device_manager, seat->wl_seat); - - zwp_primary_selection_device_v1_add_listener(seat->wp_primary_selection_device, - &primary_selection_device_listener, - &seat->primary_selection); - } - } - } } static void seat_handle_name(void *data, struct wl_seat * /*wl_seat*/, const char *name) @@ -3937,11 +4255,17 @@ static void output_handle_done(void *data, struct wl_output * /*wl_output*/) static void output_handle_scale(void *data, struct wl_output * /*wl_output*/, const int32_t factor) { CLOG_INFO(LOG, 2, "scale"); - static_cast<GWL_Output *>(data)->scale = factor; + GWL_Output *output = static_cast<GWL_Output *>(data); + output->scale = factor; + GHOST_WindowManager *window_manager = output->system->getWindowManager(); if (window_manager) { for (GHOST_IWindow *iwin : window_manager->getWindows()) { GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(iwin); + const std::vector<GWL_Output *> &outputs = win->outputs(); + if (std::find(outputs.begin(), outputs.end(), output) == outputs.cend()) { + continue; + } win->outputs_changed_update_scale(); } } @@ -4019,100 +4343,573 @@ static struct libdecor_interface libdecor_interface = { static CLG_LogRef LOG_WL_REGISTRY = {"ghost.wl.handle.registry"}; #define LOG (&LOG_WL_REGISTRY) -static void global_handle_add(void *data, - struct wl_registry *wl_registry, - const uint32_t name, - const char *interface, - const uint32_t version) +/* #GWL_Display.wl_compositor */ + +static void gwl_registry_compositor_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + display->wl_compositor = static_cast<wl_compositor *>( + wl_registry_bind(display->wl_registry, params->name, &wl_compositor_interface, 3)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_compositor_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct wl_compositor **value_p = &display->wl_compositor; + wl_compositor_destroy(*value_p); + *value_p = nullptr; +} + +/* #GWL_Display.xdg_decor.shell */ + +static void gwl_registry_xdg_wm_base_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + GWL_XDG_Decor_System &decor = *display->xdg_decor; + decor.shell = static_cast<xdg_wm_base *>( + wl_registry_bind(display->wl_registry, params->name, &xdg_wm_base_interface, 1)); + xdg_wm_base_add_listener(decor.shell, &shell_listener, nullptr); + decor.shell_name = params->name; + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_xdg_wm_base_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + GWL_XDG_Decor_System &decor = *display->xdg_decor; + struct xdg_wm_base **value_p = &decor.shell; + uint32_t *name_p = &decor.shell_name; + xdg_wm_base_destroy(*value_p); + *value_p = nullptr; + *name_p = WL_NAME_UNSET; +} + +/* #GWL_Display.xdg_decor.manager */ + +static void gwl_registry_xdg_decoration_manager_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + GWL_XDG_Decor_System &decor = *display->xdg_decor; + decor.manager = static_cast<zxdg_decoration_manager_v1 *>(wl_registry_bind( + display->wl_registry, params->name, &zxdg_decoration_manager_v1_interface, 1)); + decor.manager_name = params->name; + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_xdg_decoration_manager_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + GWL_XDG_Decor_System &decor = *display->xdg_decor; + struct zxdg_decoration_manager_v1 **value_p = &decor.manager; + uint32_t *name_p = &decor.manager_name; + zxdg_decoration_manager_v1_destroy(*value_p); + *value_p = nullptr; + *name_p = WL_NAME_UNSET; +} + +/* #GWL_Display.xdg_output_manager */ + +static void gwl_registry_xdg_output_manager_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + display->xdg_output_manager = static_cast<zxdg_output_manager_v1 *>( + wl_registry_bind(display->wl_registry, params->name, &zxdg_output_manager_v1_interface, 2)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_xdg_output_manager_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct zxdg_output_manager_v1 **value_p = &display->xdg_output_manager; + zxdg_output_manager_v1_destroy(*value_p); + *value_p = nullptr; +} + +/* #GWL_Display.wl_output */ + +static void gwl_registry_wl_output_add(GWL_Display *display, const GWL_RegisteryAdd_Params *params) { - /* Log last since it can be noted if the interface was handled or not. */ - bool found = true; - - struct GWL_Display *display = static_cast<struct GWL_Display *>(data); - if (STREQ(interface, wl_compositor_interface.name)) { - display->wl_compositor = static_cast<wl_compositor *>( - wl_registry_bind(wl_registry, name, &wl_compositor_interface, 3)); - } - else if (STREQ(interface, xdg_wm_base_interface.name)) { - GWL_XDG_Decor_System &decor = *display->xdg_decor; - decor.shell = static_cast<xdg_wm_base *>( - wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, 1)); - xdg_wm_base_add_listener(decor.shell, &shell_listener, nullptr); - } - else if (STREQ(interface, zxdg_decoration_manager_v1_interface.name)) { - GWL_XDG_Decor_System &decor = *display->xdg_decor; - decor.manager = static_cast<zxdg_decoration_manager_v1 *>( - wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1)); - } - else if (STREQ(interface, zxdg_output_manager_v1_interface.name)) { - display->xdg_output_manager = static_cast<zxdg_output_manager_v1 *>( - wl_registry_bind(wl_registry, name, &zxdg_output_manager_v1_interface, 2)); - for (GWL_Output *output : display->outputs) { + GWL_Output *output = new GWL_Output; + output->system = display->system; + output->wl_output = static_cast<wl_output *>( + wl_registry_bind(display->wl_registry, params->name, &wl_output_interface, 2)); + ghost_wl_output_tag(output->wl_output); + wl_output_set_user_data(output->wl_output, output); + + display->outputs.push_back(output); + wl_output_add_listener(output->wl_output, &output_listener, output); + gwl_registry_entry_add(display, params, static_cast<void *>(output)); +} +static void gwl_registry_wl_output_update(GWL_Display *display, + const GWL_RegisteryUpdate_Params *params) +{ + GWL_Output *output = static_cast<GWL_Output *>(params->user_data); + if (display->xdg_output_manager) { + if (output->xdg_output == nullptr) { 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 (STREQ(interface, wl_output_interface.name)) { - GWL_Output *output = new GWL_Output; - output->wl_output = static_cast<wl_output *>( - wl_registry_bind(wl_registry, name, &wl_output_interface, 2)); - ghost_wl_output_tag(output->wl_output); - wl_output_set_user_data(output->wl_output, output); + else { + output->xdg_output = nullptr; + } +} +static void gwl_registry_wl_output_remove(GWL_Display *display, + void *user_data, + const bool /*on_exit*/) +{ + /* While windows & cursors hold references to outputs, there is no need to manually remove + * these references as the compositor will remove references via #wl_surface_listener.leave. */ + GWL_Output *output = static_cast<GWL_Output *>(user_data); + wl_output_destroy(output->wl_output); + std::vector<GWL_Output *>::iterator iter = std::find( + display->outputs.begin(), display->outputs.end(), output); + const int index = (iter != display->outputs.cend()) ? + std::distance(display->outputs.begin(), iter) : + -1; + GHOST_ASSERT(index != -1, "invalid internal state"); + /* NOTE: always erase even when `on_exit` because `output->xdg_output` is cleared later. */ + display->outputs.erase(display->outputs.begin() + index); + delete output; +} - display->outputs.push_back(output); - wl_output_add_listener(output->wl_output, &output_listener, output); +/* #GWL_Display.seats */ - 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); +static void gwl_registry_wl_seat_add(GWL_Display *display, const GWL_RegisteryAdd_Params *params) +{ + GWL_Seat *seat = new GWL_Seat; + seat->system = display->system; + seat->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + seat->data_source = new GWL_DataSource; + seat->wl_seat = static_cast<wl_seat *>( + wl_registry_bind(display->wl_registry, params->name, &wl_seat_interface, 5)); + display->seats.push_back(seat); + wl_seat_add_listener(seat->wl_seat, &seat_listener, seat); + gwl_registry_entry_add(display, params, static_cast<void *>(seat)); +} +static void gwl_registry_wl_seat_update(GWL_Display *display, + const GWL_RegisteryUpdate_Params *params) +{ + GWL_Seat *seat = static_cast<GWL_Seat *>(params->user_data); + + /* Register data device per seat for IPC between WAYLAND clients. */ + if (display->wl_data_device_manager) { + if (seat->wl_data_device == nullptr) { + seat->wl_data_device = wl_data_device_manager_get_data_device( + display->wl_data_device_manager, seat->wl_seat); + wl_data_device_add_listener(seat->wl_data_device, &data_device_listener, seat); } } - else if (STREQ(interface, wl_seat_interface.name)) { - GWL_Seat *seat = new GWL_Seat; - seat->system = display->system; - seat->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - seat->data_source = new GWL_DataSource; - seat->wl_seat = static_cast<wl_seat *>( - wl_registry_bind(wl_registry, name, &wl_seat_interface, 5)); - display->seats.push_back(seat); - wl_seat_add_listener(seat->wl_seat, &seat_listener, seat); + else { + seat->wl_data_device = nullptr; + } + + if (display->wp_tablet_manager) { + if (seat->wp_tablet_seat == nullptr) { + seat->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(display->wp_tablet_manager, + seat->wl_seat); + zwp_tablet_seat_v2_add_listener(seat->wp_tablet_seat, &tablet_seat_listener, seat); + } + } + else { + seat->wp_tablet_seat = nullptr; + } + + if (display->wp_primary_selection_device_manager) { + if (seat->wp_primary_selection_device == nullptr) { + seat->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device( + display->wp_primary_selection_device_manager, seat->wl_seat); + + zwp_primary_selection_device_v1_add_listener(seat->wp_primary_selection_device, + &primary_selection_device_listener, + &seat->primary_selection); + } + } + else { + seat->wp_primary_selection_device = nullptr; + } +} +static void gwl_registry_wl_seat_remove(GWL_Display *display, void *user_data, const bool on_exit) +{ + GWL_Seat *seat = static_cast<GWL_Seat *>(user_data); + + /* First handle members that require locking. + * While highly unlikely, it's possible they are being used while this function runs. */ + { + std::lock_guard lock{seat->data_source_mutex}; + if (seat->data_source) { + gwl_simple_buffer_free_data(&seat->data_source->buffer_out); + if (seat->data_source->wl_source) { + wl_data_source_destroy(seat->data_source->wl_source); + } + delete seat->data_source; + } } - else if (STREQ(interface, wl_shm_interface.name)) { - display->wl_shm = static_cast<wl_shm *>( - wl_registry_bind(wl_registry, name, &wl_shm_interface, 1)); + + { + std::lock_guard lock{seat->data_offer_dnd_mutex}; + if (seat->data_offer_dnd) { + wl_data_offer_destroy(seat->data_offer_dnd->id); + delete seat->data_offer_dnd; + } } - else if (STREQ(interface, wl_data_device_manager_interface.name)) { - display->wl_data_device_manager = static_cast<wl_data_device_manager *>( - wl_registry_bind(wl_registry, name, &wl_data_device_manager_interface, 3)); + + { + std::lock_guard lock{seat->data_offer_copy_paste_mutex}; + if (seat->data_offer_copy_paste) { + wl_data_offer_destroy(seat->data_offer_copy_paste->id); + delete seat->data_offer_copy_paste; + } } - else if (STREQ(interface, zwp_tablet_manager_v2_interface.name)) { - display->wp_tablet_manager = static_cast<zwp_tablet_manager_v2 *>( - wl_registry_bind(wl_registry, name, &zwp_tablet_manager_v2_interface, 1)); + + { + GWL_PrimarySelection *primary = &seat->primary_selection; + std::lock_guard lock{primary->data_offer_mutex}; + gwl_primary_selection_discard_offer(primary); } - else if (STREQ(interface, zwp_relative_pointer_manager_v1_interface.name)) { - display->wp_relative_pointer_manager = static_cast<zwp_relative_pointer_manager_v1 *>( - wl_registry_bind(wl_registry, name, &zwp_relative_pointer_manager_v1_interface, 1)); + + { + GWL_PrimarySelection *primary = &seat->primary_selection; + std::lock_guard lock{primary->data_source_mutex}; + gwl_primary_selection_discard_source(primary); } - else if (STREQ(interface, zwp_pointer_constraints_v1_interface.name)) { - display->wp_pointer_constraints = static_cast<zwp_pointer_constraints_v1 *>( - wl_registry_bind(wl_registry, name, &zwp_pointer_constraints_v1_interface, 1)); + + if (seat->wp_primary_selection_device) { + zwp_primary_selection_device_v1_destroy(seat->wp_primary_selection_device); } - else if (STREQ(interface, zwp_pointer_gestures_v1_interface.name)) { - display->wp_pointer_gestures = static_cast<zwp_pointer_gestures_v1 *>( - wl_registry_bind(wl_registry, name, &zwp_pointer_gestures_v1_interface, 3)); + + if (seat->wl_data_device) { + wl_data_device_release(seat->wl_data_device); } - else if (!strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name)) { - display->wp_primary_selection_device_manager = - static_cast<zwp_primary_selection_device_manager_v1 *>(wl_registry_bind( - wl_registry, name, &zwp_primary_selection_device_manager_v1_interface, 1)); + if (seat->cursor.custom_data) { + munmap(seat->cursor.custom_data, seat->cursor.custom_data_size); } - else { - found = false; + /* Disable all capabilities as a way to free: + * - `seat.wl_pointer` (and related cursor variables). + * - `seat.wl_touch`. + * - `seat.wl_keyboard`. + */ + gwl_seat_capability_pointer_disable(seat); + gwl_seat_capability_keyboard_disable(seat); + gwl_seat_capability_touch_disable(seat); + + /* Un-referencing checks for NULL case. */ + xkb_state_unref(seat->xkb_state); + xkb_state_unref(seat->xkb_state_empty); + xkb_state_unref(seat->xkb_state_empty_with_shift); + xkb_state_unref(seat->xkb_state_empty_with_numlock); + + xkb_context_unref(seat->xkb_context); + + /* Remove the seat. */ + wl_seat_destroy(seat->wl_seat); + + std::vector<GWL_Seat *>::iterator iter = std::find( + display->seats.begin(), display->seats.end(), seat); + const int index = (iter != display->seats.cend()) ? std::distance(display->seats.begin(), iter) : + -1; + GHOST_ASSERT(index != -1, "invalid internal state"); + + if (!on_exit) { + if (display->seats_active_index >= index) { + display->seats_active_index -= 1; + } + display->seats.erase(display->seats.begin() + index); + } + delete seat; +} + +/* #GWL_Display.wl_shm */ + +static void gwl_registry_wl_shm_add(GWL_Display *display, const GWL_RegisteryAdd_Params *params) +{ + display->wl_shm = static_cast<wl_shm *>( + wl_registry_bind(display->wl_registry, params->name, &wl_shm_interface, 1)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_wl_shm_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct wl_shm **value_p = &display->wl_shm; + wl_shm_destroy(*value_p); + *value_p = nullptr; +} + +/* #GWL_Display.wl_data_device_manager */ + +static void gwl_registry_wl_data_device_manager_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + display->wl_data_device_manager = static_cast<wl_data_device_manager *>( + wl_registry_bind(display->wl_registry, params->name, &wl_data_device_manager_interface, 3)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_wl_data_device_manager_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct wl_data_device_manager **value_p = &display->wl_data_device_manager; + wl_data_device_manager_destroy(*value_p); + *value_p = nullptr; +} + +/* #GWL_Display.wp_tablet_manager */ + +static void gwl_registry_wp_tablet_manager_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + display->wp_tablet_manager = static_cast<zwp_tablet_manager_v2 *>( + wl_registry_bind(display->wl_registry, params->name, &zwp_tablet_manager_v2_interface, 1)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_wp_tablet_manager_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct zwp_tablet_manager_v2 **value_p = &display->wp_tablet_manager; + zwp_tablet_manager_v2_destroy(*value_p); + *value_p = nullptr; +} + +/* #GWL_Display.wp_relative_pointer_manager */ + +static void gwl_registry_wp_relative_pointer_manager_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + display->wp_relative_pointer_manager = static_cast<zwp_relative_pointer_manager_v1 *>( + wl_registry_bind( + display->wl_registry, params->name, &zwp_relative_pointer_manager_v1_interface, 1)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_wp_relative_pointer_manager_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct zwp_relative_pointer_manager_v1 **value_p = &display->wp_relative_pointer_manager; + zwp_relative_pointer_manager_v1_destroy(*value_p); + *value_p = nullptr; +} + +/* #GWL_Display.wp_pointer_constraints */ + +static void gwl_registry_wp_pointer_constraints_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + display->wp_pointer_constraints = static_cast<zwp_pointer_constraints_v1 *>(wl_registry_bind( + display->wl_registry, params->name, &zwp_pointer_constraints_v1_interface, 1)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_wp_pointer_constraints_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct zwp_pointer_constraints_v1 **value_p = &display->wp_pointer_constraints; + zwp_pointer_constraints_v1_destroy(*value_p); + *value_p = nullptr; +} +/* #GWL_Display.wp_pointer_gestures */ + +static void gwl_registry_wp_pointer_gestures_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + display->wp_pointer_gestures = static_cast<zwp_pointer_gestures_v1 *>( + wl_registry_bind(display->wl_registry, params->name, &zwp_pointer_gestures_v1_interface, 3)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_wp_pointer_gestures_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct zwp_pointer_gestures_v1 **value_p = &display->wp_pointer_gestures; + zwp_pointer_gestures_v1_destroy(*value_p); + *value_p = nullptr; +} + +/* #GWL_Display.wp_primary_selection_device_manager */ + +static void gwl_registry_wp_primary_selection_device_manager_add( + struct GWL_Display *display, const GWL_RegisteryAdd_Params *params) +{ + display->wp_primary_selection_device_manager = + static_cast<zwp_primary_selection_device_manager_v1 *>( + wl_registry_bind(display->wl_registry, + params->name, + &zwp_primary_selection_device_manager_v1_interface, + 1)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_wp_primary_selection_device_manager_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct zwp_primary_selection_device_manager_v1 **value_p = + &display->wp_primary_selection_device_manager; + zwp_primary_selection_device_manager_v1_destroy(*value_p); + *value_p = nullptr; +} + +/** + * Map interfaces to initialization functions. + * + * \note This list also defines the order interfaces are removed. + * On exit interface removal runs from last to first to avoid potential bugs + * caused by undefined order of removal. + * + * In general fundamental, low level objects such as the compositor and shared memory + * should be declared earlier and other interfaces that may use them should be declared later. + */ +static const GWL_RegistryHandler gwl_registry_handlers[] = { + /* Low level interfaces. */ + { + &wl_compositor_interface.name, + gwl_registry_compositor_add, + nullptr, + gwl_registry_compositor_remove, + }, + { + &wl_shm_interface.name, + gwl_registry_wl_shm_add, + nullptr, + gwl_registry_wl_shm_remove, + }, + { + &xdg_wm_base_interface.name, + gwl_registry_xdg_wm_base_add, + nullptr, + gwl_registry_xdg_wm_base_remove, + }, + /* Managers. */ + { + &zxdg_decoration_manager_v1_interface.name, + gwl_registry_xdg_decoration_manager_add, + nullptr, + gwl_registry_xdg_decoration_manager_remove, + }, + { + &zxdg_output_manager_v1_interface.name, + gwl_registry_xdg_output_manager_add, + nullptr, + gwl_registry_xdg_output_manager_remove, + }, + { + &wl_data_device_manager_interface.name, + gwl_registry_wl_data_device_manager_add, + nullptr, + gwl_registry_wl_data_device_manager_remove, + }, + { + &zwp_primary_selection_device_manager_v1_interface.name, + gwl_registry_wp_primary_selection_device_manager_add, + nullptr, + gwl_registry_wp_primary_selection_device_manager_remove, + }, + { + &zwp_tablet_manager_v2_interface.name, + gwl_registry_wp_tablet_manager_add, + nullptr, + gwl_registry_wp_tablet_manager_remove, + }, + { + &zwp_relative_pointer_manager_v1_interface.name, + gwl_registry_wp_relative_pointer_manager_add, + nullptr, + gwl_registry_wp_relative_pointer_manager_remove, + }, + /* Higher level interfaces. */ + { + &zwp_pointer_constraints_v1_interface.name, + gwl_registry_wp_pointer_constraints_add, + nullptr, + gwl_registry_wp_pointer_constraints_remove, + }, + { + &zwp_pointer_gestures_v1_interface.name, + gwl_registry_wp_pointer_gestures_add, + nullptr, + gwl_registry_wp_pointer_gestures_remove, + }, + /* Display outputs. */ + { + &wl_output_interface.name, + gwl_registry_wl_output_add, + gwl_registry_wl_output_update, + gwl_registry_wl_output_remove, + }, + /* Seats. + * Keep the seat near the end to ensure other types are created first. + * as the seat creates data based on other interfaces. */ + { + &wl_seat_interface.name, + gwl_registry_wl_seat_add, + gwl_registry_wl_seat_update, + gwl_registry_wl_seat_remove, + }, + {nullptr, nullptr, nullptr}, +}; + +/** + * Workaround for `gwl_registry_handlers` order of declaration, + * preventing `ARRAY_SIZE(gwl_registry_handlers) - 1` being used. + */ +static int gwl_registry_handler_interface_slot_max() +{ + return ARRAY_SIZE(gwl_registry_handlers) - 1; +} + +static int gwl_registry_handler_interface_slot_from_string(const char *interface) +{ + for (const GWL_RegistryHandler *handler = gwl_registry_handlers; handler->interface_p != nullptr; + handler++) { + if (STREQ(interface, *handler->interface_p)) { + return int(handler - gwl_registry_handlers); + } + } + return -1; +} + +static const GWL_RegistryHandler *gwl_registry_handler_from_interface_slot(int interface_slot) +{ + GHOST_ASSERT(uint32_t(interface_slot) < uint32_t(gwl_registry_handler_interface_slot_max()), + "Index out of range"); + return &gwl_registry_handlers[interface_slot]; +} + +static void global_handle_add(void *data, + [[maybe_unused]] struct wl_registry *wl_registry, + const uint32_t name, + const char *interface, + const uint32_t version) +{ + /* Log last since it's useful to know if the interface was handled or not. */ + GWL_Display *display = static_cast<GWL_Display *>(data); + GHOST_ASSERT(display->wl_registry == wl_registry, "Registry argument must match!"); + + const int interface_slot = gwl_registry_handler_interface_slot_from_string(interface); + bool added = false; + + if (interface_slot != -1) { + const GWL_RegistryHandler *handler = &gwl_registry_handlers[interface_slot]; + const GWL_RegistryEntry *registry_entry_prev = display->registry_entry; + + /* The interface name that is ensured not to be freed. */ + GWL_RegisteryAdd_Params params = { + .name = name, + .interface_slot = interface_slot, + .version = version, + }; + + handler->add_fn(display, ¶ms); + + added = display->registry_entry != registry_entry_prev; + } + else { + /* Not found. */ #ifdef USE_GNOME_NEEDS_LIBDECOR_HACK if (STRPREFIX(interface, "gtk_shell")) { /* `gtk_shell1` at time of writing. */ /* Only require `libdecor` when built with X11 support, @@ -4125,10 +4922,18 @@ static void global_handle_add(void *data, CLOG_INFO(LOG, 2, "add %s(interface=%s, version=%u, name=%u)", - found ? "" : "(skipped), ", + (interface_slot != -1) ? (added ? "" : "(found but not added)") : "(skipped), ", interface, version, name); + + /* Initialization avoids excessive calls by calling update after all have been initialized. */ + if (added) { + if (display->registry_skip_update_all == false) { + /* See doc-string for rationale on updating all on add/removal. */ + gwl_registry_entry_update_all(display, interface_slot); + } + } } /** @@ -4140,11 +4945,28 @@ static void global_handle_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_handle_remove(void * /*data*/, - struct wl_registry * /*wl_registry*/, +static void global_handle_remove(void *data, + [[maybe_unused]] struct wl_registry *wl_registry, const uint32_t name) { - CLOG_INFO(LOG, 2, "remove (name=%u)", name); + GWL_Display *display = static_cast<GWL_Display *>(data); + GHOST_ASSERT(display->wl_registry == wl_registry, "Registry argument must match!"); + + int interface_slot = 0; + const bool removed = gwl_registry_entry_remove_by_name(display, name, &interface_slot); + + CLOG_INFO(LOG, + 2, + "remove (name=%u, interface=%s)", + name, + removed ? *gwl_registry_handlers[interface_slot].interface_p : "(unknown)"); + + if (removed) { + if (display->registry_skip_update_all == false) { + /* See doc-string for rationale on updating all on add/removal. */ + gwl_registry_entry_update_all(display, interface_slot); + } + } } static const struct wl_registry_listener registry_listener = { @@ -4162,7 +4984,8 @@ static const struct wl_registry_listener registry_listener = { * WAYLAND specific implementation of the #GHOST_System interface. * \{ */ -GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), display_(new GWL_Display) +GHOST_SystemWayland::GHOST_SystemWayland(bool background) + : GHOST_System(), display_(new GWL_Display) { wl_log_set_handler_client(ghost_wayland_log_handler); @@ -4170,7 +4993,7 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), display_(new GWL_Di /* Connect to the Wayland server. */ display_->wl_display = wl_display_connect(nullptr); if (!display_->wl_display) { - display_destroy(display_); + gwl_display_destroy(display_); throw std::runtime_error("Wayland: unable to connect to display!"); } @@ -4178,17 +5001,33 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), display_(new GWL_Di display_->xdg_decor = new GWL_XDG_Decor_System; /* Register interfaces. */ - struct wl_registry *registry = wl_display_get_registry(display_->wl_display); - wl_registry_add_listener(registry, ®istry_listener, display_); - /* Call callback for registry listener. */ - wl_display_roundtrip(display_->wl_display); - /* Call callbacks for registered listeners. */ - wl_display_roundtrip(display_->wl_display); - wl_registry_destroy(registry); + { + display_->registry_skip_update_all = true; + struct wl_registry *registry = wl_display_get_registry(display_->wl_display); + display_->wl_registry = registry; + wl_registry_add_listener(registry, ®istry_listener, display_); + /* First round-trip to receive all registry objects. */ + wl_display_roundtrip(display_->wl_display); + /* Second round-trip to receive all output events. */ + wl_display_roundtrip(display_->wl_display); + + /* Account for dependencies between interfaces. */ + gwl_registry_entry_update_all(display_, -1); + + display_->registry_skip_update_all = false; + } #ifdef WITH_GHOST_WAYLAND_LIBDECOR + /* Ignore windowing requirements when running in background mode, + * as it doesn't make sense to fall back to X11 because of windowing functionality + * in background mode, also LIBDECOR is crashing in background mode `blender -b -f 1` + * for e.g. while it could be fixed, requiring the library at all makes no sense . */ + if (background) { + display_->libdecor_required = false; + } + if (display_->libdecor_required) { - gwl_xdg_decor_system_destroy(display_->xdg_decor); + gwl_xdg_decor_system_destroy(display_, display_->xdg_decor); display_->xdg_decor = nullptr; if (!has_libdecor) { @@ -4198,8 +5037,10 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), display_(new GWL_Di "WAYLAND found but libdecor was not, install libdecor for Wayland support, " "falling back to X11\n"); # endif - display_destroy(display_); + gwl_display_destroy(display_); throw std::runtime_error("Wayland: unable to find libdecor!"); + + use_libdecor = true; } } else { @@ -4213,7 +5054,7 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), display_(new GWL_Di GWL_LibDecor_System &decor = *display_->libdecor; decor.context = libdecor_new(display_->wl_display, &libdecor_interface); if (!decor.context) { - display_destroy(display_); + gwl_display_destroy(display_); throw std::runtime_error("Wayland: unable to create window decorations!"); } } @@ -4222,32 +5063,15 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), display_(new GWL_Di { GWL_XDG_Decor_System &decor = *display_->xdg_decor; if (!decor.shell) { - display_destroy(display_); + gwl_display_destroy(display_); throw std::runtime_error("Wayland: unable to access xdg_shell!"); } } - - /* Register data device per seat for IPC between Wayland clients. */ - if (display_->wl_data_device_manager) { - for (GWL_Seat *seat : display_->seats) { - seat->wl_data_device = wl_data_device_manager_get_data_device( - display_->wl_data_device_manager, seat->wl_seat); - wl_data_device_add_listener(seat->wl_data_device, &data_device_listener, seat); - } - } - - if (display_->wp_tablet_manager) { - for (GWL_Seat *seat : display_->seats) { - seat->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(display_->wp_tablet_manager, - seat->wl_seat); - zwp_tablet_seat_v2_add_listener(seat->wp_tablet_seat, &tablet_seat_listener, seat); - } - } } GHOST_SystemWayland::~GHOST_SystemWayland() { - display_destroy(display_); + gwl_display_destroy(display_); } GHOST_TSuccess GHOST_SystemWayland::init() @@ -4302,12 +5126,11 @@ bool GHOST_SystemWayland::setConsoleWindowState(GHOST_TConsoleWindowState /*acti GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) const { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - GWL_Seat *seat = display_->seats[0]; - const xkb_mod_mask_t state = xkb_state_serialize_mods(seat->xkb_state, XKB_STATE_MODS_DEPRESSED); bool show_warning = true; @@ -4364,11 +5187,11 @@ GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) co GHOST_TSuccess GHOST_SystemWayland::getButtons(GHOST_Buttons &buttons) const { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - GWL_Seat *seat = display_->seats[0]; - GWL_SeatStatePointer *seat_state_pointer = seat_state_pointer_active(seat); + GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_active(seat); if (!seat_state_pointer) { return GHOST_kFailure; } @@ -4377,13 +5200,131 @@ GHOST_TSuccess GHOST_SystemWayland::getButtons(GHOST_Buttons &buttons) const return GHOST_kSuccess; } -char *GHOST_SystemWayland::getClipboard(bool selection) const +/** + * Return a mime type which is supported by GHOST and exists in `types` + * (defined by the data offer). + */ +static const char *system_clipboard_text_mime_type( + const std::unordered_set<std::string> &data_offer_types) +{ + const char *ghost_supported_types[] = {ghost_wl_mime_text_utf8, ghost_wl_mime_text_plain}; + for (size_t i = 0; i < ARRAY_SIZE(ghost_supported_types); i++) { + if (data_offer_types.count(ghost_supported_types[i])) { + return ghost_supported_types[i]; + } + } + return nullptr; +} + +static char *system_clipboard_get_primary_selection(GWL_Display *display) { - const GWL_SimpleBuffer *buf = clipboard_data(selection); - if (buf->data == nullptr) { + GWL_Seat *seat = gwl_display_seat_active_get(display); + if (UNLIKELY(!seat)) { return nullptr; } - return gwl_simple_buffer_as_string(buf); + GWL_PrimarySelection *primary = &seat->primary_selection; + std::mutex &mutex = primary->data_offer_mutex; + + mutex.lock(); + bool mutex_locked = true; + char *data = nullptr; + + GWL_PrimarySelection_DataOffer *data_offer = primary->data_offer; + if (data_offer != nullptr) { + const char *mime_receive = system_clipboard_text_mime_type(data_offer->types); + if (mime_receive) { + /* Receive the clipboard in a thread, performing round-trips while waiting. + * This is needed so pasting contents from our own `primary->data_source` doesn't hang. */ + struct ThreadResult { + char *data = nullptr; + std::atomic<bool> done = false; + } thread_result; + auto read_clipboard_fn = [](GWL_PrimarySelection_DataOffer *data_offer, + const char *mime_receive, + std::mutex *mutex, + struct ThreadResult *thread_result) { + size_t data_len = 0; + thread_result->data = read_buffer_from_primary_selection_offer( + data_offer, mime_receive, mutex, true, &data_len); + thread_result->done = true; + }; + std::thread read_thread(read_clipboard_fn, data_offer, mime_receive, &mutex, &thread_result); + read_thread.detach(); + + while (!thread_result.done) { + wl_display_roundtrip(display->wl_display); + } + data = thread_result.data; + + /* Reading the data offer unlocks the mutex. */ + mutex_locked = false; + } + } + if (mutex_locked) { + mutex.unlock(); + } + return data; +} + +static char *system_clipboard_get(GWL_Display *display) +{ + GWL_Seat *seat = gwl_display_seat_active_get(display); + if (UNLIKELY(!seat)) { + return nullptr; + } + std::mutex &mutex = seat->data_offer_copy_paste_mutex; + + mutex.lock(); + bool mutex_locked = true; + char *data = nullptr; + + GWL_DataOffer *data_offer = seat->data_offer_copy_paste; + if (data_offer != nullptr) { + const char *mime_receive = system_clipboard_text_mime_type(data_offer->types); + if (mime_receive) { + /* Receive the clipboard in a thread, performing round-trips while waiting. + * This is needed so pasting contents from our own `seat->data_source` doesn't hang. */ + struct ThreadResult { + char *data = nullptr; + std::atomic<bool> done = false; + } thread_result; + auto read_clipboard_fn = [](GWL_DataOffer *data_offer, + const char *mime_receive, + std::mutex *mutex, + struct ThreadResult *thread_result) { + size_t data_len = 0; + thread_result->data = read_buffer_from_data_offer( + data_offer, mime_receive, mutex, true, &data_len); + thread_result->done = true; + }; + std::thread read_thread(read_clipboard_fn, data_offer, mime_receive, &mutex, &thread_result); + read_thread.detach(); + + while (!thread_result.done) { + wl_display_roundtrip(display->wl_display); + } + data = thread_result.data; + + /* Reading the data offer unlocks the mutex. */ + mutex_locked = false; + } + } + if (mutex_locked) { + mutex.unlock(); + } + return data; +} + +char *GHOST_SystemWayland::getClipboard(bool selection) const +{ + char *data = nullptr; + if (selection) { + data = system_clipboard_get_primary_selection(display_); + } + else { + data = system_clipboard_get(display_); + } + return data; } static void system_clipboard_put_primary_selection(GWL_Display *display, const char *buffer) @@ -4391,7 +5332,10 @@ static void system_clipboard_put_primary_selection(GWL_Display *display, const c if (!display->wp_primary_selection_device_manager) { return; } - GWL_Seat *seat = display->seats[0]; + GWL_Seat *seat = gwl_display_seat_active_get(display); + if (UNLIKELY(!seat)) { + return; + } GWL_PrimarySelection *primary = &seat->primary_selection; std::lock_guard lock{primary->data_source_mutex}; @@ -4410,8 +5354,8 @@ static void system_clipboard_put_primary_selection(GWL_Display *display, const c zwp_primary_selection_source_v1_add_listener( data_source->wp_source, &primary_selection_source_listener, primary); - for (const std::string &type : mime_send) { - zwp_primary_selection_source_v1_offer(data_source->wp_source, type.c_str()); + for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_send); i++) { + zwp_primary_selection_source_v1_offer(data_source->wp_source, ghost_wl_mime_send[i]); } if (seat->wp_primary_selection_device) { @@ -4422,8 +5366,13 @@ static void system_clipboard_put_primary_selection(GWL_Display *display, const c static void system_clipboard_put(GWL_Display *display, const char *buffer) { - GWL_Seat *seat = display->seats[0]; - + if (!display->wl_data_device_manager) { + return; + } + GWL_Seat *seat = gwl_display_seat_active_get(display); + if (UNLIKELY(!seat)) { + return; + } std::lock_guard lock{seat->data_source_mutex}; GWL_DataSource *data_source = seat->data_source; @@ -4436,8 +5385,8 @@ static void system_clipboard_put(GWL_Display *display, const char *buffer) wl_data_source_add_listener(data_source->wl_source, &data_source_listener, seat); - for (const std::string &type : mime_send) { - wl_data_source_offer(data_source->wl_source, type.c_str()); + for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_send); i++) { + wl_data_source_offer(data_source->wl_source, ghost_wl_mime_send[i]); } if (seat->wl_data_device) { @@ -4448,10 +5397,6 @@ static void system_clipboard_put(GWL_Display *display, const char *buffer) void GHOST_SystemWayland::putClipboard(const char *buffer, bool selection) const { - if (UNLIKELY(!display_->wl_data_device_manager || display_->seats.empty())) { - return; - } - if (selection) { system_clipboard_put_primary_selection(display_, buffer); } @@ -4504,12 +5449,12 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorPositionClientRelative(const GHOST_ int32_t &x, int32_t &y) const { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - GWL_Seat *seat = display_->seats[0]; - GWL_SeatStatePointer *seat_state_pointer = seat_state_pointer_active(seat); - if (!seat_state_pointer || !seat_state_pointer->wl_surface) { + GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_active(seat); + if (!seat_state_pointer || !seat_state_pointer->wl_surface_window) { return GHOST_kFailure; } const GHOST_WindowWayland *win = static_cast<const GHOST_WindowWayland *>(window); @@ -4520,26 +5465,26 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorPositionClientRelative(GHOST_IWindo const int32_t x, const int32_t y) { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - GWL_Seat *seat = display_->seats[0]; GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(window); return setCursorPositionClientRelative_impl(seat, win, x, y); } GHOST_TSuccess GHOST_SystemWayland::getCursorPosition(int32_t &x, int32_t &y) const { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - GWL_Seat *seat = display_->seats[0]; - GWL_SeatStatePointer *seat_state_pointer = seat_state_pointer_active(seat); + GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_active(seat); if (!seat_state_pointer) { return GHOST_kFailure; } - if (wl_surface *wl_surface_focus = seat_state_pointer->wl_surface) { + if (wl_surface *wl_surface_focus = seat_state_pointer->wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); return getCursorPositionClientRelative_impl(seat_state_pointer, win, x, y); } @@ -4548,14 +5493,14 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorPosition(int32_t &x, int32_t &y) co GHOST_TSuccess GHOST_SystemWayland::setCursorPosition(const int32_t x, const int32_t y) { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - GWL_Seat *seat = display_->seats[0]; /* Intentionally different from `getCursorPosition` which supports both tablet & pointer. * In the case of setting the cursor location, tablets don't support this. */ - if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); return setCursorPositionClientRelative_impl(seat, win, x, y); } @@ -4684,10 +5629,6 @@ GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title, const GHOST_IWindow *parentWindow) { /* Globally store pointer to window manager. */ - if (!window_manager) { - window_manager = getWindowManager(); - } - GHOST_WindowWayland *window = new GHOST_WindowWayland( this, title, @@ -4733,7 +5674,7 @@ static void cursor_buffer_show(const GWL_Seat *seat) const int32_t hotspot_y = int32_t(cursor->wl_image.hotspot_y) / scale; if (seat->wl_pointer) { wl_pointer_set_cursor( - seat->wl_pointer, seat->pointer.serial, cursor->wl_surface, hotspot_x, hotspot_y); + seat->wl_pointer, seat->pointer.serial, cursor->wl_surface_cursor, hotspot_x, hotspot_y); } } @@ -4749,6 +5690,9 @@ static void cursor_buffer_show(const GWL_Seat *seat) tablet_tool->wl_surface_cursor, hotspot_x, hotspot_y); +#ifdef USE_KDE_TABLET_HIDDEN_CURSOR_HACK + wl_surface_commit(tablet_tool->wl_surface_cursor); +#endif } } } @@ -4814,10 +5758,10 @@ static void cursor_buffer_set(const GWL_Seat *seat, wl_buffer *buffer) wl_image, cursor->is_custom ? cursor->custom_scale : seat->pointer.theme_scale); const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale; const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale; - cursor_buffer_set_surface_impl(seat, buffer, cursor->wl_surface, scale); + cursor_buffer_set_surface_impl(seat, buffer, cursor->wl_surface_cursor, scale); wl_pointer_set_cursor(seat->wl_pointer, seat->pointer.serial, - visible ? cursor->wl_surface : nullptr, + visible ? cursor->wl_surface_cursor : nullptr, hotspot_x, hotspot_y); } @@ -4903,15 +5847,15 @@ static bool cursor_is_software(const GHOST_TGrabCursorMode mode, const bool use_ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(const GHOST_TStandardCursor shape) { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - auto cursor_find = cursors.find(shape); - const char *cursor_name = (cursor_find == cursors.end()) ? - cursors.at(GHOST_kStandardCursorDefault) : + auto cursor_find = ghost_wl_cursors.find(shape); + const char *cursor_name = (cursor_find == ghost_wl_cursors.end()) ? + ghost_wl_cursors.at(GHOST_kStandardCursorDefault) : (*cursor_find).second; - GWL_Seat *seat = display_->seats[0]; GWL_Cursor *cursor = &seat->cursor; if (!cursor->wl_theme) { @@ -4945,8 +5889,8 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(const GHOST_TStandardCursor s GHOST_TSuccess GHOST_SystemWayland::hasCursorShape(const GHOST_TStandardCursor cursorShape) { - auto cursor_find = cursors.find(cursorShape); - if (cursor_find == cursors.end()) { + auto cursor_find = ghost_wl_cursors.find(cursorShape); + if (cursor_find == ghost_wl_cursors.end()) { return GHOST_kFailure; } const char *value = (*cursor_find).second; @@ -4964,12 +5908,12 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, const int hotY, const bool /*canInvertColor*/) { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - GWL_Cursor *cursor = &display_->seats[0]->cursor; - + GWL_Cursor *cursor = &seat->cursor; if (cursor->custom_data) { munmap(cursor->custom_data, cursor->custom_data_size); cursor->custom_data = nullptr; @@ -5027,14 +5971,19 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, cursor->wl_image.hotspot_x = uint32_t(hotX); cursor->wl_image.hotspot_y = uint32_t(hotY); - cursor_buffer_set(display_->seats[0], buffer); + cursor_buffer_set(seat, buffer); return GHOST_kSuccess; } GHOST_TSuccess GHOST_SystemWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitmap) { - GWL_Cursor *cursor = &display_->seats[0]->cursor; + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { + return GHOST_kFailure; + } + + GWL_Cursor *cursor = &seat->cursor; if (cursor->custom_data == nullptr) { return GHOST_kFailure; } @@ -5055,11 +6004,11 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitma GHOST_TSuccess GHOST_SystemWayland::setCursorVisibility(const bool visible) { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - GWL_Seat *seat = display_->seats[0]; cursor_visible_set(seat, visible, seat->cursor.is_hardware, CURSOR_VISIBLE_ALWAYS_SET); return GHOST_kSuccess; } @@ -5079,12 +6028,12 @@ bool GHOST_SystemWayland::supportsWindowPosition() bool GHOST_SystemWayland::getCursorGrabUseSoftwareDisplay(const GHOST_TGrabCursorMode mode) { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return false; } #ifdef USE_GNOME_CONFINE_HACK - GWL_Seat *seat = display_->seats[0]; const bool use_software_confine = seat->use_pointer_software_confine; #else const bool use_software_confine = false; @@ -5272,6 +6221,11 @@ GHOST_WindowWayland *ghost_wl_surface_user_data(struct wl_surface *wl_surface) * Functionality only used for the WAYLAND implementation. * \{ */ +void GHOST_SystemWayland::seat_active_set(const struct GWL_Seat *seat) +{ + gwl_display_seat_active_set(display_, seat); +} + void GHOST_SystemWayland::window_surface_unref(const wl_surface *wl_surface) { #define SURFACE_CLEAR_PTR(surface_test) \ @@ -5282,10 +6236,10 @@ void GHOST_SystemWayland::window_surface_unref(const wl_surface *wl_surface) /* Only clear window surfaces (not cursors, off-screen surfaces etc). */ for (GWL_Seat *seat : display_->seats) { - SURFACE_CLEAR_PTR(seat->pointer.wl_surface); - SURFACE_CLEAR_PTR(seat->tablet.wl_surface); - SURFACE_CLEAR_PTR(seat->keyboard.wl_surface); - SURFACE_CLEAR_PTR(seat->wl_surface_focus_dnd); + SURFACE_CLEAR_PTR(seat->pointer.wl_surface_window); + SURFACE_CLEAR_PTR(seat->tablet.wl_surface_window); + SURFACE_CLEAR_PTR(seat->keyboard.wl_surface_window); + SURFACE_CLEAR_PTR(seat->wl_surface_window_focus_dnd); } #undef SURFACE_CLEAR_PTR } @@ -5303,7 +6257,8 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod return GHOST_kFailure; } - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } /* No change, success. */ @@ -5311,8 +6266,6 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod return GHOST_kSuccess; } - GWL_Seat *seat = display_->seats[0]; - #ifdef USE_GNOME_CONFINE_HACK const bool was_software_confine = seat->use_pointer_software_confine; const bool use_software_confine = setCursorGrab_use_software_confine(mode, wl_surface); @@ -5473,11 +6426,6 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod return GHOST_kSuccess; } -struct GWL_SimpleBuffer *GHOST_SystemWayland::clipboard_data(bool selection) const -{ - return selection ? &display_->clipboard_primary : &display_->clipboard; -} - #ifdef WITH_GHOST_WAYLAND_LIBDECOR bool GHOST_SystemWayland::use_libdecor_runtime() { |