Pointer constraints seem to be broken, especially when using wlr layer shell protocol
Labwc 0.5.3 on Arch Linux
I am using a minimal test case with a GtkWindow and a GtkButton that triggers the constraint when pressed, and removes it when released. I can provide the code if needed.
As is, the confinement constraint has the same effect as the lock constraint: the pointer is effectively locked, and the "motion" signal is no longer emitted. The confinement constraint does emit the "confined" signal though.
If one activates the wlr layer shell protocol (in my case via the Gtk Layer Shell library, by a call to gtk_layer_init_for_window()), neither constraint works, the "confined" and "locked" signals are not emitted.
All this works normally on Wayfire for example.
I can provide the code if needed.
That would be welcome. Having a minimal example should speed things up.
Here is a test code that should do the trick I think.
p.c
#include <gtk/gtk.h>
#include <gdk/gdkwayland.h>
#include <gtk-layer-shell/gtk-layer-shell.h>
#include "pointer-constraints-unstable-v1-client.h"
static struct wl_registry *wl_registry;
static struct zwp_pointer_constraints_v1 *zwp_pointer_constraints = NULL;
static struct zwp_confined_pointer_v1 *zwp_confined_pointer = NULL;
static struct zwp_locked_pointer_v1 *zwp_locked_pointer = NULL;
static gboolean surface_is_layer = FALSE;
static gboolean pointer_is_locked = FALSE;
static gboolean motion_emitted = FALSE;
static void
panel_window_wl_registry_global (void *data,
struct wl_registry *registry,
uint32_t id,
const char *interface,
uint32_t version)
{
if (g_strcmp0 (interface, "zwp_pointer_constraints_v1") == 0)
zwp_pointer_constraints =
wl_registry_bind (registry, id, &zwp_pointer_constraints_v1_interface,
MIN ((uint32_t) zwp_pointer_constraints_v1_interface.version, version));
}
static void
panel_window_wl_registry_global_remove (void *data,
struct wl_registry *registry,
uint32_t id)
{
}
static const struct wl_registry_listener wl_registry_listener =
{
panel_window_wl_registry_global,
panel_window_wl_registry_global_remove
};
static void
panel_window_zwp_confined_pointer_confined (void *data,
struct zwp_confined_pointer_v1 *pointer)
{
g_printerr ("%s\n", G_STRLOC);
}
static void
panel_window_zwp_confined_pointer_unconfined (void *data,
struct zwp_confined_pointer_v1 *pointer)
{
g_printerr ("%s\n", G_STRLOC);
}
static const struct zwp_confined_pointer_v1_listener zwp_confined_pointer_listener =
{
panel_window_zwp_confined_pointer_confined,
panel_window_zwp_confined_pointer_unconfined
};
static void
panel_window_zwp_locked_pointer_locked (void *data,
struct zwp_locked_pointer_v1 *pointer)
{
g_printerr ("%s\n", G_STRLOC);
}
static void
panel_window_zwp_locked_pointer_unlocked (void *data,
struct zwp_locked_pointer_v1 *pointer)
{
g_printerr ("%s\n", G_STRLOC);
}
static const struct zwp_locked_pointer_v1_listener zwp_locked_pointer_listener =
{
panel_window_zwp_locked_pointer_locked,
panel_window_zwp_locked_pointer_unlocked
};
static gboolean
on_button_press (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
GtkWidget *window = user_data;
struct wl_surface *wl_surface;
struct wl_pointer *wl_pointer;
GdkDevice *pointer;
wl_surface = gdk_wayland_window_get_wl_surface (gtk_widget_get_window (window));
pointer = gdk_seat_get_pointer (gdk_display_get_default_seat (gdk_display_get_default ()));
wl_pointer = gdk_wayland_device_get_wl_pointer (pointer);
if (pointer_is_locked)
{
zwp_locked_pointer =
zwp_pointer_constraints_v1_lock_pointer (zwp_pointer_constraints, wl_surface, wl_pointer,
NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT);
zwp_locked_pointer_v1_add_listener (zwp_locked_pointer, &zwp_locked_pointer_listener, window);
}
else
{
zwp_confined_pointer =
zwp_pointer_constraints_v1_confine_pointer (zwp_pointer_constraints, wl_surface, wl_pointer,
NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT);
zwp_confined_pointer_v1_add_listener (zwp_confined_pointer, &zwp_confined_pointer_listener, window);
}
motion_emitted = FALSE;
return TRUE;
}
static gboolean
on_button_release (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
if (pointer_is_locked && zwp_locked_pointer != NULL)
{
zwp_locked_pointer_v1_destroy (zwp_locked_pointer);
zwp_locked_pointer = NULL;
}
else if (! pointer_is_locked && zwp_confined_pointer != NULL)
{
zwp_confined_pointer_v1_destroy (zwp_confined_pointer);
zwp_confined_pointer = NULL;
}
return TRUE;
}
static gboolean
on_motion_notify (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
if (! motion_emitted)
{
g_printerr ("%s\n", G_STRLOC);
motion_emitted = TRUE;
}
return FALSE;
}
gint main (gint argc, gchar **argv)
{
GtkWidget *window;
GMainLoop *loop;
GtkWidget *widget;
struct wl_display *wl_display;
gtk_init (&argc, &argv);
for (gchar **p = argv + 1; *p != NULL; p++)
if (g_strcmp0 (*p, "--layer") == 0)
surface_is_layer = TRUE;
else if (g_strcmp0 (*p, "--locked") == 0)
pointer_is_locked = TRUE;
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
widget = gtk_button_new_with_label ("test");
gtk_container_add (GTK_CONTAINER (window), widget);
g_signal_connect (widget, "button-press-event", G_CALLBACK (on_button_press), window);
g_signal_connect (widget, "button-release-event", G_CALLBACK (on_button_release), window);
g_signal_connect (widget, "motion-notify-event", G_CALLBACK (on_motion_notify), window);
wl_display = gdk_wayland_display_get_wl_display (gdk_display_get_default ());
wl_registry = wl_display_get_registry (wl_display);
wl_registry_add_listener (wl_registry, &wl_registry_listener, NULL);
wl_display_roundtrip (wl_display);
if (surface_is_layer)
gtk_layer_init_for_window (GTK_WINDOW (window));
gtk_widget_show (widget);
gtk_widget_show (window);
loop = g_main_loop_new (NULL, FALSE);
g_signal_connect_swapped (window, "destroy", G_CALLBACK (g_main_loop_quit), loop);
g_main_loop_run (loop);
g_main_loop_unref (loop);
if (zwp_pointer_constraints != NULL)
zwp_pointer_constraints_v1_destroy (zwp_pointer_constraints);
}
Here are also the pointer-constraints-unstable-v1-client.h and pointer-constraints-unstable-v1.c files from the wayland-scanner code generation, for convenience.
wayland-scanner.tar.gz
It can be built by
gcc pointer-constraints-unstable-v1.c p.c -o p $(pkg-config --cflags --libs gtk+-3.0 wayland-client gtk-layer-shell-0)
It is then used with
./p [--layer] [--locked]
to activate or not layer shell and lock or confine (by default) the pointer.
Thanks, will have a look during the next days if nobody else beats me to it.
I had a quick look and we are def. doing some stuff wrong in regards to pointer lock / confine. Thanks for pointing this out to us. There is no timeframe when we get to fix it as it requires some more investigation and changes throughout the cursor handling subroutines.
The layershell protocol preventing lock / confine is easy to fix but it seems we are not actually implementing confine at all.
Modified the example code a bit so it now also prints out movement coordinates to further test the confinement:
code
#include <gtk/gtk.h>
#include <gdk/gdkwayland.h>
#include <gtk-layer-shell/gtk-layer-shell.h>
#include "pointer-constraints-unstable-v1-client.h"
static struct wl_registry *wl_registry;
static struct zwp_pointer_constraints_v1 *zwp_pointer_constraints = NULL;
static struct zwp_confined_pointer_v1 *zwp_confined_pointer = NULL;
static struct zwp_locked_pointer_v1 *zwp_locked_pointer = NULL;
static gboolean surface_is_layer = FALSE;
static gboolean pointer_is_locked = FALSE;
static gboolean motion_emitted = FALSE;
static void
panel_window_wl_registry_global (void *data,
struct wl_registry *registry,
uint32_t id,
const char *interface,
uint32_t version)
{
if (g_strcmp0 (interface, "zwp_pointer_constraints_v1") == 0)
zwp_pointer_constraints =
wl_registry_bind (registry, id, &zwp_pointer_constraints_v1_interface,
MIN ((uint32_t) zwp_pointer_constraints_v1_interface.version, version));
}
static void
panel_window_wl_registry_global_remove (void *data,
struct wl_registry *registry,
uint32_t id)
{
}
static const struct wl_registry_listener wl_registry_listener =
{
panel_window_wl_registry_global,
panel_window_wl_registry_global_remove
};
static void
panel_window_zwp_confined_pointer_confined (void *data,
struct zwp_confined_pointer_v1 *pointer)
{
g_printerr ("%s\n", G_STRLOC);
}
static void
panel_window_zwp_confined_pointer_unconfined (void *data,
struct zwp_confined_pointer_v1 *pointer)
{
g_printerr ("%s\n", G_STRLOC);
}
static const struct zwp_confined_pointer_v1_listener zwp_confined_pointer_listener =
{
panel_window_zwp_confined_pointer_confined,
panel_window_zwp_confined_pointer_unconfined
};
static void
panel_window_zwp_locked_pointer_locked (void *data,
struct zwp_locked_pointer_v1 *pointer)
{
g_printerr ("%s\n", G_STRLOC);
}
static void
panel_window_zwp_locked_pointer_unlocked (void *data,
struct zwp_locked_pointer_v1 *pointer)
{
g_printerr ("%s\n", G_STRLOC);
}
static const struct zwp_locked_pointer_v1_listener zwp_locked_pointer_listener =
{
panel_window_zwp_locked_pointer_locked,
panel_window_zwp_locked_pointer_unlocked
};
static gboolean
on_button_press (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
GtkWidget *window = user_data;
struct wl_surface *wl_surface;
struct wl_pointer *wl_pointer;
GdkDevice *pointer;
wl_surface = gdk_wayland_window_get_wl_surface (gtk_widget_get_window (window));
pointer = gdk_seat_get_pointer (gdk_display_get_default_seat (gdk_display_get_default ()));
wl_pointer = gdk_wayland_device_get_wl_pointer (pointer);
if (pointer_is_locked)
{
zwp_locked_pointer =
zwp_pointer_constraints_v1_lock_pointer (zwp_pointer_constraints, wl_surface, wl_pointer,
NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT);
zwp_locked_pointer_v1_add_listener (zwp_locked_pointer, &zwp_locked_pointer_listener, window);
}
else
{
zwp_confined_pointer =
zwp_pointer_constraints_v1_confine_pointer (zwp_pointer_constraints, wl_surface, wl_pointer,
NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT);
zwp_confined_pointer_v1_add_listener (zwp_confined_pointer, &zwp_confined_pointer_listener, window);
}
motion_emitted = FALSE;
return TRUE;
}
static gboolean
on_button_release (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
if (pointer_is_locked && zwp_locked_pointer != NULL)
{
zwp_locked_pointer_v1_destroy (zwp_locked_pointer);
zwp_locked_pointer = NULL;
}
else if (! pointer_is_locked && zwp_confined_pointer != NULL)
{
zwp_confined_pointer_v1_destroy (zwp_confined_pointer);
zwp_confined_pointer = NULL;
}
return TRUE;
}
static gboolean
on_motion_notify (GtkWidget *widget,
GdkEventMotion *event,
gpointer user_data)
{
if (! motion_emitted)
{
g_printerr ("%s\n", G_STRLOC);
motion_emitted = TRUE;
}
g_printerr ("motion emitted: %.2f,%.2f\n", event->x, event->y);
return FALSE;
}
gint main (gint argc, gchar **argv)
{
GtkWidget *window;
GMainLoop *loop;
GtkWidget *widget;
struct wl_display *wl_display;
gtk_init (&argc, &argv);
for (gchar **p = argv + 1; *p != NULL; p++)
if (g_strcmp0 (*p, "--layer") == 0)
surface_is_layer = TRUE;
else if (g_strcmp0 (*p, "--locked") == 0)
pointer_is_locked = TRUE;
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_set_size_request(window, 200, 100);
widget = gtk_button_new_with_label ("test");
gtk_container_add (GTK_CONTAINER (window), widget);
g_signal_connect (widget, "button-press-event", G_CALLBACK (on_button_press), window);
g_signal_connect (widget, "button-release-event", G_CALLBACK (on_button_release), window);
g_signal_connect (widget, "motion-notify-event", G_CALLBACK (on_motion_notify), window);
wl_display = gdk_wayland_display_get_wl_display (gdk_display_get_default ());
wl_registry = wl_display_get_registry (wl_display);
wl_registry_add_listener (wl_registry, &wl_registry_listener, NULL);
wl_display_roundtrip (wl_display);
if (surface_is_layer)
gtk_layer_init_for_window (GTK_WINDOW (window));
gtk_widget_show (widget);
gtk_widget_show (window);
loop = g_main_loop_new (NULL, FALSE);
g_signal_connect_swapped (window, "destroy", G_CALLBACK (g_main_loop_quit), loop);
g_main_loop_run (loop);
g_main_loop_unref (loop);
if (zwp_pointer_constraints != NULL)
zwp_pointer_constraints_v1_destroy (zwp_pointer_constraints);
}