Implement key binds to control virtual outputs (#1287)

Add actions `VirtualOutputAdd` and `VirtualOutputRemove`
This commit is contained in:
kyak 2023-12-09 12:01:11 +03:00 committed by GitHub
parent e303281333
commit 111b955b53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 150 additions and 1 deletions

View file

@ -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.
*<action name="VirtualOutputAdd" output_name="value" />*
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:
```
<keybind key="W-v">
<action name="VirtualOutputAdd" output_name="ScreenCasting"/>
<action name="Execute" command='sh -c "wlr-randr --output ScreenCasting --pos 0,0 --scale 2 --custom-mode 3840x2110; wlr-randr --output eDP-1 --pos 0,0 --scale 2 --mode 3840x2160"'/>
</keybind>
<keybind key="W-c">
<action name="VirtualOutputRemove"/>
</keybind>
```
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".
*<action name="VirtualOutputRemove" output_name="value" />*
Remove virtual output (headless backend).
*output_name* The name of virtual output. If not supplied, will remove the
last virtual output added.
*<action name="None" />*
If used as the only action for a binding: clear an earlier defined binding.

View file

@ -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);

View file

@ -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;

View file

@ -9,6 +9,8 @@
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <strings.h>
#include <wlr/backend/drm.h>
#include <wlr/backend/headless.h>
#include <wlr/types/wlr_buffer.h>
#include <wlr/types/wlr_drm_lease_v1.h>
#include <wlr/types/wlr_output.h>
@ -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;
}
}
}
}

View file

@ -3,6 +3,8 @@
#include "config.h"
#include <signal.h>
#include <sys/wait.h>
#include <wlr/backend/headless.h>
#include <wlr/backend/multi.h>
#include <wlr/types/wlr_data_control_v1.h>
#include <wlr/types/wlr_export_dmabuf_v1.h>
#include <wlr/types/wlr_fractional_scale_v1.h>
@ -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.