Skip to content

Incorrect coordinates given to pointer_motion listener during a drag from a sub-surface #2542

@CyprinusCarpio

Description

@CyprinusCarpio

Problem description

I'm developing a widget docking implementation for the FLTK toolkit. It allows detaching widget groups from the main window, which on Wayland creates a child window, or a sub-surface in Wayland parlance, the parent being the top-level. The sub surface can be moved outside the top level, and/or rescaled. However, if the user starts a drag in the detached window, and the pointer goes outside the detached window, FLTK and in turn my code get wrong coordinates. I have narrowed down the problem to the pointer_motion listener, which gets incorrect coordinates during a drag initiated in a sub-surface if the pointer is dragged outside said sub-surface. The coordinates appear to be in relation to the top-level instead. This is not the case in KWin and Weston, the other two compositors I tried. I've also modified the hello-wayland to exhibit the same behavior without the overhead of FLTK: modified main.c

modified main.c
/*#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <linux/input-event-codes.h>

#include "cat.h"
#include "shm.h"
#include "xdg-shell-client-protocol.h"

static const int width = 128;
static const int height = 128;

static bool configured = false;
static bool running = true;

static struct wl_shm *shm = NULL;
static struct wl_compositor *compositor = NULL;
static struct xdg_wm_base *xdg_wm_base = NULL;

static void *shm_data = NULL;
static struct wl_surface *surface = NULL;
static struct xdg_toplevel *xdg_toplevel = NULL;

static void noop() {
	// This space intentionally left blank
}

static void xdg_wm_base_handle_ping(void *data,
		struct xdg_wm_base *xdg_wm_base, uint32_t serial) {
	// The compositor will send us a ping event to check that we're responsive.
	// We need to send back a pong request immediately.
	xdg_wm_base_pong(xdg_wm_base, serial);
}

static const struct xdg_wm_base_listener xdg_wm_base_listener = {
	.ping = xdg_wm_base_handle_ping,
};

static void xdg_surface_handle_configure(void *data,
		struct xdg_surface *xdg_surface, uint32_t serial) {
	// The compositor configures our surface, acknowledge the configure event
	xdg_surface_ack_configure(xdg_surface, serial);

	if (configured) {
		// If this isn't the first configure event we've received, we already
		// have a buffer attached, so no need to do anything. Commit the
		// surface to apply the configure acknowledgement.
		wl_surface_commit(surface);
	}

	configured = true;
}

static const struct xdg_surface_listener xdg_surface_listener = {
	.configure = xdg_surface_handle_configure,
};

static void xdg_toplevel_handle_close(void *data,
		struct xdg_toplevel *xdg_toplevel) {
	// Stop running if the user requests to close the toplevel
	running = false;
}

static const struct xdg_toplevel_listener xdg_toplevel_listener = {
	.configure = noop,
	.close = xdg_toplevel_handle_close,
};

static void pointer_motion(void *data, struct wl_pointer *wl_pointer,
         uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
	printf("pointer_motion %d %d\n", surface_x, surface_y);
}

static const struct wl_pointer_listener pointer_listener = {
	.enter = noop,
	.leave = noop,
	.motion = pointer_motion,
	.button = noop,
	.axis = noop,
};

static void seat_handle_capabilities(void *data, struct wl_seat *seat,
		uint32_t capabilities) {
	// If the wl_seat has the pointer capability, start listening to pointer
	// events
	if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
		struct wl_pointer *pointer = wl_seat_get_pointer(seat);
		wl_pointer_add_listener(pointer, &pointer_listener, seat);
	}
}

static const struct wl_seat_listener seat_listener = {
	.capabilities = seat_handle_capabilities,
};

static void handle_global(void *data, struct wl_registry *registry,
		uint32_t name, const char *interface, uint32_t version) {
	if (strcmp(interface, wl_shm_interface.name) == 0) {
		shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
	} else if (strcmp(interface, wl_seat_interface.name) == 0) {
		struct wl_seat *seat =
			wl_registry_bind(registry, name, &wl_seat_interface, 1);
		wl_seat_add_listener(seat, &seat_listener, NULL);
	} else if (strcmp(interface, wl_compositor_interface.name) == 0) {
		compositor = wl_registry_bind(registry, name,
			&wl_compositor_interface, 1);
	} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
		xdg_wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
		xdg_wm_base_add_listener(xdg_wm_base, &xdg_wm_base_listener, NULL);
	}
}

static void handle_global_remove(void *data, struct wl_registry *registry,
		uint32_t name) {
	// Who cares
}

static const struct wl_registry_listener registry_listener = {
	.global = handle_global,
	.global_remove = handle_global_remove,
};

static struct wl_buffer *create_buffer(void) {
	int stride = width * 4;
	int size = stride * height;

	// Allocate a shared memory file with the right size
	int fd = create_shm_file(size);
	if (fd < 0) {
		fprintf(stderr, "creating a buffer file for %d B failed: %m\n", size);
		return NULL;
	}

	// Map the shared memory file
	shm_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (shm_data == MAP_FAILED) {
		fprintf(stderr, "mmap failed: %m\n");
		close(fd);
		return NULL;
	}

	// Create a wl_buffer from our shared memory file descriptor
	struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
	struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
		stride, WL_SHM_FORMAT_ARGB8888);
	wl_shm_pool_destroy(pool);

	// Now that we've mapped the file and created the wl_buffer, we no longer
	// need to keep file descriptor opened
	close(fd);

	// Copy pixels into our shared memory file (MagickImage is from cat.h)
	memcpy(shm_data, MagickImage, size);

	return buffer;
}

int main(int argc, char *argv[]) {
	// Connect to the Wayland compositor
	struct wl_display *display = wl_display_connect(NULL);
	if (display == NULL) {
		fprintf(stderr, "failed to create display\n");
		return EXIT_FAILURE;
	}

	// Obtain the wl_registry and fetch the list of globals
	struct wl_registry *registry = wl_display_get_registry(display);
	wl_registry_add_listener(registry, &registry_listener, NULL);
	if (wl_display_roundtrip(display) == -1) {
		return EXIT_FAILURE;
	}

	// Check that all globals we require are available
	if (shm == NULL || compositor == NULL || xdg_wm_base == NULL) {
		fprintf(stderr, "no wl_shm, wl_compositor or xdg_wm_base support\n");
		return EXIT_FAILURE;
	}

	// Create a wl_surface, a xdg_surface and a xdg_toplevel
	surface = wl_compositor_create_surface(compositor);
	struct xdg_surface *xdg_surface =
		xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
	xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);

	xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
	xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL);

	// Perform the initial commit and wait for the first configure event
	wl_surface_commit(surface);
	while (wl_display_dispatch(display) != -1 && !configured) {
		// This space intentionally left blank
	}

	// Create a wl_buffer, attach it to the surface and commit the surface
	struct wl_buffer *buffer = create_buffer();
	if (buffer == NULL) {
		return EXIT_FAILURE;
	}

	wl_surface_attach(surface, buffer, 0, 0);
	wl_surface_commit(surface);

	// Continue dispatching events until the user closes the toplevel
	while (wl_display_dispatch(display) != -1 && running) {
		// This space intentionally left blank
	}

	xdg_toplevel_destroy(xdg_toplevel);
	xdg_surface_destroy(xdg_surface);
	wl_surface_destroy(surface);
	wl_buffer_destroy(buffer);

	return EXIT_SUCCESS;
}
*/

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <linux/input-event-codes.h>

