struct wlr_backend *backend;
struct headless {
struct wlr_backend *backend;
- char pending_output_name[4096];
} headless;
struct wlr_session *session;
struct wlr_box output_usable_area_scaled(struct output *output);
void handle_output_power_manager_set_mode(struct wl_listener *listener,
void *data);
-void output_add_virtual(struct server *server, const char *output_name);
-void output_remove_virtual(struct server *server, const char *output_name);
void output_enable_adaptive_sync(struct wlr_output *output, bool enabled);
void new_tearing_hint(struct wl_listener *listener, void *data);
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef LABWC_OUTPUT_VIRTUAL_H
+#define LABWC_OUTPUT_VIRTUAL_H
+
+struct server;
+struct wlr_output;
+
+void output_virtual_add(struct server *server, const char *output_name,
+ struct wlr_output **store_wlr_output);
+void output_virtual_remove(struct server *server, const char *output_name);
+
+#endif
#include "debug.h"
#include "labwc.h"
#include "menu/menu.h"
+#include "output-virtual.h"
#include "placement.h"
#include "regions.h"
#include "ssd.h"
{
const char *output_name = action_get_str(action, "output_name",
NULL);
- output_add_virtual(server, output_name);
+ output_virtual_add(server, output_name,
+ /*store_wlr_output*/ NULL);
}
break;
case ACTION_TYPE_VIRTUAL_OUTPUT_REMOVE:
{
const char *output_name = action_get_str(action, "output_name",
NULL);
- output_remove_virtual(server, output_name);
+ output_virtual_remove(server, output_name);
}
break;
case ACTION_TYPE_AUTO_PLACE:
'node.c',
'osd.c',
'output.c',
+ 'output-virtual.c',
'placement.c',
'regions.c',
'resistance.c',
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <wlr/backend/headless.h>
+#include <wlr/types/wlr_output.h>
+#include "labwc.h"
+#include "output-virtual.h"
+
+void
+output_virtual_add(struct server *server, const char *output_name,
+ struct wlr_output **store_wlr_output)
+{
+ 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) &&
+ !strcmp(output->wlr_output->name, output_name)) {
+ wlr_log(WLR_DEBUG,
+ "refusing to create virtual output with duplicate name");
+ return;
+ }
+ }
+ }
+
+ /*
+ * The headless backend will always emit the new output signal (and
+ * thus call our handler) before `wlr_headless_add_output()` returns.
+ *
+ * This makes it impossible to
+ * - modify the output before it gets enabled in the handler
+ * - use a pointer of the new wlr_output within the handler
+ *
+ * So we temporarily disconnect the handler when creating the output
+ * and then call the handler manually.
+ *
+ * This is important because some operations like `wlr_output_set_name()`
+ * can only be done before the output has been enabled.
+ *
+ * If we add a virtual output before the headless backend has been started
+ * we may end up calling the new output handler twice, one time manually
+ * and one time by the headless backend when it starts up and sends the
+ * signal for all its configured outputs. Rather than keeping a global
+ * server->headless.started state around that we could check here we just
+ * ignore duplicated new output calls in new_output_notify().
+ */
+ wl_list_remove(&server->new_output.link);
+
+ struct wlr_output *wlr_output = wlr_headless_add_output(
+ server->headless.backend, 1920, 1080);
+
+ if (!wlr_output) {
+ wlr_log(WLR_ERROR, "Failed to create virtual output %s",
+ output_name ? output_name : "");
+ goto restore_handler;
+ }
+
+ if (output_name) {
+ wlr_output_set_name(wlr_output, output_name);
+ }
+ if (store_wlr_output) {
+ /* Ensures that we can use the new wlr_output pointer within new_output_nofity() */
+ *store_wlr_output = wlr_output;
+ }
+
+ /* Notify about the new output manually */
+ if (server->new_output.notify) {
+ server->new_output.notify(&server->new_output, wlr_output);
+ }
+
+restore_handler:
+ /* And finally restore output notifications */
+ wl_signal_add(&server->backend->events.new_output, &server->new_output);
+}
+
+void
+output_virtual_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)) {
+ continue;
+ }
+
+ 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 not supplied by user,
+ * simply destroy the first virtual output found.
+ */
+ wlr_output_destroy(output->wlr_output);
+ return;
+ }
+ }
+}
#include <assert.h>
#include <strings.h>
#include <wlr/backend/drm.h>
-#include <wlr/backend/headless.h>
#include <wlr/backend/wayland.h>
#include <wlr/types/wlr_buffer.h>
#include <wlr/types/wlr_drm_lease_v1.h>
struct server *server = wl_container_of(listener, server, new_output);
struct wlr_output *wlr_output = data;
- /* Name virtual output */
- 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);
- server->headless.pending_output_name[0] = '\0';
+ struct output *output;
+ wl_list_for_each(output, &server->outputs, link) {
+ if (output->wlr_output == wlr_output) {
+ /*
+ * This is a duplicated notification.
+ * We may end up here when a virtual output
+ * was added before the headless backend was
+ * started up.
+ */
+ return;
+ }
}
/*
* We offer any display as available for lease, some apps like
- * gamescope, want to take ownership of a display when they can
+ * gamescope want to take ownership of a display when they can
* to use planes and present directly.
* This is also useful for debugging the DRM parts of
* another compositor.
wlr_output_commit(wlr_output);
- struct output *output = znew(*output);
+ output = znew(*output);
output->wlr_output = wlr_output;
wlr_output->data = output;
output->server = server;
}
}
-void
-output_add_virtual(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) &&
- !strcmp(output->wlr_output->name, output_name)) {
- wlr_log(WLR_DEBUG,
- "refusing to create virtual output with duplicate name");
- return;
- }
- }
- snprintf(server->headless.pending_output_name,
- sizeof(server->headless.pending_output_name), "%s", output_name);
- } else {
- server->headless.pending_output_name[0] = '\0';
- }
- /*
- * Setting it to (0, 0) here disallows changing resolution from tools like
- * wlr-randr (returns error)
- */
- wlr_headless_add_output(server->headless.backend, 1920, 1080);
-}
-
-void
-output_remove_virtual(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
output_enable_adaptive_sync(struct wlr_output *output, bool enabled)
{