~jf/tinywl-output-management

87a45d89ae0e7975e2a59f84e960380dd2f5ac08 — Jason Francis 2 years ago a362d77 master
Add support for output management
2 files changed, 243 insertions(+), 0 deletions(-)

M README.md
M tinywl.c
M README.md => README.md +12 -0
@@ 1,3 1,15 @@
# TinyWL-Output-Management

This is a sample compositor with a very basic implementation of the
wlr-output-management-unstable-v1 protocol for testing and reference purposes.
It's based on [tinywl](https://github.com/swaywm/wlroots/tree/master/tinywl)
and has a few additional keybindings:

- `Alt+A`: Apply a pending output test
- `Alt+C`: Cancel a pending output test

The original TinyWL README is below.

# TinyWL

This is the "minimum viable product" Wayland compositor based on wlroots. It

M tinywl.c => tinywl.c +231 -0
@@ 16,9 16,11 @@
#include <wlr/types/wlr_matrix.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_output_management_v1.h>
#include <wlr/types/wlr_pointer.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/types/wlr_xdg_output_v1.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
#include <xkbcommon/xkbcommon.h>


@@ 58,8 60,16 @@ struct tinywl_server {
	uint32_t resize_edges;

	struct wlr_output_layout *output_layout;
	struct wl_listener output_layout_change;
	struct wl_list outputs;
	struct wl_listener new_output;

	struct wlr_xdg_output_manager_v1 *xdg_output_manager;
	struct wlr_output_manager_v1 *output_manager;
	struct wl_listener output_manager_apply;
	struct wlr_output_configuration_v1 *pending_output_config;
	struct wl_listener output_manager_test;
	struct tinywl_output_test *pending_output_test;
};

