diff options
author | Campbell Barton <campbell@blender.org> | 2022-10-22 08:49:09 +0300 |
---|---|---|
committer | Campbell Barton <campbell@blender.org> | 2022-10-22 10:01:39 +0300 |
commit | 8bb211a771642144ac75ba6be0c1897c836a6276 (patch) | |
tree | 6eb5b4e5d1c310f0c05b33bd87839b3148afd66d | |
parent | e54ea0f90ee22396147ad6d17d0263bf6b965fd7 (diff) |
GHOST/Wayland: multi-touch gesture support
Add support for zoom & rotate gestures, hold and swipe may
be used in the future although swipe maps to 2D smooth-scroll for
Gnome & KDE.
Tested to work with Apple track-pad & Wacom tablet on Gnome & KDE.
-rw-r--r-- | intern/ghost/CMakeLists.txt | 4 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_SystemWayland.cpp | 322 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_SystemWayland.h | 1 |
3 files changed, 325 insertions, 2 deletions
diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt index c05f2a327b1..fb10530bfae 100644 --- a/intern/ghost/CMakeLists.txt +++ b/intern/ghost/CMakeLists.txt @@ -372,6 +372,10 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND) generate_protocol_bindings( "${WAYLAND_PROTOCOLS_DIR}/unstable/relative-pointer/relative-pointer-unstable-v1.xml" ) + # Pointer-gestures (multi-touch). + generate_protocol_bindings( + "${WAYLAND_PROTOCOLS_DIR}/unstable/pointer-gestures/pointer-gestures-unstable-v1.xml" + ) # Tablet. generate_protocol_bindings( "${WAYLAND_PROTOCOLS_DIR}/unstable/tablet/tablet-unstable-v2.xml" diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 39f392e62b9..442e51d4f7c 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -51,6 +51,7 @@ /* 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> @@ -462,6 +463,39 @@ struct GWL_SeatStatePointerScroll { }; /** + * 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 { @@ -570,6 +604,10 @@ struct GWL_Seat { struct wl_keyboard *wl_keyboard = 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; @@ -578,6 +616,7 @@ struct GWL_Seat { 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; @@ -683,6 +722,7 @@ struct GWL_Display { 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; @@ -844,8 +884,8 @@ static void display_destroy(GWL_Display *display) zwp_pointer_constraints_v1_destroy(display->wp_pointer_constraints); } - if (display->wp_primary_selection_device_manager) { - zwp_primary_selection_device_manager_v1_destroy(display->wp_primary_selection_device_manager); + if (display->wp_pointer_gestures) { + zwp_pointer_gestures_v1_destroy(display->wp_pointer_gestures); } if (display->wl_compositor) { @@ -2270,6 +2310,223 @@ 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 @@ -3311,6 +3568,31 @@ static void gwl_seat_capability_pointer_enable(GWL_Seat *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) @@ -3318,6 +3600,32 @@ 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; @@ -3791,6 +4099,11 @@ static void global_handle_add(void *data, 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( @@ -4892,6 +5205,11 @@ struct zwp_primary_selection_device_manager_v1 *GHOST_SystemWayland::wp_primary_ 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() diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h index c19791fd792..f08e9fdcf9c 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.h +++ b/intern/ghost/intern/GHOST_SystemWayland.h @@ -159,6 +159,7 @@ class GHOST_SystemWayland : public GHOST_System { struct wl_display *wl_display(); struct wl_compositor *wl_compositor(); struct zwp_primary_selection_device_manager_v1 *wp_primary_selection_manager(); + struct zwp_pointer_gestures_v1 *wp_pointer_gestures(); #ifdef WITH_GHOST_WAYLAND_LIBDECOR libdecor *libdecor_context(); |