Skip to content
This repository was archived by the owner on Nov 1, 2021. It is now read-only.
This repository was archived by the owner on Nov 1, 2021. It is now read-only.

Thoughts on configure/ack_configure flow in xdg-surface and layer-surface #3206

Open
@vyivel

Description

@vyivel

configure/ack_configure flow is currently used by two objects: xdg_surface and zwlr_layer_surface_v1. wlroots implements that flow differently for these objects and provides different APIs for them, which I believe is something that needs to be fixed.


xdg_surface configure events are delayed and aren't sent immediately. This allows for xdg_surface extensions/roles to be configured effectively, avoiding sending too many configure events and sending useless events. The current implementation of this mechanism is imperfect however, as it generates a serial in advance without knowing if that serial will actually be used.

18:41:13 <ifreund> the fact that we might decide to cancel the configure is still a nasty edge case though
18:41:30 <ifreund> because with the current code serials may not actually end up being used
18:42:05 <ifreund> however they are still returned to the compositor, which has no way to know the serial won't be used
...
18:50:56 <vyivel> let's say we call set_fullscreen(false) on a fullscreen view
18:51:19 <vyivel> that's a different state, so it schedules a configure and returns a newly created serial
18:51:40 <vyivel> then we call set_fullscreen(true) without dropping to the event loop
18:51:52 <vyivel> what should it return?
18:52:54 <ifreund> the serial that will be used for the next configure that actually gets sent I believe
18:53:50 <vyivel> assuming we don't configure anything after that, compositor would receive a serial that is never going to be used
18:54:37 <ifreund> it will be used next time the compositor causes any configure to be sent
18:55:02 <ifreund> but I agree this is probably not what the compositor author would expect

On top of that, 0 serial is returned wrongly as an indication of that a configure isn't necessary, even though 0 is a perfectly valid configure serial.


zwlr_layer_surface_v1 configure events are sent immediately on wlr_layer_surface_v1_configure(). This means that calling

wlr_layer_surface_v1_configure(new_size);
wlr_layer_surface_v1_configure(current_size);

will result in sending two configures, and, as the actual state didn't change, the client can ignore both of them.

Ref #3201 (comment)


For the purposes of unifying the API and avoiding code duplication, I propose introducing wlr_configurable to deal with this logic.

Draft:

struct wlr_configurable {
	struct wl_display *display;
	const struct wlr_configurable_impl *impl;

	struct wl_list sources; // wlr_configure_source::link
	bool force;
	struct wl_event_source *idle;
};

struct wlr_configurable_impl {
	void (*send_event)(struct wlr_configurable *configurable,
			uint32_t serial);
};

struct wlr_configure_source {
	struct wl_list link; // wlr_configurable::sources

	struct {
		// Sends a pointer to serial if must wait
		// for an ack, NULL otherwise
		struct wl_signal configure; // uint32_t *
	} events;
};

struct wlr_configure_source {
	void (*needs_configure)(struct wlr_configure_source *source);
};

static void send_configure(void *data) {
	struct wlr_configurable *configurable = data;
	configurable->idle = NULL;

	bool needs_event = false;
	uint32_t serial;

	if (configurable->force) {
		serial = wl_display_next_serial(configurable->display);
		needs_event = true;
		configurable->force = false;
	}

	struct wlr_configure_source *source, *tmp;
	wl_list_for_each_safe (source, tmp, &configurable->sources, link) {
		if (source->impl->needs_configure(source)) {
			if (!needs_event) {
				serial = wl_display_next_serial(configurable->display);
				needs_event = true;
			}
			// `serial` could be wrapped in a struct here
			wlr_signal_emit_safe(&source->events.configure, &serial);
		} else {
			wlr_signal_emit_safe(&source->events.configure, NULL);
		}
		wl_list_remove(&source->link);
		wl_list_init(&source->link);
	}

	if (needs_event) {
		configurable->impl->send_event(configurable, serial);
	}
}

void wlr_configurable_schedule(struct wlr_configurable *configurable,
		struct wlr_configure_source *source) {
	if (configurable->idle == NULL) {
		struct wl_event_loop *loop =
			wl_display_get_event_loop(configurable->display);
		configurable->idle = wl_event_loop_add_idle(loop,
				send_configure, configurable);
	}
	if (source) {
		wl_list_remove(&source->link);
		wl_list_insert(&configurable->sources, &source->link);
	} else {
		// Scheduling a configure without a source is always
		// successful, can be used to e.g. respond to set_maximized
		configurable->force = true;
	}
}

// Example wlr usage

struct wlr_xdg_surface {
	/* ... */
	struct wlr_configurable configurable;
};

// wlr_xdg_surface.configurable.impl.send_event
static void xdg_surface_configurable_send_event(
		struct wlr_configurable *configurable, uint32_t serial) {
	struct wlr_xdg_surface *xdg_surface = wl_container_of(
			configurable, xdg_surface, configurable);
	xdg_surface_send_configure(xdg_surface->resource, serial);
}

struct wlr_xdg_toplevel {
	/* ... */
	struct wlr_configure_source configure_source;

	// Listens to wlr_xdg_toplevel.configure_source.events.configure
	struct wl_listener configure;
};

// wlr_xdg_toplevel.configure_source.impl.needs_configure
static void xdg_toplevel_needs_configure(
		struct wlr_configure_source *source) {
	struct wlr_xdg_toplevel *toplevel = wl_container_of(
			source, toplevel, configure_source);
	// Compare scheduled and last sent states here
	return xdg_toplevel_state_changed(toplevel);
}

// wlr_xdg_toplevel.configure.notify
static void xdg_toplevel_configure_source_handle_configure(
		struct wl_listener *listener, void *data) {
	struct wlr_xdg_toplevel *toplevel = wl_container_of(
		listener, toplevel, configure);
	if (data) {
		uint32_t serial = *(uint32_t *)data;
		// Allocate a new wlr_xdg_toplevel_configure here,
		// set its serial to `serial` and wait
		xdg_toplevel_configure(toplevel->resource, width, height, states);
	} else {
		// A configure might or might not be sent, but
		// xdg-toplevel state didn't change, so we do nothing
	}
}

void wlr_xdg_toplevel_set_maximized(
		struct wlr_xdg_toplevel *toplevel, bool maximized) {
	toplevel->scheduled.maximized = maximized;
	// Could be wrapped in wlr_xdg_toplevel_schedule_configure()
	wlr_configurable_schedule(&toplevel->surface->configurable,
		&toplevel->configure_source);
}

Acking a configure is done by the wlr_configure_source implementation by listening to its ack_configure signal, which could be moved to wlr_configurable.


A compositor wishing to implement atomic layout updates would do the following:

  1. Schedule configures for all modified configure sources, mark them as "scheduled to configure"
  2. Wait for wlr_configurable.idle to be handled
  3. Listen to wlr_configure_source.events.configure
  4. If a configure source gets NULL serial pointer, mark as "ready" immediately
  5. Otherwise mark as "configuring"
  6. Wait for configures to be acked and committed, mark as "ready"
  7. When all configure sources are "ready", apply the transaction

cc @ifreund


wlroots has migrated to gitlab.freedesktop.org. This issue has been moved to:

https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3206

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions