From 71bf56c9e49e94078c443196af13b809edc86e55 Mon Sep 17 00:00:00 2001 From: Sylvain Becker Date: Sun, 12 Oct 2025 23:44:23 +0200 Subject: [PATCH] Add SDL Pinch events (#9445) --- .../main/java/org/libsdl/app/SDLActivity.java | 3 + .../main/java/org/libsdl/app/SDLSurface.java | 30 ++- cmake/sdlchecks.cmake | 17 ++ include/SDL3/SDL_events.h | 18 ++ include/build_config/SDL_build_config.h.cmake | 1 + src/core/android/SDL_android.c | 50 ++++ src/events/SDL_events.c | 17 +- src/events/SDL_touch.c | 16 ++ src/events/SDL_touch_c.h | 3 + src/test/SDL_test_common.c | 11 + src/video/cocoa/SDL_cocoawindow.h | 1 + src/video/cocoa/SDL_cocoawindow.m | 21 ++ src/video/uikit/SDL_uikitview.h | 3 + src/video/uikit/SDL_uikitview.m | 43 +++ src/video/wayland/SDL_waylandevents.c | 48 ++++ src/video/wayland/SDL_waylandevents_c.h | 1 + src/video/wayland/SDL_waylandvideo.c | 8 + src/video/wayland/SDL_waylandvideo.h | 1 + src/video/x11/SDL_x11xinput2.c | 51 +++- test/testgeometry.c | 22 +- .../pointer-gestures-unstable-v1.xml | 253 ++++++++++++++++++ 21 files changed, 605 insertions(+), 13 deletions(-) create mode 100644 wayland-protocols/pointer-gestures-unstable-v1.xml diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java index 796417588a..da7bbee793 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -1084,6 +1084,9 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh public static native boolean nativeAllowRecreateActivity(); public static native int nativeCheckSDLThreadCounter(); public static native void onNativeFileDialog(int requestCode, String[] filelist, int filter); + public static native void onNativePinchStart(); + public static native void onNativePinchUpdate(float scale); + public static native void onNativePinchEnd(); /** * This method is called by SDL using JNI. diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java b/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java index 8cd1262159..b8fae21e27 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java @@ -23,6 +23,7 @@ import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; +import android.view.ScaleGestureDetector; /** SDLSurface. This is what we draw on, so we need to know when it's created @@ -31,7 +32,8 @@ import android.view.WindowManager; Because of this, that's where we set up the SDL thread */ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, - View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnTouchListener, SensorEventListener { + View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnTouchListener, + SensorEventListener, ScaleGestureDetector.OnScaleGestureListener { // Sensors protected SensorManager mSensorManager; @@ -43,11 +45,16 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, // Is SurfaceView ready for rendering protected boolean mIsSurfaceReady; + // Pinch events + private final ScaleGestureDetector scaleGestureDetector; + // Startup protected SDLSurface(Context context) { super(context); getHolder().addCallback(this); + scaleGestureDetector = new ScaleGestureDetector(context, this); + setFocusable(true); setFocusableInTouchMode(true); requestFocus(); @@ -294,6 +301,8 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, break; } while (++i < pointerCount); + scaleGestureDetector.onTouchEvent(event); + return true; } @@ -415,4 +424,23 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, return false; } + + @Override + public boolean onScale(ScaleGestureDetector detector) { + float scale = detector.getScaleFactor(); + SDLActivity.onNativePinchUpdate(scale); + return true; + } + + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + SDLActivity.onNativePinchStart(); + return true; + } + + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + SDLActivity.onNativePinchEnd(); + } + } diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake index cd8576d44b..d444c4cb16 100644 --- a/cmake/sdlchecks.cmake +++ b/cmake/sdlchecks.cmake @@ -441,6 +441,23 @@ macro(CheckX11) if(HAVE_XINPUT2_MULTITOUCH) set(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1) endif() + + # Check for gesture + check_c_source_compiles(" + #include + #include + #include + int event_type = XI_GesturePinchBegin; + XITouchClassInfo *t; + Status XIAllowTouchEvents(Display *a,int b,unsigned int c,Window d,int f) { + return (Status)0; + } + int main(int argc, char **argv) { return 0; }" HAVE_XINPUT2_GESTURE) + if(HAVE_XINPUT2_GESTURE) + set(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE 1) + endif() + + endif() # check along with XInput2.h because we use Xfixes with XIBarrierReleasePointer diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index 999ede9bee..11924cf082 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -218,6 +218,11 @@ typedef enum SDL_EventType SDL_EVENT_FINGER_MOTION, SDL_EVENT_FINGER_CANCELED, + /* Pinch events */ + SDL_EVENT_PINCH_BEGIN = 0x710, /**< Pinch gesture started */ + SDL_EVENT_PINCH_UPDATE, /**< Pinch gesture updated */ + SDL_EVENT_PINCH_END, /**< Pinch gesture ended */ + /* 0x800, 0x801, and 0x802 were the Gesture events from SDL2. Do not reuse these values! sdl2-compat needs them! */ /* Clipboard events */ @@ -788,6 +793,18 @@ typedef struct SDL_TouchFingerEvent SDL_WindowID windowID; /**< The window underneath the finger, if any */ } SDL_TouchFingerEvent; +/** + * Pinch event structure (event.pinch.*) + */ +typedef struct SDL_PinchFingerEvent +{ + SDL_EventType type; /**< ::SDL_EVENT_PINCH_BEGIN or ::SDL_EVENT_PINCH_UPDATE or ::SDL_EVENT_PINCH_END */ + Uint32 reserved; + Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ + float scale; /**< The scale change since the last SDL_EVENT_PINCH_UPDATE. Scale < 1 is "zoom out". Scale > 1 is "zoom in". */ + SDL_WindowID windowID; /**< The window underneath the finger, if any */ +} SDL_PinchFingerEvent; + /** * Pressure-sensitive pen proximity event structure (event.pproximity.*) * @@ -1025,6 +1042,7 @@ typedef union SDL_Event SDL_QuitEvent quit; /**< Quit request event data */ SDL_UserEvent user; /**< Custom event data */ SDL_TouchFingerEvent tfinger; /**< Touch finger event data */ + SDL_PinchFingerEvent pinch; /**< Pinch event data */ SDL_PenProximityEvent pproximity; /**< Pen proximity event data */ SDL_PenTouchEvent ptouch; /**< Pen tip touching event data */ SDL_PenMotionEvent pmotion; /**< Pen motion event data */ diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 14dd86deaf..b4e721ca6b 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -436,6 +436,7 @@ #cmakedefine SDL_VIDEO_DRIVER_X11_XINPUT2 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 1 +#cmakedefine SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE @SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE@ #cmakedefine SDL_VIDEO_DRIVER_X11_XRANDR 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XSCRNSAVER 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XSHAPE 1 diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index c5c3683e26..88a2754d6d 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -121,6 +121,16 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)( jint touch_device_id_in, jint pointer_finger_id_in, jint action, jfloat x, jfloat y, jfloat p); +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchStart)( + JNIEnv *env, jclass jcls); + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchUpdate)( + JNIEnv *env, jclass jcls, + jfloat scale); + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchEnd)( + JNIEnv *env, jclass jcls); + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)( JNIEnv *env, jclass jcls, jint button, jint action, jfloat x, jfloat y, jboolean relative); @@ -221,6 +231,9 @@ static JNINativeMethod SDLActivity_tab[] = { { "onNativeSoftReturnKey", "()Z", SDL_JAVA_INTERFACE(onNativeSoftReturnKey) }, { "onNativeKeyboardFocusLost", "()V", SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost) }, { "onNativeTouch", "(IIIFFF)V", SDL_JAVA_INTERFACE(onNativeTouch) }, + { "onNativePinchStart", "()V", SDL_JAVA_INTERFACE(onNativePinchStart) }, + { "onNativePinchUpdate", "(F)V", SDL_JAVA_INTERFACE(onNativePinchUpdate) }, + { "onNativePinchEnd", "()V", SDL_JAVA_INTERFACE(onNativePinchEnd) }, { "onNativeMouse", "(IIFFZ)V", SDL_JAVA_INTERFACE(onNativeMouse) }, { "onNativePen", "(IIIFFF)V", SDL_JAVA_INTERFACE(onNativePen) }, { "onNativeAccel", "(FFF)V", SDL_JAVA_INTERFACE(onNativeAccel) }, @@ -1366,6 +1379,43 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)( SDL_UnlockMutex(Android_ActivityMutex); } +// Pinch +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchStart)( + JNIEnv *env, jclass jcls) +{ + SDL_LockMutex(Android_ActivityMutex); + + if (Android_Window) { + SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, Android_Window, 0); + } + + SDL_UnlockMutex(Android_ActivityMutex); +} + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchUpdate)( + JNIEnv *env, jclass jcls, jfloat scale) +{ + SDL_LockMutex(Android_ActivityMutex); + + if (Android_Window) { + SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, Android_Window, scale); + } + + SDL_UnlockMutex(Android_ActivityMutex); +} + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchEnd)( + JNIEnv *env, jclass jcls) +{ + SDL_LockMutex(Android_ActivityMutex); + + if (Android_Window) { + SDL_SendPinch(SDL_EVENT_PINCH_END, 0, Android_Window, 0); + } + + SDL_UnlockMutex(Android_ActivityMutex); +} + // Mouse JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)( JNIEnv *env, jclass jcls, diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index adff57e895..bf7cedaf2c 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -770,6 +770,20 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) break; #undef PRINT_FINGER_EVENT +#define PRINT_PINCH_EVENT(event) \ + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u scale=%f)", \ + (uint)event->pinch.timestamp, event->pinch.scale) + SDL_EVENT_CASE(SDL_EVENT_PINCH_BEGIN) + PRINT_PINCH_EVENT(event); + break; + SDL_EVENT_CASE(SDL_EVENT_PINCH_UPDATE) + PRINT_PINCH_EVENT(event); + break; + SDL_EVENT_CASE(SDL_EVENT_PINCH_END) + PRINT_PINCH_EVENT(event); + break; +#undef PRINT_PINCH_EVENT + #define PRINT_PTOUCH_EVENT(event) \ (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u pen_state=%u x=%g y=%g eraser=%s state=%s)", \ (uint)event->ptouch.timestamp, (uint)event->ptouch.windowID, (uint)event->ptouch.which, (uint)event->ptouch.pen_state, event->ptouch.x, event->ptouch.y, \ @@ -902,12 +916,13 @@ static void SDL_LogEvent(const SDL_Event *event) return; } - // sensor/mouse/pen/finger motion are spammy, ignore these if they aren't demanded. + // sensor/mouse/pen/finger/pinch motion are spammy, ignore these if they aren't demanded. if ((SDL_EventLoggingVerbosity < 2) && ((event->type == SDL_EVENT_MOUSE_MOTION) || (event->type == SDL_EVENT_FINGER_MOTION) || (event->type == SDL_EVENT_PEN_AXIS) || (event->type == SDL_EVENT_PEN_MOTION) || + (event->type == SDL_EVENT_PINCH_UPDATE) || (event->type == SDL_EVENT_GAMEPAD_AXIS_MOTION) || (event->type == SDL_EVENT_GAMEPAD_SENSOR_UPDATE) || (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION) || diff --git a/src/events/SDL_touch.c b/src/events/SDL_touch.c index e825117c82..6c0388e7f7 100644 --- a/src/events/SDL_touch.c +++ b/src/events/SDL_touch.c @@ -500,3 +500,19 @@ void SDL_QuitTouch(void) SDL_free(SDL_touchDevices); SDL_touchDevices = NULL; } + +int SDL_SendPinch(SDL_EventType type, Uint64 timestamp, SDL_Window *window, float scale) +{ + /* Post the event, if desired */ + int posted = 0; + if (SDL_EventEnabled(type)) { + SDL_Event event; + event.type = type; + event.common.timestamp = timestamp; + event.pinch.scale = scale; + event.pinch.windowID = window ? SDL_GetWindowID(window) : 0; + posted = (SDL_PushEvent(&event) > 0); + } + return posted; +} + diff --git a/src/events/SDL_touch_c.h b/src/events/SDL_touch_c.h index db2d64b85f..4c64a04a9b 100644 --- a/src/events/SDL_touch_c.h +++ b/src/events/SDL_touch_c.h @@ -57,4 +57,7 @@ extern void SDL_DelTouch(SDL_TouchID id); // Shutdown the touch subsystem extern void SDL_QuitTouch(void); +// Send Gesture events +extern int SDL_SendPinch(SDL_EventType type, Uint64 timestamp, SDL_Window *window, float scale); + #endif // SDL_touch_c_h_ diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index b5ffc9a46f..c1158df700 100644 --- a/src/test/SDL_test_common.c +++ b/src/test/SDL_test_common.c @@ -1928,6 +1928,16 @@ void SDLTest_PrintEvent(const SDL_Event *event) event->tfinger.dx, event->tfinger.dy, event->tfinger.pressure); break; + case SDL_EVENT_PINCH_BEGIN: + SDL_Log("SDL EVENT: Pinch Begin"); + break; + case SDL_EVENT_PINCH_UPDATE: + SDL_Log("SDL EVENT: Pinch Update, scale=%f", event->pinch.scale); + break; + case SDL_EVENT_PINCH_END: + SDL_Log("SDL EVENT: Pinch End"); + break; + case SDL_EVENT_RENDER_TARGETS_RESET: SDL_Log("SDL EVENT: render targets reset in window %" SDL_PRIu32, event->render.windowID); break; @@ -2238,6 +2248,7 @@ SDL_AppResult SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const event->type != SDL_EVENT_FINGER_MOTION && event->type != SDL_EVENT_PEN_MOTION && event->type != SDL_EVENT_PEN_AXIS && + event->type != SDL_EVENT_PINCH_UPDATE && event->type != SDL_EVENT_JOYSTICK_AXIS_MOTION) || (state->verbose & VERBOSE_MOTION)) { SDLTest_PrintEvent(event); diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h index e4ab6efed4..cce02c2dab 100644 --- a/src/video/cocoa/SDL_cocoawindow.h +++ b/src/video/cocoa/SDL_cocoawindow.h @@ -122,6 +122,7 @@ typedef enum - (void)touchesMovedWithEvent:(NSEvent *)theEvent; - (void)touchesEndedWithEvent:(NSEvent *)theEvent; - (void)touchesCancelledWithEvent:(NSEvent *)theEvent; +- (void)magnifyWithEvent:(NSEvent *) theEvent; // Touch event handling - (void)handleTouches:(NSTouchPhase)phase withEvent:(NSEvent *)theEvent; diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m index 72741fc6ff..2fee316dfc 100644 --- a/src/video/cocoa/SDL_cocoawindow.m +++ b/src/video/cocoa/SDL_cocoawindow.m @@ -1990,6 +1990,27 @@ static void Cocoa_SendMouseButtonClicks(SDL_Mouse *mouse, NSEvent *theEvent, SDL [self handleTouches:NSTouchPhaseCancelled withEvent:theEvent]; } +- (void)magnifyWithEvent:(NSEvent *)theEvent +{ + switch ([theEvent phase]) { + case NSEventPhaseBegan: + SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, Cocoa_GetEventTimestamp([theEvent timestamp]), NULL, 0); + break; + case NSEventPhaseChanged: + { + CGFloat scale = 1.0f + [theEvent magnification]; + SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, Cocoa_GetEventTimestamp([theEvent timestamp]), NULL, scale); + } + break; + case NSEventPhaseEnded: + case NSEventPhaseCancelled: + SDL_SendPinch(SDL_EVENT_PINCH_END, Cocoa_GetEventTimestamp([theEvent timestamp]), NULL, 0); + break; + default: + break; + } +} + - (void)handleTouches:(NSTouchPhase)phase withEvent:(NSEvent *)theEvent { NSSet *touches = [theEvent touchesMatchingPhase:phase inView:nil]; diff --git a/src/video/uikit/SDL_uikitview.h b/src/video/uikit/SDL_uikitview.h index 78c2beda36..53425674d2 100644 --- a/src/video/uikit/SDL_uikitview.h +++ b/src/video/uikit/SDL_uikitview.h @@ -46,6 +46,9 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; +#if !defined(SDL_PLATFORM_TVOS) +- (IBAction)sdlPinchGesture:(UIPinchGestureRecognizer *)sender; +#endif - (void)safeAreaInsetsDidChange; diff --git a/src/video/uikit/SDL_uikitview.m b/src/video/uikit/SDL_uikitview.m index ed649b1fc0..da5b6d75d7 100644 --- a/src/video/uikit/SDL_uikitview.m +++ b/src/video/uikit/SDL_uikitview.m @@ -48,6 +48,7 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick; SDL_TouchID directTouchId; SDL_TouchID indirectTouchId; + float pinch_scale; #if !defined(SDL_PLATFORM_TVOS) UIPointerInteraction *indirectPointerInteraction API_AVAILABLE(ios(13.4)); @@ -76,6 +77,15 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick; [self addGestureRecognizer:swipeRight]; #endif +#if !defined(SDL_PLATFORM_TVOS) + /* Pinch gestures */ + UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(sdlPinchGesture:)]; + pinchGesture.cancelsTouchesInView = NO; + pinchGesture.delaysTouchesBegan = NO; + pinchGesture.delaysTouchesEnded = NO; + [self addGestureRecognizer:pinchGesture]; +#endif + self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.autoresizesSubviews = YES; @@ -470,6 +480,39 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick; (int)SDL_ceilf(self.safeAreaInsets.bottom)); } +#if !defined(SDL_PLATFORM_TVOS) +- (IBAction)sdlPinchGesture:(UIPinchGestureRecognizer *)sender +{ + CGFloat scale = sender.scale; + UIGestureRecognizerState state = sender.state; + + switch (state) { + + case UIGestureRecognizerStateBegan: + pinch_scale = 1.0f; + SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, sdlwindow, 0); + break; + + case UIGestureRecognizerStateChanged: + if (pinch_scale > 0.0f) { + SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, sdlwindow, scale / pinch_scale); + } + pinch_scale = scale; + break; + + case UIGestureRecognizerStateFailed: + case UIGestureRecognizerStateEnded: + case UIGestureRecognizerStateCancelled: + SDL_SendPinch(SDL_EVENT_PINCH_END, 0, sdlwindow, 0); + break; + + default: + break; + } + +} +#endif + - (SDL_Scancode)scancodeFromPress:(UIPress *)press { if (press.key != nil) { diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 4fdc9c6df3..2b89953f4b 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -45,6 +45,7 @@ #include "tablet-v2-client-protocol.h" #include "primary-selection-unstable-v1-client-protocol.h" #include "input-timestamps-unstable-v1-client-protocol.h" +#include "pointer-gestures-unstable-v1-client-protocol.h" #ifdef HAVE_LIBDECOR_H #include @@ -1416,6 +1417,43 @@ static const struct wl_touch_listener touch_listener = { touch_handler_orientation // Version 6 }; +void pinch_begin(void *data, + struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, + uint32_t serial, + uint32_t time, + struct wl_surface *surface, + uint32_t fingers) +{ + SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, NULL, 0); +} +void pinch_update(void *data, + struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, + uint32_t time, + wl_fixed_t dx, + wl_fixed_t dy, + wl_fixed_t scale, + wl_fixed_t rotation) +{ + + float s = (float)(wl_fixed_to_double(scale)); + SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, NULL, s); +} + +void pinch_end(void *data, + struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, + uint32_t serial, + uint32_t time, + int32_t cancelled) +{ + SDL_SendPinch(SDL_EVENT_PINCH_END, 0, NULL, 0); +} + +static const struct zwp_pointer_gesture_pinch_v1_listener gesture_pinch_listener = { + pinch_begin, + pinch_update, + pinch_end +}; + // Fallback for xkb_keymap_key_get_mods_for_level(), which is only available from 1.0.0, while the SDL minimum is 0.5.0. #if !SDL_XKBCOMMON_CHECK_VERSION(1, 0, 0) static size_t xkb_legacy_get_mods_for_level(SDL_WaylandSeat *seat, xkb_keycode_t key, xkb_layout_index_t layout, xkb_level_index_t level, xkb_mod_mask_t *masks_out, size_t masks_size) @@ -2387,6 +2425,10 @@ static void Wayland_SeatDestroyTouch(SDL_WaylandSeat *seat) } } + if (seat->touch.gesture_pinch) { + zwp_pointer_gesture_pinch_v1_destroy(seat->touch.gesture_pinch); + } + SDL_zero(seat->touch); WAYLAND_wl_list_init(&seat->touch.points); } @@ -2430,6 +2472,12 @@ static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, enum w } SDL_AddTouch((SDL_TouchID)(uintptr_t)seat->touch.wl_touch, SDL_TOUCH_DEVICE_DIRECT, name_fmt); + + /* Pinch gesture */ + seat->touch.gesture_pinch = zwp_pointer_gestures_v1_get_pinch_gesture(seat->display->zwp_pointer_gestures, seat->pointer.wl_pointer); + zwp_pointer_gesture_pinch_v1_set_user_data(seat->touch.gesture_pinch, seat); + zwp_pointer_gesture_pinch_v1_add_listener(seat->touch.gesture_pinch, &gesture_pinch_listener, seat); + } else if (!(capabilities & WL_SEAT_CAPABILITY_TOUCH) && seat->touch.wl_touch) { Wayland_SeatDestroyTouch(seat); } diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h index c56a3adf06..70da05763a 100644 --- a/src/video/wayland/SDL_waylandevents_c.h +++ b/src/video/wayland/SDL_waylandevents_c.h @@ -192,6 +192,7 @@ typedef struct SDL_WaylandSeat struct zwp_input_timestamps_v1 *timestamps; Uint64 highres_timestamp_ns; struct wl_list points; + struct zwp_pointer_gesture_pinch_v1 *gesture_pinch; } touch; struct diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 158a0e541b..cdfe4227b9 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -67,6 +67,7 @@ #include "xdg-toplevel-icon-v1-client-protocol.h" #include "color-management-v1-client-protocol.h" #include "pointer-warp-v1-client-protocol.h" +#include "pointer-gestures-unstable-v1-client-protocol.h" #ifdef HAVE_LIBDECOR_H #include @@ -1322,6 +1323,8 @@ static void handle_registry_global(void *data, struct wl_registry *registry, uin Wayland_InitColorManager(d); } else if (SDL_strcmp(interface, "wp_pointer_warp_v1") == 0) { d->wp_pointer_warp_v1 = wl_registry_bind(d->registry, id, &wp_pointer_warp_v1_interface, 1); + } else if (SDL_strcmp(interface, "zwp_pointer_gestures_v1") == 0) { + d->zwp_pointer_gestures = wl_registry_bind(d->registry, id, &zwp_pointer_gestures_v1_interface, 1); } #ifdef SDL_WL_FIXES_VERSION else if (SDL_strcmp(interface, "wl_fixes") == 0) { @@ -1645,6 +1648,11 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this) data->wp_pointer_warp_v1 = NULL; } + if (data->zwp_pointer_gestures) { + zwp_pointer_gestures_v1_destroy(data->zwp_pointer_gestures); + data->zwp_pointer_gestures = NULL; + } + if (data->compositor) { wl_compositor_destroy(data->compositor); data->compositor = NULL; diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h index 837df3e4a4..644264a4a8 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/src/video/wayland/SDL_waylandvideo.h @@ -86,6 +86,7 @@ struct SDL_VideoData struct wp_color_manager_v1 *wp_color_manager_v1; struct zwp_tablet_manager_v2 *tablet_manager; struct wl_fixes *wl_fixes; + struct zwp_pointer_gestures_v1 *zwp_pointer_gestures; struct xkb_context *xkb_context; diff --git a/src/video/x11/SDL_x11xinput2.c b/src/video/x11/SDL_x11xinput2.c index e889aa2488..9b3c7c543f 100644 --- a/src/video/x11/SDL_x11xinput2.c +++ b/src/video/x11/SDL_x11xinput2.c @@ -38,6 +38,11 @@ static bool xinput2_initialized; #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH static bool xinput2_multitouch_supported; #endif +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE +static int xinput2_gesture_supported = 0; +#endif + +static int X11_Xinput2IsGestureSupported(void); /* Opcode returned X11_XQueryExtension * It will be used in event processing @@ -272,8 +277,8 @@ bool X11_InitXinput2(SDL_VideoDevice *_this) return false; // X server does not have XInput at all } - // We need at least 2.2 for Multitouch, 2.0 otherwise. - version = query_xinput2_version(data->display, 2, 2); + // We need at least 2.4 for Gesture, 2.2 for Multitouch, 2.0 otherwise. + version = query_xinput2_version(data->display, 2, 4); if (!xinput2_version_atleast(version, 2, 0)) { return false; // X server does not support the version we want at all. } @@ -287,6 +292,9 @@ bool X11_InitXinput2(SDL_VideoDevice *_this) #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH // Multitouch needs XInput 2.2 xinput2_multitouch_supported = xinput2_version_atleast(version, 2, 2); #endif +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE // Gesture needs XInput 2.4 + xinput2_gesture_supported = xinput2_version_atleast(version, 2, 4); +#endif // Populate the atoms for finding relative axes xinput2_rel_x_atom = X11_XInternAtom(data->display, "Rel X", False); @@ -735,6 +743,28 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) SDL_SendTouchMotion(0, xev->sourceid, xev->detail, window, x, y, 1.0); } break; #endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE + case XI_GesturePinchBegin: + case XI_GesturePinchUpdate: + case XI_GesturePinchEnd: + { + const XIGesturePinchEvent *xev = (const XIGesturePinchEvent *)cookie->data; + float x, y; + SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); + xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); + + if (cookie->evtype == XI_GesturePinchBegin) { + SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, window, 0); + } else if (cookie->evtype == XI_GesturePinchUpdate) { + SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, window, (float)xev->scale); + } else { + SDL_SendPinch(SDL_EVENT_PINCH_END, 0, window, 0); + } + } break; + +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE + } #endif // SDL_VIDEO_DRIVER_X11_XINPUT2 } @@ -774,6 +804,14 @@ void X11_Xinput2Select(SDL_VideoDevice *_this, SDL_Window *window) XISetMask(mask, XI_Motion); } + if (X11_Xinput2IsGestureSupported()) { +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE + XISetMask(mask, XI_GesturePinchBegin); + XISetMask(mask, XI_GesturePinchUpdate); + XISetMask(mask, XI_GesturePinchEnd); +#endif + } + X11_XISelectEvents(data->display, window_data->xwindow, &eventmask, 1); #endif } @@ -836,6 +874,15 @@ bool X11_Xinput2SelectMouseAndKeyboard(SDL_VideoDevice *_this, SDL_Window *windo return false; } +int X11_Xinput2IsGestureSupported(void) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE + return xinput2_initialized && xinput2_gesture_supported; +#else + return 0; +#endif +} + void X11_Xinput2GrabTouch(SDL_VideoDevice *_this, SDL_Window *window) { #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH diff --git a/test/testgeometry.c b/test/testgeometry.c index 1c33fbdb41..a3befff652 100644 --- a/test/testgeometry.c +++ b/test/testgeometry.c @@ -30,6 +30,7 @@ static SDL_BlendMode blendMode = SDL_BLENDMODE_NONE; static float angle = 0.0f; static int translate_cx = 0; static int translate_cy = 0; +static float pinch_scale = 1.0f; static int done; @@ -103,11 +104,14 @@ static void loop(void) } else if (event.key.key == SDLK_DOWN) { translate_cy += 1; } else { - SDLTest_CommonEvent(state, &event, &done); + } - } else { - SDLTest_CommonEvent(state, &event, &done); + } else if (event.type == SDL_EVENT_PINCH_BEGIN) { + } else if (event.type == SDL_EVENT_PINCH_UPDATE) { + pinch_scale *= event.pinch.scale; + } else if (event.type == SDL_EVENT_PINCH_END) { } + SDLTest_CommonEvent(state, &event, &done); } for (i = 0; i < state->num_windows; ++i) { @@ -136,24 +140,24 @@ static void loop(void) cy += translate_cy; a = (angle * SDL_PI_F) / 180.0f; - verts[0].position.x = cx + d * SDL_cosf(a); - verts[0].position.y = cy + d * SDL_sinf(a); + verts[0].position.x = cx + (d * SDL_cosf(a)) * pinch_scale; + verts[0].position.y = cy + (d * SDL_sinf(a)) * pinch_scale; verts[0].color.r = 1.0f; verts[0].color.g = 0; verts[0].color.b = 0; verts[0].color.a = 1.0f; a = ((angle + 120) * SDL_PI_F) / 180.0f; - verts[1].position.x = cx + d * SDL_cosf(a); - verts[1].position.y = cy + d * SDL_sinf(a); + verts[1].position.x = cx + (d * SDL_cosf(a)) * pinch_scale; + verts[1].position.y = cy + (d * SDL_sinf(a)) * pinch_scale; verts[1].color.r = 0; verts[1].color.g = 1.0f; verts[1].color.b = 0; verts[1].color.a = 1.0f; a = ((angle + 240) * SDL_PI_F) / 180.0f; - verts[2].position.x = cx + d * SDL_cosf(a); - verts[2].position.y = cy + d * SDL_sinf(a); + verts[2].position.x = cx + (d * SDL_cosf(a)) * pinch_scale; + verts[2].position.y = cy + (d * SDL_sinf(a)) * pinch_scale; verts[2].color.r = 0; verts[2].color.g = 0; verts[2].color.b = 1.0f; diff --git a/wayland-protocols/pointer-gestures-unstable-v1.xml b/wayland-protocols/pointer-gestures-unstable-v1.xml new file mode 100644 index 0000000000..f92a116059 --- /dev/null +++ b/wayland-protocols/pointer-gestures-unstable-v1.xml @@ -0,0 +1,253 @@ + + + + + + A global interface to provide semantic touchpad gestures for a given + pointer. + + Three gestures are currently supported: swipe, pinch, and hold. + Pinch and swipe gestures follow a three-stage cycle: begin, update, + end, hold gestures follow a two-stage cycle: begin and end. All + gestures are identified by a unique id. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + Create a swipe gesture object. See the + wl_pointer_gesture_swipe interface for details. + + + + + + + + Create a pinch gesture object. See the + wl_pointer_gesture_pinch interface for details. + + + + + + + + + + Destroy the pointer gesture object. Swipe, pinch and hold objects + created via this gesture object remain valid. + + + + + + + + Create a hold gesture object. See the + wl_pointer_gesture_hold interface for details. + + + + + + + + + + A swipe gesture object notifies a client about a multi-finger swipe + gesture detected on an indirect input device such as a touchpad. + The gesture is usually initiated by multiple fingers moving in the + same direction but once initiated the direction may change. + The precise conditions of when such a gesture is detected are + implementation-dependent. + + A gesture consists of three stages: begin, update (optional) and end. + There cannot be multiple simultaneous hold, pinch or swipe gestures on a + same pointer/seat, how compositors prevent these situations is + implementation-dependent. + + A gesture may be cancelled by the compositor or the hardware. + Clients should not consider performing permanent or irreversible + actions until the end of a gesture has been received. + + + + + + + + + This event is sent when a multi-finger swipe gesture is detected + on the device. + + + + + + + + + + This event is sent when a multi-finger swipe gesture changes the + position of the logical center. + + The dx and dy coordinates are relative coordinates of the logical + center of the gesture compared to the previous event. + + + + + + + + + This event is sent when a multi-finger swipe gesture ceases to + be valid. This may happen when one or more fingers are lifted or + the gesture is cancelled. + + When a gesture is cancelled, the client should undo state changes + caused by this gesture. What causes a gesture to be cancelled is + implementation-dependent. + + + + + + + + + + A pinch gesture object notifies a client about a multi-finger pinch + gesture detected on an indirect input device such as a touchpad. + The gesture is usually initiated by multiple fingers moving towards + each other or away from each other, or by two or more fingers rotating + around a logical center of gravity. The precise conditions of when + such a gesture is detected are implementation-dependent. + + A gesture consists of three stages: begin, update (optional) and end. + There cannot be multiple simultaneous hold, pinch or swipe gestures on a + same pointer/seat, how compositors prevent these situations is + implementation-dependent. + + A gesture may be cancelled by the compositor or the hardware. + Clients should not consider performing permanent or irreversible + actions until the end of a gesture has been received. + + + + + + + + + This event is sent when a multi-finger pinch gesture is detected + on the device. + + + + + + + + + + This event is sent when a multi-finger pinch gesture changes the + position of the logical center, the rotation or the relative scale. + + The dx and dy coordinates are relative coordinates in the + surface coordinate space of the logical center of the gesture. + + The scale factor is an absolute scale compared to the + pointer_gesture_pinch.begin event, e.g. a scale of 2 means the fingers + are now twice as far apart as on pointer_gesture_pinch.begin. + + The rotation is the relative angle in degrees clockwise compared to the previous + pointer_gesture_pinch.begin or pointer_gesture_pinch.update event. + + + + + + + + + + + This event is sent when a multi-finger pinch gesture ceases to + be valid. This may happen when one or more fingers are lifted or + the gesture is cancelled. + + When a gesture is cancelled, the client should undo state changes + caused by this gesture. What causes a gesture to be cancelled is + implementation-dependent. + + + + + + + + + + + A hold gesture object notifies a client about a single- or + multi-finger hold gesture detected on an indirect input device such as + a touchpad. The gesture is usually initiated by one or more fingers + being held down without significant movement. The precise conditions + of when such a gesture is detected are implementation-dependent. + + In particular, this gesture may be used to cancel kinetic scrolling. + + A hold gesture consists of two stages: begin and end. Unlike pinch and + swipe there is no update stage. + There cannot be multiple simultaneous hold, pinch or swipe gestures on a + same pointer/seat, how compositors prevent these situations is + implementation-dependent. + + A gesture may be cancelled by the compositor or the hardware. + Clients should not consider performing permanent or irreversible + actions until the end of a gesture has been received. + + + + + + + + + This event is sent when a hold gesture is detected on the device. + + + + + + + + + + This event is sent when a hold gesture ceases to + be valid. This may happen when the holding fingers are lifted or + the gesture is cancelled, for example if the fingers move past an + implementation-defined threshold, the finger count changes or the hold + gesture changes into a different type of gesture. + + When a gesture is cancelled, the client may need to undo state changes + caused by this gesture. What causes a gesture to be cancelled is + implementation-dependent. + + + + + + + +