struct tinywl_output {


@@ 69,6 79,13 @@ struct tinywl_output {
	struct wl_listener frame;
};

struct tinywl_output_test {
	struct tinywl_server *server;
	struct wlr_output_configuration_v1 *old_config;
	struct wlr_output_configuration_v1 *new_config;
	struct wl_event_source *timer;
};

struct tinywl_view {
	struct wl_list link;
	struct tinywl_server *server;


@@ 146,6 163,9 @@ static void keyboard_handle_modifiers(
		&keyboard->device->keyboard->modifiers);
}

static void apply_output_test(struct tinywl_output_test *test);
static void cancel_output_test(struct tinywl_output_test *test);

static bool handle_keybinding(struct tinywl_server *server, xkb_keysym_t sym) {
	/*
	 * Here we handle compositor keybindings. This is when the compositor is


@@ 172,6 192,16 @@ static bool handle_keybinding(struct tinywl_server *server, xkb_keysym_t sym) {
		wl_list_remove(&current_view->link);
		wl_list_insert(server->views.prev, &current_view->link);
		break;
	case XKB_KEY_a:
		if (server->pending_output_test) {
			apply_output_test(server->pending_output_test);
		}
		break;
	case XKB_KEY_c:
		if (server->pending_output_test) {
			cancel_output_test(server->pending_output_test);
		}
		break;
	default:
		return false;
	}


@@ 638,6 668,186 @@ static void output_frame(struct wl_listener *listener, void *data) {
	wlr_output_commit(output->wlr_output);
}

static struct wlr_output_configuration_v1 *create_output_config(
		struct tinywl_server *server) {
	/* Takes the current configuration of all outputs and turns it into a
	 * wlr_output_configuration_v1 object suitable for sending to clients. */
	struct wlr_output_configuration_v1 *config
		= wlr_output_configuration_v1_create();
	if (!config)
		return NULL;
	struct tinywl_output *output;
	wl_list_for_each(output, &server->outputs, link) {
		struct wlr_output_configuration_head_v1 *head
			= wlr_output_configuration_head_v1_create(config, output->wlr_output);
		if (!head) {
			wlr_output_configuration_v1_destroy(config);
			return NULL;
		}
		struct wlr_box *box = wlr_output_layout_get_box(server->output_layout,
				output->wlr_output);
		if (box) {
			head->state.x = box->x;
			head->state.y = box->y;
		}
	}
	return config;
}

static void server_output_layout_change(struct wl_listener *listener,
		void *data) {
	/* This event is rasied by the output layout whenever any of its outputs
	 * has its parameters changed, or if an output is added or removed. */
	struct tinywl_server *server =
		wl_container_of(listener, server, output_layout_change);
	/* Multiple change events will be sent while applying an output config.
	 * Don't bother sending an updated configuration in that case,
	 * the configuration will be sent by output_config_apply(). */
	if (!server->pending_output_config) {
		/* Create a new configuration object and send it to all connected
		 * clients. */
		struct wlr_output_configuration_v1 *config = create_output_config(server);
		if (config) {
			/* wlr_output_configuration_v1_destroy() doesn't need to be called here.
			 * Setting the configuration calls destroy for us. */
			wlr_output_manager_v1_set_configuration(server->output_manager, config);
		}
	}
}

static void output_config_apply(struct tinywl_server *server,
		struct wlr_output_configuration_v1 *config) {
	/* Takes an output configuration object and commits its settings to all
	 * active outputs. We need to store this configuration while it's applying
	 * because wlr_output_commit() is being called in a loop, and it can trigger
	 * an output_layout.change event each time it's called. */
	server->pending_output_config = config;
	struct wlr_output_configuration_head_v1 *head;
	wl_list_for_each(head, &config->heads, link) {
		struct wlr_output *o = head->state.output;
		if (head->state.enabled && !o->enabled) {
			wlr_output_layout_add_auto(server->output_layout, o);
		} else if (!head->state.enabled && o->enabled) {
			wlr_output_layout_remove(server->output_layout, o);
		}
		wlr_output_enable(o, head->state.enabled);
		/* All other settings only have an effect if the output is enabled. */
		if (head->state.enabled) {
			if (head->state.mode) {
				wlr_output_set_mode(o, head->state.mode);
			} else {
				wlr_output_set_custom_mode(o,
						head->state.custom_mode.width, head->state.custom_mode.height,
						head->state.custom_mode.refresh);
			}
			wlr_output_layout_move(server->output_layout, o,
					head->state.x, head->state.y);
			wlr_output_set_scale(o, head->state.scale);
			wlr_output_set_transform(o, head->state.transform);
		}
		wlr_output_commit(o);
	}
	/* Clear the configuration so output change events now work as normal. */
	server->pending_output_config = NULL;
}

static void server_output_manager_apply(struct wl_listener *listener,
		void *data) {
	/* This event is raised by a client requesting a permanent change to the
	 * output configuration. */
	struct tinywl_server *server =
		wl_container_of(listener, server, output_manager_apply);
	struct wlr_output_configuration_v1 *config = data;
	output_config_apply(server, config);
	wlr_output_configuration_v1_send_succeeded(config);
	wlr_output_configuration_v1_destroy(config);
}

static void output_test_destroy(struct tinywl_output_test *test) {
	/* Destroys an output_test object. */
	if (test) {
		/* Clear potential dangling references from the tinywl_server object. */
		if (test->server && test->server->pending_output_test == test)
			test->server->pending_output_test = NULL;
		if (test->timer)
			wl_event_source_remove(test->timer);
		if (test->new_config)
			wlr_output_configuration_v1_destroy(test->new_config);
		if (test->old_config)
			wlr_output_configuration_v1_destroy(test->old_config);
		free(test);
	}
}

static int output_config_test_revert(void *data) {
	/* Called by the timer when an output test expires. Behaves the same as if
	 * the user cancelled the test interactively. */
	struct tinywl_output_test *test = data;
	cancel_output_test(test);
	return 0;
}

#define TEST_TIMEOUT_SECS 15

static void server_output_manager_test(struct wl_listener *listener,
		void *data) {
	/* This event is raised by a client requesting a test for a new output
	 * configuration. */
	struct tinywl_server *server =
		wl_container_of(listener, server, output_manager_test);
	struct wlr_output_configuration_v1 *config = data;
	/* If a test is already active, immediately tell the client that the request
	 * failed and that the test configuration should be destroyed. */
	if (server->pending_output_test) {
		wlr_output_configuration_v1_send_failed(config);
		wlr_output_configuration_v1_destroy(config);
		return;
	}

	/* Create an object storing the new configuration and the old configuration
	 * so that we can later apply whichever one the user chooses. */
	struct tinywl_output_test *test = calloc(1, sizeof(struct tinywl_output_test));
	if (!test)
		goto failure;

	test->server = server;
	test->new_config = config;
	test->old_config = create_output_config(server);
	if (!test->old_config)
		goto failure;

	/* Also create a timer on the server's event loop to automatically revert
	 * the configuration in 15 seconds. */
	struct wl_event_loop *loop = wl_display_get_event_loop(server->wl_display);
	test->timer = wl_event_loop_add_timer(loop, output_config_test_revert, test);
	if (!test->timer)
		goto failure;
	if (wl_event_source_timer_update(test->timer, TEST_TIMEOUT_SECS * 1000))
		goto failure;

	server->pending_output_test = test;
	/* Now apply the new configuration so the user can see the result. */
	output_config_apply(server, test->new_config);

	return;
failure:
	wlr_output_configuration_v1_send_failed(config);
	output_test_destroy(test);
}

static void apply_output_test(struct tinywl_output_test *test) {
	/* Just tell the client the test succeeded and then clean up. */
	output_test_destroy(test);
}

static void cancel_output_test(struct tinywl_output_test *test) {
	/* Change the output configuration back to the old one and tell the client
	 * the new one failed, then clean up. */
	output_config_apply(test->server, test->old_config);
	wlr_output_configuration_v1_send_failed(test->new_config);
	output_test_destroy(test);
}

static void server_new_output(struct wl_listener *listener, void *data) {
	/* This event is rasied by the backend when a new output (aka a display or
	 * monitor) becomes available. */


@@ 837,6 1047,25 @@ int main(int argc, char *argv[]) {
	/* Creates an output layout, which a wlroots utility for working with an
	 * arrangement of screens in a physical layout. */
	server.output_layout = wlr_output_layout_create();
	server.output_layout_change.notify = server_output_layout_change;
	wl_signal_add(&server.output_layout->events.change,
			&server.output_layout_change);

	/* Creates the xdg output manager, which automatically sends extra
	 * information about connected outputs to clients. */
	server.xdg_output_manager =
		wlr_xdg_output_manager_v1_create(server.wl_display, server.output_layout);

	/* Creates the wlr output manager, which implements the
	 * wlr-output-management protocol. This protocol allows clients to configure
	 * and test size, position, scale, etc. of connected outputs. */
	server.output_manager = wlr_output_manager_v1_create(server.wl_display);
	server.output_manager_apply.notify = server_output_manager_apply;
	wl_signal_add(&server.output_manager->events.apply,
			&server.output_manager_apply);
	server.output_manager_test.notify = server_output_manager_test;
	wl_signal_add(&server.output_manager->events.test,
			&server.output_manager_test);

	/* Configure a listener to be notified when new outputs are available on the
	 * backend. */


@@ 940,6 1169,8 @@ int main(int argc, char *argv[]) {
	wl_display_run(server.wl_display);

	/* Once wl_display_run returns, we shut down the server. */
	if (server.pending_output_test)
		output_test_destroy(server.pending_output_test);
	wl_display_destroy_clients(server.wl_display);
	wl_display_destroy(server.wl_display);
	return 0;