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.
This commit is contained in:
Campbell Barton 2022-10-22 16:49:09 +11:00
parent e54ea0f90e
commit 8bb211a771
Notes: blender-bot 2023-02-14 11:29:52 +01:00
Referenced by issue #76428, GHOST/Wayland Support
3 changed files with 325 additions and 2 deletions

View File

@ -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"

View File

@ -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>
@ -461,6 +462,39 @@ struct GWL_SeatStatePointerScroll {
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).
*/
@ -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) {
@ -2269,6 +2309,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
*
@ -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()

View File

@ -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();