#include "cat.h"
#include "shm.h"
#include "xdg-shell-client-protocol.h"

static const int width = 128;
static const int height = 128;

static bool configured = false;
static bool running = true;

static struct wl_shm *shm = NULL;
static struct wl_compositor *compositor = NULL;
static struct wl_subcompositor *subcompositor = NULL;
static struct xdg_wm_base *xdg_wm_base = NULL;

static void *shm_data = NULL;
static struct wl_surface *surface = NULL;
static struct wl_surface *sub_surface = NULL;
static struct xdg_toplevel *xdg_toplevel = NULL;

static void noop() {
        // This space intentionally left blank
}

static void xdg_wm_base_handle_ping(void *data,
                struct xdg_wm_base *xdg_wm_base, uint32_t serial) {
        xdg_wm_base_pong(xdg_wm_base, serial);
}

static const struct xdg_wm_base_listener xdg_wm_base_listener = {
        .ping = xdg_wm_base_handle_ping,
};

static void xdg_surface_handle_configure(void *data,
                struct xdg_surface *xdg_surface, uint32_t serial) {
        xdg_surface_ack_configure(xdg_surface, serial);

        if (configured) {
                wl_surface_commit(surface);
        }

        configured = true;
}

static const struct xdg_surface_listener xdg_surface_listener = {
        .configure = xdg_surface_handle_configure,
};

static void xdg_toplevel_handle_close(void *data,
                struct xdg_toplevel *xdg_toplevel) {
        running = false;
}

static const struct xdg_toplevel_listener xdg_toplevel_listener = {
        .configure = noop,
        .close = xdg_toplevel_handle_close,
};

static void pointer_motion(void *data, struct wl_pointer *wl_pointer,
         uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
        printf("pointer_motion %d %d\n", surface_x, surface_y);
}

static const struct wl_pointer_listener pointer_listener = {
        .enter = noop,
        .leave = noop,
        .motion = pointer_motion,
        .button = noop,
        .axis = noop,
};

static void seat_handle_capabilities(void *data, struct wl_seat *seat,
                uint32_t capabilities) {
        if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
                struct wl_pointer *pointer = wl_seat_get_pointer(seat);
                wl_pointer_add_listener(pointer, &pointer_listener, seat);
        }
}

static const struct wl_seat_listener seat_listener = {
        .capabilities = seat_handle_capabilities,
};

