mirror of
https://github.com/swaywm/sway.git
synced 2026-04-22 06:46:27 -04:00
sway_mirror: implement wlr_mirror_v1 allowing mirroring of variable source on a single destination output
This commit is contained in:
parent
b8995ced8f
commit
d39d8180c8
14 changed files with 760 additions and 8 deletions
|
|
@ -198,6 +198,7 @@ sway_cmd cmd_workspace;
|
||||||
sway_cmd cmd_workspace_layout;
|
sway_cmd cmd_workspace_layout;
|
||||||
sway_cmd cmd_ws_auto_back_and_forth;
|
sway_cmd cmd_ws_auto_back_and_forth;
|
||||||
sway_cmd cmd_xwayland;
|
sway_cmd cmd_xwayland;
|
||||||
|
sway_cmd cmd_mirror;
|
||||||
|
|
||||||
sway_cmd bar_cmd_bindcode;
|
sway_cmd bar_cmd_bindcode;
|
||||||
sway_cmd bar_cmd_binding_mode_indicator;
|
sway_cmd bar_cmd_binding_mode_indicator;
|
||||||
|
|
|
||||||
94
include/sway/mirror.h
Normal file
94
include/sway/mirror.h
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
#ifndef _SWAY_MIRROR_H
|
||||||
|
#define _SWAY_MIRROR_H
|
||||||
|
|
||||||
|
#include <wlr/types/wlr_mirror_v1.h>
|
||||||
|
#include <wlr/util/box.h>
|
||||||
|
#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
|
||||||
|
|
||||||
|
|
@ -108,8 +108,9 @@ void output_render(struct sway_output *output, struct timespec *when,
|
||||||
pixman_region32_t *damage);
|
pixman_region32_t *damage);
|
||||||
|
|
||||||
void output_surface_for_each_surface(struct sway_output *output,
|
void output_surface_for_each_surface(struct sway_output *output,
|
||||||
struct wlr_surface *surface, double ox, double oy,
|
struct sway_view *view, struct wlr_surface *surface,
|
||||||
sway_surface_iterator_func_t iterator, void *user_data);
|
double ox, double oy, sway_surface_iterator_func_t iterator,
|
||||||
|
void *user_data);
|
||||||
|
|
||||||
void output_view_for_each_surface(struct sway_output *output,
|
void output_view_for_each_surface(struct sway_output *output,
|
||||||
struct sway_view *view, sway_surface_iterator_func_t iterator,
|
struct sway_view *view, sway_surface_iterator_func_t iterator,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include <wlr/types/wlr_foreign_toplevel_management_v1.h>
|
#include <wlr/types/wlr_foreign_toplevel_management_v1.h>
|
||||||
#include <wlr/types/wlr_drm_lease_v1.h>
|
#include <wlr/types/wlr_drm_lease_v1.h>
|
||||||
#include <wlr/types/wlr_layer_shell_v1.h>
|
#include <wlr/types/wlr_layer_shell_v1.h>
|
||||||
|
#include <wlr/types/wlr_mirror_v1.h>
|
||||||
#include <wlr/types/wlr_output_management_v1.h>
|
#include <wlr/types/wlr_output_management_v1.h>
|
||||||
#include <wlr/types/wlr_output_power_management_v1.h>
|
#include <wlr/types/wlr_output_power_management_v1.h>
|
||||||
#include <wlr/types/wlr_presentation_time.h>
|
#include <wlr/types/wlr_presentation_time.h>
|
||||||
|
|
@ -98,6 +99,8 @@ struct sway_server {
|
||||||
struct wlr_xdg_activation_v1 *xdg_activation_v1;
|
struct wlr_xdg_activation_v1 *xdg_activation_v1;
|
||||||
struct wl_listener xdg_activation_v1_request_activate;
|
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
|
// The timeout for transactions, after which a transaction is applied
|
||||||
// regardless of readiness.
|
// regardless of readiness.
|
||||||
size_t txn_timeout_ms;
|
size_t txn_timeout_ms;
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,10 @@ struct sway_view {
|
||||||
// when a transaction is applied.
|
// when a transaction is applied.
|
||||||
struct wlr_box saved_geometry;
|
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 wlr_foreign_toplevel_handle_v1 *foreign_toplevel;
|
||||||
struct wl_listener foreign_activate_request;
|
struct wl_listener foreign_activate_request;
|
||||||
struct wl_listener foreign_fullscreen_request;
|
struct wl_listener foreign_fullscreen_request;
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ static const struct cmd_handler handlers[] = {
|
||||||
{ "gaps", cmd_gaps },
|
{ "gaps", cmd_gaps },
|
||||||
{ "hide_edge_borders", cmd_hide_edge_borders },
|
{ "hide_edge_borders", cmd_hide_edge_borders },
|
||||||
{ "input", cmd_input },
|
{ "input", cmd_input },
|
||||||
|
{ "mirror", cmd_mirror },
|
||||||
{ "mode", cmd_mode },
|
{ "mode", cmd_mode },
|
||||||
{ "mouse_warping", cmd_mouse_warping },
|
{ "mouse_warping", cmd_mouse_warping },
|
||||||
{ "new_float", cmd_new_float },
|
{ "new_float", cmd_new_float },
|
||||||
|
|
|
||||||
297
sway/commands/mirror.c
Normal file
297
sway/commands/mirror.c
Normal file
|
|
@ -0,0 +1,297 @@
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#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 <full|aspect|center>.", 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 "
|
||||||
|
"<dst_name> <full|aspect|center> <src_name> [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 "
|
||||||
|
"<dst_name> <full|aspect|center> "
|
||||||
|
"<x> <y> <width> <height> [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 "
|
||||||
|
"<dst_name> <full|aspect|center> [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 <entire|container|box> ...'";
|
||||||
|
|
||||||
|
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 <entire|container|box> ...' 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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include <wlr/backend/drm.h>
|
#include <wlr/backend/drm.h>
|
||||||
#include "sway/config.h"
|
#include "sway/config.h"
|
||||||
#include "sway/input/cursor.h"
|
#include "sway/input/cursor.h"
|
||||||
|
#include "sway/mirror.h"
|
||||||
#include "sway/output.h"
|
#include "sway/output.h"
|
||||||
#include "sway/tree/root.h"
|
#include "sway/tree/root.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
@ -482,6 +483,13 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) {
|
||||||
return false;
|
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;
|
struct wlr_output *wlr_output = output->wlr_output;
|
||||||
|
|
||||||
// Flag to prevent the output mode event handler from calling us
|
// Flag to prevent the output mode event handler from calling us
|
||||||
|
|
|
||||||
288
sway/desktop/mirror.c
Normal file
288
sway/desktop/mirror.c
Normal file
|
|
@ -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
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
@ -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,
|
void output_surface_for_each_surface(struct sway_output *output,
|
||||||
|
struct sway_view *view,
|
||||||
struct wlr_surface *surface, double ox, double oy,
|
struct wlr_surface *surface, double ox, double oy,
|
||||||
sway_surface_iterator_func_t iterator, void *user_data) {
|
sway_surface_iterator_func_t iterator, void *user_data) {
|
||||||
struct surface_iterator_data data = {
|
struct surface_iterator_data data = {
|
||||||
.user_iterator = iterator,
|
.user_iterator = iterator,
|
||||||
.user_data = user_data,
|
.user_data = user_data,
|
||||||
.output = output,
|
.output = output,
|
||||||
.view = NULL,
|
.view = view,
|
||||||
.ox = ox,
|
.ox = ox,
|
||||||
.oy = oy,
|
.oy = oy,
|
||||||
.width = surface->current.width,
|
.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) {
|
wl_list_for_each(layer_surface, layer_surfaces, link) {
|
||||||
struct wlr_layer_surface_v1 *wlr_layer_surface_v1 =
|
struct wlr_layer_surface_v1 *wlr_layer_surface_v1 =
|
||||||
layer_surface->layer_surface;
|
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,
|
layer_surface->geo.x, layer_surface->geo.y, iterator,
|
||||||
user_data);
|
user_data);
|
||||||
}
|
}
|
||||||
|
|
@ -240,7 +242,7 @@ void output_unmanaged_for_each_surface(struct sway_output *output,
|
||||||
double ox = unmanaged_surface->lx - output->lx;
|
double ox = unmanaged_surface->lx - output->lx;
|
||||||
double oy = unmanaged_surface->ly - output->ly;
|
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);
|
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;
|
double oy = drag_icon->y - output->ly;
|
||||||
|
|
||||||
if (drag_icon->wlr_drag_icon->mapped) {
|
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,
|
drag_icon->wlr_drag_icon->surface, ox, oy,
|
||||||
iterator, user_data);
|
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,
|
void output_damage_surface(struct sway_output *output, double ox, double oy,
|
||||||
struct wlr_surface *surface, bool whole) {
|
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);
|
damage_surface_iterator, &whole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,11 @@ static void render_surface_iterator(struct sway_output *output,
|
||||||
}
|
}
|
||||||
scale_box(&dst_box, wlr_output->scale);
|
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,
|
render_texture(wlr_output, output_damage, texture,
|
||||||
&src_box, &dst_box, matrix, alpha);
|
&src_box, &dst_box, matrix, alpha);
|
||||||
|
|
||||||
|
|
@ -272,7 +277,7 @@ static void render_view_toplevels(struct sway_view *view,
|
||||||
output->lx - view->geometry.x;
|
output->lx - view->geometry.x;
|
||||||
double oy = view->container->surface_y -
|
double oy = view->container->surface_y -
|
||||||
output->ly - view->geometry.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);
|
render_surface_iterator, &data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ sway_sources = files(
|
||||||
'desktop/desktop.c',
|
'desktop/desktop.c',
|
||||||
'desktop/idle_inhibit_v1.c',
|
'desktop/idle_inhibit_v1.c',
|
||||||
'desktop/layer_shell.c',
|
'desktop/layer_shell.c',
|
||||||
|
'desktop/mirror.c',
|
||||||
'desktop/output.c',
|
'desktop/output.c',
|
||||||
'desktop/render.c',
|
'desktop/render.c',
|
||||||
'desktop/surface.c',
|
'desktop/surface.c',
|
||||||
|
|
@ -74,6 +75,7 @@ sway_sources = files(
|
||||||
'commands/include.c',
|
'commands/include.c',
|
||||||
'commands/input.c',
|
'commands/input.c',
|
||||||
'commands/layout.c',
|
'commands/layout.c',
|
||||||
|
'commands/mirror.c',
|
||||||
'commands/mode.c',
|
'commands/mode.c',
|
||||||
'commands/mouse_warping.c',
|
'commands/mouse_warping.c',
|
||||||
'commands/move.c',
|
'commands/move.c',
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,8 @@ bool server_init(struct sway_server *server) {
|
||||||
wl_signal_add(&server->xdg_activation_v1->events.request_activate,
|
wl_signal_add(&server->xdg_activation_v1->events.request_activate,
|
||||||
&server->xdg_activation_v1_request_activate);
|
&server->xdg_activation_v1_request_activate);
|
||||||
|
|
||||||
|
wl_list_init(&server->mirrors);
|
||||||
|
|
||||||
// Avoid using "wayland-0" as display socket
|
// Avoid using "wayland-0" as display socket
|
||||||
char name_candidate[16];
|
char name_candidate[16];
|
||||||
for (int i = 1; i <= 32; ++i) {
|
for (int i = 1; i <= 32; ++i) {
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,50 @@ set|plus|minus|toggle <amount>
|
||||||
effect on the output the window is currently on. See *sway-output*(5) for
|
effect on the output the window is currently on. See *sway-output*(5) for
|
||||||
further details.
|
further details.
|
||||||
|
|
||||||
|
*mirror* start <entire|container|box> <dst_name> <full|aspect|center> ...
|
||||||
|
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_ <dst_name> <full|aspect|center> <src_name> [show_cursor]
|
||||||
|
Mirrors the whole of the source (_src_) output on _dst_. The cursor on
|
||||||
|
_src_ is optionally shown.
|
||||||
|
|
||||||
|
*mirror* start _box_ <dst_name> <full|aspect|center> <x> <y> <width> <height> [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_ <dst_name> <full|aspect|center> [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> px]
|
*move* left|right|up|down [<px> px]
|
||||||
Moves the focused container in the direction specified. If the container,
|
Moves the focused container in the direction specified. If the container,
|
||||||
the optional _px_ argument specifies how many pixels to move the container.
|
the optional _px_ argument specifies how many pixels to move the container.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue