From 111b955b5389f03638397d06f17c54f1a2ba9f49 Mon Sep 17 00:00:00 2001 From: kyak <699631+kyak@users.noreply.github.com> Date: Sat, 9 Dec 2023 12:01:11 +0300 Subject: [PATCH] Implement key binds to control virtual outputs (#1287) Add actions `VirtualOutputAdd` and `VirtualOutputRemove` --- docs/labwc-actions.5.scd | 39 +++++++++++++++++++++++++ include/labwc.h | 6 ++++ src/action.c | 25 ++++++++++++++++ src/output.c | 63 +++++++++++++++++++++++++++++++++++++++- src/server.c | 18 ++++++++++++ 5 files changed, 150 insertions(+), 1 deletion(-) diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index 984f35f0..f639c490 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -180,6 +180,45 @@ 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). + + 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. + + Virtual output 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 the + last virtual output added. + ** 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..a4729178 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; @@ -467,6 +471,8 @@ struct wlr_box output_usable_area_in_layout_coords(struct output *output); 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 server_init(struct server *server); void server_start(struct server *server); diff --git a/src/action.c b/src/action.c index 3a97efe4..7762bc92 100644 --- a/src/action.c +++ b/src/action.c @@ -98,6 +98,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 +144,8 @@ const char *action_names[] = { "FocusOutput", "If", "ForEach", + "VirtualOutputAdd", + "VirtualOutputRemove", NULL }; @@ -363,6 +367,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'", @@ -915,6 +926,20 @@ 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); + output_add_virtual(server, output_name); + } + break; + case ACTION_TYPE_VIRTUAL_OUTPUT_REMOVE: + { + const char *output_name = action_get_str(action, "output_name", + NULL); + output_remove_virtual(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 e9ed9800..5be82fb4 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 @@ -172,6 +174,12 @@ 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 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'; + } + /* * We offer any display as available for lease, some apps like * gamescope, want to take ownership of a display when they can @@ -179,7 +187,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); } @@ -746,3 +754,56 @@ handle_output_power_manager_set_mode(struct wl_listener *listener, void *data) break; } } + +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; + } + } + strncpy(server->headless.pending_output_name, output_name, + sizeof(server->headless.pending_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; + } + } + } +} diff --git a/src/server.c b/src/server.c index 0aa07a09..e7a58be9 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,22 @@ 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, "unable to create headless backend"); + 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 overlaid 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.