diff options
author | Joseph Eagar <joeedh@gmail.com> | 2022-10-23 02:33:47 +0300 |
---|---|---|
committer | Joseph Eagar <joeedh@gmail.com> | 2022-10-23 02:33:47 +0300 |
commit | fe7088c99ff084f7218ab20136d49eb1a1dccd0b (patch) | |
tree | 3b3df4270110bbffa4a738e379dce5319fec9653 /intern/ghost/intern/GHOST_SystemWayland.cpp | |
parent | b5a69061e7d464914662ae0dd6fed46a999a56bb (diff) | |
parent | b70bbfadfecec049ad1ac2de7a949198ca6c15bc (diff) |
Merge remote-tracking branch 'origin' into temp-sculpt-brush-channel
Diffstat (limited to 'intern/ghost/intern/GHOST_SystemWayland.cpp')
-rw-r--r-- | intern/ghost/intern/GHOST_SystemWayland.cpp | 1577 |
1 files changed, 1324 insertions, 253 deletions
diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 04c7e103bde..442e51d4f7c 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -10,6 +10,7 @@ #include "GHOST_EventCursor.h" #include "GHOST_EventDragnDrop.h" #include "GHOST_EventKey.h" +#include "GHOST_EventTrackpad.h" #include "GHOST_EventWheel.h" #include "GHOST_PathUtils.h" #include "GHOST_TimerManager.h" @@ -50,6 +51,8 @@ /* Generated by `wayland-scanner`. */ #include <pointer-constraints-unstable-v1-client-protocol.h> +#include <pointer-gestures-unstable-v1-client-protocol.h> +#include <primary-selection-unstable-v1-client-protocol.h> #include <relative-pointer-unstable-v1-client-protocol.h> #include <tablet-unstable-v2-client-protocol.h> #include <xdg-output-unstable-v1-client-protocol.h> @@ -82,6 +85,10 @@ static void keyboard_handle_key_repeat_cancel(struct GWL_Seat *seat); static void output_handle_done(void *data, struct wl_output *wl_output); +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); + /* -------------------------------------------------------------------- */ /** \name Local Defines * @@ -116,6 +123,8 @@ static bool use_gnome_confine_hack = false; * This define could be removed without changing any functionality, * it just means GNOME users will see verbose warning messages that alert them about * a known problem that needs to be fixed up-stream. + * + * This has been fixed for GNOME 43. Keep the workaround until support for gnome 42 is dropped. * See: https://gitlab.gnome.org/GNOME/mutter/-/issues/2457 */ #define USE_GNOME_KEYBOARD_SUPPRESS_WARNING @@ -140,8 +149,7 @@ static bool use_gnome_confine_hack = false; * \{ */ /** - * The event codes are used to - * to differentiate from which mouse button an event comes from. + * The event codes are used to differentiate from which mouse button an event comes from. */ #define BTN_LEFT 0x110 #define BTN_RIGHT 0x111 @@ -190,48 +198,93 @@ struct GWL_ModifierInfo { }; static const GWL_ModifierInfo g_modifier_info_table[MOD_INDEX_NUM] = { - [MOD_INDEX_SHIFT] = - { - .display_name = "Shift", - .xkb_id = XKB_MOD_NAME_SHIFT, - .key_l = GHOST_kKeyLeftShift, - .key_r = GHOST_kKeyRightShift, - .mod_l = GHOST_kModifierKeyLeftShift, - .mod_r = GHOST_kModifierKeyRightShift, - }, - [MOD_INDEX_ALT] = - { - .display_name = "Alt", - .xkb_id = XKB_MOD_NAME_ALT, - .key_l = GHOST_kKeyLeftAlt, - .key_r = GHOST_kKeyRightAlt, - .mod_l = GHOST_kModifierKeyLeftAlt, - .mod_r = GHOST_kModifierKeyRightAlt, - }, - [MOD_INDEX_CTRL] = - { - .display_name = "Control", - .xkb_id = XKB_MOD_NAME_CTRL, - .key_l = GHOST_kKeyLeftControl, - .key_r = GHOST_kKeyRightControl, - .mod_l = GHOST_kModifierKeyLeftControl, - .mod_r = GHOST_kModifierKeyRightControl, - }, - [MOD_INDEX_OS] = - { - .display_name = "OS", - .xkb_id = XKB_MOD_NAME_LOGO, - .key_l = GHOST_kKeyLeftOS, - .key_r = GHOST_kKeyRightOS, - .mod_l = GHOST_kModifierKeyLeftOS, - .mod_r = GHOST_kModifierKeyRightOS, - }, + /* MOD_INDEX_SHIFT */ + { + /* display_name */ "Shift", + /* xkb_id */ XKB_MOD_NAME_SHIFT, + /* key_l */ GHOST_kKeyLeftShift, + /* key_r */ GHOST_kKeyRightShift, + /* mod_l */ GHOST_kModifierKeyLeftShift, + /* mod_r */ GHOST_kModifierKeyRightShift, + }, + /* MOD_INDEX_ALT */ + { + /* display_name */ "Alt", + /* xkb_id */ XKB_MOD_NAME_ALT, + /* key_l */ GHOST_kKeyLeftAlt, + /* key_r */ GHOST_kKeyRightAlt, + /* mod_l */ GHOST_kModifierKeyLeftAlt, + /* mod_r */ GHOST_kModifierKeyRightAlt, + }, + /* MOD_INDEX_CTRL */ + { + /* display_name */ "Control", + /* xkb_id */ XKB_MOD_NAME_CTRL, + /* key_l */ GHOST_kKeyLeftControl, + /* key_r */ GHOST_kKeyRightControl, + /* mod_l */ GHOST_kModifierKeyLeftControl, + /* mod_r */ GHOST_kModifierKeyRightControl, + }, + /* MOD_INDEX_OS */ + { + /* display_name */ "OS", + /* xkb_id */ XKB_MOD_NAME_LOGO, + /* key_l */ GHOST_kKeyLeftOS, + /* key_r */ GHOST_kKeyRightOS, + /* mod_l */ GHOST_kModifierKeyLeftOS, + /* mod_r */ GHOST_kModifierKeyRightOS, + }, }; /** \} */ /* -------------------------------------------------------------------- */ -/** \name Private Types & Defines +/** \name Internal #GWL_SimpleBuffer Type + * \{ */ + +struct GWL_SimpleBuffer { + /** Constant data, but may be freed. */ + const char *data = nullptr; + size_t data_size = 0; +}; + +static void gwl_simple_buffer_free_data(GWL_SimpleBuffer *buffer) +{ + free(const_cast<char *>(buffer->data)); + buffer->data = nullptr; + 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)); + buffer->data_size = strlen(str); + char *data = static_cast<char *>(malloc(buffer->data_size)); + std::memcpy(data, str, buffer->data_size); + 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; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_Cursor Type * \{ */ /** @@ -249,16 +302,26 @@ struct GWL_Cursor { * the hardware cursor is used. */ 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_buffer *wl_buffer = nullptr; struct wl_cursor_image wl_image = {0}; struct wl_cursor_theme *wl_theme = nullptr; void *custom_data = nullptr; + /** The size of `custom_data` in bytes. */ size_t custom_data_size = 0; - int size = 0; + /** + * The name of the theme (loaded by DBUS, depends on #WITH_GHOST_WAYLAND_DBUS). + * When disabled, leave as an empty string and the default theme will be used. + */ std::string theme_name; - + /** + * The size of the cursor (when looking up a cursor theme). + * This must be scaled by the maximum output scale when passing to wl_cursor_theme_load. + * See #update_cursor_scale. + * */ + int theme_size = 0; int custom_scale = 1; }; @@ -269,6 +332,7 @@ struct GWL_Cursor { */ struct GWL_TabletTool { struct GWL_Seat *seat = nullptr; + /** Tablets have a separate cursor to the 'pointer', this surface is used for cursor drawing. */ struct wl_surface *wl_surface_cursor = nullptr; /** Used to delay clearing tablet focused wl_surface until the frame is handled. */ bool proximity = false; @@ -276,23 +340,51 @@ struct GWL_TabletTool { GHOST_TabletData data = GHOST_TABLET_DATA_NONE; }; +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_DataOffer Type + * \{ */ + +/** + * Data storage used for clipboard paste & drag-and-drop. + */ struct GWL_DataOffer { - std::unordered_set<std::string> types; - uint32_t source_actions = 0; - uint32_t dnd_action = 0; struct wl_data_offer *id = nullptr; + std::unordered_set<std::string> types; std::atomic<bool> in_use = false; + struct { + /** + * 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 + * \note when dragging begins. Currently ghost doesn't make use of these. + */ + enum wl_data_device_manager_dnd_action source_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + enum wl_data_device_manager_dnd_action action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; /** Compatible with #GWL_Seat.xy coordinates. */ wl_fixed_t xy[2] = {0, 0}; } dnd; }; +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_DataSource Type + * \{ */ + struct GWL_DataSource { - struct wl_data_source *data_source = nullptr; - char *buffer_out = nullptr; + struct wl_data_source *wl_source = nullptr; + GWL_SimpleBuffer buffer_out; }; +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_Seat Type (#wl_seat wrapper & associated types) + * \{ */ + /** * Data used to implement client-side key-repeat. * @@ -358,6 +450,52 @@ struct GWL_SeatStatePointer { }; /** + * Scroll state, applying to pointer (not tablet) events. + * Otherwise this would be part of #GWL_SeatStatePointer. + */ +struct GWL_SeatStatePointerScroll { + /** Smooth scrolling (handled & reset with pointer "frame" callback). */ + wl_fixed_t smooth_xy[2] = {0, 0}; + /** Discrete scrolling (handled & reset with pointer "frame" callback). */ + int32_t discrete_xy[2] = {0, 0}; + /** The source of scroll event. */ + enum wl_pointer_axis_source axis_source = WL_POINTER_AXIS_SOURCE_WHEEL; +}; + +/** + * Utility struct to access rounded values from a scaled `wl_fixed_t`, + * without loosing information. + * + * As the rounded result is rounded to a lower precision integer, + * the high precision value is accumulated and converted to an integer to + * prevent the accumulation of rounded values giving an inaccurate result. + * + * \note This is simple but doesn't read well when expanded multiple times inline. + */ +struct GWL_ScaledFixedT { + wl_fixed_t value = 0; + wl_fixed_t factor = 1; +}; + +static int gwl_scaled_fixed_t_add_and_calc_rounded_delta(GWL_ScaledFixedT *sf, + const wl_fixed_t add) +{ + const int result_prev = wl_fixed_to_int(sf->value * sf->factor); + sf->value += add; + const int result_curr = wl_fixed_to_int(sf->value * sf->factor); + return result_curr - result_prev; +} + +/** + * Gesture state. + * This is needed so the gesture values can be converted to deltas. + */ +struct GWL_SeatStatePointerGesture_Pinch { + GWL_ScaledFixedT scale; + GWL_ScaledFixedT rotation; +}; + +/** * State of the keyboard (in #GWL_Seat). */ struct GWL_SeatStateKeyboard { @@ -376,16 +514,16 @@ struct GWL_SeatStateKeyboard { * * Needed as #GWL_Seat.xkb_state doesn't store which modifier keys are held. */ -struct WGL_KeyboardDepressedState { +struct GWL_KeyboardDepressedState { int16_t mods[GHOST_KEY_MODIFIER_NUM] = {0}; }; #ifdef WITH_GHOST_WAYLAND_LIBDECOR -struct WGL_LibDecor_System { +struct GWL_LibDecor_System { struct libdecor *context = nullptr; }; -static void wgl_libdecor_system_destroy(WGL_LibDecor_System *decor) +static void gwl_libdecor_system_destroy(GWL_LibDecor_System *decor) { if (decor->context) { libdecor_unref(decor->context); @@ -394,12 +532,12 @@ static void wgl_libdecor_system_destroy(WGL_LibDecor_System *decor) } #endif -struct WGL_XDG_Decor_System { +struct GWL_XDG_Decor_System { struct xdg_wm_base *shell = nullptr; struct zxdg_decoration_manager_v1 *manager = nullptr; }; -static void wgl_xdg_decor_system_destroy(WGL_XDG_Decor_System *decor) +static void gwl_xdg_decor_system_destroy(GWL_XDG_Decor_System *decor) { if (decor->manager) { zxdg_decoration_manager_v1_destroy(decor->manager); @@ -410,14 +548,65 @@ static void wgl_xdg_decor_system_destroy(WGL_XDG_Decor_System *decor) 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; +}; + +struct GWL_PrimarySelection_DataSource { + struct zwp_primary_selection_source_v1 *wp_source = nullptr; + GWL_SimpleBuffer buffer_out; +}; + +/** Primary selection support. */ +struct GWL_PrimarySelection { + + GWL_PrimarySelection_DataSource *data_source = nullptr; + std::mutex data_source_mutex; + + GWL_PrimarySelection_DataOffer *data_offer = nullptr; + std::mutex data_offer_mutex; +}; + +static void gwl_primary_selection_discard_offer(GWL_PrimarySelection *primary) +{ + if (primary->data_offer == nullptr) { + return; + } + zwp_primary_selection_offer_v1_destroy(primary->data_offer->id); + delete primary->data_offer; + primary->data_offer = nullptr; +} + +static void gwl_primary_selection_discard_source(GWL_PrimarySelection *primary) +{ + GWL_PrimarySelection_DataSource *data_source = primary->data_source; + if (data_source == nullptr) { + return; + } + gwl_simple_buffer_free_data(&data_source->buffer_out); + if (data_source->wp_source) { + zwp_primary_selection_source_v1_destroy(data_source->wp_source); + } + delete primary->data_source; + primary->data_source = nullptr; +} + struct GWL_Seat { GHOST_SystemWayland *system = nullptr; std::string name; struct wl_seat *wl_seat = nullptr; struct wl_pointer *wl_pointer = nullptr; + struct wl_touch *wl_touch = nullptr; struct wl_keyboard *wl_keyboard = nullptr; - struct zwp_tablet_seat_v2 *tablet_seat = nullptr; + struct zwp_tablet_seat_v2 *wp_tablet_seat = nullptr; + + struct zwp_pointer_gesture_hold_v1 *wp_pointer_gesture_hold = nullptr; + struct zwp_pointer_gesture_pinch_v1 *wp_pointer_gesture_pinch = nullptr; + struct zwp_pointer_gesture_swipe_v1 *wp_pointer_gesture_swipe = nullptr; /** All currently active tablet tools (needed for changing the cursor). */ std::unordered_set<zwp_tablet_tool_v2 *> tablet_tools; @@ -426,6 +615,8 @@ struct GWL_Seat { uint32_t cursor_source_serial = 0; GWL_SeatStatePointer pointer; + GWL_SeatStatePointerScroll pointer_scroll; + GWL_SeatStatePointerGesture_Pinch pointer_gesture_pinch; /** Mostly this can be interchanged with `pointer` however it can't be locked/confined. */ GWL_SeatStatePointer tablet; @@ -440,9 +631,9 @@ struct GWL_Seat { struct GWL_Cursor cursor; - struct zwp_relative_pointer_v1 *relative_pointer = nullptr; - struct zwp_locked_pointer_v1 *locked_pointer = nullptr; - struct zwp_confined_pointer_v1 *confined_pointer = nullptr; + struct zwp_relative_pointer_v1 *wp_relative_pointer = nullptr; + struct zwp_locked_pointer_v1 *wp_locked_pointer = nullptr; + struct zwp_confined_pointer_v1 *wp_confined_pointer = nullptr; struct xkb_context *xkb_context = nullptr; @@ -458,7 +649,7 @@ struct GWL_Seat { struct xkb_state *xkb_state_empty_with_numlock = nullptr; /** Keys held matching `xkb_state`. */ - struct WGL_KeyboardDepressedState key_depressed; + struct GWL_KeyboardDepressedState key_depressed; #ifdef USE_GNOME_KEYBOARD_SUPPRESS_WARNING struct { @@ -485,7 +676,7 @@ struct GWL_Seat { struct wl_surface *wl_surface_focus_dnd = nullptr; - struct wl_data_device *data_device = nullptr; + struct wl_data_device *wl_data_device = nullptr; /** Drag & Drop. */ struct GWL_DataOffer *data_offer_dnd = nullptr; std::mutex data_offer_dnd_mutex; @@ -497,10 +688,19 @@ struct GWL_Seat { struct GWL_DataSource *data_source = nullptr; std::mutex data_source_mutex; + struct zwp_primary_selection_device_v1 *wp_primary_selection_device = nullptr; + struct GWL_PrimarySelection primary_selection; + /** Last device that was active. */ uint32_t data_source_serial = 0; }; +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_Display Type (#wl_display & #wl_compositor wrapper) + * \{ */ + struct GWL_Display { GHOST_SystemWayland *system = nullptr; @@ -508,23 +708,27 @@ struct GWL_Display { struct wl_compositor *wl_compositor = nullptr; #ifdef WITH_GHOST_WAYLAND_LIBDECOR - WGL_LibDecor_System *libdecor = nullptr; + GWL_LibDecor_System *libdecor = nullptr; bool libdecor_required = false; #endif - WGL_XDG_Decor_System *xdg_decor = nullptr; + GWL_XDG_Decor_System *xdg_decor = nullptr; struct zxdg_output_manager_v1 *xdg_output_manager = nullptr; struct wl_shm *wl_shm = nullptr; std::vector<GWL_Output *> outputs; std::vector<GWL_Seat *> seats; - struct wl_data_device_manager *data_device_manager = nullptr; - struct zwp_tablet_manager_v2 *tablet_manager = nullptr; - struct zwp_relative_pointer_manager_v1 *relative_pointer_manager = nullptr; - struct zwp_pointer_constraints_v1 *pointer_constraints = nullptr; -}; + 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; -#undef LOG + struct zwp_primary_selection_device_manager_v1 *wp_primary_selection_device_manager = nullptr; + + GWL_SimpleBuffer clipboard; + GWL_SimpleBuffer clipboard_primary; +}; /** \} */ @@ -580,12 +784,12 @@ static GWL_SeatStatePointer *seat_state_pointer_from_cursor_surface(GWL_Seat *se static void display_destroy(GWL_Display *display) { - if (display->data_device_manager) { - wl_data_device_manager_destroy(display->data_device_manager); + if (display->wl_data_device_manager) { + wl_data_device_manager_destroy(display->wl_data_device_manager); } - if (display->tablet_manager) { - zwp_tablet_manager_v2_destroy(display->tablet_manager); + if (display->wp_tablet_manager) { + zwp_tablet_manager_v2_destroy(display->wp_tablet_manager); } for (GWL_Output *output : display->outputs) { @@ -600,9 +804,9 @@ static void display_destroy(GWL_Display *display) { std::lock_guard lock{seat->data_source_mutex}; if (seat->data_source) { - free(seat->data_source->buffer_out); - if (seat->data_source->data_source) { - wl_data_source_destroy(seat->data_source->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; } @@ -624,32 +828,39 @@ static void display_destroy(GWL_Display *display) } } - if (seat->data_device) { - wl_data_device_release(seat->data_device); + { + GWL_PrimarySelection *primary = &seat->primary_selection; + std::lock_guard lock{primary->data_offer_mutex}; + gwl_primary_selection_discard_offer(primary); } - if (seat->cursor.custom_data) { - munmap(seat->cursor.custom_data, seat->cursor.custom_data_size); + { + GWL_PrimarySelection *primary = &seat->primary_selection; + std::lock_guard lock{primary->data_source_mutex}; + gwl_primary_selection_discard_source(primary); } - if (seat->wl_pointer) { - if (seat->cursor.wl_surface) { - wl_surface_destroy(seat->cursor.wl_surface); - } - if (seat->cursor.wl_theme) { - wl_cursor_theme_destroy(seat->cursor.wl_theme); - } - if (seat->wl_pointer) { - wl_pointer_destroy(seat->wl_pointer); - } + if (seat->wp_primary_selection_device) { + zwp_primary_selection_device_v1_destroy(seat->wp_primary_selection_device); } - if (seat->wl_keyboard) { - if (seat->key_repeat.timer) { - keyboard_handle_key_repeat_cancel(seat); - } - wl_keyboard_destroy(seat->wl_keyboard); + + if (seat->wl_data_device) { + wl_data_device_release(seat->wl_data_device); + } + + 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); + /* Un-referencing checks for NULL case. */ xkb_state_unref(seat->xkb_state); xkb_state_unref(seat->xkb_state_empty); @@ -665,12 +876,16 @@ static void display_destroy(GWL_Display *display) wl_shm_destroy(display->wl_shm); } - if (display->relative_pointer_manager) { - zwp_relative_pointer_manager_v1_destroy(display->relative_pointer_manager); + if (display->wp_relative_pointer_manager) { + zwp_relative_pointer_manager_v1_destroy(display->wp_relative_pointer_manager); } - if (display->pointer_constraints) { - zwp_pointer_constraints_v1_destroy(display->pointer_constraints); + if (display->wp_pointer_constraints) { + zwp_pointer_constraints_v1_destroy(display->wp_pointer_constraints); + } + + if (display->wp_pointer_gestures) { + zwp_pointer_gestures_v1_destroy(display->wp_pointer_gestures); } if (display->wl_compositor) { @@ -680,14 +895,14 @@ static void display_destroy(GWL_Display *display) #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (use_libdecor) { if (display->libdecor) { - wgl_libdecor_system_destroy(display->libdecor); + gwl_libdecor_system_destroy(display->libdecor); } } else #endif { if (display->xdg_decor) { - wgl_xdg_decor_system_destroy(display->xdg_decor); + gwl_xdg_decor_system_destroy(display->xdg_decor); } } @@ -699,6 +914,12 @@ static void display_destroy(GWL_Display *display) wl_display_disconnect(display->wl_display); } + { + std::lock_guard lock{system_clipboard_mutex}; + gwl_simple_buffer_free_data(&display->clipboard); + gwl_simple_buffer_free_data(&display->clipboard_primary); + } + delete display; } @@ -837,9 +1058,24 @@ static GHOST_TKey xkb_map_gkey_or_scan_code(const xkb_keysym_t sym, const uint32 return gkey; } -static GHOST_TTabletMode tablet_tool_map_type(enum zwp_tablet_tool_v2_type wl_tablet_tool_type) +static int pointer_axis_as_index(const uint32_t axis) +{ + switch (axis) { + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: { + return 0; + } + case WL_POINTER_AXIS_VERTICAL_SCROLL: { + return 1; + } + default: { + return -1; + } + } +} + +static GHOST_TTabletMode tablet_tool_map_type(enum zwp_tablet_tool_v2_type wp_tablet_tool_type) { - switch (wl_tablet_tool_type) { + switch (wp_tablet_tool_type) { case ZWP_TABLET_TOOL_V2_TYPE_ERASER: { return GHOST_kTabletModeEraser; } @@ -1045,7 +1281,7 @@ static void keyboard_depressed_state_key_event(GWL_Seat *seat, } static void keyboard_depressed_state_push_events_from_change( - GWL_Seat *seat, const WGL_KeyboardDepressedState &key_depressed_prev) + GWL_Seat *seat, const GWL_KeyboardDepressedState &key_depressed_prev) { GHOST_IWindow *win = ghost_wl_surface_user_data(seat->keyboard.wl_surface); GHOST_SystemWayland *system = seat->system; @@ -1183,15 +1419,81 @@ static void dnd_events(const GWL_Seat *const seat, const GHOST_TEventType event) } } -static std::string read_pipe(GWL_DataOffer *data_offer, - const std::string mime_receive, - std::mutex *mutex) +/** + * 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) +{ + struct ByteChunk { + ByteChunk *next; + char data[4096 - sizeof(ByteChunk *)]; + }; + ByteChunk *chunk_first = nullptr, **chunk_link_p = &chunk_first; + bool ok = true; + size_t len = 0; + while (true) { + ByteChunk *chunk = static_cast<typeof(chunk)>(malloc(sizeof(*chunk))); + if (UNLIKELY(chunk == nullptr)) { + CLOG_WARN(LOG, "unable to allocate chunk for file buffer"); + ok = false; + break; + } + chunk->next = nullptr; + const ssize_t len_chunk = read(fd, chunk->data, sizeof(chunk->data)); + if (len_chunk <= 0) { + if (UNLIKELY(len_chunk < 0)) { + CLOG_WARN(LOG, "error reading from pipe: %s", std::strerror(errno)); + ok = false; + } + free(chunk); + break; + } + if (chunk_first == nullptr) { + chunk_first = chunk; + } + *chunk_link_p = chunk; + chunk_link_p = &chunk->next; + len += len_chunk; + } + + char *buf = nullptr; + if (ok) { + buf = static_cast<char *>(malloc(len)); + if (UNLIKELY(buf == nullptr)) { + CLOG_WARN(LOG, "unable to allocate file buffer: %zu bytes", len); + ok = false; + } + } + + *r_len = ok ? len : 0; + char *buf_stride = buf; + while (chunk_first) { + if (ok) { + const size_t len_chunk = std::min(len, sizeof(chunk_first->data)); + memcpy(buf_stride, chunk_first->data, len_chunk); + buf_stride += len_chunk; + len -= len_chunk; + } + ByteChunk *chunk = chunk_first->next; + free(chunk_first); + chunk_first = chunk; + } + + 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) { int pipefd[2]; if (UNLIKELY(pipe(pipefd) != 0)) { - return {}; + CLOG_WARN(LOG, "error creating pipe: %s", std::strerror(errno)); + return nullptr; } - wl_data_offer_receive(data_offer->id, mime_receive.c_str(), pipefd[1]); + wl_data_offer_receive(data_offer->id, mime_receive, pipefd[1]); close(pipefd[1]); data_offer->in_use.store(false); @@ -1201,15 +1503,34 @@ static std::string read_pipe(GWL_DataOffer *data_offer, } /* WARNING: `data_offer` may be freed from now on. */ - std::string data; - ssize_t len; - char buffer[4096]; - while ((len = read(pipefd[0], buffer, sizeof(buffer))) > 0) { - data.insert(data.end(), buffer, buffer + len); - } + const char *buf = read_file_as_buffer(pipefd[0], 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) +{ + int pipefd[2]; + if (UNLIKELY(pipe(pipefd) != 0)) { + 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); - return data; + 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]); + return buf; } /** @@ -1235,16 +1556,22 @@ static void data_source_handle_send(void *data, CLOG_INFO(LOG, 2, "send"); - const char *const buffer = seat->data_source->buffer_out; - if (write(fd, buffer, strlen(buffer)) < 0) { - GHOST_PRINT("error writing to clipboard: " << std::strerror(errno) << std::endl); + 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); } -static void data_source_handle_cancelled(void * /*data*/, struct wl_data_source *wl_data_source) +static void data_source_handle_cancelled(void *data, struct wl_data_source *wl_data_source) { CLOG_INFO(LOG, 2, "cancelled"); + GWL_Seat *seat = static_cast<GWL_Seat *>(data); + GWL_DataSource *data_source = seat->data_source; + if (seat->data_source->wl_source == wl_data_source) { + data_source->wl_source = nullptr; + } + wl_data_source_destroy(wl_data_source); } @@ -1313,7 +1640,8 @@ static void data_offer_handle_offer(void *data, const char *mime_type) { CLOG_INFO(LOG, 2, "offer (mime_type=%s)", mime_type); - static_cast<GWL_DataOffer *>(data)->types.insert(mime_type); + GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(data); + data_offer->types.insert(mime_type); } static void data_offer_handle_source_actions(void *data, @@ -1321,7 +1649,8 @@ static void data_offer_handle_source_actions(void *data, const uint32_t source_actions) { CLOG_INFO(LOG, 2, "source_actions (%u)", source_actions); - static_cast<GWL_DataOffer *>(data)->source_actions = source_actions; + GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(data); + data_offer->dnd.source_actions = (enum wl_data_device_manager_dnd_action)source_actions; } static void data_offer_handle_action(void *data, @@ -1329,7 +1658,8 @@ static void data_offer_handle_action(void *data, const uint32_t dnd_action) { CLOG_INFO(LOG, 2, "actions (%u)", dnd_action); - static_cast<GWL_DataOffer *>(data)->dnd_action = dnd_action; + GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(data); + data_offer->dnd.action = (enum wl_data_device_manager_dnd_action)dnd_action; } static const struct wl_data_offer_listener data_offer_listener = { @@ -1452,7 +1782,11 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat const std::string mime_receive) { const wl_fixed_t xy[2] = {UNPACK2(data_offer->dnd.xy)}; - const std::string data = read_pipe(data_offer, mime_receive, nullptr); + 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); + 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()); @@ -1567,12 +1901,15 @@ static void data_device_handle_selection(void *data, break; } } - const std::string data = read_pipe( - data_offer, mime_receive, &seat->data_offer_copy_paste_mutex); + + 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}; - system->clipboard_set(data); + GWL_SimpleBuffer *buf = system->clipboard_data(false); + gwl_simple_buffer_set_and_take_ownership(buf, data, data_len); } }; @@ -1646,7 +1983,8 @@ static bool update_cursor_scale(GWL_Cursor &cursor, wl_surface_set_buffer_scale(wl_cursor_surface, scale); } wl_cursor_theme_destroy(cursor.wl_theme); - cursor.wl_theme = wl_cursor_theme_load(cursor.theme_name.c_str(), scale * cursor.size, shm); + cursor.wl_theme = wl_cursor_theme_load( + cursor.theme_name.c_str(), scale * cursor.theme_size, shm); return true; } return false; @@ -1726,6 +2064,15 @@ static void pointer_handle_enter(void *data, seat->pointer.serial = serial; seat->pointer.xy[0] = surface_x; seat->pointer.xy[1] = surface_y; + + /* Resetting scroll events is likely unnecessary, + * do this to avoid any possible problems as it's harmless. */ + seat->pointer_scroll.smooth_xy[0] = 0; + seat->pointer_scroll.smooth_xy[1] = 0; + seat->pointer_scroll.discrete_xy[0] = 0; + seat->pointer_scroll.discrete_xy[1] = 0; + seat->pointer_scroll.axis_source = WL_POINTER_AXIS_SOURCE_WHEEL; + seat->pointer.wl_surface = wl_surface; win->setCursorShape(win->getCursorShape()); @@ -1837,24 +2184,91 @@ static void pointer_handle_button(void *data, } } -static void pointer_handle_axis(void * /*data*/, +static void pointer_handle_axis(void *data, struct wl_pointer * /*wl_pointer*/, const uint32_t /*time*/, const uint32_t axis, const wl_fixed_t value) { + /* NOTE: this is used for touch based scrolling - or other input that doesn't scroll with + * discrete "steps". This allows supporting smooth-scrolling without "touch" gesture support. */ CLOG_INFO(LOG, 2, "axis (axis=%u, value=%d)", axis, value); + const int index = pointer_axis_as_index(axis); + if (UNLIKELY(index == -1)) { + return; + } + GWL_Seat *seat = static_cast<GWL_Seat *>(data); + seat->pointer_scroll.smooth_xy[index] = value; } -static void pointer_handle_frame(void * /*data*/, struct wl_pointer * /*wl_pointer*/) +static void pointer_handle_frame(void *data, struct wl_pointer * /*wl_pointer*/) { CLOG_INFO(LOG, 2, "frame"); + GWL_Seat *seat = static_cast<GWL_Seat *>(data); + + /* Both discrete and smooth events may be set at once, never generate events for both + * as this will be handling the same event in to different ways. + * Prioritize discrete axis events for the mouse wheel, otherwise smooth scroll. */ + if (seat->pointer_scroll.axis_source == WL_POINTER_AXIS_SOURCE_WHEEL) { + if (seat->pointer_scroll.discrete_xy[0]) { + seat->pointer_scroll.smooth_xy[0] = 0; + } + if (seat->pointer_scroll.discrete_xy[1]) { + seat->pointer_scroll.smooth_xy[1] = 0; + } + } + else { + if (seat->pointer_scroll.smooth_xy[0]) { + seat->pointer_scroll.discrete_xy[0] = 0; + } + if (seat->pointer_scroll.smooth_xy[1]) { + seat->pointer_scroll.discrete_xy[1] = 0; + } + } + + /* Discrete X axis currently unsupported. */ + if (seat->pointer_scroll.discrete_xy[1]) { + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + 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( + seat->system->getMilliSeconds(), win, std::signbit(discrete) ? +1 : -1)); + } + seat->pointer_scroll.discrete_xy[0] = 0; + seat->pointer_scroll.discrete_xy[1] = 0; + } + + if (seat->pointer_scroll.smooth_xy[0] || seat->pointer_scroll.smooth_xy[1]) { + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); + const wl_fixed_t scale = win->scale(); + seat->system->pushEvent(new GHOST_EventTrackpad( + seat->system->getMilliSeconds(), + win, + GHOST_kTrackpadEventScroll, + 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. */ + -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)); + } + + seat->pointer_scroll.smooth_xy[0] = 0; + seat->pointer_scroll.smooth_xy[1] = 0; + } + + seat->pointer_scroll.axis_source = WL_POINTER_AXIS_SOURCE_WHEEL; } -static void pointer_handle_axis_source(void * /*data*/, +static void pointer_handle_axis_source(void *data, struct wl_pointer * /*wl_pointer*/, uint32_t axis_source) { CLOG_INFO(LOG, 2, "axis_source (axis_source=%u)", axis_source); + GWL_Seat *seat = static_cast<GWL_Seat *>(data); + seat->pointer_scroll.axis_source = (enum wl_pointer_axis_source)axis_source; } static void pointer_handle_axis_stop(void * /*data*/, struct wl_pointer * /*wl_pointer*/, @@ -1868,18 +2282,15 @@ static void pointer_handle_axis_discrete(void *data, uint32_t axis, int32_t discrete) { + /* NOTE: a discrete axis are typically mouse wheel events. + * The non-discrete version of this function is used for touch-pad. */ CLOG_INFO(LOG, 2, "axis_discrete (axis=%u, discrete=%d)", axis, discrete); - - GWL_Seat *seat = static_cast<GWL_Seat *>(data); - if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) { + const int index = pointer_axis_as_index(axis); + if (UNLIKELY(index == -1)) { return; } - - if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { - GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); - seat->system->pushEvent(new GHOST_EventWheel( - seat->system->getMilliSeconds(), win, std::signbit(discrete) ? +1 : -1)); - } + GWL_Seat *seat = static_cast<GWL_Seat *>(data); + seat->pointer_scroll.discrete_xy[index] = discrete; } static const struct wl_pointer_listener pointer_listener = { @@ -1899,6 +2310,306 @@ static const struct wl_pointer_listener pointer_listener = { /** \} */ /* -------------------------------------------------------------------- */ +/** \name Listener (Pointer Gesture: Hold), #zwp_pointer_gesture_hold_v1_listener + * \{ */ + +static CLG_LogRef LOG_WL_POINTER_GESTURE_HOLD = {"ghost.wl.handle.pointer_gesture.hold"}; +#define LOG (&LOG_WL_POINTER_GESTURE_HOLD) + +static void gesture_hold_handle_begin( + void * /*data*/, + struct zwp_pointer_gesture_hold_v1 * /*zwp_pointer_gesture_hold_v1*/, + uint32_t /*serial*/, + uint32_t /*time*/, + struct wl_surface * /*surface*/, + uint32_t fingers) +{ + CLOG_INFO(LOG, 2, "begin (fingers=%u)", fingers); +} + +static void gesture_hold_handle_end( + void * /*data*/, + struct zwp_pointer_gesture_hold_v1 * /*zwp_pointer_gesture_hold_v1*/, + uint32_t /*serial*/, + uint32_t /*time*/, + int32_t cancelled) +{ + CLOG_INFO(LOG, 2, "end (cancelled=%i)", cancelled); +} + +static const struct zwp_pointer_gesture_hold_v1_listener gesture_hold_listener = { + gesture_hold_handle_begin, + gesture_hold_handle_end, +}; + +#undef LOG + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Pointer Gesture: Pinch), #zwp_pointer_gesture_pinch_v1_listener + * \{ */ + +static CLG_LogRef LOG_WL_POINTER_GESTURE_PINCH = {"ghost.wl.handle.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*/, + uint32_t /*serial*/, + uint32_t /*time*/, + struct wl_surface * /*surface*/, + uint32_t fingers) +{ + CLOG_INFO(LOG, 2, "begin (fingers=%u)", fingers); + GWL_Seat *seat = static_cast<GWL_Seat *>(data); + /* Reset defaults. */ + seat->pointer_gesture_pinch = GWL_SeatStatePointerGesture_Pinch{}; + + GHOST_WindowWayland *win = nullptr; + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + win = ghost_wl_surface_user_data(wl_surface_focus); + } + const wl_fixed_t win_scale = win ? win->scale() : 1; + + /* NOTE(@campbellbarton): Scale factors match Blender's operators & default preferences. + * For these values to work correctly, operator logic will need to be changed not to scale input + * by the region size (as with 3D view zoom) or preference for 3D view orbit sensitivity. + * + * By working "correctly" I mean that a rotation action where the users fingers rotate to + * opposite locations should always rotate the viewport 180d, since users will expect the + * physical location of their fingers to match the viewport. + * Similarly with zoom, the scale value from the pinch action can be mapped to a zoom level + * although unlike rotation, an inexact mapping is less noticeable. + * Users may even prefer the zoom level to be scaled - which could be a preference. */ + seat->pointer_gesture_pinch.scale.value = wl_fixed_from_int(1); + /* The value 300 matches a value used in clip & image zoom operators. + * It seems OK for the 3D view too. */ + seat->pointer_gesture_pinch.scale.factor = 300 * win_scale; + /* The value 5 is used on macOS and roughly maps 1:1 with turntable rotation, + * although preferences can scale the sensitivity (which would be skipped ideally). */ + seat->pointer_gesture_pinch.rotation.factor = 5 * win_scale; +} + +static void gesture_pinch_handle_update(void *data, + struct zwp_pointer_gesture_pinch_v1 * /*pinch*/, + uint32_t /*time*/, + wl_fixed_t dx, + wl_fixed_t dy, + wl_fixed_t scale, + wl_fixed_t rotation) +{ + CLOG_INFO(LOG, + 2, + "update (dx=%.3f, dy=%.3f, scale=%.3f, rotation=%.3f)", + wl_fixed_to_double(dx), + wl_fixed_to_double(dy), + wl_fixed_to_double(scale), + wl_fixed_to_double(rotation)); + + GWL_Seat *seat = static_cast<GWL_Seat *>(data); + + GHOST_WindowWayland *win = nullptr; + + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + win = ghost_wl_surface_user_data(wl_surface_focus); + } + + /* Scale defaults to `wl_fixed_from_int(1)` which may change while pinching. + * This needs to be converted to a delta. */ + const wl_fixed_t scale_delta = scale - seat->pointer_gesture_pinch.scale.value; + const int scale_as_delta_px = gwl_scaled_fixed_t_add_and_calc_rounded_delta( + &seat->pointer_gesture_pinch.scale, scale_delta); + + /* Rotation in degrees, unlike scale this is a delta. */ + const int rotation_as_delta_px = gwl_scaled_fixed_t_add_and_calc_rounded_delta( + &seat->pointer_gesture_pinch.rotation, rotation); + + if (win) { + const wl_fixed_t win_scale = win->scale(); + const int32_t event_xy[2] = { + wl_fixed_to_int(win_scale * seat->pointer.xy[0]), + wl_fixed_to_int(win_scale * seat->pointer.xy[1]), + }; + if (scale_as_delta_px) { + seat->system->pushEvent(new GHOST_EventTrackpad(seat->system->getMilliSeconds(), + win, + GHOST_kTrackpadEventMagnify, + event_xy[0], + event_xy[1], + scale_as_delta_px, + 0, + false)); + } + + if (rotation_as_delta_px) { + seat->system->pushEvent(new GHOST_EventTrackpad(seat->system->getMilliSeconds(), + win, + GHOST_kTrackpadEventRotate, + event_xy[0], + event_xy[1], + rotation_as_delta_px, + 0, + false)); + } + } +} + +static void gesture_pinch_handle_end(void * /*data*/, + struct zwp_pointer_gesture_pinch_v1 * /*pinch*/, + uint32_t /*serial*/, + uint32_t /*time*/, + int32_t cancelled) +{ + CLOG_INFO(LOG, 2, "end (cancelled=%i)", cancelled); +} + +static const struct zwp_pointer_gesture_pinch_v1_listener gesture_pinch_listener = { + gesture_pinch_handle_begin, + gesture_pinch_handle_update, + gesture_pinch_handle_end, +}; + +#undef LOG + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Pointer Gesture: Swipe), #zwp_pointer_gesture_swipe_v1 + * + * \note In both Gnome-Shell & KDE this gesture isn't emitted at time of writing, + * instead, high resolution 2D #wl_pointer_listener.axis data is generated which works well. + * There may be some situations where WAYLAND compositors generate this gesture + * (swiping with 3+ fingers, for e.g.). So keep this to allow logging & testing gestures. + * \{ */ + +static CLG_LogRef LOG_WL_POINTER_GESTURE_SWIPE = {"ghost.wl.handle.pointer_gesture.swipe"}; +#define LOG (&LOG_WL_POINTER_GESTURE_SWIPE) + +static void gesture_swipe_handle_begin( + void * /*data*/, + struct zwp_pointer_gesture_swipe_v1 * /*zwp_pointer_gesture_swipe_v1*/, + uint32_t /*serial*/, + uint32_t /*time*/, + struct wl_surface * /*surface*/, + uint32_t fingers) +{ + CLOG_INFO(LOG, 2, "begin (fingers=%u)", fingers); +} + +static void gesture_swipe_handle_update( + void * /*data*/, + struct zwp_pointer_gesture_swipe_v1 * /*zwp_pointer_gesture_swipe_v1*/, + uint32_t /*time*/, + wl_fixed_t dx, + wl_fixed_t dy) +{ + CLOG_INFO(LOG, 2, "update (dx=%.3f, dy=%.3f)", wl_fixed_to_double(dx), wl_fixed_to_double(dy)); +} + +static void gesture_swipe_handle_end( + void * /*data*/, + struct zwp_pointer_gesture_swipe_v1 * /*zwp_pointer_gesture_swipe_v1*/, + uint32_t /*serial*/, + uint32_t /*time*/, + int32_t cancelled) +{ + CLOG_INFO(LOG, 2, "end (cancelled=%i)", cancelled); +} + +static const struct zwp_pointer_gesture_swipe_v1_listener gesture_swipe_listener = { + gesture_swipe_handle_begin, + gesture_swipe_handle_update, + gesture_swipe_handle_end, +}; + +#undef LOG + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \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. + * \{ */ + +static CLG_LogRef LOG_WL_TOUCH = {"ghost.wl.handle.touch"}; +#define LOG (&LOG_WL_TOUCH) + +static void touch_seat_handle_down(void * /*data*/, + struct wl_touch * /*wl_touch*/, + uint32_t /*serial*/, + uint32_t /*time*/, + struct wl_surface * /*wl_surface*/, + int32_t /*id*/, + wl_fixed_t /*x*/, + wl_fixed_t /*y*/) +{ + CLOG_INFO(LOG, 2, "down"); +} + +static void touch_seat_handle_up(void * /*data*/, + struct wl_touch * /*wl_touch*/, + uint32_t /*serial*/, + uint32_t /*time*/, + int32_t /*id*/) +{ + CLOG_INFO(LOG, 2, "up"); +} + +static void touch_seat_handle_motion(void * /*data*/, + struct wl_touch * /*wl_touch*/, + uint32_t /*time*/, + int32_t /*id*/, + wl_fixed_t /*x*/, + wl_fixed_t /*y*/) +{ + CLOG_INFO(LOG, 2, "motion"); +} + +static void touch_seat_handle_frame(void * /*data*/, struct wl_touch * /*wl_touch*/) +{ + CLOG_INFO(LOG, 2, "frame"); +} + +static void touch_seat_handle_cancel(void * /*data*/, struct wl_touch * /*wl_touch*/) +{ + + CLOG_INFO(LOG, 2, "cancel"); +} + +static void touch_seat_handle_shape(void * /*data*/, + struct wl_touch * /*touch*/, + int32_t /*id*/, + wl_fixed_t /*major*/, + wl_fixed_t /*minor*/) +{ + CLOG_INFO(LOG, 2, "shape"); +} + +static void touch_seat_handle_orientation(void * /*data*/, + struct wl_touch * /*touch*/, + int32_t /*id*/, + wl_fixed_t /*orientation*/) +{ + CLOG_INFO(LOG, 2, "orientation"); +} + +static const struct wl_touch_listener touch_seat_listener = { + touch_seat_handle_down, + touch_seat_handle_up, + touch_seat_handle_motion, + touch_seat_handle_frame, + touch_seat_handle_cancel, + touch_seat_handle_shape, + touch_seat_handle_orientation, +}; + +#undef LOG + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Listener (Tablet Tool), #zwp_tablet_tool_v2_listener * \{ */ @@ -2377,7 +3088,7 @@ static void keyboard_handle_enter(void *data, /* If there are any keys held when activating the window, * modifiers will be compared against the seat state, * only enabling modifiers that were previously disabled. */ - WGL_KeyboardDepressedState key_depressed_prev = seat->key_depressed; + GWL_KeyboardDepressedState key_depressed_prev = seat->key_depressed; keyboard_depressed_state_reset(seat); uint32_t *key; @@ -2587,11 +3298,10 @@ static void keyboard_handle_key(void *data, /* Start timer for repeating key, if applicable. */ if ((seat->key_repeat.rate > 0) && (etype == GHOST_kEventKeyDown) && xkb_keymap_key_repeats(xkb_state_get_keymap(seat->xkb_state), key_code)) { - key_repeat_payload = new GWL_KeyRepeatPlayload({ - .seat = seat, - .key_code = key_code, - .key_data = {.gkey = gkey}, - }); + key_repeat_payload = new GWL_KeyRepeatPlayload(); + key_repeat_payload->seat = seat; + key_repeat_payload->key_code = key_code; + key_repeat_payload->key_data.gkey = gkey; } } @@ -2681,12 +3391,294 @@ static const struct wl_keyboard_listener keyboard_listener = { /** \} */ /* -------------------------------------------------------------------- */ +/** \name Listener (Primary Selection Offer), #zwp_primary_selection_offer_v1_listener + * \{ */ + +static CLG_LogRef LOG_WL_PRIMARY_SELECTION_OFFER = {"ghost.wl.handle.primary_selection_offer"}; +#define LOG (&LOG_WL_PRIMARY_SELECTION_OFFER) + +static void primary_selection_offer_offer(void *data, + struct zwp_primary_selection_offer_v1 *id, + const char *type) +{ + GWL_PrimarySelection_DataOffer *data_offer = static_cast<GWL_PrimarySelection_DataOffer *>(data); + if (data_offer->id != id) { + CLOG_INFO(LOG, 2, "offer: %p: offer for unknown selection %p of %s (skipped)", data, id, type); + return; + } + + data_offer->types.insert(std::string(type)); +} + +static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = { + primary_selection_offer_offer, +}; + +#undef LOG + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Primary Selection Device), #zwp_primary_selection_device_v1_listener + * \{ */ + +static CLG_LogRef LOG_WL_PRIMARY_SELECTION_DEVICE = {"ghost.wl.handle.primary_selection_device"}; +#define LOG (&LOG_WL_PRIMARY_SELECTION_DEVICE) + +static void primary_selection_device_handle_data_offer( + void * /*data*/, + struct zwp_primary_selection_device_v1 * /*zwp_primary_selection_device_v1*/, + struct zwp_primary_selection_offer_v1 *id) +{ + CLOG_INFO(LOG, 2, "data_offer"); + + GWL_PrimarySelection_DataOffer *data_offer = new GWL_PrimarySelection_DataOffer; + data_offer->id = id; + zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, data_offer); +} + +static void primary_selection_device_handle_selection( + void *data, + struct zwp_primary_selection_device_v1 * /*zwp_primary_selection_device_v1*/, + struct zwp_primary_selection_offer_v1 *id) +{ + GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data); + + std::lock_guard lock{primary->data_offer_mutex}; + + /* Delete old data offer. */ + if (primary->data_offer != nullptr) { + gwl_primary_selection_discard_offer(primary); + } + + if (id == nullptr) { + CLOG_INFO(LOG, 2, "selection: (skipped)"); + return; + } + CLOG_INFO(LOG, 2, "selection"); + /* Get new data offer. */ + 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 = { + primary_selection_device_handle_data_offer, + primary_selection_device_handle_selection, +}; + +#undef LOG + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Primary Selection Source), #zwp_primary_selection_source_v1_listener + * \{ */ + +static CLG_LogRef LOG_WL_PRIMARY_SELECTION_SOURCE = {"ghost.wl.handle.primary_selection_source"}; +#define LOG (&LOG_WL_PRIMARY_SELECTION_SOURCE) + +static void primary_selection_source_send(void *data, + struct zwp_primary_selection_source_v1 * /*source*/, + const char * /*mime_type*/, + int32_t fd) +{ + CLOG_INFO(LOG, 2, "send"); + + GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data); + + std::lock_guard lock{primary->data_source_mutex}; + GWL_PrimarySelection_DataSource *data_source = primary->data_source; + + 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); +} + +static void primary_selection_source_cancelled(void *data, + struct zwp_primary_selection_source_v1 *source) +{ + CLOG_INFO(LOG, 2, "cancelled"); + + GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data); + + if (source == primary->data_source->wp_source) { + gwl_primary_selection_discard_source(primary); + } +} + +static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = { + primary_selection_source_send, + primary_selection_source_cancelled, +}; + +#undef LOG + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Listener (Seat), #wl_seat_listener * \{ */ static CLG_LogRef LOG_WL_SEAT = {"ghost.wl.handle.seat"}; #define LOG (&LOG_WL_SEAT) +static void gwl_seat_capability_pointer_enable(GWL_Seat *seat) +{ + if (seat->wl_pointer) { + 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.visible = true; + seat->cursor.wl_buffer = nullptr; + if (!get_cursor_settings(seat->cursor.theme_name, seat->cursor.theme_size)) { + seat->cursor.theme_name = std::string(); + seat->cursor.theme_size = default_cursor_size; + } + 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); + + zwp_pointer_gestures_v1 *pointer_gestures = seat->system->wp_pointer_gestures(); + if (pointer_gestures) { + { /* Hold gesture. */ + struct zwp_pointer_gesture_hold_v1 *gesture = zwp_pointer_gestures_v1_get_hold_gesture( + pointer_gestures, seat->wl_pointer); + zwp_pointer_gesture_hold_v1_set_user_data(gesture, seat); + zwp_pointer_gesture_hold_v1_add_listener(gesture, &gesture_hold_listener, seat); + seat->wp_pointer_gesture_hold = gesture; + } + { /* Pinch gesture. */ + struct zwp_pointer_gesture_pinch_v1 *gesture = zwp_pointer_gestures_v1_get_pinch_gesture( + pointer_gestures, seat->wl_pointer); + zwp_pointer_gesture_pinch_v1_set_user_data(gesture, seat); + zwp_pointer_gesture_pinch_v1_add_listener(gesture, &gesture_pinch_listener, seat); + seat->wp_pointer_gesture_pinch = gesture; + } + { /* Swipe gesture. */ + struct zwp_pointer_gesture_swipe_v1 *gesture = zwp_pointer_gestures_v1_get_swipe_gesture( + pointer_gestures, seat->wl_pointer); + zwp_pointer_gesture_swipe_v1_set_user_data(gesture, seat); + zwp_pointer_gesture_swipe_v1_add_listener(gesture, &gesture_swipe_listener, seat); + seat->wp_pointer_gesture_swipe = gesture; + } + } +} + +static void gwl_seat_capability_pointer_disable(GWL_Seat *seat) +{ + if (!seat->wl_pointer) { + return; + } + + zwp_pointer_gestures_v1 *pointer_gestures = seat->system->wp_pointer_gestures(); + if (pointer_gestures) { + { /* Hold gesture. */ + struct zwp_pointer_gesture_hold_v1 **gesture_p = &seat->wp_pointer_gesture_hold; + if (*gesture_p) { + zwp_pointer_gesture_hold_v1_destroy(*gesture_p); + *gesture_p = nullptr; + } + } + { /* Pinch gesture. */ + struct zwp_pointer_gesture_pinch_v1 **gesture_p = &seat->wp_pointer_gesture_pinch; + if (*gesture_p) { + zwp_pointer_gesture_pinch_v1_destroy(*gesture_p); + *gesture_p = nullptr; + } + } + { /* Swipe gesture. */ + struct zwp_pointer_gesture_swipe_v1 **gesture_p = &seat->wp_pointer_gesture_swipe; + if (*gesture_p) { + zwp_pointer_gesture_swipe_v1_destroy(*gesture_p); + *gesture_p = nullptr; + } + } + } + + if (seat->cursor.wl_surface) { + wl_surface_destroy(seat->cursor.wl_surface); + seat->cursor.wl_surface = nullptr; + } + if (seat->cursor.wl_theme) { + wl_cursor_theme_destroy(seat->cursor.wl_theme); + seat->cursor.wl_theme = nullptr; + } + + wl_pointer_destroy(seat->wl_pointer); + seat->wl_pointer = nullptr; +} + +static void gwl_seat_capability_keyboard_enable(GWL_Seat *seat) +{ + if (seat->wl_keyboard) { + return; + } + seat->wl_keyboard = wl_seat_get_keyboard(seat->wl_seat); + wl_keyboard_add_listener(seat->wl_keyboard, &keyboard_listener, seat); +} + +static void gwl_seat_capability_keyboard_disable(GWL_Seat *seat) +{ + if (!seat->wl_keyboard) { + return; + } + if (seat->key_repeat.timer) { + keyboard_handle_key_repeat_cancel(seat); + } + wl_keyboard_destroy(seat->wl_keyboard); + seat->wl_keyboard = nullptr; +} + +static void gwl_seat_capability_touch_enable(GWL_Seat *seat) +{ + if (seat->wl_touch) { + return; + } + seat->wl_touch = wl_seat_get_touch(seat->wl_seat); + wl_touch_set_user_data(seat->wl_touch, seat); + wl_touch_add_listener(seat->wl_touch, &touch_seat_listener, seat); +} + +static void gwl_seat_capability_touch_disable(GWL_Seat *seat) +{ + if (!seat->wl_touch) { + return; + } + wl_touch_destroy(seat->wl_touch); + seat->wl_touch = nullptr; +} + static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, const uint32_t capabilities) @@ -2699,27 +3691,43 @@ static void seat_handle_capabilities(void *data, (capabilities & WL_SEAT_CAPABILITY_TOUCH) != 0); GWL_Seat *seat = static_cast<GWL_Seat *>(data); - seat->wl_pointer = nullptr; - seat->wl_keyboard = nullptr; + GHOST_ASSERT(seat->wl_seat == wl_seat, "Seat mismatch"); if (capabilities & WL_SEAT_CAPABILITY_POINTER) { - seat->wl_pointer = wl_seat_get_pointer(wl_seat); - seat->cursor.wl_surface = 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.size)) { - seat->cursor.theme_name = std::string(); - seat->cursor.size = default_cursor_size; - } - wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, data); - - wl_surface_add_listener(seat->cursor.wl_surface, &cursor_surface_listener, data); - ghost_wl_surface_tag_cursor_pointer(seat->cursor.wl_surface); + gwl_seat_capability_pointer_enable(seat); + } + else { + gwl_seat_capability_pointer_disable(seat); } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { - seat->wl_keyboard = wl_seat_get_keyboard(wl_seat); - wl_keyboard_add_listener(seat->wl_keyboard, &keyboard_listener, data); + gwl_seat_capability_keyboard_enable(seat); + } + else { + gwl_seat_capability_keyboard_disable(seat); + } + + if (capabilities & WL_SEAT_CAPABILITY_TOUCH) { + gwl_seat_capability_touch_enable(seat); + } + 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); + } + } } } @@ -3026,13 +4034,13 @@ static void global_handle_add(void *data, wl_registry_bind(wl_registry, name, &wl_compositor_interface, 3)); } else if (STREQ(interface, xdg_wm_base_interface.name)) { - WGL_XDG_Decor_System &decor = *display->xdg_decor; + 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)) { - WGL_XDG_Decor_System &decor = *display->xdg_decor; + 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)); } @@ -3076,21 +4084,32 @@ static void global_handle_add(void *data, wl_registry_bind(wl_registry, name, &wl_shm_interface, 1)); } else if (STREQ(interface, wl_data_device_manager_interface.name)) { - display->data_device_manager = static_cast<wl_data_device_manager *>( + display->wl_data_device_manager = static_cast<wl_data_device_manager *>( wl_registry_bind(wl_registry, name, &wl_data_device_manager_interface, 3)); } else if (STREQ(interface, zwp_tablet_manager_v2_interface.name)) { - display->tablet_manager = static_cast<zwp_tablet_manager_v2 *>( + display->wp_tablet_manager = static_cast<zwp_tablet_manager_v2 *>( wl_registry_bind(wl_registry, name, &zwp_tablet_manager_v2_interface, 1)); } else if (STREQ(interface, zwp_relative_pointer_manager_v1_interface.name)) { - display->relative_pointer_manager = static_cast<zwp_relative_pointer_manager_v1 *>( + 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)); } else if (STREQ(interface, zwp_pointer_constraints_v1_interface.name)) { - display->pointer_constraints = static_cast<zwp_pointer_constraints_v1 *>( + display->wp_pointer_constraints = static_cast<zwp_pointer_constraints_v1 *>( wl_registry_bind(wl_registry, name, &zwp_pointer_constraints_v1_interface, 1)); } + 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)); + } + + 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)); + } + else { found = false; @@ -3156,7 +4175,7 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), display_(new GWL_Di } /* This may be removed later if decorations are required, needed as part of registration. */ - display_->xdg_decor = new WGL_XDG_Decor_System; + display_->xdg_decor = new GWL_XDG_Decor_System; /* Register interfaces. */ struct wl_registry *registry = wl_display_get_registry(display_->wl_display); @@ -3169,7 +4188,7 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), display_(new GWL_Di #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (display_->libdecor_required) { - wgl_xdg_decor_system_destroy(display_->xdg_decor); + gwl_xdg_decor_system_destroy(display_->xdg_decor); display_->xdg_decor = nullptr; if (!has_libdecor) { @@ -3190,8 +4209,8 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), display_(new GWL_Di #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (use_libdecor) { - display_->libdecor = new WGL_LibDecor_System; - WGL_LibDecor_System &decor = *display_->libdecor; + display_->libdecor = new GWL_LibDecor_System; + GWL_LibDecor_System &decor = *display_->libdecor; decor.context = libdecor_new(display_->wl_display, &libdecor_interface); if (!decor.context) { display_destroy(display_); @@ -3201,7 +4220,7 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), display_(new GWL_Di else #endif { - WGL_XDG_Decor_System &decor = *display_->xdg_decor; + GWL_XDG_Decor_System &decor = *display_->xdg_decor; if (!decor.shell) { display_destroy(display_); throw std::runtime_error("Wayland: unable to access xdg_shell!"); @@ -3209,19 +4228,19 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), display_(new GWL_Di } /* Register data device per seat for IPC between Wayland clients. */ - if (display_->data_device_manager) { + if (display_->wl_data_device_manager) { for (GWL_Seat *seat : display_->seats) { - seat->data_device = wl_data_device_manager_get_data_device(display_->data_device_manager, - seat->wl_seat); - wl_data_device_add_listener(seat->data_device, &data_device_listener, seat); + 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_->tablet_manager) { + if (display_->wp_tablet_manager) { for (GWL_Seat *seat : display_->seats) { - seat->tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(display_->tablet_manager, - seat->wl_seat); - zwp_tablet_seat_v2_add_listener(seat->tablet_seat, &tablet_seat_listener, seat); + 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); } } } @@ -3300,7 +4319,7 @@ GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) co } #endif - /* Use local #WGL_KeyboardDepressedState to check which key is pressed. + /* Use local #GWL_KeyboardDepressedState to check which key is pressed. * Use XKB as the source of truth, if there is any discrepancy. */ for (int i = 0; i < MOD_INDEX_NUM; i++) { if (UNLIKELY(seat->xkb_keymap_mod_index[i] == XKB_MOD_INVALID)) { @@ -3358,43 +4377,86 @@ GHOST_TSuccess GHOST_SystemWayland::getButtons(GHOST_Buttons &buttons) const return GHOST_kSuccess; } -char *GHOST_SystemWayland::getClipboard(bool /*selection*/) const +char *GHOST_SystemWayland::getClipboard(bool selection) const { - char *clipboard = static_cast<char *>(malloc(clipboard_.size() + 1)); - memcpy(clipboard, clipboard_.data(), clipboard_.size() + 1); - return clipboard; + const GWL_SimpleBuffer *buf = clipboard_data(selection); + if (buf->data == nullptr) { + return nullptr; + } + return gwl_simple_buffer_as_string(buf); } -void GHOST_SystemWayland::putClipboard(const char *buffer, bool /*selection*/) const +static void system_clipboard_put_primary_selection(GWL_Display *display, const char *buffer) { - if (UNLIKELY(!display_->data_device_manager || display_->seats.empty())) { + if (!display->wp_primary_selection_device_manager) { return; } + GWL_Seat *seat = display->seats[0]; + GWL_PrimarySelection *primary = &seat->primary_selection; - GWL_Seat *seat = display_->seats[0]; + std::lock_guard lock{primary->data_source_mutex}; + + gwl_primary_selection_discard_source(primary); + + GWL_PrimarySelection_DataSource *data_source = new GWL_PrimarySelection_DataSource; + primary->data_source = data_source; + + /* Copy buffer. */ + gwl_simple_buffer_set_from_string(&data_source->buffer_out, buffer); + + data_source->wp_source = zwp_primary_selection_device_manager_v1_create_source( + display->wp_primary_selection_device_manager); + + 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()); + } + + if (seat->wp_primary_selection_device) { + zwp_primary_selection_device_v1_set_selection( + seat->wp_primary_selection_device, data_source->wp_source, seat->data_source_serial); + } +} + +static void system_clipboard_put(GWL_Display *display, const char *buffer) +{ + GWL_Seat *seat = display->seats[0]; std::lock_guard lock{seat->data_source_mutex}; GWL_DataSource *data_source = seat->data_source; /* Copy buffer. */ - free(data_source->buffer_out); - const size_t buffer_size = strlen(buffer) + 1; - data_source->buffer_out = static_cast<char *>(malloc(buffer_size)); - std::memcpy(data_source->buffer_out, buffer, buffer_size); + gwl_simple_buffer_set_from_string(&data_source->buffer_out, buffer); - data_source->data_source = wl_data_device_manager_create_data_source( - display_->data_device_manager); + data_source->wl_source = wl_data_device_manager_create_data_source( + display->wl_data_device_manager); - wl_data_source_add_listener(data_source->data_source, &data_source_listener, seat); + 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->data_source, type.c_str()); + wl_data_source_offer(data_source->wl_source, type.c_str()); } - if (seat->data_device) { + if (seat->wl_data_device) { wl_data_device_set_selection( - seat->data_device, data_source->data_source, seat->data_source_serial); + seat->wl_data_device, data_source->wl_source, seat->data_source_serial); + } +} + +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); + } + else { + system_clipboard_put(display_, buffer); } } @@ -3423,7 +4485,7 @@ static GHOST_TSuccess setCursorPositionClientRelative_impl(GWL_Seat *seat, /* NOTE: WAYLAND doesn't support warping the cursor. * However when grab is enabled, we already simulate a cursor location * so that can be set to a new location. */ - if (!seat->relative_pointer) { + if (!seat->wp_relative_pointer) { return GHOST_kFailure; } const wl_fixed_t scale = win->scale(); @@ -3616,7 +4678,6 @@ GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title, const uint32_t width, const uint32_t height, const GHOST_TWindowState state, - const GHOST_TDrawingContextType type, const GHOST_GLSettings glSettings, const bool exclusive, const bool is_dialog, @@ -3636,7 +4697,7 @@ GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title, height, state, parentWindow, - type, + glSettings.context_type, is_dialog, ((glSettings.flags & GHOST_glStereoVisual) != 0), exclusive); @@ -3664,22 +4725,22 @@ GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title, */ static void cursor_buffer_show(const GWL_Seat *seat) { - const GWL_Cursor *c = &seat->cursor; + const GWL_Cursor *cursor = &seat->cursor; if (seat->wl_pointer) { - const int scale = c->is_custom ? c->custom_scale : seat->pointer.theme_scale; - const int32_t hotspot_x = int32_t(c->wl_image.hotspot_x) / scale; - const int32_t hotspot_y = int32_t(c->wl_image.hotspot_y) / scale; + const int scale = cursor->is_custom ? cursor->custom_scale : seat->pointer.theme_scale; + const int32_t hotspot_x = int32_t(cursor->wl_image.hotspot_x) / scale; + 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, c->wl_surface, hotspot_x, hotspot_y); + seat->wl_pointer, seat->pointer.serial, cursor->wl_surface, hotspot_x, hotspot_y); } } if (!seat->tablet_tools.empty()) { - const int scale = c->is_custom ? c->custom_scale : seat->tablet.theme_scale; - const int32_t hotspot_x = int32_t(c->wl_image.hotspot_x) / scale; - const int32_t hotspot_y = int32_t(c->wl_image.hotspot_y) / scale; + const int scale = cursor->is_custom ? cursor->custom_scale : seat->tablet.theme_scale; + const int32_t hotspot_x = int32_t(cursor->wl_image.hotspot_x) / scale; + const int32_t hotspot_y = int32_t(cursor->wl_image.hotspot_y) / scale; for (struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->tablet_tools) { GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>( zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2)); @@ -3742,21 +4803,21 @@ static void cursor_buffer_set_surface_impl(const GWL_Seat *seat, static void cursor_buffer_set(const GWL_Seat *seat, wl_buffer *buffer) { - const GWL_Cursor *c = &seat->cursor; + const GWL_Cursor *cursor = &seat->cursor; const wl_cursor_image *wl_image = &seat->cursor.wl_image; - const bool visible = (c->visible && c->is_hardware); + const bool visible = (cursor->visible && cursor->is_hardware); /* This is a requirement of WAYLAND, when this isn't the case, * it causes Blender's window to close intermittently. */ if (seat->wl_pointer) { const int scale = cursor_buffer_compatible_scale_from_image( - wl_image, c->is_custom ? c->custom_scale : seat->pointer.theme_scale); + 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, c->wl_surface, scale); + cursor_buffer_set_surface_impl(seat, buffer, cursor->wl_surface, scale); wl_pointer_set_cursor(seat->wl_pointer, seat->pointer.serial, - visible ? c->wl_surface : nullptr, + visible ? cursor->wl_surface : nullptr, hotspot_x, hotspot_y); } @@ -3764,7 +4825,7 @@ static void cursor_buffer_set(const GWL_Seat *seat, wl_buffer *buffer) /* Set the cursor for all tablet tools as well. */ if (!seat->tablet_tools.empty()) { const int scale = cursor_buffer_compatible_scale_from_image( - wl_image, c->is_custom ? c->custom_scale : seat->tablet.theme_scale); + wl_image, cursor->is_custom ? cursor->custom_scale : seat->tablet.theme_scale); const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale; const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale; for (struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->tablet_tools) { @@ -3851,31 +4912,31 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(const GHOST_TStandardCursor s (*cursor_find).second; GWL_Seat *seat = display_->seats[0]; - GWL_Cursor *c = &seat->cursor; + GWL_Cursor *cursor = &seat->cursor; - if (!c->wl_theme) { + if (!cursor->wl_theme) { /* The cursor wl_surface hasn't entered an output yet. Initialize theme with scale 1. */ - c->wl_theme = wl_cursor_theme_load( - c->theme_name.c_str(), c->size, display_->seats[0]->system->wl_shm()); + cursor->wl_theme = wl_cursor_theme_load( + cursor->theme_name.c_str(), cursor->theme_size, wl_shm()); } - wl_cursor *cursor = wl_cursor_theme_get_cursor(c->wl_theme, cursor_name); + wl_cursor *wl_cursor = wl_cursor_theme_get_cursor(cursor->wl_theme, cursor_name); - if (!cursor) { + if (!wl_cursor) { GHOST_PRINT("cursor '" << cursor_name << "' does not exist" << std::endl); return GHOST_kFailure; } - struct wl_cursor_image *image = cursor->images[0]; + struct wl_cursor_image *image = wl_cursor->images[0]; struct wl_buffer *buffer = wl_cursor_image_get_buffer(image); if (!buffer) { return GHOST_kFailure; } - c->visible = true; - c->is_custom = false; - c->wl_buffer = buffer; - c->wl_image = *image; + cursor->visible = true; + cursor->is_custom = false; + cursor->wl_buffer = buffer; + cursor->wl_image = *image; cursor_buffer_set(seat, buffer); @@ -4062,11 +5123,10 @@ static GWL_SeatStateGrab seat_grab_state_from_mode(const GHOST_TGrabCursorMode m const bool use_software_confine) { /* Initialize all members. */ - const struct GWL_SeatStateGrab grab_state = { - /* Warping happens to require software cursor which also hides. */ - .use_lock = ELEM(mode, GHOST_kGrabWrap, GHOST_kGrabHide) || use_software_confine, - .use_confine = (mode == GHOST_kGrabNormal) && (use_software_confine == false), - }; + GWL_SeatStateGrab grab_state; + /* Warping happens to require software cursor which also hides. */ + grab_state.use_lock = ELEM(mode, GHOST_kGrabWrap, GHOST_kGrabHide) || use_software_confine; + grab_state.use_confine = (mode == GHOST_kGrabNormal) && (use_software_confine == false); return grab_state; } @@ -4140,6 +5200,16 @@ wl_compositor *GHOST_SystemWayland::wl_compositor() return display_->wl_compositor; } +struct zwp_primary_selection_device_manager_v1 *GHOST_SystemWayland::wp_primary_selection_manager() +{ + return display_->wp_primary_selection_device_manager; +} + +struct zwp_pointer_gestures_v1 *GHOST_SystemWayland::wp_pointer_gestures() +{ + return display_->wp_pointer_gestures; +} + #ifdef WITH_GHOST_WAYLAND_LIBDECOR libdecor *GHOST_SystemWayland::libdecor_context() @@ -4202,11 +5272,6 @@ GHOST_WindowWayland *ghost_wl_surface_user_data(struct wl_surface *wl_surface) * Functionality only used for the WAYLAND implementation. * \{ */ -void GHOST_SystemWayland::clipboard_set(const std::string &clipboard) -{ - clipboard_ = clipboard; -} - void GHOST_SystemWayland::window_surface_unref(const wl_surface *wl_surface) { #define SURFACE_CLEAR_PTR(surface_test) \ @@ -4234,7 +5299,7 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod const int scale) { /* Ignore, if the required protocols are not supported. */ - if (UNLIKELY(!display_->relative_pointer_manager || !display_->pointer_constraints)) { + if (UNLIKELY(!display_->wp_relative_pointer_manager || !display_->wp_pointer_constraints)) { return GHOST_kFailure; } @@ -4273,11 +5338,11 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod * in this case disable the current locks as it makes logic confusing, * postpone changing the cursor to avoid flickering. */ if (!grab_state_next.use_lock) { - if (seat->relative_pointer) { - zwp_relative_pointer_v1_destroy(seat->relative_pointer); - seat->relative_pointer = nullptr; + if (seat->wp_relative_pointer) { + zwp_relative_pointer_v1_destroy(seat->wp_relative_pointer); + seat->wp_relative_pointer = nullptr; } - if (seat->locked_pointer) { + if (seat->wp_locked_pointer) { /* Potentially add a motion event so the application has updated X/Y coordinates. */ int32_t xy_motion[2] = {0, 0}; bool xy_motion_create_event = false; @@ -4306,7 +5371,7 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod seat->pointer.xy[0] = xy_next[0]; seat->pointer.xy[1] = xy_next[1]; - zwp_locked_pointer_v1_set_cursor_position_hint(seat->locked_pointer, UNPACK2(xy_next)); + zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp_locked_pointer, UNPACK2(xy_next)); wl_surface_commit(wl_surface); } else if (mode_current == GHOST_kGrabHide) { @@ -4316,7 +5381,8 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod wl_fixed_from_int(init_grab_xy[0]) / scale, wl_fixed_from_int(init_grab_xy[1]) / scale, }; - zwp_locked_pointer_v1_set_cursor_position_hint(seat->locked_pointer, UNPACK2(xy_next)); + zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp_locked_pointer, + UNPACK2(xy_next)); wl_surface_commit(wl_surface); /* NOTE(@campbellbarton): The new cursor position is a hint, @@ -4330,7 +5396,7 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod #ifdef USE_GNOME_CONFINE_HACK else if (mode_current == GHOST_kGrabNormal) { if (was_software_confine) { - zwp_locked_pointer_v1_set_cursor_position_hint(seat->locked_pointer, + zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp_locked_pointer, UNPACK2(seat->pointer.xy)); wl_surface_commit(wl_surface); } @@ -4346,15 +5412,15 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod GHOST_TABLET_DATA_NONE)); } - zwp_locked_pointer_v1_destroy(seat->locked_pointer); - seat->locked_pointer = nullptr; + zwp_locked_pointer_v1_destroy(seat->wp_locked_pointer); + seat->wp_locked_pointer = nullptr; } } if (!grab_state_next.use_confine) { - if (seat->confined_pointer) { - zwp_confined_pointer_v1_destroy(seat->confined_pointer); - seat->confined_pointer = nullptr; + if (seat->wp_confined_pointer) { + zwp_confined_pointer_v1_destroy(seat->wp_confined_pointer); + seat->wp_confined_pointer = nullptr; } } @@ -4365,12 +5431,12 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod * possible to support #GHOST_kGrabWrap by pragmatically settings it's coordinates. * An alternative could be to draw the cursor in software (and hide the real cursor), * or just accept a locked cursor on WAYLAND. */ - seat->relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer( - display_->relative_pointer_manager, seat->wl_pointer); + seat->wp_relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer( + display_->wp_relative_pointer_manager, seat->wl_pointer); zwp_relative_pointer_v1_add_listener( - seat->relative_pointer, &relative_pointer_listener, seat); - seat->locked_pointer = zwp_pointer_constraints_v1_lock_pointer( - display_->pointer_constraints, + seat->wp_relative_pointer, &relative_pointer_listener, seat); + seat->wp_locked_pointer = zwp_pointer_constraints_v1_lock_pointer( + display_->wp_pointer_constraints, wl_surface, seat->wl_pointer, nullptr, @@ -4387,8 +5453,8 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod } else if (grab_state_next.use_confine) { if (!grab_state_prev.use_confine) { - seat->confined_pointer = zwp_pointer_constraints_v1_confine_pointer( - display_->pointer_constraints, + seat->wp_confined_pointer = zwp_pointer_constraints_v1_confine_pointer( + display_->wp_pointer_constraints, wl_surface, seat->wl_pointer, nullptr, @@ -4407,6 +5473,11 @@ 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() { |