From d39d8180c8ea763ea41d0b85a195050101ed091d Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Fri, 7 Jan 2022 11:21:31 +1100 Subject: [PATCH] sway_mirror: implement wlr_mirror_v1 allowing mirroring of variable source on a single destination output --- include/sway/commands.h | 1 + include/sway/mirror.h | 94 +++++++++++++ include/sway/output.h | 5 +- include/sway/server.h | 3 + include/sway/tree/view.h | 4 + sway/commands.c | 1 + sway/commands/mirror.c | 297 +++++++++++++++++++++++++++++++++++++++ sway/config/output.c | 8 ++ sway/desktop/mirror.c | 288 +++++++++++++++++++++++++++++++++++++ sway/desktop/output.c | 12 +- sway/desktop/render.c | 7 +- sway/meson.build | 2 + sway/server.c | 2 + sway/sway.5.scd | 44 ++++++ 14 files changed, 760 insertions(+), 8 deletions(-) create mode 100644 include/sway/mirror.h create mode 100644 sway/commands/mirror.c create mode 100644 sway/desktop/mirror.c diff --git a/include/sway/commands.h b/include/sway/commands.h index 2746ef28f..a99725279 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -198,6 +198,7 @@ sway_cmd cmd_workspace; sway_cmd cmd_workspace_layout; sway_cmd cmd_ws_auto_back_and_forth; sway_cmd cmd_xwayland; +sway_cmd cmd_mirror; sway_cmd bar_cmd_bindcode; sway_cmd bar_cmd_binding_mode_indicator; diff --git a/include/sway/mirror.h b/include/sway/mirror.h new file mode 100644 index 000000000..203a911c5 --- /dev/null +++ b/include/sway/mirror.h @@ -0,0 +1,94 @@ +#ifndef _SWAY_MIRROR_H +#define _SWAY_MIRROR_H + +#include +#include +#include "sway/output.h" + +/** + * Allows mirroring: rendering some contents of one output (the src) on another + * output (the dst). dst is fixed for the duration of the session, src may vary. + * + * See wlr_mirror_v1.h for full details. + */ + +enum sway_mirror_flavour { + /** + * Mirror the entirety of src on dst. + */ + SWAY_MIRROR_FLAVOUR_ENTIRE, + + /** + * Mirror a fixed box on one src on dst. + */ + SWAY_MIRROR_FLAVOUR_BOX, + + /** + * Mirror a container from its current src on dst, adjusting size and + * position as required. + */ + SWAY_MIRROR_FLAVOUR_CONTAINER, +}; + +/** + * Immutable over session. + */ +struct sway_mirror_params { + + struct wlr_mirror_v1_params wlr_params; + + enum sway_mirror_flavour flavour; + + // ENTIRE, BOX + struct wlr_output *output_src; + struct wlr_box box; + + // CONTAINER + size_t con_id; +}; + +struct sway_mirror { + struct wl_list link; + + struct sway_mirror_params params; + + /** + * Frame is ready, from the potential src passed. + */ + struct wl_listener ready; + + /** + * Mirror session ended prematurely. + */ + struct wl_listener destroy; + + struct wlr_mirror_v1 *wlr_mirror_v1; +}; + +/** + * Start a mirror session, adding a sway_mirror to server::mirrors. + */ +bool mirror_create(struct sway_mirror_params *params); + +/** + * Stop a mirror session. + */ +void mirror_destroy(struct sway_mirror *mirror); + +/** + * Stop all mirror sessions. + */ +void mirror_destroy_all(); + +/** + * Output is currently in use as a mirror. + */ +bool mirror_output_is_mirror_dst(struct sway_output *output); + +/** + * Translate a layout box to local output. Returns true if within output. + */ +bool mirror_layout_box_within_output(struct wlr_box *box, struct wlr_output *output); + +#endif + diff --git a/include/sway/output.h b/include/sway/output.h index 26b9709f9..7dc15e1c4 100644 --- a/include/sway/output.h +++ b/include/sway/output.h @@ -108,8 +108,9 @@ void output_render(struct sway_output *output, struct timespec *when, pixman_region32_t *damage); void output_surface_for_each_surface(struct sway_output *output, - struct wlr_surface *surface, double ox, double oy, - sway_surface_iterator_func_t iterator, void *user_data); + struct sway_view *view, struct wlr_surface *surface, + double ox, double oy, sway_surface_iterator_func_t iterator, + void *user_data); void output_view_for_each_surface(struct sway_output *output, struct sway_view *view, sway_surface_iterator_func_t iterator, diff --git a/include/sway/server.h b/include/sway/server.h index 0bd860b24..8b4ff3884 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -98,6 +99,8 @@ struct sway_server { struct wlr_xdg_activation_v1 *xdg_activation_v1; struct wl_listener xdg_activation_v1_request_activate; + struct wl_list mirrors; // sway_mirror::link + // The timeout for transactions, after which a transaction is applied // regardless of readiness. size_t txn_timeout_ms; diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h index 008361f7b..447dce84b 100644 --- a/include/sway/tree/view.h +++ b/include/sway/tree/view.h @@ -97,6 +97,10 @@ struct sway_view { // when a transaction is applied. struct wlr_box saved_geometry; + // The most recently rendered output and destination box. + struct sway_output *last_output; + struct wlr_box last_destination; + struct wlr_foreign_toplevel_handle_v1 *foreign_toplevel; struct wl_listener foreign_activate_request; struct wl_listener foreign_fullscreen_request; diff --git a/sway/commands.c b/sway/commands.c index 5a1fd32ef..f2d2cc90c 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -74,6 +74,7 @@ static const struct cmd_handler handlers[] = { { "gaps", cmd_gaps }, { "hide_edge_borders", cmd_hide_edge_borders }, { "input", cmd_input }, + { "mirror", cmd_mirror }, { "mode", cmd_mode }, { "mouse_warping", cmd_mouse_warping }, { "new_float", cmd_new_float }, diff --git a/sway/commands/mirror.c b/sway/commands/mirror.c new file mode 100644 index 000000000..730638e63 --- /dev/null +++ b/sway/commands/mirror.c @@ -0,0 +1,297 @@ +#include +#include +#include "sway/commands.h" +#include "sway/input/seat.h" +#include "sway/mirror.h" +#include "sway/output.h" +#include "sway/server.h" +#include "log.h" +#include "util.h" + +static char params_failure_message[1024]; + +static bool test_con_id(struct sway_container *container, void *data) { + size_t *con_id = data; + return container->node.id == *con_id; +} + +bool build_src_params(struct sway_mirror_params *params, char *src_name_or_id) { + + struct sway_output *output_src = all_output_by_name_or_id(src_name_or_id); + if (!output_src) { + snprintf(params_failure_message, sizeof(params_failure_message), + "src_name '%s' not found.", src_name_or_id); + return false; + } + if (!output_src->enabled) { + snprintf(params_failure_message, sizeof(params_failure_message), + "src_name '%s' not enabled.", src_name_or_id); + return false; + } + + params->output_src = output_src->wlr_output; + + return true; +} + +bool build_dst_params(struct sway_mirror_params *params, char *dst_name_or_id, + char *scale_str) { + + struct sway_output *output_dst = all_output_by_name_or_id(dst_name_or_id); + if (!output_dst) { + snprintf(params_failure_message, sizeof(params_failure_message), + "dst_name '%s' not found.", dst_name_or_id); + return false; + } + if (mirror_output_is_mirror_dst(output_dst)) { + snprintf(params_failure_message, sizeof(params_failure_message), + "dst_name '%s' already mirroring.", dst_name_or_id); + return false; + } + if (!output_dst->enabled) { + snprintf(params_failure_message, sizeof(params_failure_message), + "dst_name '%s' not enabled.", dst_name_or_id); + return false; + } + params->wlr_params.output_dst = output_dst->wlr_output; + + if (!scale_str) { + snprintf(params_failure_message, sizeof(params_failure_message), + "Invalid scale."); + return false; + } else if (strcmp(scale_str, "full") == 0) { + params->wlr_params.scale = WLR_MIRROR_V1_SCALE_FULL; + } else if (strcmp(scale_str, "aspect") == 0) { + params->wlr_params.scale = WLR_MIRROR_V1_SCALE_ASPECT; + } else if (strcmp(scale_str, "center") == 0) { + params->wlr_params.scale = WLR_MIRROR_V1_SCALE_CENTER; + } else { + snprintf(params_failure_message, sizeof(params_failure_message), + "Invalid scale '%s', expected .", scale_str); + return false; + } + + return true; +} + +bool build_focussed_params(struct sway_mirror_params *params) { + struct sway_seat *seat = input_manager_current_seat(); + struct sway_container *container = seat_get_focused_container(seat); + if (!container) { + snprintf(params_failure_message, sizeof(params_failure_message), + "No focussed container."); + return false; + } + params->con_id = container->node.id; + + return true; +} + +bool build_con_id_params(struct sway_mirror_params *params, char *str) { + char *end; + size_t con_id = strtol(str, &end, 10); + if (end[0] != '\0') { + snprintf(params_failure_message, sizeof(params_failure_message), + "Invalid con_id '%s'.", str); + return false; + } + struct sway_container *container = root_find_container(test_con_id, &con_id); + if (!container) { + snprintf(params_failure_message, sizeof(params_failure_message), + "con_id '%ld' not found.", con_id); + return false; + } + + params->con_id = con_id; + return true; +} + +struct cmd_results *cmd_mirror_start_entire(int argc, char **argv) { + const char usage[] = "Expected 'mirror start entire " + " [show_cursor]'"; + + if (argc < 3 || argc > 4) { + return cmd_results_new(CMD_INVALID, usage); + } + + struct sway_mirror_params params = { 0 }; + params.flavour = SWAY_MIRROR_FLAVOUR_ENTIRE; + + if (!build_dst_params(¶ms, argv[0], argv[1])) { + return cmd_results_new(CMD_FAILURE, params_failure_message); + } + + if (!build_src_params(¶ms, argv[2])) { + return cmd_results_new(CMD_FAILURE, params_failure_message); + } + + if (params.output_src == params.wlr_params.output_dst) { + return cmd_results_new(CMD_FAILURE, "src and dst must be different"); + } + + if (argc == 4) { + if (strcmp(argv[3], "show_cursor") != 0) { + return cmd_results_new(CMD_FAILURE, usage); + } + params.wlr_params.overlay_cursor = true; + } + + if (!mirror_create(¶ms)) { + return cmd_results_new(CMD_FAILURE, "Mirror failed to start, check logs."); + } + + return cmd_results_new(CMD_SUCCESS, NULL); +} + +struct cmd_results *cmd_mirror_start_box(int argc, char **argv) { + const char usage[] = "Expected 'mirror start box " + " " + " [show_cursor]'"; + + if (argc < 6 || argc > 7) { + return cmd_results_new(CMD_INVALID, usage); + } + + struct sway_mirror_params params = { 0 }; + params.flavour = SWAY_MIRROR_FLAVOUR_BOX; + + if (!build_dst_params(¶ms, argv[0], argv[1])) { + return cmd_results_new(CMD_FAILURE, params_failure_message); + } + + char *end; + params.box.x = strtol(argv[2], &end, 10); + if (end[0] != '\0' || params.box.x < 0) { + return cmd_results_new(CMD_FAILURE, "Invalid x '%s'.", argv[2]); + } + params.box.y = strtol(argv[3], &end, 10); + if (end[0] != '\0' || params.box.y < 0) { + return cmd_results_new(CMD_FAILURE, "Invalid y '%s'.", argv[3]); + } + params.box.width = strtol(argv[4], &end, 10); + if (end[0] != '\0' || params.box.width < 1) { + return cmd_results_new(CMD_FAILURE, "Invalid width '%s'.", argv[4]); + } + params.box.height = strtol(argv[5], &end, 10); + if (end[0] != '\0' || params.box.height < 1) { + return cmd_results_new(CMD_FAILURE, "Invalid height '%s'.", argv[5]); + } + + // find the output for the origin of the box + params.output_src = wlr_output_layout_output_at(root->output_layout, + params.box.x, params.box.y); + if (!params.output_src) { + return cmd_results_new(CMD_FAILURE, "Box not on any output."); + } + + // box cannot be on the dst + if (params.output_src == params.wlr_params.output_dst) { + return cmd_results_new(CMD_FAILURE, "Box on dst."); + } + + // convert to local output coordinates, ensuring within output_src + if (!mirror_layout_box_within_output(¶ms.box, params.output_src)) { + return cmd_results_new(CMD_FAILURE, "Box covers multiple outputs."); + } + + if (argc == 7) { + if (strcmp(argv[6], "show_cursor") != 0) { + return cmd_results_new(CMD_FAILURE, usage); + } + params.wlr_params.overlay_cursor = true; + } + + if (!mirror_create(¶ms)) { + return cmd_results_new(CMD_FAILURE, "Mirror failed to start, check logs."); + } + + return cmd_results_new(CMD_SUCCESS, NULL); +} + +struct cmd_results *cmd_mirror_start_container(int argc, char **argv) { + const char usage[] = "Expected 'mirror start container " + " [con_id] [show_cursor]'"; + + if (argc < 2 || argc > 4) { + return cmd_results_new(CMD_INVALID, usage); + } + + struct sway_mirror_params params = { 0 }; + params.flavour = SWAY_MIRROR_FLAVOUR_CONTAINER; + + if (!build_dst_params(¶ms, argv[0], argv[1])) { + return cmd_results_new(CMD_FAILURE, params_failure_message); + } + + switch(argc) { + case 2: + if (!build_focussed_params(¶ms)) { + return cmd_results_new(CMD_FAILURE, params_failure_message); + } + break; + case 3: + if (strcmp(argv[2], "show_cursor") == 0) { + params.wlr_params.overlay_cursor = true; + if (!build_focussed_params(¶ms)) { + return cmd_results_new(CMD_FAILURE, params_failure_message); + } + } else if (!build_con_id_params(¶ms, argv[2])) { + return cmd_results_new(CMD_FAILURE, params_failure_message); + } + break; + case 4: + if (!build_con_id_params(¶ms, argv[2])) { + return cmd_results_new(CMD_FAILURE, params_failure_message); + } + if (strcmp(argv[3], "show_cursor") != 0) { + return cmd_results_new(CMD_FAILURE, usage); + } + params.wlr_params.overlay_cursor = true; + break; + default: + break; + } + + if (!mirror_create(¶ms)) { + return cmd_results_new(CMD_FAILURE, "Mirror failed to start, check logs."); + } + + return cmd_results_new(CMD_SUCCESS, NULL); +} + +struct cmd_results *cmd_mirror_start(int argc, char **argv) { + const char usage[] = "Expected 'mirror start ...'"; + + if (strcasecmp(argv[0], "entire") == 0) { + return cmd_mirror_start_entire(argc - 1, &argv[1]); + } else if (strcasecmp(argv[0], "container") == 0) { + return cmd_mirror_start_container(argc - 1, &argv[1]); + } else if (strcasecmp(argv[0], "box") == 0) { + return cmd_mirror_start_box(argc - 1, &argv[1]); + } else { + return cmd_results_new(CMD_INVALID, usage); + } +} + +struct cmd_results *cmd_mirror_stop(void) { + mirror_destroy_all(); + return cmd_results_new(CMD_SUCCESS, NULL); +} + +struct cmd_results *cmd_mirror(int argc, char **argv) { + const char usage[] = "Expected 'mirror start ...' or " + "'mirror stop'"; + + if (argc < 1) { + return cmd_results_new(CMD_INVALID, usage); + } + if (strcasecmp(argv[0], "stop") == 0) { + return cmd_mirror_stop(); + } + if (argc < 2 || strcasecmp(argv[0], "start") != 0) { + return cmd_results_new(CMD_INVALID, usage); + } + + return cmd_mirror_start(argc - 1, &argv[1]); +} + diff --git a/sway/config/output.c b/sway/config/output.c index fa509252c..8dfb8b505 100644 --- a/sway/config/output.c +++ b/sway/config/output.c @@ -12,6 +12,7 @@ #include #include "sway/config.h" #include "sway/input/cursor.h" +#include "sway/mirror.h" #include "sway/output.h" #include "sway/tree/root.h" #include "log.h" @@ -482,6 +483,13 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) { return false; } + // block changes to active mirror dsts; these will be applied during reclaim_output + if (mirror_output_is_mirror_dst(output)) { + sway_log(SWAY_DEBUG, "Not configuring mirror dst output %s", + output->wlr_output->name); + return false; + } + struct wlr_output *wlr_output = output->wlr_output; // Flag to prevent the output mode event handler from calling us diff --git a/sway/desktop/mirror.c b/sway/desktop/mirror.c new file mode 100644 index 000000000..771ca5638 --- /dev/null +++ b/sway/desktop/mirror.c @@ -0,0 +1,288 @@ +#include "log.h" +#include "sway/mirror.h" +#include "sway/output.h" +#include "sway/server.h" +#include "sway/tree/container.h" +#include "sway/tree/root.h" +#include "sway/tree/view.h" +#include "sway/tree/workspace.h" + +/** + * BEGIN helper functions + */ + +static bool test_con_id(struct sway_container *container, void *data) { + size_t *con_id = data; + return container->node.id == *con_id; +} + +static struct wlr_output *container_output(struct sway_container *container) { + struct sway_workspace *workspace = container->current.workspace; + if (workspace) { + struct sway_output *output = workspace->current.output; + if (output) { + return output->wlr_output; + } + } + return NULL; +} + +/** + * Stop rendering on output, arranging root as thought the output has been + * disabled or unplugged. + */ +void vacate_output(struct sway_output *output) { + + // arranges root + if (output->enabled) { + output_disable(output); + } + + // idempotent + wlr_output_layout_remove(root->output_layout, output->wlr_output); +} + +/** + * Reclaim an output, arranging root as though it is a newly enabled output. + * + * Any "pending" changes that were blocked in apply_output_config during the + * mirror session will be applied. + */ +void reclaim_output(struct sway_output *output) { + + struct output_config *oc = find_output_config(output); + + // calls output_enable + apply_output_config(oc, output); + + free_output_config(oc); +} + +/** + * END helper functions + */ + +/** + * BEGIN sway_mirror handler functions + */ + +static void handle_ready_entire(struct wl_listener *listener, void *data) { + struct sway_mirror *mirror = wl_container_of(listener, mirror, ready); + struct wlr_output *output = data; + + if (output != mirror->params.output_src) { + return; + } + + struct wlr_box box_output = { 0 }; + wlr_output_transformed_resolution(output, &box_output.width, &box_output.height); + + wlr_mirror_v1_request_box(mirror->wlr_mirror_v1, output, box_output); +} + +static void handle_ready_box(struct wl_listener *listener, void *data) { + struct sway_mirror *mirror = wl_container_of(listener, mirror, ready); + struct wlr_output *output = data; + + if (output != mirror->params.output_src) { + return; + } + + wlr_mirror_v1_request_box(mirror->wlr_mirror_v1, output, mirror->params.box); +} + +static void handle_ready_container(struct wl_listener *listener, void *data) { + struct sway_mirror *mirror = wl_container_of(listener, mirror, ready); + struct wlr_output *output = data; + + // does the container still exist? + struct sway_container *container = root_find_container(test_con_id, &mirror->params.con_id); + if (!container) { + sway_log(SWAY_DEBUG, "Mirror container %ld destroyed, stopping", mirror->params.con_id); + wlr_mirror_v1_destroy(mirror->wlr_mirror_v1); + return; + } + + // is the container visible? + struct sway_view *view = container->view; + if (!view_is_visible(view)) { + wlr_mirror_v1_request_blank(mirror->wlr_mirror_v1); + return; + } + + // is the container on this output? + if (output != container_output(container)) { + return; + } + + // is the container's last rendered output this output? + if (output != view->last_output->wlr_output) { + return; + } + + // intersection with output should always be last_destination + struct wlr_box box_output = { 0 }; + wlr_output_transformed_resolution(output, &box_output.width, &box_output.height); + struct wlr_box box_src_intersected; + wlr_box_intersection(&box_src_intersected, &view->last_destination, &box_output); + + wlr_mirror_v1_request_box(mirror->wlr_mirror_v1, output, box_src_intersected); +} + +static void handle_destroy(struct wl_listener *listener, void *data) { + struct sway_mirror *mirror = wl_container_of(listener, mirror, destroy); + + sway_log(SWAY_DEBUG, "Mirror destroy dst '%s'", mirror->params.wlr_params.output_dst->name); + + wl_list_remove(&mirror->ready.link); + wl_list_remove(&mirror->destroy.link); + + struct sway_output *output_dst = output_from_wlr_output(mirror->params.wlr_params.output_dst); + if (output_dst) { + reclaim_output(output_dst); + } + + wl_list_remove(&mirror->link); + + wl_array_release(&mirror->params.wlr_params.output_srcs); + free(mirror); +} + +/** + * END sway_mirror handler functions + */ + +/** + * BEGIN public functions + */ + +bool mirror_create(struct sway_mirror_params *params) { + if (!params || !params->wlr_params.output_dst) { + sway_log(SWAY_ERROR, "Missing params or params->wlr_params.output_dst"); + return false; + } + + struct sway_output *output_dst = output_from_wlr_output(params->wlr_params.output_dst); + + struct sway_mirror *mirror = calloc(1, sizeof(struct sway_mirror)); + + memcpy(&mirror->params, params, sizeof(struct sway_mirror_params)); + struct wlr_mirror_v1_params *wlr_params = &mirror->params.wlr_params; + wl_array_init(&wlr_params->output_srcs); + + switch (mirror->params.flavour) { + case SWAY_MIRROR_FLAVOUR_ENTIRE: + sway_log(SWAY_DEBUG, "Mirror creating dst '%s' entire", output_dst->wlr_output->name); + mirror->ready.notify = handle_ready_entire; + break; + case SWAY_MIRROR_FLAVOUR_BOX: + sway_log(SWAY_DEBUG, "Mirror creating dst '%s' box %d,%d %dx%d", + output_dst->wlr_output->name, + mirror->params.box.x, mirror->params.box.y, + mirror->params.box.width, mirror->params.box.height); + mirror->ready.notify = handle_ready_box; + break; + case SWAY_MIRROR_FLAVOUR_CONTAINER: + sway_log(SWAY_DEBUG, "Mirror creating dst '%s' container con_id %ld", + output_dst->wlr_output->name, mirror->params.con_id); + mirror->ready.notify = handle_ready_container; + break; + default: + sway_log(SWAY_ERROR, "Invalid sway_mirror_flavour."); + goto error_params; + break; + } + + if (params->flavour == SWAY_MIRROR_FLAVOUR_CONTAINER) { + // listen for ready for all enabled srcs except the dst + for (int i = 0; i < root->outputs->length; ++i) { + struct sway_output *output = root->outputs->items[i]; + if (output->wlr_output != wlr_params->output_dst) { + struct wlr_output **output_src_ptr = + wl_array_add(&wlr_params->output_srcs, sizeof(struct output_src_ptr*)); + *output_src_ptr = output->wlr_output; + } + } + } else { + // listen for ready on just the specified src + struct wlr_output **output_src_ptr = + wl_array_add(&wlr_params->output_srcs, sizeof(struct output_src_ptr*)); + *output_src_ptr = mirror->params.output_src; + } + + // start the session + mirror->wlr_mirror_v1 = wlr_mirror_v1_create(&mirror->params.wlr_params); + if (!mirror->wlr_mirror_v1) { + goto error_create; + } + + vacate_output(output_dst); + + // ready events from all srcs + wl_signal_add(&mirror->wlr_mirror_v1->events.ready, &mirror->ready); + + // mirror session end + wl_signal_add(&mirror->wlr_mirror_v1->events.destroy, &mirror->destroy); + mirror->destroy.notify = handle_destroy; + + // add to the global server list + wl_list_insert(&server.mirrors, &mirror->link); + + return true; + +error_params: +error_create: + wl_array_release(&mirror->params.wlr_params.output_srcs); + free(mirror); + return false; +} + +void mirror_destroy(struct sway_mirror *mirror) { + if (!mirror) { + return; + } + sway_log(SWAY_DEBUG, "Mirror destroying dst '%s'", mirror->params.wlr_params.output_dst->name); + + wlr_mirror_v1_destroy(mirror->wlr_mirror_v1); +} + +void mirror_destroy_all() { + struct sway_mirror *mirror, *next; + wl_list_for_each_safe(mirror, next, &server.mirrors, link) { + mirror_destroy(mirror); + } +} + +bool mirror_output_is_mirror_dst(struct sway_output *output) { + return output && output->wlr_output && output->wlr_output->mirror_dst; +} + +bool mirror_layout_box_within_output(struct wlr_box *box, struct wlr_output *output) { + // translate origin to local output + double x = box->x; + double y = box->y; + wlr_output_layout_output_coords(root->output_layout, output, &x, &y); + box->x = round(x); + box->y = round(y); + + // scale to local output + scale_box(box, output->scale); + + // local output's box + struct wlr_box box_output = { 0 }; + wlr_output_transformed_resolution(output, &box_output.width, &box_output.height); + + // box must be within the output + struct wlr_box box_intersected = { 0 }; + wlr_box_intersection(&box_intersected, &box_output, box); + if (memcmp(box, &box_intersected, sizeof(struct wlr_box)) != 0) { + return false; + } + + return true; +} + +/** + * END public functions + */ + diff --git a/sway/desktop/output.c b/sway/desktop/output.c index 68f095c04..9f6df0cac 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -114,13 +114,14 @@ static void output_for_each_surface_iterator(struct wlr_surface *surface, } void output_surface_for_each_surface(struct sway_output *output, + struct sway_view *view, struct wlr_surface *surface, double ox, double oy, sway_surface_iterator_func_t iterator, void *user_data) { struct surface_iterator_data data = { .user_iterator = iterator, .user_data = user_data, .output = output, - .view = NULL, + .view = view, .ox = ox, .oy = oy, .width = surface->current.width, @@ -199,7 +200,8 @@ void output_layer_for_each_toplevel_surface(struct sway_output *output, wl_list_for_each(layer_surface, layer_surfaces, link) { struct wlr_layer_surface_v1 *wlr_layer_surface_v1 = layer_surface->layer_surface; - output_surface_for_each_surface(output, wlr_layer_surface_v1->surface, + output_surface_for_each_surface(output, NULL, + wlr_layer_surface_v1->surface, layer_surface->geo.x, layer_surface->geo.y, iterator, user_data); } @@ -240,7 +242,7 @@ void output_unmanaged_for_each_surface(struct sway_output *output, double ox = unmanaged_surface->lx - output->lx; double oy = unmanaged_surface->ly - output->ly; - output_surface_for_each_surface(output, xsurface->surface, ox, oy, + output_surface_for_each_surface(output, NULL, xsurface->surface, ox, oy, iterator, user_data); } } @@ -255,7 +257,7 @@ void output_drag_icons_for_each_surface(struct sway_output *output, double oy = drag_icon->y - output->ly; if (drag_icon->wlr_drag_icon->mapped) { - output_surface_for_each_surface(output, + output_surface_for_each_surface(output, NULL, drag_icon->wlr_drag_icon->surface, ox, oy, iterator, user_data); } @@ -659,7 +661,7 @@ static void damage_surface_iterator(struct sway_output *output, void output_damage_surface(struct sway_output *output, double ox, double oy, struct wlr_surface *surface, bool whole) { - output_surface_for_each_surface(output, surface, ox, oy, + output_surface_for_each_surface(output, NULL, surface, ox, oy, damage_surface_iterator, &whole); } diff --git a/sway/desktop/render.c b/sway/desktop/render.c index c088c936d..57287b098 100644 --- a/sway/desktop/render.c +++ b/sway/desktop/render.c @@ -162,6 +162,11 @@ static void render_surface_iterator(struct sway_output *output, } scale_box(&dst_box, wlr_output->scale); + if (view) { + memcpy(&view->last_destination, &dst_box, sizeof(struct wlr_box)); + view->last_output = output; + } + render_texture(wlr_output, output_damage, texture, &src_box, &dst_box, matrix, alpha); @@ -272,7 +277,7 @@ static void render_view_toplevels(struct sway_view *view, output->lx - view->geometry.x; double oy = view->container->surface_y - output->ly - view->geometry.y; - output_surface_for_each_surface(output, view->surface, ox, oy, + output_surface_for_each_surface(output, view, view->surface, ox, oy, render_surface_iterator, &data); } diff --git a/sway/meson.build b/sway/meson.build index 8eab31a22..a26f5b8b3 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -14,6 +14,7 @@ sway_sources = files( 'desktop/desktop.c', 'desktop/idle_inhibit_v1.c', 'desktop/layer_shell.c', + 'desktop/mirror.c', 'desktop/output.c', 'desktop/render.c', 'desktop/surface.c', @@ -74,6 +75,7 @@ sway_sources = files( 'commands/include.c', 'commands/input.c', 'commands/layout.c', + 'commands/mirror.c', 'commands/mode.c', 'commands/mouse_warping.c', 'commands/move.c', diff --git a/sway/server.c b/sway/server.c index f50a0987d..381d4c0a3 100644 --- a/sway/server.c +++ b/sway/server.c @@ -208,6 +208,8 @@ bool server_init(struct sway_server *server) { wl_signal_add(&server->xdg_activation_v1->events.request_activate, &server->xdg_activation_v1_request_activate); + wl_list_init(&server->mirrors); + // Avoid using "wayland-0" as display socket char name_candidate[16]; for (int i = 1; i <= 32; ++i) { diff --git a/sway/sway.5.scd b/sway/sway.5.scd index 641d09253..5e66394a2 100644 --- a/sway/sway.5.scd +++ b/sway/sway.5.scd @@ -209,6 +209,50 @@ set|plus|minus|toggle effect on the output the window is currently on. See *sway-output*(5) for further details. +*mirror* start ... + Begins a mirror session. Multiple sessions may run concurrently. + The destination (_dst_) output will be vacated by sway and the mirrored + content displayed there. + See *sway-output*(5) for details on specifying the output name. + + _full_: content will be stretched to fill _dst_, possibly distorting. + + _aspect_: content will be stretched to fit to the width or height of + _dst_ without distortion. + + _center_: content will be displayed at the center of _dst_ at a 1:1 ratio. + +*mirror* start _entire_ [show_cursor] + Mirrors the whole of the source (_src_) output on _dst_. The cursor on + _src_ is optionally shown. + +*mirror* start _box_ [show_cursor] + Mirrors a fixed area (_box_) on the _dst_ output. The _box_ must be entirely + within a single output. _box_ is specified in the global coordinate space; + see *sway-output*(5) for further details. The cursor within the _box_ is + optionally shown. + + A selection tool such as *slurp*(1) may facilitate easier _box_ selection + e.g.: + mirror start box eDP-1 aspect $(slurp -f "%x %y %w %h") + +*mirror* start _container_ [con_id] [show_cursor] + Mirrors a single sway container on _dst_. The cursor within the container + is optionally shown. The currently focussed container will be used if + _con_id_ is not specified; see *CRITERIA* for details on _con_id_. + + The container may be moved between outputs and may be resized, floated + and moved. _dst_ will be blanked when the container is not visible. + + The mirror session will end if the container is destroyed. + + In the case of a floating container spanning multiple outputs, the + portion of the container visible on the output of its current workspace + will be mirrored. + +*mirror* stop + Ceases all mirror sessions. + *move* left|right|up|down [ px] Moves the focused container in the direction specified. If the container, the optional _px_ argument specifies how many pixels to move the container.