git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commitec1b8c8250b9ac48c41cab186268885462399602
parent8291aa3693
authorLeon Henrik Plickat <[email protected]>
date2020-12-07 01:34
contrib: update layouts for river-layout and river-options

- Remove old layouts which no longer work.
- Add new C layout.

 contrib/layout.c                   | 476 +++++++++++++++++++++++++++++++++++++
 contrib/random-correct-layout.sh   |  16 --
 contrib/random-incorrect-layout.sh |  44 ----
 contrib/tiled.py                   |  68 ------
 4 files changed, 476 insertions(+), 128 deletions(-)

diff --git a/contrib/layout.c b/contrib/layout.c
new file mode 100644
index 0000000..e8ec8e1
--- /dev/null
+++ b/contrib/layout.c
@@ -0,0 +1,476 @@
+/*
+ * Tiled layout for river, implemented in understandable, simple, commented code.
+ * Reading this code should help you get a basic understanding of how to use
+ * river-layout to create a basic layout generator and how your layouts can
+ * depend on values of river-options.
+ *
+ * Q: Wow, this is a lot of code just for a layout!
+ * A: No, it really is not. Most of the code here is just generic Wayland client
+ *    boilerplate. The actual layout part is pretty small.
+ *
+ * Q: Can I use this to port dwm layouts to river?
+ * A: Yes you can! You just need to replace the logic in layout_handle_layout_demand().
+ *    You don't even need to fully understand the protocol if all you want to
+ *    do is just port some simple layouts.
+ *
+ * Q: I have no idea how any of this works.
+ * A: If all you want to do is create simple layouts, you do not need to
+ *    understand the Wayland parts of the code. If you still want to understand
+ *    it and are already familiar with how Wayland clients work, read the
+ *    protocol. If you are new to writing Wayland client code, you can read
+ *    https://wayland-book.com, then read the protocol.
+ *
+ * Q: How do I build this?
+ * A: To build, you need to generate the header and code of the layout protocol
+ *    extension and link against them. This is achieved with the following
+ *    commands (You may want to setup a build system).
+ *
+ *        wayland-scanner private-code < river-layout-v1.xml > river-layout-v1.c
+ *        wayland-scanner client-header < river-layout-v1.xml > river-layout-v1.h
+ *        wayland-scanner private-code < river-options-v2.xml > river-options-v2.c
+ *        wayland-scanner client-header < river-options-v2.xml > river-options-v2.h
+ *        gcc -Wall -Wextra -Wpedantic -Wno-unused-parameter -c -o layout.o layout.c
+ *        gcc -Wall -Wextra -Wpedantic -Wno-unused-parameter -c -o river-layout-v1.o river-layout-v1.c
+ *        gcc -Wall -Wextra -Wpedantic -Wno-unused-parameter -c -o river-options-v2.o river-options-v2.c
+ *        gcc -o layout layout.o river-layout-v1.o river-options-v2.o -lwayland-client
+ */
+
+#include<assert.h>
+#include<stdbool.h>
+#include<stdio.h>
+#include<stdlib.h>
+#include<string.h>
+
+#include<wayland-client.h>
+#include<wayland-client-protocol.h>
+
+#include"river-layout-v1.h"
+#include"river-options-v2.h"
+
+/* A few macros to indulge the inner glibc user. */
+#define MIN(a, b) ( a < b ? a : b )
+#define MAX(a, b) ( a > b ? a : b )
+#define CLAMP(a, b, c) ( MIN(MAX(b, c), MAX(MIN(b, c), a)) )
+
+enum Option_type
+{
+	UINT_OPTION,
+	DOUBLE_OPTION
+};
+
+struct Option
+{
+	struct Output *output;
+	struct river_option_handle_v2 *handle;
+	enum Option_type type;
+	union
+	{
+		uint32_t u;
+		double   d;
+	} value;
+};
+
+struct Output
+{
+	struct wl_list link;
+
+	struct wl_output       *output;
+	struct river_layout_v1 *layout;
+
+	struct Option main_count;
+	struct Option main_factor;
+	struct Option view_padding;
+	struct Option outer_padding;
+
+	bool configured;
+};
+
+/* In Wayland it's a good idea to have your main data global, since you'll need
+ * it everywhere anyway.
+ */
+struct wl_display  *wl_display;
+struct wl_registry *wl_registry;
+struct wl_callback *sync_callback;
+struct river_layout_manager_v1 *layout_manager;
+struct river_options_manager_v2 *options_manager;
+struct wl_list outputs;
+bool loop = true;
+int ret = EXIT_FAILURE;
+
+static void layout_handle_layout_demand (void *data, struct river_layout_v1 *river_layout_v1,
+		uint32_t view_count, uint32_t width, uint32_t height, uint32_t tags, uint32_t serial)
+{
+	struct Output *output = (struct Output *)data;
+
+	/* Simple tiled layout with no frills.
+	 *
+	 * If you want to create your own simple layout, just rip the following
+	 * code out and replace it with your own logic. All content un-aware
+	 * dynamic tiling layouts you know, for example from dwm, can be easily
+	 * ported to river this way. If you want to create layouts that are
+	 * content aware, meaning they react to the currently visible windows,
+	 * you have to create handlers for the advertise_view and advertise_done
+	 * events. Happy hacking!
+	 */
+	width -= 2 * output->outer_padding.value.u, height -= 2 * output->outer_padding.value.u;
+	const double main_factor = CLAMP(output->main_factor.value.d, 0.1, 0.9);
+	unsigned int main_size, stack_size, view_x, view_y, view_width, view_height;
+	if ( output->main_count.value.u == 0 )
+	{
+		main_size  = 0;
+		stack_size = width;
+	}
+	else if ( view_count <= output->main_count.value.u )
+	{
+		main_size  = width;
+		stack_size = 0;
+	}
+	else
+	{
+		main_size  = width * main_factor;
+		stack_size = width - main_size;
+	}
+	for (unsigned int i = 0; i < view_count; i++)
+	{
+		if ( i < output->main_count.value.u ) /* main area. */
+		{
+			view_x      = 0;
+			view_width  = main_size;
+			view_height = height / MIN(output->main_count.value.u, view_count);
+			view_y      = i * view_height;
+		}
+		else /* Stack area. */
+		{
+			view_x      = main_size;
+			view_width  = stack_size;
+			view_height = height / ( view_count - output->main_count.value.u);
+			view_y      = (i - output->main_count.value.u) * view_height;
+		}
+
+		river_layout_v1_push_view_dimensions(output->layout, serial,
+				view_x + output->view_padding.value.u + output->outer_padding.value.u,
+				view_y + output->view_padding.value.u + output->outer_padding.value.u,
+				view_width - (2 * output->view_padding.value.u),
+				view_height - (2 * output->view_padding.value.u));
+	}
+
+	river_layout_v1_commit(output->layout, serial);
+}
+
+static void layout_handle_namespace_in_use (void *data, struct river_layout_v1 *river_layout_v1)
+{
+	/* Oh no, the namespace we choose is already used by another client!
+	 * All we can do now is destroy the river_layout object. Because we are
+	 * lazy, we just abort and let our cleanup mechanism destroy it. A more
+	 * sophisticated client could instead destroy only the one single
+	 * affected river_layout object and recover from this mishap. Writing
+	 * such a client is left as an exercise for the reader.
+	 */
+	fputs("Namespace already in use.\n", stderr);
+	loop = false;
+}
+
+/* A no-op function we plug into listeners when we don't want to handle an event. */
+static void noop () {}
+
+static const struct river_layout_v1_listener layout_listener = {
+	.namespace_in_use = layout_handle_namespace_in_use,
+	.layout_demand    = layout_handle_layout_demand,
+	.advertise_view   = noop,
+	.advertise_done   = noop,
+};
+
+static void option_handle_uint (void *data, struct river_option_handle_v2 *handle,
+		uint32_t value)
+{
+	struct Option *option = (struct Option *)data;
+
+	/* We have received an event with the value of this option. But we
+	 * can only use it if it matches the type we want.
+	 */
+	if ( option->type == UINT_OPTION )
+	{
+		option->value.u = value;
+
+		/* Our layout depends on the value of this option. We need to
+		 * signal the compositor that one of the parameters we use to
+		 * generate the layout has changed. It may then decide to start
+		 * a new layout demand process.
+		 */
+		river_layout_v1_parameters_changed(option->output->layout);
+	}
+}
+
+static void option_handle_fixed (void *data, struct river_option_handle_v2 *handle,
+		wl_fixed_t value)
+{
+	struct Option *option = (struct Option *)data;
+
+	if ( option->type == DOUBLE_OPTION )
+	{
+		option->value.d = wl_fixed_to_double(value);
+		river_layout_v1_parameters_changed(option->output->layout);
+	}
+}
+
+static const struct river_option_handle_v2_listener option_listener = {
+	.int_value    = noop,
+	.uint_value   = option_handle_uint,
+	.fixed_value  = option_handle_fixed,
+	.string_value = noop,
+
+	/* This event will be sent by the compositor when the requested option does
+	 * not exist. Since we declared all options we plan on using at startup, we
+	 * can safely ignore this event.
+	 */
+	.undeclared   = noop,
+};
+
+static void configure_output (struct Output *output)
+{
+	output->configured = true;
+
+	/* The namespace of the layout is how the compositor chooses what layout
+	 * to use. It can be any arbitrary string. It should describe roughly
+	 * what kind of layout your client will create, so here we use "tile".
+	 */
+	output->layout = river_layout_manager_v1_get_layout(layout_manager,
+			output->output, "tile");
+	river_layout_v1_add_listener(output->layout, &layout_listener, output);
+
+	/* The amount of main views and other such values are communicated using
+	 * river-options. You can have an arbitrary amount of options which hold
+	 * arbitrary values. Here we are boring and just use the ones you'd
+	 * typically expect for typical tiled layouts.
+	 *
+	 * Careful: Options can have a wrong type (set by other clients) which
+	 * is a special case we have to handle. In case of this example layout
+	 * generator it is handled by simply ignoring the wrong events and falling
+	 * back to defaults.
+	 */
+	output->main_count.handle = river_options_manager_v2_get_option_handle(
+		options_manager, "main_count", output->output);
+	river_option_handle_v2_add_listener(output->main_count.handle,
+		&option_listener, &output->main_count);
+
+	output->main_factor.handle = river_options_manager_v2_get_option_handle(
+		options_manager, "main_factor", output->output);
+	river_option_handle_v2_add_listener(output->main_factor.handle,
+		&option_listener, &output->main_factor);
+
+	output->view_padding.handle = river_options_manager_v2_get_option_handle(
+		options_manager, "view_padding", output->output);
+	river_option_handle_v2_add_listener(output->view_padding.handle,
+		&option_listener, &output->view_padding);
+
+	output->outer_padding.handle = river_options_manager_v2_get_option_handle(
+		options_manager, "outer_padding", output->output);
+	river_option_handle_v2_add_listener(output->outer_padding.handle,
+		&option_listener, &output->outer_padding);
+}
+
+static bool create_output (struct wl_output *wl_output)
+{
+	struct Output *output = calloc(1, sizeof(struct Output));
+	if ( output == NULL )
+	{
+		fputs("Failed to allocate.\n", stderr);
+		return false;
+	}
+
+	output->output     = wl_output;
+	output->layout     = NULL;
+	output->configured = false;
+
+	output->main_count.value.u = 1;
+	output->main_count.handle  = NULL;
+	output->main_count.type    = UINT_OPTION;
+	output->main_count.output  = output;
+
+	output->main_factor.value.d = 0.6;
+	output->main_factor.handle  = NULL;
+	output->main_factor.type    = DOUBLE_OPTION;
+	output->main_factor.output  = output;
+
+	output->view_padding.value.u = 5;
+	output->view_padding.handle  = NULL;
+	output->view_padding.type    = UINT_OPTION;
+	output->view_padding.output  = output;
+
+	output->outer_padding.value.u = 5;
+	output->outer_padding.handle  = NULL;
+	output->outer_padding.type    = UINT_OPTION;
+	output->outer_padding.output  = output;
+
+	/* If we already have the river_layout_manager and the river_options_manager,
+	 * we can get a river_layout for this output.
+	 */
+	if ( layout_manager != NULL && options_manager != NULL )
+		configure_output(output);
+
+	wl_list_insert(&outputs, &output->link);
+	return true;
+}
+
+static void destroy_output (struct Output *output)
+{
+	if ( output->layout != NULL )
+		river_layout_v1_destroy(output->layout);
+	if ( output->main_count.handle != NULL )
+		river_option_handle_v2_destroy(output->main_count.handle);
+	if ( output->main_factor.handle != NULL )
+		river_option_handle_v2_destroy(output->main_factor.handle);
+	if ( output->view_padding.handle != NULL )
+		river_option_handle_v2_destroy(output->view_padding.handle);
+	if ( output->outer_padding.handle != NULL )
+		river_option_handle_v2_destroy(output->outer_padding.handle);
+	wl_output_destroy(output->output);
+	wl_list_remove(&output->link);
+	free(output);
+}
+
+static void destroy_all_outputs ()
+{
+	struct Output *output, *tmp;
+	wl_list_for_each_safe(output, tmp, &outputs, link)
+		destroy_output(output);
+}
+
+static void registry_handle_global (void *data, struct wl_registry *registry,
+		uint32_t name, const char *interface, uint32_t version)
+{
+	if (! strcmp(interface, river_layout_manager_v1_interface.name))
+		layout_manager = wl_registry_bind(registry, name,
+				&river_layout_manager_v1_interface, 1);
+	else if (! strcmp(interface, river_options_manager_v2_interface.name))
+		options_manager = wl_registry_bind(registry, name,
+				&river_options_manager_v2_interface, 1);
+	else if (! strcmp(interface, wl_output_interface.name))
+	{
+		struct wl_output *wl_output = wl_registry_bind(registry, name,
+				&wl_output_interface, version);
+		if (! create_output(wl_output))
+		{
+			loop = false;
+			ret = EXIT_FAILURE;
+		}
+	}
+}
+
+static const struct wl_registry_listener registry_listener = {
+	.global        = registry_handle_global,
+	.global_remove = noop
+};
+
+static void sync_handle_done (void *data, struct wl_callback *wl_callback,
+		uint32_t irrelevant)
+{
+	wl_callback_destroy(wl_callback);
+	sync_callback = NULL;
+
+	/* When this function is called, the registry finished advertising all
+	 * available globals. Let's check if we have everything we need.
+	 */
+	if ( layout_manager == NULL )
+	{
+		fputs("Wayland compositor does not support river-layout-v1.\n", stderr);
+		ret = EXIT_FAILURE;
+		loop = false;
+		return;
+	}
+
+	if ( options_manager == NULL )
+	{
+		fputs("Wayland compositor does not support river-options-v2.\n", stderr);
+		ret = EXIT_FAILURE;
+		loop = false;
+		return;
+	}
+
+	/* The options we want to use may not exist yet, so let's declare them with
+	 * some sensible defaults. If they do already exists, river will ignore this.
+	 * How these options are named and what you end up doing with them is totally
+	 * up to your creativity.
+	 */
+	 river_options_manager_v2_declare_uint_option(options_manager, "main_count", 1);
+	 river_options_manager_v2_declare_fixed_option(options_manager, "main_factor", wl_fixed_from_double(0.6));
+	 river_options_manager_v2_declare_uint_option(options_manager, "view_padding", 5);
+	 river_options_manager_v2_declare_uint_option(options_manager, "outer_padding", 5);
+
+	/* If outputs were registered before both river_layout_manager and
+	 * river_options_manager where available, they won't have a river_layout
+	 * nor the option handles, so we need to create those here.
+	 */
+	struct Output *output;
+	wl_list_for_each(output, &outputs, link)
+		if (! output->configured)
+			configure_output(output);
+}
+
+static const struct wl_callback_listener sync_callback_listener = {
+	.done = sync_handle_done,
+};
+
+static bool init_wayland (void)
+{
+	/* We query the display name here instead of letting wl_display_connect()
+	 * figure it out itself, because libwayland (for legacy reasons) falls
+	 * back to using "wayland-0" when $WAYLAND_DISPLAY is not set, which is
+	 * generally not desirable.
+	 */
+	const char *display_name = getenv("WAYLAND_DISPLAY");
+	if ( display_name == NULL )
+	{
+		fputs("WAYLAND_DISPLAY is not set.\n", stderr);
+		return false;
+	}
+
+	wl_display = wl_display_connect(display_name);
+	if ( wl_display == NULL )
+	{
+		fputs("Can not connect to Wayland server.\n", stderr);
+		return false;
+	}
+
+	wl_list_init(&outputs);
+
+	wl_registry = wl_display_get_registry(wl_display);
+	wl_registry_add_listener(wl_registry, &registry_listener, NULL);
+
+	/* The sync callback we attach here will be called when all previous
+	 * requests have been handled by the server.
+	 */
+	sync_callback = wl_display_sync(wl_display);
+	wl_callback_add_listener(sync_callback, &sync_callback_listener, NULL);
+
+	return true;
+}
+
+static void finish_wayland (void)
+{
+	if ( wl_display == NULL )
+		return;
+
+	destroy_all_outputs();
+
+	if ( sync_callback != NULL )
+		wl_callback_destroy(sync_callback);
+	if ( layout_manager != NULL )
+		river_layout_manager_v1_destroy(layout_manager);
+	if ( options_manager != NULL )
+		river_options_manager_v2_destroy(options_manager);
+
+	wl_registry_destroy(wl_registry);
+	wl_display_disconnect(wl_display);
+}
+
+int main (int argc, char *argv[])
+{
+	if (init_wayland())
+	{
+		ret = EXIT_SUCCESS;
+		while ( loop && wl_display_dispatch(wl_display) != -1 );
+	}
+	finish_wayland();
+	return ret;
+}
diff --git a/contrib/random-correct-layout.sh b/contrib/random-correct-layout.sh
deleted file mode 100755
index cf663a1..0000000
--- a/contrib/random-correct-layout.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/bash
-# Randomized Layout for debug purposes.
-
-CLIENTS="$1"
-OUTPUT_WIDTH="$4"
-OUTPUT_HEIGHT="$5"
-
-for _ in $(seq 1 "$CLIENTS")
-do
-	WIDTH="$(( ( OUTPUT_WIDTH  / 5 ) ))"
-	HEIGHT="$(( ( OUTPUT_HEIGHT  / 5 ) ))"
-	X="$(( ( RANDOM % ( OUTPUT_WIDTH  - WIDTH  ) )  + 1 ))"
-	Y="$(( ( RANDOM % ( OUTPUT_HEIGHT - HEIGHT ) )  + 1 ))"
-	echo "$X $Y $WIDTH $HEIGHT"
-done
-
diff --git a/contrib/random-incorrect-layout.sh b/contrib/random-incorrect-layout.sh
deleted file mode 100755
index d6b250f..0000000
--- a/contrib/random-incorrect-layout.sh
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/bin/bash
-# Randomized Layout for debug purposes. This version randomly makes some errors
-# see how river handles incorrect output of layout executables.
-
-CLIENTS="$1"
-OUTPUT_WIDTH="$4"
-OUTPUT_HEIGHT="$5"
-
-for _ in $(seq 1 "$CLIENTS")
-do
-	WIDTH="$(( ( OUTPUT_WIDTH  / 5 ) ))"
-	HEIGHT="$(( ( OUTPUT_HEIGHT  / 5 ) ))"
-	X="$(( ( RANDOM % ( OUTPUT_WIDTH  - WIDTH  ) )  + 1 ))"
-	Y="$(( ( RANDOM % ( OUTPUT_HEIGHT - HEIGHT ) )  + 1 ))"
-
-	# Mix in some errors
-	case "$(( ( RANDOM % 10 ) ))" in
-		0) # Too few layout rows
-			;;
-
-		1) # Too many layout rows
-			echo "$X $Y $WIDTH $HEIGHT"
-			echo "$X $Y $WIDTH $HEIGHT"
-			;;
-
-		2) # Too few layout columns
-			echo "$X $Y $WIDTH"
-			;;
-
-		3) # Too many layout columns
-			echo "$X $Y $WIDTH $HEIGHT $X"
-			;;
-
-
-		4) # Negative view size
-			echo "$X $Y -$WIDTH $HEIGHT $X"
-			;;
-
-		*) # Expected behaviour
-			echo "$X $Y $WIDTH $HEIGHT"
-			;;
-	esac
-done
-
diff --git a/contrib/tiled.py b/contrib/tiled.py
deleted file mode 100755
index 325854c..0000000
--- a/contrib/tiled.py
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/bin/env python
-
-from sys import argv
-
-# This is an implementation of the  default "tiled" layout of dwm
-#
-# With 4 views and one main view, the layout looks something like this:
-#
-# +-----------------------+------------+
-# |                       |            |
-# |                       |            |
-# |                       |            |
-# |                       +------------+
-# |                       |            |
-# |                       |            |
-# |                       |            |
-# |                       +------------+
-# |                       |            |
-# |                       |            |
-# |                       |            |
-# +-----------------------+------------+
-
-# Assign the arguments to variables. The order and meaning of the arguments
-# is explained in the river-layouts(7) man page
-num_views = int(argv[1])
-main_count = int(argv[2])
-main_factor = float(argv[3])
-output_width = int(argv[4])
-output_height = int(argv[5])
-
-secondary_count = num_views - main_count
-
-# handle the cases where there are no main or no secondary views
-main_width = 0
-secondary_width = 0
-if main_count > 0 and secondary_count > 0:
-    main_width = int(main_factor * output_width)
-    secondary_width = output_width - main_width
-elif main_count > 0:
-    main_width = output_width
-elif secondary_count > 0:
-    secondary_width = output_width
-
-
-# for each view, output the location/dimensions separated by spaces on a new line
-for i in range(num_views):
-    if i < main_count:
-        # to make things pixel-perfect, we make the first main and first secondary
-        # view slightly larger if the height is not evenly divisible
-        main_height = output_height // main_count
-        main_height_rem = output_height % main_count
-
-        x = 0
-        y = i * main_height + (main_height_rem if i > 0 else 0)
-        width = main_width
-        height = main_height + (main_height_rem if i == 0 else 0)
-
-        print(x, y, width, height)
-    else:
-        secondary_height = output_height // secondary_count
-        secondary_height_rem = output_height % secondary_count
-
-        x = main_width
-        y = (i - main_count) * secondary_height + (secondary_height_rem if i > main_count else 0)
-        width = secondary_width
-        height = secondary_height + (secondary_height_rem if i == main_count else 0)
-
-        print(x, y, width, height)