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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCampbell Barton <campbell@blender.org>2022-10-22 08:49:09 +0300
committerCampbell Barton <campbell@blender.org>2022-10-22 10:01:39 +0300
commit8bb211a771642144ac75ba6be0c1897c836a6276 (patch)
tree6eb5b4e5d1c310f0c05b33bd87839b3148afd66d
parente54ea0f90ee22396147ad6d17d0263bf6b965fd7 (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.txt4
-rw-r--r--intern/ghost/intern/GHOST_SystemWayland.cpp322
-rw-r--r--intern/ghost/intern/GHOST_SystemWayland.h1
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();