mirror of
https://gitlab.freedesktop.org/wlroots/wlroots.git
synced 2026-04-19 06:47:02 -04:00
wlr_mirror_v1: adds mirroring of variable source on a single destination output
This commit is contained in:
parent
59b9518f07
commit
392dde7bed
8 changed files with 1271 additions and 0 deletions
|
|
@ -56,6 +56,9 @@ compositors = {
|
||||||
'src': 'scene-graph.c',
|
'src': 'scene-graph.c',
|
||||||
'proto': ['xdg-shell'],
|
'proto': ['xdg-shell'],
|
||||||
},
|
},
|
||||||
|
'mirror': {
|
||||||
|
'src': 'mirror.c',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
clients = {
|
clients = {
|
||||||
|
|
|
||||||
519
examples/mirror.c
Normal file
519
examples/mirror.c
Normal file
|
|
@ -0,0 +1,519 @@
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <wayland-server-core.h>
|
||||||
|
#include <wlr/backend/session.h>
|
||||||
|
#include <wlr/render/allocator.h>
|
||||||
|
#include <wlr/render/wlr_renderer.h>
|
||||||
|
#include <wlr/types/wlr_cursor.h>
|
||||||
|
#include <wlr/types/wlr_keyboard.h>
|
||||||
|
#include <wlr/types/wlr_mirror_v1.h>
|
||||||
|
#include <wlr/types/wlr_output_layout.h>
|
||||||
|
#include <wlr/types/wlr_pointer.h>
|
||||||
|
#include <wlr/types/wlr_xcursor_manager.h>
|
||||||
|
#include <wlr/util/log.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates wlr_mirror_v1. Comments describe mirror specific code.
|
||||||
|
*
|
||||||
|
* Mirrors the source output (src) on the destination output (dst).
|
||||||
|
*
|
||||||
|
* A moving square portion of the src (blue) is rendered on the dst (initially
|
||||||
|
* red). The cursor is included in the mirrored content.
|
||||||
|
*/
|
||||||
|
static const char usage[] =
|
||||||
|
"usage: mirror <src> <dst>\n"
|
||||||
|
" e.g. mirror eDP-1 HDMI-A-1\n"
|
||||||
|
"keys:\n"
|
||||||
|
" a: WLR_MIRROR_V1_SCALE_ASPECT\n"
|
||||||
|
" f: WLR_MIRROR_V1_SCALE_FULL\n"
|
||||||
|
" c: WLR_MIRROR_V1_SCALE_CENTER\n"
|
||||||
|
" esc: exit\n";
|
||||||
|
|
||||||
|
struct sample_state {
|
||||||
|
struct wl_display *display;
|
||||||
|
struct wlr_renderer *renderer;
|
||||||
|
struct wlr_allocator *allocator;
|
||||||
|
struct wlr_xcursor_manager *xcursor_manager;
|
||||||
|
struct wlr_cursor *cursor;
|
||||||
|
struct wlr_output_layout *layout;
|
||||||
|
|
||||||
|
struct wl_listener new_output;
|
||||||
|
struct wl_listener new_input;
|
||||||
|
struct wl_listener cursor_motion;
|
||||||
|
|
||||||
|
struct sample_mirror *mirror;
|
||||||
|
struct sample_output *output_src;
|
||||||
|
struct sample_output *output_dst;
|
||||||
|
|
||||||
|
char *src_name;
|
||||||
|
char *dst_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
// lifetime: mirror session
|
||||||
|
struct sample_mirror {
|
||||||
|
struct sample_state *state;
|
||||||
|
|
||||||
|
struct wlr_mirror_v1 *wlr_mirror;
|
||||||
|
|
||||||
|
struct wlr_mirror_v1_params params;
|
||||||
|
|
||||||
|
struct wl_listener ready;
|
||||||
|
struct wl_listener destroy;
|
||||||
|
|
||||||
|
int dx, dy;
|
||||||
|
struct wlr_box box;
|
||||||
|
struct timespec last_request;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sample_output {
|
||||||
|
struct sample_state *state;
|
||||||
|
struct wlr_output *wlr_output;
|
||||||
|
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
|
||||||
|
struct wl_listener frame;
|
||||||
|
struct wl_listener destroy;
|
||||||
|
struct wl_listener restore;
|
||||||
|
struct wl_listener remove;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sample_keyboard {
|
||||||
|
struct sample_state *state;
|
||||||
|
struct wlr_input_device *device;
|
||||||
|
struct wl_listener key;
|
||||||
|
struct wl_listener destroy;
|
||||||
|
};
|
||||||
|
|
||||||
|
void start_mirror(struct sample_state *state, enum wlr_mirror_v1_scale scale);
|
||||||
|
void end_mirror(struct sample_state *state);
|
||||||
|
void handle_mirror_ready(struct wl_listener *listener, void *data);
|
||||||
|
void handle_mirror_destroy(struct wl_listener *listener, void *data);
|
||||||
|
void handle_output_remove(struct wl_listener *listener, void *data);
|
||||||
|
void handle_output_restore(struct wl_listener *listener, void *data);
|
||||||
|
void handle_output_frame(struct wl_listener *listener, void *data);
|
||||||
|
void handle_output_destroy(struct wl_listener *listener, void *data);
|
||||||
|
void handle_new_input(struct wl_listener *listener, void *data);
|
||||||
|
void handle_new_output(struct wl_listener *listener, void *data);
|
||||||
|
void handle_cursor_motion(struct wl_listener *listener, void *data);
|
||||||
|
void handle_keyboard_key(struct wl_listener *listener, void *data);
|
||||||
|
void handle_keyboard_destroy(struct wl_listener *listener, void *data);
|
||||||
|
void render_rects(struct wlr_renderer *renderer, struct sample_output *output, float colour[]);
|
||||||
|
|
||||||
|
// start a mirror session
|
||||||
|
void start_mirror(struct sample_state *state, enum wlr_mirror_v1_scale scale) {
|
||||||
|
struct sample_output *output_src = state->output_src;
|
||||||
|
struct sample_output *output_dst = state->output_dst;
|
||||||
|
if (!output_src || !output_dst) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wlr_log(WLR_DEBUG, "mirror start dst '%s'", state->output_dst->wlr_output->name);
|
||||||
|
|
||||||
|
struct sample_mirror *mirror = calloc(1, sizeof(struct sample_mirror));
|
||||||
|
mirror->state = state;
|
||||||
|
state->mirror = mirror;
|
||||||
|
|
||||||
|
int wh = MIN(output_src->width, output_src->height) * 3 / 4;
|
||||||
|
mirror->box.width = wh;
|
||||||
|
mirror->box.height = wh;
|
||||||
|
mirror->dx = 1;
|
||||||
|
mirror->dy = 1;
|
||||||
|
|
||||||
|
// params immutable over the session
|
||||||
|
mirror->params.scale = scale;
|
||||||
|
mirror->params.overlay_cursor = true;
|
||||||
|
mirror->params.output_dst = state->output_dst->wlr_output;
|
||||||
|
|
||||||
|
wl_array_init(&mirror->params.output_srcs);
|
||||||
|
struct wlr_output **output_src_ptr =
|
||||||
|
wl_array_add(&mirror->params.output_srcs, sizeof(struct output_src_ptr*));
|
||||||
|
*output_src_ptr = state->output_src->wlr_output;
|
||||||
|
|
||||||
|
// stop rendering frames on this output
|
||||||
|
wl_list_remove(&state->output_dst->frame.link);
|
||||||
|
wl_list_init(&state->output_dst->frame.link);
|
||||||
|
|
||||||
|
struct wlr_mirror_v1 *wlr_mirror = wlr_mirror_v1_create(&mirror->params);
|
||||||
|
mirror->wlr_mirror = wlr_mirror;
|
||||||
|
|
||||||
|
// ready events enabling us to make requests for the upcoming commit
|
||||||
|
wl_signal_add(&wlr_mirror->events.ready, &mirror->ready);
|
||||||
|
mirror->ready.notify = handle_mirror_ready;
|
||||||
|
|
||||||
|
// destroy at session end
|
||||||
|
wl_signal_add(&wlr_mirror->events.destroy, &mirror->destroy);
|
||||||
|
mirror->destroy.notify = handle_mirror_destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// request that we end the session
|
||||||
|
void end_mirror(struct sample_state *state) {
|
||||||
|
wlr_log(WLR_DEBUG, "mirror end dst '%s'", state->output_dst->wlr_output->name);
|
||||||
|
|
||||||
|
if (state->mirror) {
|
||||||
|
// immediately emits wlr_mirror_v1::events::destroy
|
||||||
|
wlr_mirror_v1_destroy(state->mirror->wlr_mirror);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mirror is ready to display content from an output; called at src precommit
|
||||||
|
void handle_mirror_ready(struct wl_listener *listener, void *data) {
|
||||||
|
struct sample_mirror *mirror = wl_container_of(listener, mirror, ready);
|
||||||
|
struct sample_state *state = mirror->state;
|
||||||
|
struct sample_output *output_src = state->output_src;
|
||||||
|
struct wlr_mirror_v1 *wlr_mirror = state->mirror->wlr_mirror;
|
||||||
|
struct wlr_output *wlr_output = data;
|
||||||
|
|
||||||
|
// only request for src
|
||||||
|
if (wlr_output != state->output_src->wlr_output) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct timespec now;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
long ms = (now.tv_sec - mirror->last_request.tv_sec) * 1000 +
|
||||||
|
(now.tv_nsec - mirror->last_request.tv_nsec) / 1000000;
|
||||||
|
if (ms > 10) {
|
||||||
|
mirror->last_request = now;
|
||||||
|
|
||||||
|
// request a portion of src
|
||||||
|
wlr_mirror_v1_request_box(wlr_mirror, wlr_output, mirror->box);
|
||||||
|
|
||||||
|
if ((mirror->box.x + mirror->box.width + mirror->dx) > output_src->width) {
|
||||||
|
mirror->dx = -1;
|
||||||
|
} else if ((mirror->box.x + mirror->dx) < 0) {
|
||||||
|
mirror->dx = 1;
|
||||||
|
}
|
||||||
|
if ((mirror->box.y + mirror->box.height + mirror->dy) > output_src->height) {
|
||||||
|
mirror->dy = -1;
|
||||||
|
} else if ((mirror->box.y + mirror->dy) < 0) {
|
||||||
|
mirror->dy = 1;
|
||||||
|
}
|
||||||
|
mirror->box.x += mirror->dx;
|
||||||
|
mirror->box.y += mirror->dy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mirror session is over
|
||||||
|
void handle_mirror_destroy(struct wl_listener *listener, void *data) {
|
||||||
|
struct sample_mirror *mirror = wl_container_of(listener, mirror, destroy);
|
||||||
|
struct sample_state *state = mirror->state;
|
||||||
|
|
||||||
|
wlr_log(WLR_DEBUG, "mirror destroy dst '%s'", state->output_dst->wlr_output->name);
|
||||||
|
|
||||||
|
wl_list_remove(&mirror->ready.link);
|
||||||
|
wl_list_remove(&mirror->destroy.link);
|
||||||
|
|
||||||
|
wl_array_release(&mirror->params.output_srcs);
|
||||||
|
free(mirror);
|
||||||
|
state->mirror = NULL;
|
||||||
|
|
||||||
|
// start rendering our frames again
|
||||||
|
struct sample_output *output = state->output_dst;
|
||||||
|
if (output) {
|
||||||
|
wl_signal_add(&output->wlr_output->events.frame, &output->frame);
|
||||||
|
output->frame.notify = handle_output_frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shrinking rects alternating colour / grey
|
||||||
|
void render_rects(struct wlr_renderer *renderer, struct sample_output *output,
|
||||||
|
float colour[]) {
|
||||||
|
struct wlr_output *wlr_output = output->wlr_output;
|
||||||
|
|
||||||
|
static float colour_black[] = { 0, 0, 0, 1 };
|
||||||
|
wlr_renderer_clear(renderer, colour_black);
|
||||||
|
|
||||||
|
struct wlr_box box_rect = {
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
.width = output->width,
|
||||||
|
.height = output->height,
|
||||||
|
};
|
||||||
|
int delta_box = MIN(output->width / 16, output->height / 16);
|
||||||
|
|
||||||
|
static float colour_grey[] = { 0.05, 0.05, 0.05, 1 };
|
||||||
|
static float delta_grey = 0.002;
|
||||||
|
static struct timespec last_grey = { 0 };
|
||||||
|
struct timespec now;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
long ms = (now.tv_sec - last_grey.tv_sec) * 1000 +
|
||||||
|
(now.tv_nsec - last_grey.tv_nsec) / 1000000;
|
||||||
|
if (ms > 10) {
|
||||||
|
last_grey = now;
|
||||||
|
if (colour_grey[0] + delta_grey > 0.2) {
|
||||||
|
delta_grey = -0.002;
|
||||||
|
} else if (colour_grey[0] - delta_grey < 0.05) {
|
||||||
|
delta_grey = 0.002;
|
||||||
|
}
|
||||||
|
colour_grey[0] += delta_grey;
|
||||||
|
colour_grey[1] += delta_grey;
|
||||||
|
colour_grey[2] += delta_grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool grey = false;
|
||||||
|
while (box_rect.x < output->width / 2 && box_rect.y < output->height / 2) {
|
||||||
|
wlr_render_rect(renderer, &box_rect, grey ? colour_grey : colour,
|
||||||
|
wlr_output->transform_matrix);
|
||||||
|
grey = !grey;
|
||||||
|
box_rect.x += delta_box;
|
||||||
|
box_rect.y += delta_box;
|
||||||
|
box_rect.width -= 2 * delta_box;
|
||||||
|
box_rect.height -= 2 * delta_box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// will not be invoked for dst during mirror session
|
||||||
|
void handle_output_frame(struct wl_listener *listener, void *data) {
|
||||||
|
struct sample_output *output = wl_container_of(listener, output, frame);
|
||||||
|
struct sample_state *state = output->state;
|
||||||
|
struct wlr_output *wlr_output = output->wlr_output;
|
||||||
|
struct wlr_renderer *renderer = state->renderer;
|
||||||
|
|
||||||
|
static float colour_red[] = { 0.75, 0, 0, 1 };
|
||||||
|
static float colour_blue[] = { 0, 0, 0.75, 1 };
|
||||||
|
|
||||||
|
wlr_output_attach_render(wlr_output, NULL);
|
||||||
|
wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height);
|
||||||
|
|
||||||
|
render_rects(renderer, output, output == state->output_src ? colour_blue : colour_red);
|
||||||
|
|
||||||
|
wlr_output_render_software_cursors(wlr_output, NULL);
|
||||||
|
wlr_renderer_end(renderer);
|
||||||
|
wlr_output_commit(wlr_output);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_cursor_motion(struct wl_listener *listener, void *data) {
|
||||||
|
struct sample_state *state = wl_container_of(listener, state, cursor_motion);
|
||||||
|
struct wlr_event_pointer_motion *event = data;
|
||||||
|
wlr_cursor_move(state->cursor, event->device, event->delta_x, event->delta_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_keyboard_key(struct wl_listener *listener, void *data) {
|
||||||
|
struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, key);
|
||||||
|
struct sample_state *state = keyboard->state;
|
||||||
|
struct wlr_event_keyboard_key *event = data;
|
||||||
|
|
||||||
|
uint32_t keycode = event->keycode + 8;
|
||||||
|
const xkb_keysym_t *syms;
|
||||||
|
int nsyms = xkb_state_key_get_syms(keyboard->device->keyboard->xkb_state, keycode, &syms);
|
||||||
|
bool start_end_mirror = false;
|
||||||
|
enum wlr_mirror_v1_scale scale;
|
||||||
|
|
||||||
|
for (int i = 0; i < nsyms; i++) {
|
||||||
|
xkb_keysym_t sym = syms[i];
|
||||||
|
if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
|
||||||
|
switch (sym) {
|
||||||
|
case XKB_KEY_Escape:
|
||||||
|
wl_display_terminate(state->display);
|
||||||
|
break;
|
||||||
|
case XKB_KEY_f:
|
||||||
|
scale = WLR_MIRROR_V1_SCALE_FULL;
|
||||||
|
start_end_mirror = true;
|
||||||
|
break;
|
||||||
|
case XKB_KEY_a:
|
||||||
|
scale = WLR_MIRROR_V1_SCALE_ASPECT;
|
||||||
|
start_end_mirror = true;
|
||||||
|
break;
|
||||||
|
case XKB_KEY_c:
|
||||||
|
scale = WLR_MIRROR_V1_SCALE_CENTER;
|
||||||
|
start_end_mirror = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start_end_mirror) {
|
||||||
|
if (state->mirror) {
|
||||||
|
end_mirror(state);
|
||||||
|
} else {
|
||||||
|
start_mirror(state, scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_output_destroy(struct wl_listener *listener, void *data) {
|
||||||
|
struct sample_output *output = wl_container_of(listener, output, destroy);
|
||||||
|
struct sample_state *state = output->state;
|
||||||
|
|
||||||
|
wlr_log(WLR_DEBUG, "output destroyed '%s'", output->wlr_output->name);
|
||||||
|
|
||||||
|
wlr_output_layout_remove(state->layout, output->wlr_output);
|
||||||
|
wl_list_remove(&output->frame.link);
|
||||||
|
wl_list_remove(&output->destroy.link);
|
||||||
|
if (output == state->output_dst) {
|
||||||
|
state->output_dst = NULL;
|
||||||
|
} else if (output == state->output_src) {
|
||||||
|
state->output_src = NULL;
|
||||||
|
}
|
||||||
|
free(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_new_output(struct wl_listener *listener, void *data) {
|
||||||
|
struct wlr_output *wlr_output = data;
|
||||||
|
struct sample_state *state = wl_container_of(listener, state, new_output);
|
||||||
|
|
||||||
|
struct sample_output *output = calloc(1, sizeof(struct sample_output));
|
||||||
|
output->wlr_output = wlr_output;
|
||||||
|
output->state = state;
|
||||||
|
|
||||||
|
if (strcmp(wlr_output->name, state->src_name) == 0) {
|
||||||
|
wlr_log(WLR_DEBUG, "found src '%s'", wlr_output->name);
|
||||||
|
state->output_src = output;
|
||||||
|
} else if (strcmp(wlr_output->name, state->dst_name) == 0) {
|
||||||
|
wlr_log(WLR_DEBUG, "found dst '%s'", wlr_output->name);
|
||||||
|
state->output_dst = output;
|
||||||
|
} else {
|
||||||
|
free(output);
|
||||||
|
wlr_log(WLR_DEBUG, "ignoring extraneous output '%s'", wlr_output->name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wlr_output_init_render(wlr_output, state->allocator, state->renderer);
|
||||||
|
|
||||||
|
wl_signal_add(&wlr_output->events.destroy, &output->destroy);
|
||||||
|
output->destroy.notify = handle_output_destroy;
|
||||||
|
wlr_output_enable(wlr_output, true);
|
||||||
|
wlr_output_layout_add_auto(state->layout, output->wlr_output);
|
||||||
|
struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output);
|
||||||
|
if (mode != NULL) {
|
||||||
|
wlr_output_set_mode(wlr_output, mode);
|
||||||
|
}
|
||||||
|
wlr_xcursor_manager_load(state->xcursor_manager, wlr_output->scale);
|
||||||
|
wlr_xcursor_manager_set_cursor_image(state->xcursor_manager, "left_ptr", state->cursor);
|
||||||
|
|
||||||
|
// draw frames, stopping for dst when we start the mirror session
|
||||||
|
wl_signal_add(&wlr_output->events.frame, &output->frame);
|
||||||
|
output->frame.notify = handle_output_frame;
|
||||||
|
|
||||||
|
if (!wlr_output_commit(wlr_output)) {
|
||||||
|
wlr_log(WLR_ERROR, "Failed to setup output %s, exiting", wlr_output->name);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
wlr_output_transformed_resolution(wlr_output, &output->width, &output->height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_keyboard_destroy(struct wl_listener *listener, void *data) {
|
||||||
|
struct sample_keyboard *keyboard =
|
||||||
|
wl_container_of(listener, keyboard, destroy);
|
||||||
|
wl_list_remove(&keyboard->destroy.link);
|
||||||
|
wl_list_remove(&keyboard->key.link);
|
||||||
|
free(keyboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_new_input(struct wl_listener *listener, void *data) {
|
||||||
|
struct wlr_input_device *device = data;
|
||||||
|
struct sample_state *state = wl_container_of(listener, state, new_input);
|
||||||
|
switch (device->type) {
|
||||||
|
case WLR_INPUT_DEVICE_POINTER:
|
||||||
|
case WLR_INPUT_DEVICE_TOUCH:
|
||||||
|
case WLR_INPUT_DEVICE_TABLET_TOOL:
|
||||||
|
wlr_cursor_attach_input_device(state->cursor, device);
|
||||||
|
break;
|
||||||
|
case WLR_INPUT_DEVICE_KEYBOARD:
|
||||||
|
;
|
||||||
|
struct sample_keyboard *keyboard = calloc(1, sizeof(struct sample_keyboard));
|
||||||
|
keyboard->device = device;
|
||||||
|
keyboard->state = state;
|
||||||
|
wl_signal_add(&device->events.destroy, &keyboard->destroy);
|
||||||
|
keyboard->destroy.notify = handle_keyboard_destroy;
|
||||||
|
wl_signal_add(&device->keyboard->events.key, &keyboard->key);
|
||||||
|
keyboard->key.notify = handle_keyboard_key;
|
||||||
|
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||||
|
if (!context) {
|
||||||
|
wlr_log(WLR_ERROR, "Failed to create XKB context");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL,
|
||||||
|
XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||||
|
if (!keymap) {
|
||||||
|
wlr_log(WLR_ERROR, "Failed to create XKB keymap");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
wlr_keyboard_set_keymap(device->keyboard, keymap);
|
||||||
|
xkb_keymap_unref(keymap);
|
||||||
|
xkb_context_unref(context);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
if (argc != 3) {
|
||||||
|
fprintf(stderr, "%s", usage);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
wlr_log_init(WLR_DEBUG, NULL);
|
||||||
|
|
||||||
|
struct wl_display *display = wl_display_create();
|
||||||
|
struct sample_state state = {
|
||||||
|
.display = display,
|
||||||
|
.src_name = strdup(argv[1]),
|
||||||
|
.dst_name = strdup(argv[2]),
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wlr_backend *backend = wlr_backend_autocreate(display);
|
||||||
|
if (!backend) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.renderer = wlr_renderer_autocreate(backend);
|
||||||
|
state.allocator = wlr_allocator_autocreate(backend, state.renderer);
|
||||||
|
state.cursor = wlr_cursor_create();
|
||||||
|
state.layout = wlr_output_layout_create();
|
||||||
|
wlr_cursor_attach_output_layout(state.cursor, state.layout);
|
||||||
|
state.xcursor_manager = wlr_xcursor_manager_create("default", 24);
|
||||||
|
if (!state.xcursor_manager) {
|
||||||
|
wlr_log(WLR_ERROR, "Failed to load left_ptr cursor");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
wl_signal_add(&backend->events.new_output, &state.new_output);
|
||||||
|
state.new_output.notify = handle_new_output;
|
||||||
|
wl_signal_add(&backend->events.new_input, &state.new_input);
|
||||||
|
state.new_input.notify = handle_new_input;
|
||||||
|
wl_signal_add(&state.cursor->events.motion, &state.cursor_motion);
|
||||||
|
state.cursor_motion.notify = handle_cursor_motion;
|
||||||
|
|
||||||
|
if (!wlr_backend_start(backend)) {
|
||||||
|
wlr_log(WLR_ERROR, "Failed to start backend");
|
||||||
|
wlr_backend_destroy(backend);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.output_src) {
|
||||||
|
wlr_log(WLR_ERROR, "missing src %s, exiting", state.src_name);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (!state.output_dst) {
|
||||||
|
wlr_log(WLR_ERROR, "missing dst %s, exiting", state.dst_name);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// restrict cursor to src
|
||||||
|
wlr_cursor_warp_absolute(state.cursor, NULL, 1, 1);
|
||||||
|
wlr_cursor_map_to_output(state.cursor, state.output_src->wlr_output);
|
||||||
|
wlr_cursor_warp_absolute(state.cursor, NULL, 0, 0);
|
||||||
|
|
||||||
|
wl_display_run(display);
|
||||||
|
|
||||||
|
// stops and destroys the mirror
|
||||||
|
wl_display_destroy(display);
|
||||||
|
|
||||||
|
wlr_xcursor_manager_destroy(state.xcursor_manager);
|
||||||
|
wlr_cursor_destroy(state.cursor);
|
||||||
|
wlr_output_layout_destroy(state.layout);
|
||||||
|
|
||||||
|
free(state.src_name);
|
||||||
|
free(state.dst_name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -43,6 +43,11 @@ void wlr_matrix_rotate(float mat[static 9], float rad);
|
||||||
void wlr_matrix_transform(float mat[static 9],
|
void wlr_matrix_transform(float mat[static 9],
|
||||||
enum wl_output_transform transform);
|
enum wl_output_transform transform);
|
||||||
|
|
||||||
|
/** Writes a transformation matrix which applies the inverse of
|
||||||
|
* transform to mat, such that transform × inverse = identity */
|
||||||
|
void wlr_matrix_transform_inv(float mat[static 9],
|
||||||
|
enum wl_output_transform transform);
|
||||||
|
|
||||||
/** Writes a 2D orthographic projection matrix to mat of (width, height) with a
|
/** Writes a 2D orthographic projection matrix to mat of (width, height) with a
|
||||||
* specified wl_output_transform.
|
* specified wl_output_transform.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
123
include/wlr/types/wlr_mirror_v1.h
Normal file
123
include/wlr/types/wlr_mirror_v1.h
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* This an unstable interface of wlroots. No guarantees are made regarding the
|
||||||
|
* future consistency of this API.
|
||||||
|
*/
|
||||||
|
#ifndef WLR_USE_UNSTABLE
|
||||||
|
#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef WLR_TYPES_wlr_mirror_v1_V1_H
|
||||||
|
#define WLR_TYPES_wlr_mirror_v1_V1_H
|
||||||
|
|
||||||
|
#include <wayland-client-protocol.h>
|
||||||
|
#include <wayland-server-core.h>
|
||||||
|
#include <wlr/util/box.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.
|
||||||
|
*
|
||||||
|
* On output_srcs precommit, wlr_mirror_v1::ready is emitted. The compositor may
|
||||||
|
* call wlr_mirror_v1_request_ to request to render a frame on dst.
|
||||||
|
*
|
||||||
|
* Compositor must not render on dst for the duration of the session.
|
||||||
|
*
|
||||||
|
* Multiple sessions may run concurrently and one session may mirror another.
|
||||||
|
*
|
||||||
|
* Session will end:
|
||||||
|
* disable/destroy of dst or all srcs
|
||||||
|
* wlr_mirror_v1_request_box called with box outside of src
|
||||||
|
* wlr_mirror_v1_destroy
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum wlr_mirror_v1_scale {
|
||||||
|
/**
|
||||||
|
* src will be stretched to cover dst, distorting if necessary.
|
||||||
|
*/
|
||||||
|
WLR_MIRROR_V1_SCALE_FULL,
|
||||||
|
/**
|
||||||
|
* src will be stretched to the width or the height of dst, preserving the
|
||||||
|
* aspect ratio.
|
||||||
|
*/
|
||||||
|
WLR_MIRROR_V1_SCALE_ASPECT,
|
||||||
|
/**
|
||||||
|
* src will be rendered 1:1 at the center of dst. Content may be lost.
|
||||||
|
*/
|
||||||
|
WLR_MIRROR_V1_SCALE_CENTER,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable over session.
|
||||||
|
*/
|
||||||
|
struct wlr_mirror_v1_params {
|
||||||
|
|
||||||
|
enum wlr_mirror_v1_scale scale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the src cursor on dst.
|
||||||
|
*/
|
||||||
|
bool overlay_cursor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* srcs to send wlr_mirror_v1::events::ready
|
||||||
|
*/
|
||||||
|
struct wl_array output_srcs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dst, will have mirror_dst set for the duration of the session.
|
||||||
|
*/
|
||||||
|
struct wlr_output *output_dst;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wlr_mirror_v1_state;
|
||||||
|
struct wlr_mirror_v1 {
|
||||||
|
|
||||||
|
struct {
|
||||||
|
/**
|
||||||
|
* Ready to render a frame. Handler should call wlr_mirror_v1_request_
|
||||||
|
* Emitted at precommit, passes potential src.
|
||||||
|
*/
|
||||||
|
struct wl_signal ready;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mirror session is over.
|
||||||
|
*/
|
||||||
|
struct wl_signal destroy;
|
||||||
|
} events;
|
||||||
|
|
||||||
|
// private state
|
||||||
|
struct wlr_mirror_v1_state *state;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a mirror session.
|
||||||
|
*
|
||||||
|
* Compositor must stop rendering on dst immediately after this.
|
||||||
|
*/
|
||||||
|
struct wlr_mirror_v1 *wlr_mirror_v1_create(struct wlr_mirror_v1_params *params);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy a mirror session.
|
||||||
|
*
|
||||||
|
* Compositor may resume rendering on dst.
|
||||||
|
*/
|
||||||
|
void wlr_mirror_v1_destroy(struct wlr_mirror_v1 *mirror);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a blank frame on dst.
|
||||||
|
*
|
||||||
|
* Should be invoked during the wlr_mirror_v1::events::ready handler.
|
||||||
|
*/
|
||||||
|
void wlr_mirror_v1_request_blank(struct wlr_mirror_v1 *mirror);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a frame to render a box within src on dst. box is in output local
|
||||||
|
* coordinates, with respect to its transformation.
|
||||||
|
*
|
||||||
|
* Should be invoked during the wlr_mirror_v1::events::ready handler.
|
||||||
|
*/
|
||||||
|
void wlr_mirror_v1_request_box(struct wlr_mirror_v1 *mirror,
|
||||||
|
struct wlr_output *output_src, struct wlr_box box);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
@ -151,6 +151,9 @@ struct wlr_output {
|
||||||
// Commit sequence number. Incremented on each commit, may overflow.
|
// Commit sequence number. Incremented on each commit, may overflow.
|
||||||
uint32_t commit_seq;
|
uint32_t commit_seq;
|
||||||
|
|
||||||
|
// dst for an active wlr_mirror_v1 session
|
||||||
|
bool mirror_dst;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
// Request to render a frame
|
// Request to render a frame
|
||||||
struct wl_signal frame;
|
struct wl_signal frame;
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ wlr_files += files(
|
||||||
'wlr_layer_shell_v1.c',
|
'wlr_layer_shell_v1.c',
|
||||||
'wlr_linux_dmabuf_v1.c',
|
'wlr_linux_dmabuf_v1.c',
|
||||||
'wlr_matrix.c',
|
'wlr_matrix.c',
|
||||||
|
'wlr_mirror_v1.c',
|
||||||
'wlr_output_damage.c',
|
'wlr_output_damage.c',
|
||||||
'wlr_output_layout.c',
|
'wlr_output_layout.c',
|
||||||
'wlr_output_management_v1.c',
|
'wlr_output_management_v1.c',
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,33 @@ void wlr_matrix_transform(float mat[static 9],
|
||||||
wlr_matrix_multiply(mat, mat, transforms[transform]);
|
wlr_matrix_multiply(mat, mat, transforms[transform]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void wlr_matrix_transform_inv(float mat[static 9],
|
||||||
|
enum wl_output_transform transform) {
|
||||||
|
enum wl_output_transform inv;
|
||||||
|
|
||||||
|
switch (transform) {
|
||||||
|
case WL_OUTPUT_TRANSFORM_90:
|
||||||
|
inv = WL_OUTPUT_TRANSFORM_270;
|
||||||
|
break;
|
||||||
|
case WL_OUTPUT_TRANSFORM_180:
|
||||||
|
inv = WL_OUTPUT_TRANSFORM_180;
|
||||||
|
break;
|
||||||
|
case WL_OUTPUT_TRANSFORM_270:
|
||||||
|
inv = WL_OUTPUT_TRANSFORM_90;
|
||||||
|
break;
|
||||||
|
case WL_OUTPUT_TRANSFORM_NORMAL:
|
||||||
|
case WL_OUTPUT_TRANSFORM_FLIPPED:
|
||||||
|
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
|
||||||
|
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
|
||||||
|
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
|
||||||
|
default:
|
||||||
|
inv = transform;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
wlr_matrix_multiply(mat, mat, transforms[inv]);
|
||||||
|
}
|
||||||
|
|
||||||
// Equivalent to glOrtho(0, width, 0, height, 1, -1) with the transform applied
|
// Equivalent to glOrtho(0, width, 0, height, 1, -1) with the transform applied
|
||||||
void wlr_matrix_projection(float mat[static 9], int width, int height,
|
void wlr_matrix_projection(float mat[static 9], int width, int height,
|
||||||
enum wl_output_transform transform) {
|
enum wl_output_transform transform) {
|
||||||
|
|
|
||||||
590
types/wlr_mirror_v1.c
Normal file
590
types/wlr_mirror_v1.c
Normal file
|
|
@ -0,0 +1,590 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <wlr/render/wlr_renderer.h>
|
||||||
|
#include <wlr/render/wlr_texture.h>
|
||||||
|
#include <wlr/types/wlr_matrix.h>
|
||||||
|
#include <wlr/types/wlr_mirror_v1.h>
|
||||||
|
#include <wlr/types/wlr_output.h>
|
||||||
|
#include <wlr/util/log.h>
|
||||||
|
#include <util/signal.h>
|
||||||
|
|
||||||
|
struct wlr_mirror_v1_output_src {
|
||||||
|
struct wl_list link;
|
||||||
|
|
||||||
|
struct wlr_mirror_v1_state *state;
|
||||||
|
|
||||||
|
struct wlr_output *output;
|
||||||
|
|
||||||
|
struct wl_listener enable;
|
||||||
|
struct wl_listener commit;
|
||||||
|
struct wl_listener precommit;
|
||||||
|
struct wl_listener destroy;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wlr_mirror_v1_stats {
|
||||||
|
long requested_boxes;
|
||||||
|
long rendered_boxes;
|
||||||
|
|
||||||
|
long requested_blanks;
|
||||||
|
long rendered_blanks;
|
||||||
|
|
||||||
|
long frames_dropped;
|
||||||
|
long buffers_incomplete;
|
||||||
|
long dmabufs_unavailable;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All immutable during session, except noted.
|
||||||
|
*/
|
||||||
|
struct wlr_mirror_v1_state {
|
||||||
|
struct wlr_mirror_v1 *mirror;
|
||||||
|
|
||||||
|
struct wlr_mirror_v1_params params;
|
||||||
|
|
||||||
|
struct wlr_output *output_src; // mutable
|
||||||
|
struct wlr_output *output_dst;
|
||||||
|
|
||||||
|
struct wl_list m_output_srcs; // wlr_mirror_v1_output_src::link
|
||||||
|
|
||||||
|
struct wlr_texture *texture; // mutable
|
||||||
|
struct wlr_box box_src; // mutable
|
||||||
|
bool needs_blank; // mutable
|
||||||
|
bool cursor_locked; // mutable
|
||||||
|
|
||||||
|
// events (ready) may result in a call to wlr_mirror_v1_destroy.
|
||||||
|
// During emission, wlr_mirror_v1_destroy will not free mirror (specifically
|
||||||
|
// the wl_signal) and state.
|
||||||
|
// mirror and state will be free'd after wlr_signal_emit_safe is complete
|
||||||
|
// and has cleaned up the signal's list.
|
||||||
|
bool signal_emitting, needs_state_mirror_free;
|
||||||
|
|
||||||
|
struct wl_listener output_dst_enable;
|
||||||
|
struct wl_listener output_dst_frame;
|
||||||
|
struct wl_listener output_dst_destroy;
|
||||||
|
|
||||||
|
struct wlr_mirror_v1_stats stats;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BEGIN helper functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swaps v, h depending on rotation of the transform.
|
||||||
|
*/
|
||||||
|
static void rotate_v_h(int32_t *v_rotated, int32_t *h_rotated,
|
||||||
|
enum wl_output_transform transform, int32_t v, int32_t h) {
|
||||||
|
if (transform % 2 == 0) {
|
||||||
|
*v_rotated = v;
|
||||||
|
*h_rotated = h;
|
||||||
|
} else {
|
||||||
|
*v_rotated = h;
|
||||||
|
*h_rotated = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a box with absolute coordinates inside a (0, 0, width, height)
|
||||||
|
* box without rotating or translating it.
|
||||||
|
*/
|
||||||
|
static void calculate_absolute_box(struct wlr_box *absolute,
|
||||||
|
struct wlr_box *relative, enum wl_output_transform transform,
|
||||||
|
int32_t width, int32_t height) {
|
||||||
|
|
||||||
|
rotate_v_h(&absolute->x, &absolute->y, transform, relative->x, relative->y);
|
||||||
|
rotate_v_h(&absolute->width, &absolute->height, transform, relative->width, relative->height);
|
||||||
|
|
||||||
|
switch (transform) {
|
||||||
|
case WL_OUTPUT_TRANSFORM_180:
|
||||||
|
case WL_OUTPUT_TRANSFORM_270:
|
||||||
|
case WL_OUTPUT_TRANSFORM_FLIPPED:
|
||||||
|
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
|
||||||
|
absolute->x = width - absolute->width - absolute->x;
|
||||||
|
break;
|
||||||
|
case WL_OUTPUT_TRANSFORM_NORMAL:
|
||||||
|
case WL_OUTPUT_TRANSFORM_90:
|
||||||
|
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
|
||||||
|
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (transform) {
|
||||||
|
case WL_OUTPUT_TRANSFORM_90:
|
||||||
|
case WL_OUTPUT_TRANSFORM_180:
|
||||||
|
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
|
||||||
|
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
|
||||||
|
absolute->y = height - absolute->height - absolute->y;
|
||||||
|
break;
|
||||||
|
case WL_OUTPUT_TRANSFORM_NORMAL:
|
||||||
|
case WL_OUTPUT_TRANSFORM_270:
|
||||||
|
case WL_OUTPUT_TRANSFORM_FLIPPED:
|
||||||
|
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Position a box according to the scale method. It will be rotated from src
|
||||||
|
* to dst.
|
||||||
|
*/
|
||||||
|
static void calculate_dst_box(struct wlr_fbox *box_dst,
|
||||||
|
enum wlr_mirror_v1_scale scale_method,
|
||||||
|
enum wl_output_transform transform_src,
|
||||||
|
enum wl_output_transform transform_dst,
|
||||||
|
int32_t width_src, int32_t height_src,
|
||||||
|
int32_t width_dst, int32_t height_dst) {
|
||||||
|
|
||||||
|
double width_scaled, height_scaled;
|
||||||
|
int32_t width_src_rotated, height_src_rotated;
|
||||||
|
int32_t width_dst_rotated, height_dst_rotated;
|
||||||
|
|
||||||
|
bool src_rotated = transform_src % 2 != 0;
|
||||||
|
|
||||||
|
rotate_v_h(&width_src_rotated, &height_src_rotated, transform_src, width_src, height_src);
|
||||||
|
rotate_v_h(&width_dst_rotated, &height_dst_rotated, transform_dst, width_dst, height_dst);
|
||||||
|
|
||||||
|
switch (scale_method) {
|
||||||
|
case WLR_MIRROR_V1_SCALE_CENTER:
|
||||||
|
box_dst->width = width_src;
|
||||||
|
box_dst->height = height_src;
|
||||||
|
box_dst->x = (((float) width_dst_rotated) - width_src_rotated) / 2;
|
||||||
|
box_dst->y = (((float) height_dst_rotated) - height_src_rotated) / 2;
|
||||||
|
break;
|
||||||
|
case WLR_MIRROR_V1_SCALE_ASPECT:
|
||||||
|
if (width_dst_rotated * height_src_rotated > height_dst_rotated * width_src_rotated) {
|
||||||
|
// expand to dst height
|
||||||
|
width_scaled = ((float) width_src_rotated) * height_dst_rotated / height_src_rotated;
|
||||||
|
height_scaled = height_dst_rotated;
|
||||||
|
} else {
|
||||||
|
// expand to dst width
|
||||||
|
width_scaled = width_dst_rotated;
|
||||||
|
height_scaled = ((float) height_src_rotated) * width_dst_rotated / width_src_rotated;
|
||||||
|
}
|
||||||
|
if (src_rotated) {
|
||||||
|
box_dst->width = height_scaled;
|
||||||
|
box_dst->height = width_scaled;
|
||||||
|
} else {
|
||||||
|
box_dst->width = width_scaled;
|
||||||
|
box_dst->height = height_scaled;
|
||||||
|
}
|
||||||
|
box_dst->x = (((float) width_dst_rotated) - width_scaled) / 2;
|
||||||
|
box_dst->y = (((float) height_dst_rotated) - height_scaled) / 2;
|
||||||
|
break;
|
||||||
|
case WLR_MIRROR_V1_SCALE_FULL:
|
||||||
|
default:
|
||||||
|
if (src_rotated) {
|
||||||
|
box_dst->width = height_dst_rotated;
|
||||||
|
box_dst->height = width_dst_rotated;
|
||||||
|
} else {
|
||||||
|
box_dst->width = width_dst_rotated;
|
||||||
|
box_dst->height = height_dst_rotated;
|
||||||
|
}
|
||||||
|
box_dst->x = 0;
|
||||||
|
box_dst->y = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce a transformation matrix that rotates/translates a box to the
|
||||||
|
* dst.
|
||||||
|
*/
|
||||||
|
static void calculate_render_matrix(float mat[static 9], struct wlr_fbox *box_dst,
|
||||||
|
struct wlr_output *output_src, struct wlr_output *output_dst) {
|
||||||
|
|
||||||
|
// position at the dst
|
||||||
|
wlr_matrix_identity(mat);
|
||||||
|
wlr_matrix_translate(mat, box_dst->x, box_dst->y);
|
||||||
|
|
||||||
|
// un-rotate and transform from src
|
||||||
|
if (output_src->transform % 2 == 0) {
|
||||||
|
wlr_matrix_translate(mat, box_dst->width / 2.0, box_dst->height / 2.0);
|
||||||
|
} else {
|
||||||
|
wlr_matrix_translate(mat, box_dst->height / 2.0, box_dst->width / 2.0);
|
||||||
|
}
|
||||||
|
wlr_matrix_transform_inv(mat, output_src->transform);
|
||||||
|
wlr_matrix_translate(mat, - box_dst->width / 2.0, - box_dst->height / 2.0);
|
||||||
|
|
||||||
|
// scale to the dst
|
||||||
|
wlr_matrix_scale(mat, box_dst->width, box_dst->height);
|
||||||
|
|
||||||
|
// apply the dst transform
|
||||||
|
wlr_matrix_multiply(mat, output_dst->transform_matrix, mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void schedule_frame_dst(struct wlr_mirror_v1_state *state) {
|
||||||
|
|
||||||
|
wlr_output_schedule_frame(state->output_dst);
|
||||||
|
|
||||||
|
wl_list_remove(&state->output_dst_frame.link);
|
||||||
|
wl_signal_add(&state->output_dst->events.frame, &state->output_dst_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all listeners for a src and remove it from state::m_output_srcs
|
||||||
|
* Invoke wlr_mirror_v1_destroy if no other srcs remain.
|
||||||
|
*/
|
||||||
|
static void remove_output_src(struct wlr_mirror_v1_output_src *src) {
|
||||||
|
struct wlr_mirror_v1_state *state = src->state;
|
||||||
|
|
||||||
|
wl_list_remove(&src->commit.link);
|
||||||
|
wl_list_remove(&src->enable.link);
|
||||||
|
wl_list_remove(&src->precommit.link);
|
||||||
|
wl_list_remove(&src->destroy.link);
|
||||||
|
wl_list_remove(&src->link);
|
||||||
|
free(src);
|
||||||
|
|
||||||
|
if (wl_list_length(&state->m_output_srcs) == 0) {
|
||||||
|
wlr_mirror_v1_destroy(state->mirror);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* END helper functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BEGIN wlr_mirror handler functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void output_src_handle_precommit(struct wl_listener *listener, void *data) {
|
||||||
|
struct wlr_mirror_v1_output_src *m_output_src =
|
||||||
|
wl_container_of(listener, m_output_src, precommit);
|
||||||
|
struct wlr_mirror_v1_state *state = m_output_src->state;
|
||||||
|
struct wlr_mirror_v1 *mirror = state->mirror;
|
||||||
|
|
||||||
|
state->signal_emitting = true;
|
||||||
|
wlr_signal_emit_safe(&mirror->events.ready, m_output_src->output);
|
||||||
|
state->signal_emitting = false;
|
||||||
|
if (state->needs_state_mirror_free) {
|
||||||
|
free(state);
|
||||||
|
free(mirror);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void output_src_handle_commit(struct wl_listener *listener, void *data) {
|
||||||
|
struct wlr_mirror_v1_output_src *m_output_src = wl_container_of(listener, m_output_src, commit);
|
||||||
|
struct wlr_mirror_v1_state *state = m_output_src->state;
|
||||||
|
struct wlr_output *output_src = m_output_src->output;
|
||||||
|
struct wlr_output_event_commit *event = data;
|
||||||
|
|
||||||
|
state->output_src = output_src;
|
||||||
|
|
||||||
|
wl_list_remove(&m_output_src->commit.link);
|
||||||
|
wl_list_init(&m_output_src->commit.link);
|
||||||
|
|
||||||
|
if (state->texture) {
|
||||||
|
state->stats.frames_dropped++;
|
||||||
|
wlr_texture_destroy(state->texture);
|
||||||
|
state->texture = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(event->committed & WLR_OUTPUT_STATE_BUFFER)) {
|
||||||
|
state->stats.buffers_incomplete++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->params.overlay_cursor) {
|
||||||
|
wlr_output_lock_software_cursors(output_src, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wlr_dmabuf_attributes attribs = {0};
|
||||||
|
|
||||||
|
wlr_output_lock_attach_render(output_src, true);
|
||||||
|
|
||||||
|
if (wlr_buffer_get_dmabuf(event->buffer, &attribs)) {
|
||||||
|
state->texture = wlr_texture_from_dmabuf(output_src->renderer, &attribs);
|
||||||
|
schedule_frame_dst(state);
|
||||||
|
} else {
|
||||||
|
state->stats.dmabufs_unavailable++;
|
||||||
|
}
|
||||||
|
|
||||||
|
wlr_output_lock_attach_render(output_src, false);
|
||||||
|
|
||||||
|
if (state->params.overlay_cursor) {
|
||||||
|
wlr_output_lock_software_cursors(output_src, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void output_dst_handle_frame(struct wl_listener *listener, void *data) {
|
||||||
|
struct wlr_mirror_v1_state *state = wl_container_of(listener, state, output_dst_frame);
|
||||||
|
|
||||||
|
wl_list_remove(&state->output_dst_frame.link);
|
||||||
|
wl_list_init(&state->output_dst_frame.link);
|
||||||
|
|
||||||
|
struct wlr_output *output_dst = state->output_dst;
|
||||||
|
struct wlr_output *output_src = state->output_src;
|
||||||
|
|
||||||
|
wlr_output_attach_render(output_dst, NULL);
|
||||||
|
|
||||||
|
wlr_renderer_begin(output_dst->renderer, output_dst->width, output_dst->height);
|
||||||
|
|
||||||
|
static float col_blank[] = { 0, 0, 0, 1 };
|
||||||
|
wlr_renderer_clear(output_dst->renderer, col_blank);
|
||||||
|
|
||||||
|
if (state->needs_blank) {
|
||||||
|
state->stats.rendered_blanks++;
|
||||||
|
|
||||||
|
state->needs_blank = false;
|
||||||
|
|
||||||
|
} else if (output_src && state->texture) {
|
||||||
|
state->stats.rendered_boxes++;
|
||||||
|
|
||||||
|
// tranform src box to real coordinates for the src
|
||||||
|
struct wlr_box box_src;
|
||||||
|
calculate_absolute_box(&box_src, &state->box_src, output_src->transform,
|
||||||
|
output_src->width, output_src->height);
|
||||||
|
|
||||||
|
// scale and position a box for the dst
|
||||||
|
struct wlr_fbox fbox_dst = {0};
|
||||||
|
calculate_dst_box(&fbox_dst, state->params.scale,
|
||||||
|
output_src->transform, output_dst->transform,
|
||||||
|
box_src.width, box_src.height,
|
||||||
|
output_dst->width, output_dst->height);
|
||||||
|
|
||||||
|
// transform to dst
|
||||||
|
float mat[9];
|
||||||
|
calculate_render_matrix(mat, &fbox_dst, output_src, output_dst);
|
||||||
|
|
||||||
|
// render the subtexture
|
||||||
|
struct wlr_fbox fbox_sub = {
|
||||||
|
.x = box_src.x,
|
||||||
|
.y = box_src.y,
|
||||||
|
.width = box_src.width,
|
||||||
|
.height = box_src.height,
|
||||||
|
};
|
||||||
|
wlr_render_subtexture_with_matrix(output_dst->renderer, state->texture, &fbox_sub, mat, 1.0f);
|
||||||
|
|
||||||
|
wlr_texture_destroy(state->texture);
|
||||||
|
state->texture = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
wlr_renderer_end(output_dst->renderer);
|
||||||
|
wlr_output_commit(output_dst);
|
||||||
|
|
||||||
|
state->output_src = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void output_src_handle_enable(struct wl_listener *listener, void *data) {
|
||||||
|
struct wlr_mirror_v1_output_src *src = wl_container_of(listener, src, enable);
|
||||||
|
|
||||||
|
if (!src->output->enabled) {
|
||||||
|
wlr_log(WLR_DEBUG, "Mirror src '%s' disabled", src->output->name);
|
||||||
|
remove_output_src(src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void output_src_handle_destroy(struct wl_listener *listener, void *data) {
|
||||||
|
struct wlr_mirror_v1_output_src *src = wl_container_of(listener, src, destroy);
|
||||||
|
|
||||||
|
wlr_log(WLR_DEBUG, "Mirror src '%s' destroyed", src->output->name);
|
||||||
|
|
||||||
|
remove_output_src(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void output_dst_handle_enable(struct wl_listener *listener, void *data) {
|
||||||
|
struct wlr_mirror_v1_state *state = wl_container_of(listener, state, output_dst_enable);
|
||||||
|
struct wlr_mirror_v1 *mirror = state->mirror;
|
||||||
|
|
||||||
|
if (!state->output_dst->enabled) {
|
||||||
|
wlr_log(WLR_DEBUG, "Mirror dst '%s' disabled", state->output_dst->name);
|
||||||
|
wlr_mirror_v1_destroy(mirror);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void output_dst_handle_destroy(struct wl_listener *listener, void *data) {
|
||||||
|
struct wlr_mirror_v1_state *state = wl_container_of(listener, state, output_dst_destroy);
|
||||||
|
struct wlr_mirror_v1 *mirror = state->mirror;
|
||||||
|
|
||||||
|
wlr_log(WLR_DEBUG, "Mirror dst '%s' destroyed", state->output_dst->name);
|
||||||
|
|
||||||
|
wlr_mirror_v1_destroy(mirror);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* END wlr_mirror handler functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BEGIN public functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct wlr_mirror_v1 *wlr_mirror_v1_create(struct wlr_mirror_v1_params *params) {
|
||||||
|
if (!params->output_dst->enabled) {
|
||||||
|
wlr_log(WLR_ERROR, "Mirror dst '%s' not enabled", params->output_dst->name);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (params->output_dst->mirror_dst) {
|
||||||
|
wlr_log(WLR_ERROR, "Mirror dst '%s' in use by another mirror session",
|
||||||
|
params->output_dst->name);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
struct wlr_output **output_src_ptr;
|
||||||
|
wl_array_for_each(output_src_ptr, ¶ms->output_srcs) {
|
||||||
|
if (!(*output_src_ptr)->enabled) {
|
||||||
|
wlr_log(WLR_ERROR, "Mirror src '%s' not enabled", (*output_src_ptr)->name);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wlr_mirror_v1 *mirror = calloc(1, sizeof(struct wlr_mirror_v1));
|
||||||
|
mirror->state = calloc(1, sizeof(struct wlr_mirror_v1_state));
|
||||||
|
struct wlr_mirror_v1_state *state = mirror->state;
|
||||||
|
state->mirror = mirror;
|
||||||
|
state->output_dst = params->output_dst;
|
||||||
|
|
||||||
|
wl_list_init(&state->m_output_srcs);
|
||||||
|
wl_signal_init(&mirror->events.ready);
|
||||||
|
wl_signal_init(&mirror->events.destroy);
|
||||||
|
|
||||||
|
// clone params
|
||||||
|
memcpy(&state->params, params, sizeof(struct wlr_mirror_v1_params));
|
||||||
|
wl_array_init(&state->params.output_srcs);
|
||||||
|
wl_array_copy(&state->params.output_srcs, ¶ms->output_srcs);
|
||||||
|
|
||||||
|
// dst events
|
||||||
|
wl_list_init(&state->output_dst_frame.link);
|
||||||
|
state->output_dst_frame.notify = output_dst_handle_frame;
|
||||||
|
|
||||||
|
wl_list_init(&state->output_dst_enable.link);
|
||||||
|
wl_signal_add(&state->output_dst->events.enable, &state->output_dst_enable);
|
||||||
|
state->output_dst_enable.notify = output_dst_handle_enable;
|
||||||
|
|
||||||
|
wl_list_init(&state->output_dst_destroy.link);
|
||||||
|
wl_signal_add(&state->output_dst->events.destroy, &state->output_dst_destroy);
|
||||||
|
state->output_dst_destroy.notify = output_dst_handle_destroy;
|
||||||
|
|
||||||
|
wlr_log(WLR_DEBUG, "Mirror creating dst '%s'", state->output_dst->name);
|
||||||
|
|
||||||
|
// srcs events
|
||||||
|
wl_array_for_each(output_src_ptr, &state->params.output_srcs) {
|
||||||
|
struct wlr_mirror_v1_output_src *m_output_src =
|
||||||
|
calloc(1, sizeof(struct wlr_mirror_v1_output_src));
|
||||||
|
wl_list_insert(state->m_output_srcs.prev, &m_output_src->link);
|
||||||
|
|
||||||
|
m_output_src->state = state;
|
||||||
|
m_output_src->output = *output_src_ptr;
|
||||||
|
|
||||||
|
wl_list_init(&m_output_src->commit.link);
|
||||||
|
|
||||||
|
wl_list_init(&m_output_src->enable.link);
|
||||||
|
wl_signal_add(&m_output_src->output->events.enable, &m_output_src->enable);
|
||||||
|
m_output_src->enable.notify = output_src_handle_enable;
|
||||||
|
|
||||||
|
wl_list_init(&m_output_src->precommit.link);
|
||||||
|
wl_signal_add(&m_output_src->output->events.precommit, &m_output_src->precommit);
|
||||||
|
m_output_src->precommit.notify = output_src_handle_precommit;
|
||||||
|
|
||||||
|
wl_list_init(&m_output_src->destroy.link);
|
||||||
|
wl_signal_add(&m_output_src->output->events.destroy, &m_output_src->destroy);
|
||||||
|
m_output_src->destroy.notify = output_src_handle_destroy;
|
||||||
|
|
||||||
|
wlr_log(WLR_DEBUG, " src '%s'", m_output_src->output->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// blank initially, in case compositor delays requests
|
||||||
|
state->needs_blank = true;
|
||||||
|
schedule_frame_dst(state);
|
||||||
|
|
||||||
|
state->output_dst->mirror_dst = true;
|
||||||
|
|
||||||
|
return mirror;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wlr_mirror_v1_destroy(struct wlr_mirror_v1 *mirror) {
|
||||||
|
if (!mirror) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
struct wlr_mirror_v1_state *state = mirror->state;
|
||||||
|
|
||||||
|
wlr_log(WLR_DEBUG, "Mirror destroying dst '%s': "
|
||||||
|
"requested_boxes:%ld, rendered_boxes:%ld, "
|
||||||
|
"requested_blanks:%ld, rendered_blanks:%ld, "
|
||||||
|
"frames_dropped:%ld, buffers_incomplete:%ld, "
|
||||||
|
"dmabufs_unavailable:%ld",
|
||||||
|
state->output_dst->name,
|
||||||
|
state->stats.requested_boxes, state->stats.rendered_boxes,
|
||||||
|
state->stats.requested_blanks, state->stats.rendered_blanks,
|
||||||
|
state->stats.frames_dropped, state->stats.buffers_incomplete,
|
||||||
|
state->stats.dmabufs_unavailable);
|
||||||
|
|
||||||
|
// dst output events
|
||||||
|
wl_list_remove(&state->output_dst_enable.link);
|
||||||
|
wl_list_remove(&state->output_dst_frame.link);
|
||||||
|
wl_list_remove(&state->output_dst_destroy.link);
|
||||||
|
|
||||||
|
// all src output events
|
||||||
|
struct wlr_mirror_v1_output_src *src, *next;
|
||||||
|
wl_list_for_each_safe(src, next, &state->m_output_srcs, link) {
|
||||||
|
wl_list_remove(&src->commit.link);
|
||||||
|
wl_list_remove(&src->enable.link);
|
||||||
|
wl_list_remove(&src->precommit.link);
|
||||||
|
wl_list_remove(&src->destroy.link);
|
||||||
|
wl_list_remove(&src->link);
|
||||||
|
free(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
// destroy any frames in flight
|
||||||
|
if (state->texture) {
|
||||||
|
wlr_texture_destroy(state->texture);
|
||||||
|
state->texture = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the compositor may reclaim dst
|
||||||
|
state->output_dst->mirror_dst = false;
|
||||||
|
|
||||||
|
// end the user's mirror "session"
|
||||||
|
wlr_signal_emit_safe(&mirror->events.destroy, mirror);
|
||||||
|
|
||||||
|
wl_array_release(&state->params.output_srcs);
|
||||||
|
if (state->signal_emitting) {
|
||||||
|
state->needs_state_mirror_free = true;
|
||||||
|
} else {
|
||||||
|
free(state);
|
||||||
|
free(mirror);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void wlr_mirror_v1_request_blank(struct wlr_mirror_v1 *mirror) {
|
||||||
|
struct wlr_mirror_v1_state *state = mirror->state;
|
||||||
|
|
||||||
|
state->needs_blank = true;
|
||||||
|
|
||||||
|
schedule_frame_dst(state);
|
||||||
|
|
||||||
|
mirror->state->stats.requested_blanks++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wlr_mirror_v1_request_box(struct wlr_mirror_v1 *mirror,
|
||||||
|
struct wlr_output *output_src, struct wlr_box box) {
|
||||||
|
struct wlr_mirror_v1_state *state = mirror->state;
|
||||||
|
|
||||||
|
state->needs_blank = false;
|
||||||
|
|
||||||
|
// restrict the box to the src
|
||||||
|
struct wlr_box box_output = { 0 };
|
||||||
|
wlr_output_transformed_resolution(output_src, &box_output.width, &box_output.height);
|
||||||
|
if (!wlr_box_intersection(&state->box_src, &box_output, &box)) {
|
||||||
|
wlr_log(WLR_ERROR, "Mirror box not within src, ending session.");
|
||||||
|
wlr_mirror_v1_destroy(mirror);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// listen for a commit on the specified output only
|
||||||
|
struct wlr_mirror_v1_output_src *m_output_src;
|
||||||
|
wl_list_for_each(m_output_src, &state->m_output_srcs, link) {
|
||||||
|
if (m_output_src->output == output_src) {
|
||||||
|
wl_list_remove(&m_output_src->commit.link);
|
||||||
|
wl_signal_add(&m_output_src->output->events.commit, &m_output_src->commit);
|
||||||
|
m_output_src->commit.notify = output_src_handle_commit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state->stats.requested_boxes++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* END public functions
|
||||||
|
*/
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue