diff --git a/common/view/wx_view_controls.cpp b/common/view/wx_view_controls.cpp index 29fde7d6e3..cc29245c91 100644 --- a/common/view/wx_view_controls.cpp +++ b/common/view/wx_view_controls.cpp @@ -447,6 +447,7 @@ void WX_VIEW_CONTROLS::onButton( wxMouseEvent& aEvent ) { m_dragStartPoint = VECTOR2D( aEvent.GetX(), aEvent.GetY() ); setState( DRAG_PANNING ); + KIPLATFORM::UI::InfiniteDragPrepareWindow( m_parentPanel ); #if defined USE_MOUSE_CAPTURE if( !m_parentPanel->HasCapture() ) @@ -476,6 +477,7 @@ void WX_VIEW_CONTROLS::onButton( wxMouseEvent& aEvent ) if( aEvent.MiddleUp() || aEvent.LeftUp() || aEvent.RightUp() ) { setState( IDLE ); + KIPLATFORM::UI::InfiniteDragReleaseWindow(); #if defined USE_MOUSE_CAPTURE if( !m_settings.m_cursorCaptured && m_parentPanel->HasCapture() ) diff --git a/libs/kiplatform/gtk/ui.cpp b/libs/kiplatform/gtk/ui.cpp index a7b0164255..dd7d8e5da0 100644 --- a/libs/kiplatform/gtk/ui.cpp +++ b/libs/kiplatform/gtk/ui.cpp @@ -212,12 +212,21 @@ bool KIPLATFORM::UI::AllowIconsInMenus() #ifdef KICAD_WAYLAND +static bool wl_initialized = false; +static struct wl_compositor* compositor = NULL; static struct zwp_pointer_constraints_v1* pointer_constraints = NULL; +static struct zwp_confined_pointer_v1* confined_pointer = NULL; +static struct wl_region* confinement_region = NULL; static void handle_global( void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version ) { - if( strcmp( interface, zwp_pointer_constraints_v1_interface.name ) == 0 ) + if( strcmp( interface, wl_compositor_interface.name ) == 0 ) + { + compositor = static_cast( + wl_registry_bind( registry, name, &wl_compositor_interface, version ) ); + } + else if( strcmp( interface, zwp_pointer_constraints_v1_interface.name ) == 0 ) { pointer_constraints = static_cast( wl_registry_bind( registry, name, &zwp_pointer_constraints_v1_interface, version ) ); @@ -225,10 +234,26 @@ static void handle_global( void* data, struct wl_registry* registry, uint32_t na } static const struct wl_registry_listener registry_listener = { - .global = handle_global, - .global_remove = NULL, + .global = handle_global, + .global_remove = NULL, }; +/** + * Initializes `compositor` and `pointer_constraints` global variables. + */ +static void initialize_wayland( wl_display* wldisp ) +{ + if( wl_initialized ) + { + return; + } + + struct wl_registry* registry = wl_display_get_registry( wldisp ); + wl_registry_add_listener( registry, ®istry_listener, NULL ); + wl_display_roundtrip( wldisp ); + wl_initialized = true; +} + #endif @@ -246,21 +271,24 @@ void KIPLATFORM::UI::WarpPointer( wxWindow* aWindow, int aX, int aY ) GdkSeat* seat = gdk_display_get_default_seat( disp ); GdkDevice* ptrdev = gdk_seat_get_pointer( seat ); -#if defined( GDK_WINDOWING_WAYLAND ) && defined KICAD_WAYLAND +#if defined( GDK_WINDOWING_WAYLAND ) && defined( KICAD_WAYLAND ) if( GDK_IS_WAYLAND_DISPLAY( disp ) ) { GdkWindow* win = aWindow->GTKGetDrawingWindow(); wl_display* wldisp = gdk_wayland_display_get_wl_display( disp ); - - struct wl_registry* registry = wl_display_get_registry( wldisp ); - int lret = wl_registry_add_listener( registry, ®istry_listener, NULL ); - - wl_display_roundtrip( wldisp ); - wl_surface* wlsurf = gdk_wayland_window_get_wl_surface( win ); wl_pointer* wlptr = gdk_wayland_device_get_wl_pointer( ptrdev ); + initialize_wayland( wldisp ); + + // temporary disable confinement to allow pointer warping + if( confinement_region != NULL ) + { + zwp_confined_pointer_v1_destroy( confined_pointer ); + confined_pointer = NULL; + } + struct zwp_locked_pointer_v1* locked_pointer = zwp_pointer_constraints_v1_lock_pointer( pointer_constraints, wlsurf, wlptr, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT ); @@ -272,10 +300,18 @@ void KIPLATFORM::UI::WarpPointer( wxWindow* aWindow, int aX, int aY ) zwp_locked_pointer_v1_set_cursor_position_hint( locked_pointer, wl_fixed_from_int( aX + wx ), wl_fixed_from_int( aY + wy ) ); + zwp_locked_pointer_v1_destroy( locked_pointer ); + + // restore confinement + if( confinement_region != NULL ) + { + confined_pointer = zwp_pointer_constraints_v1_confine_pointer( + pointer_constraints, wlsurf, wlptr, confinement_region, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT ); + } + wl_surface_commit( wlsurf ); wl_display_roundtrip( wldisp ); - - zwp_locked_pointer_v1_destroy( locked_pointer ); } #endif #ifdef GDK_WINDOWING_X11 @@ -299,4 +335,63 @@ void KIPLATFORM::UI::WarpPointer( wxWindow* aWindow, int aX, int aY ) void KIPLATFORM::UI::ImmControl( wxWindow* aWindow, bool aEnable ) { -} \ No newline at end of file +} + + +void KIPLATFORM::UI::InfiniteDragPrepareWindow( wxWindow* aWindow ) +{ +#if defined( GDK_WINDOWING_WAYLAND ) && defined( KICAD_WAYLAND ) + if( confined_pointer != NULL ) + { + KIPLATFORM::UI::InfiniteDragReleaseWindow(); + } + + GtkWidget* widget = static_cast( aWindow->GetHandle() ); + GdkDisplay* disp = gtk_widget_get_display( widget ); + + if( !GDK_IS_WAYLAND_DISPLAY( disp ) ) + { + return; + } + + GdkSeat* seat = gdk_display_get_default_seat( disp ); + GdkDevice* ptrdev = gdk_seat_get_pointer( seat ); + GdkWindow* win = aWindow->GTKGetDrawingWindow(); + + wl_display* wldisp = gdk_wayland_display_get_wl_display( disp ); + wl_surface* wlsurf = gdk_wayland_window_get_wl_surface( win ); + wl_pointer* wlptr = gdk_wayland_device_get_wl_pointer( ptrdev ); + + initialize_wayland( wldisp ); + + gint x, y, width, height, wx, wy; + gdk_window_get_geometry( win, &x, &y, &width, &height ); + gtk_widget_translate_coordinates( widget, gtk_widget_get_toplevel( widget ), 0, 0, &wx, &wy ); + + confinement_region = wl_compositor_create_region( compositor ); + wl_region_add( confinement_region, x + wx - 1, y + wy - 1, width + 2, height + 2 ); + + confined_pointer = zwp_pointer_constraints_v1_confine_pointer( + pointer_constraints, wlsurf, wlptr, confinement_region, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT ); + + wl_surface_commit( wlsurf ); + wl_display_roundtrip( wldisp ); +#endif +}; + +void KIPLATFORM::UI::InfiniteDragReleaseWindow() +{ +#if defined( GDK_WINDOWING_WAYLAND ) && defined( KICAD_WAYLAND ) + if( confined_pointer == NULL ) + { + return; + } + + zwp_confined_pointer_v1_destroy( confined_pointer ); + wl_region_destroy( confinement_region ); + + confined_pointer = NULL; + confinement_region = NULL; +#endif +}; diff --git a/libs/kiplatform/include/kiplatform/ui.h b/libs/kiplatform/include/kiplatform/ui.h index ec920dd6ef..e880beb352 100644 --- a/libs/kiplatform/include/kiplatform/ui.h +++ b/libs/kiplatform/include/kiplatform/ui.h @@ -150,6 +150,20 @@ namespace KIPLATFORM * Configures the IME mode of a given control handle */ void ImmControl( wxWindow* aWindow, bool aEnable ); + + /** + * On Wayland, restricts the pointer movement to a rectangle slightly bigger than the given `wxWindow`. + * This way, the cursor doesn't exit the (bigger) application window and we retain control on it. + * Required to make the infinite mouse-drag work with fast movement. + * See https://gitlab.com/kicad/code/kicad/-/issues/7207#note_1562089503 + * @param aWindow Window in which to position to mouse cursor + */ + void InfiniteDragPrepareWindow( wxWindow* aWindow ); + + /** + * On Wayland, allows the cursor to freely move again after a drag (see `InfiniteDragPrepareWindow`). + */ + void InfiniteDragReleaseWindow(); } } diff --git a/libs/kiplatform/msw/ui.cpp b/libs/kiplatform/msw/ui.cpp index e80c8b5441..ed6c5d7dd2 100644 --- a/libs/kiplatform/msw/ui.cpp +++ b/libs/kiplatform/msw/ui.cpp @@ -174,4 +174,16 @@ void KIPLATFORM::UI::ImmControl( wxWindow* aWindow, bool aEnable ) { ImmAssociateContextEx( aWindow->GetHWND(), 0, IACE_DEFAULT ); } -} \ No newline at end of file +} + + +void KIPLATFORM::UI::InfiniteDragPrepareWindow( wxWindow* aWindow ) +{ + // Not needed on this platform +} + + +void KIPLATFORM::UI::InfiniteDragReleaseWindow() +{ + // Not needed on this platform +} diff --git a/libs/kiplatform/osx/ui.mm b/libs/kiplatform/osx/ui.mm index 1f46fab130..030a3c9c60 100644 --- a/libs/kiplatform/osx/ui.mm +++ b/libs/kiplatform/osx/ui.mm @@ -174,4 +174,16 @@ void KIPLATFORM::UI::WarpPointer( wxWindow* aWindow, int aX, int aY ) void KIPLATFORM::UI::ImmControl( wxWindow* aWindow, bool aEnable ) { -} \ No newline at end of file +} + + +void KIPLATFORM::UI::InfiniteDragPrepareWindow( wxWindow* aWindow ) +{ + // Not needed on this platform +} + + +void KIPLATFORM::UI::InfiniteDragReleaseWindow() +{ + // Not needed on this platform +}