diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd
index 984f35f0..b6d6b934 100644
--- a/docs/labwc-actions.5.scd
+++ b/docs/labwc-actions.5.scd
@@ -180,6 +180,46 @@ Actions are used in menus and keyboard/mouse bindings.
*wrap* [yes|no] Wrap around from last desktop to first, and vice
versa. Default yes.
+**
+ Add virtual output (headless backend).
+
+ Virtual outputs is a useful mechanism. For example, it can be used to overlay
+ virtual output on real output, but with a different resolution (this can be
+ done with `wlr-randr` or `wdisplays`). After that, virtual output can be
+ selected for screen sharing (casting), effectively sharing only the region of
+ the screen.
+
+ It must be noted that overlaying virtual output and real output is not
+ endorsed or explicitely supported by wlroots. For example, after configuring
+ virtual output, real output must be reconfigured as well (for the overlay
+ configuration to work correctly). This is the example configuration:
+
+```
+
+
+
+
+
+
+
+```
+
+ Note that the vertical resolution of "ScreenCasting" output is just 50px
+ smaller than "eDP-1" output to cut off bottom panel from screen sharing.
+
+ This setup is also useful for extending the desktop to (maybe mobile) remote
+ systems like tablets. E.g. simply adding a virtual output, attaching wayvnc to
+ it and running a VNC client on the remote system.
+
+ *output_name* The name of virtual output. Providing virtual output name is
+ beneficial for further automation. Default is "HEADLESS-X".
+
+**
+ Remove virtual output (headless backend).
+
+ *output_name* The name of virtual output. If not supplied, will remove first
+ found virtual output.
+
**
If used as the only action for a binding: clear an earlier defined binding.
diff --git a/include/labwc.h b/include/labwc.h
index 86198a03..17a42463 100644
--- a/include/labwc.h
+++ b/include/labwc.h
@@ -203,6 +203,10 @@ struct server {
struct wlr_renderer *renderer;
struct wlr_allocator *allocator;
struct wlr_backend *backend;
+ struct headless {
+ struct wlr_backend *backend;
+ char pending_output_name[4096];
+ } headless;
struct wlr_session *session;
struct wlr_xdg_shell *xdg_shell;
diff --git a/src/action.c b/src/action.c
index 3a97efe4..0e6a15a9 100644
--- a/src/action.c
+++ b/src/action.c
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
#include
#include "action.h"
#include "common/macros.h"
@@ -98,6 +99,8 @@ enum action_type {
ACTION_TYPE_FOCUS_OUTPUT,
ACTION_TYPE_IF,
ACTION_TYPE_FOR_EACH,
+ ACTION_TYPE_VIRTUAL_OUTPUT_ADD,
+ ACTION_TYPE_VIRTUAL_OUTPUT_REMOVE,
};
const char *action_names[] = {
@@ -142,6 +145,8 @@ const char *action_names[] = {
"FocusOutput",
"If",
"ForEach",
+ "VirtualOutputAdd",
+ "VirtualOutputRemove",
NULL
};
@@ -363,6 +368,13 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
goto cleanup;
}
break;
+ case ACTION_TYPE_VIRTUAL_OUTPUT_ADD:
+ case ACTION_TYPE_VIRTUAL_OUTPUT_REMOVE:
+ if (!strcmp(argument, "output_name")) {
+ action_arg_add_str(action, argument, content);
+ goto cleanup;
+ }
+ break;
}
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s'",
@@ -617,6 +629,57 @@ run_if_action(struct view *view, struct server *server, struct action *action)
}
}
+static void
+virtual_output_add(struct server *server, const char *output_name)
+{
+ if (output_name) {
+ /*
+ * Prevent creating outputs with the same name
+ */
+ struct output *output;
+ wl_list_for_each(output, &server->outputs, link) {
+ if (wlr_output_is_headless(output->wlr_output)) {
+ if (!strcmp(output->wlr_output->name, output_name)) {
+ wlr_log(WLR_DEBUG, "refusing to create virtual output with duplicate name");
+ return;
+ }
+ }
+ }
+ strncpy(server->headless.pending_output_name, output_name,
+ sizeof(server->headless.pending_output_name));
+ } else {
+ server->headless.pending_output_name[0] = '\0';
+ }
+ wlr_headless_add_output(server->headless.backend, 1920, 1080);
+}
+
+static void
+virtual_output_remove(struct server *server, const char *output_name)
+{
+ struct output *output;
+ wl_list_for_each(output, &server->outputs, link) {
+ if (wlr_output_is_headless(output->wlr_output)) {
+ if (output_name) {
+ /*
+ * Given virtual output name, find and destroy virtual output by
+ * that name.
+ */
+ if (!strcmp(output->wlr_output->name, output_name)) {
+ wlr_output_destroy(output->wlr_output);
+ return;
+ }
+ } else {
+ /*
+ * When virtual output name was no supplied by user, simply
+ * destroy the first virtual output found.
+ */
+ wlr_output_destroy(output->wlr_output);
+ return;
+ }
+ }
+ }
+}
+
void
actions_run(struct view *activator, struct server *server,
struct wl_list *actions, uint32_t resize_edges)
@@ -915,6 +978,18 @@ actions_run(struct view *activator, struct server *server,
wl_array_release(&views);
}
break;
+ case ACTION_TYPE_VIRTUAL_OUTPUT_ADD:
+ {
+ const char *output_name = action_get_str(action, "output_name", NULL);
+ virtual_output_add(server, output_name);
+ }
+ break;
+ case ACTION_TYPE_VIRTUAL_OUTPUT_REMOVE:
+ {
+ const char *output_name = action_get_str(action, "output_name", NULL);
+ virtual_output_remove(server, output_name);
+ }
+ break;
case ACTION_TYPE_INVALID:
wlr_log(WLR_ERROR, "Not executing unknown action");
break;
diff --git a/src/output.c b/src/output.c
index dc441f8f..01426502 100644
--- a/src/output.c
+++ b/src/output.c
@@ -9,6 +9,8 @@
#define _POSIX_C_SOURCE 200809L
#include
#include
+#include
+#include
#include
#include
#include
@@ -194,6 +196,13 @@ new_output_notify(struct wl_listener *listener, void *data)
struct server *server = wl_container_of(listener, server, new_output);
struct wlr_output *wlr_output = data;
+ /*
+ * Name virtual outputs.
+ */
+ if (wlr_output_is_headless(wlr_output) && server->headless.pending_output_name[0] != '\0') {
+ wlr_output_set_name(wlr_output, server->headless.pending_output_name);
+ }
+
/*
* We offer any display as available for lease, some apps like
* gamescope, want to take ownership of a display when they can
@@ -201,7 +210,7 @@ new_output_notify(struct wl_listener *listener, void *data)
* This is also useful for debugging the DRM parts of
* another compositor.
*/
- if (server->drm_lease_manager) {
+ if (server->drm_lease_manager && wlr_output_is_drm(wlr_output)) {
wlr_drm_lease_v1_manager_offer_output(
server->drm_lease_manager, wlr_output);
}
diff --git a/src/server.c b/src/server.c
index 0aa07a09..120b19f4 100644
--- a/src/server.c
+++ b/src/server.c
@@ -3,6 +3,8 @@
#include "config.h"
#include
#include
+#include
+#include
#include
#include
#include
@@ -256,6 +258,24 @@ server_init(struct server *server)
exit(EXIT_FAILURE);
}
+ /*
+ * Create headless backend to enable adding virtual outputs later on.
+ */
+ server->headless.backend = wlr_headless_backend_create(server->wl_display);
+ if (!server->headless.backend) {
+ wlr_log(WLR_ERROR, "failed to create virtual output");
+ exit(EXIT_FAILURE);
+ }
+ wlr_multi_backend_add(server->backend, server->headless.backend);
+
+ /*
+ * If we don't populate headless backend with a virtual output (that we
+ * create and immediately destroy), then virtual outputs being added
+ * later do not work properly when overlayed on real output. Content is
+ * drawn on the virtual output, but not drawn on the real output.
+ */
+ wlr_output_destroy(wlr_headless_add_output(server->headless.backend, 0, 0));
+
/*
* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The
* user can also specify a renderer using the WLR_RENDERER env var.