static void handle_global(void *data, struct wl_registry *registry,
                uint32_t name, const char *interface, uint32_t version) {
        if (strcmp(interface, wl_shm_interface.name) == 0) {
                shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
        } else if (strcmp(interface, wl_seat_interface.name) == 0) {
                struct wl_seat *seat =
                        wl_registry_bind(registry, name, &wl_seat_interface, 1);
                wl_seat_add_listener(seat, &seat_listener, NULL);
        } else if (strcmp(interface, wl_compositor_interface.name) == 0) {
                compositor = wl_registry_bind(registry, name,
                        &wl_compositor_interface, 1);
        } else if (strcmp(interface, wl_subcompositor_interface.name) == 0) {
                subcompositor = wl_registry_bind(registry, name,
                        &wl_subcompositor_interface, 1);
        } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
                xdg_wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
                xdg_wm_base_add_listener(xdg_wm_base, &xdg_wm_base_listener, NULL);
        }
}

static void handle_global_remove(void *data, struct wl_registry *registry,
                uint32_t name) {
        // Who cares
}

static const struct wl_registry_listener registry_listener = {
        .global = handle_global,
        .global_remove = handle_global_remove,
};

static struct wl_buffer *create_buffer(void) {
        int stride = width * 4;
        int size = stride * height;

        int fd = create_shm_file(size);
        if (fd < 0) {
                fprintf(stderr, "creating a buffer file for %d B failed: %m\n", size);
                return NULL;
        }

        shm_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if (shm_data == MAP_FAILED) {
                fprintf(stderr, "mmap failed: %m\n");
                close(fd);
                return NULL;
        }

        struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
        struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
                stride, WL_SHM_FORMAT_ARGB8888);
        wl_shm_pool_destroy(pool);

        close(fd);

        memcpy(shm_data, MagickImage, size);

        return buffer;
}

static struct wl_buffer *create_white_buffer(void) {
        int stride = width * 4;
        int size = stride * height;

        int fd = create_shm_file(size);
        if (fd < 0) {
                fprintf(stderr, "creating a buffer file for %d B failed: %m\n", size);
                return NULL;
        }

        void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if (data == MAP_FAILED) {
                fprintf(stderr, "mmap failed: %m\n");
                close(fd);
                return NULL;
        }

        struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
        struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
                stride, WL_SHM_FORMAT_ARGB8888);
        wl_shm_pool_destroy(pool);

        close(fd);

        // Fill the buffer with pure white
        memset(data, 0xFF, size);

        munmap(data, size);

        return buffer;
}

int main(int argc, char *argv[]) {
        struct wl_display *display = wl_display_connect(NULL);
        if (display == NULL) {
                fprintf(stderr, "failed to create display\n");
                return EXIT_FAILURE;
        }

        struct wl_registry *registry = wl_display_get_registry(display);
        wl_registry_add_listener(registry, &registry_listener, NULL);
        if (wl_display_roundtrip(display) == -1) {
                return EXIT_FAILURE;
        }

        if (shm == NULL || compositor == NULL || subcompositor == NULL || xdg_wm_base == NULL) {
                fprintf(stderr, "missing required Wayland globals\n");
                return EXIT_FAILURE;
        }

        surface = wl_compositor_create_surface(compositor);
        struct xdg_surface *xdg_surface =
                xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
        xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);

        xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
        xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL);

        wl_surface_commit(surface);
        while (wl_display_dispatch(display) != -1 && !configured) {
        }

        struct wl_buffer *buffer = create_buffer();
        if (buffer == NULL) {
                return EXIT_FAILURE;
        }

        wl_surface_attach(surface, buffer, 0, 0);
        wl_surface_commit(surface);

        // Create sub-surface
        sub_surface = wl_compositor_create_surface(compositor);
        struct wl_subsurface *subsurface = wl_subcompositor_get_subsurface(
                subcompositor, sub_surface, surface);
        wl_subsurface_set_position(subsurface, 200, 0);

        struct wl_buffer *white_buffer = create_white_buffer();
        if (white_buffer == NULL) {
                return EXIT_FAILURE;
        }

        wl_surface_attach(sub_surface, white_buffer, 0, 0);
        wl_surface_commit(sub_surface);

        while (wl_display_dispatch(display) != -1 && running) {
        }

        xdg_toplevel_destroy(xdg_toplevel);
        xdg_surface_destroy(xdg_surface);
        wl_surface_destroy(surface);
        wl_surface_destroy(sub_surface);
        wl_buffer_destroy(buffer);
        wl_buffer_destroy(white_buffer);

        return EXIT_SUCCESS;
}

Apologies if I mixed something up, Wayland is pretty new to me all in all.

Steps to reproduce

  1. Launch the modified program
  2. Press a mouse button with the pointer inside the white, "detached" subsurface
  3. Drag the pointer around holding the button, and observe how the coordinates printed differ when dragging in and out of the white surface
  4. Do the same for the main window (displaying the cat) and observe how the coordinates are consistent.

labwc build source

Local build

labwc version

36d6e9e

labwc environment

LXQT session

Distribution

Arch Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions