diff --git a/examples/meson.build b/examples/meson.build index 51e7d3117..169902841 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -57,6 +57,9 @@ compositors = { 'src': 'scene-graph.c', 'proto': ['xdg-shell'], }, + 'mirror': { + 'src': 'mirror.c', + }, } clients = { diff --git a/examples/mirror.c b/examples/mirror.c new file mode 100644 index 000000000..0eb9d55f0 --- /dev/null +++ b/examples/mirror.c @@ -0,0 +1,501 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Demonstrates wlr_mirror. 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 \n" +" e.g. mirror eDP-1 HDMI-A-1\n" +"keys:\n" +" m: toggle mirroring\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 *wlr_mirror; + + struct wlr_mirror_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); +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) { + 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.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 *wlr_mirror = wlr_mirror_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::events::destroy + wlr_mirror_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 *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_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); + + 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_m: + if (state->mirror) { + end_mirror(state); + } else { + start_mirror(state); + } + break; + default: + break; + } + } + } +} + +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; +} + diff --git a/include/wlr/types/wlr_mirror.h b/include/wlr/types/wlr_mirror.h new file mode 100644 index 000000000..31f5136f3 --- /dev/null +++ b/include/wlr/types/wlr_mirror.h @@ -0,0 +1,111 @@ +/* + * 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_H +#define WLR_TYPES_WLR_MIRROR_H + +#include +#include +#include +#include + +/** + * 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. + * + * Content will be scaled to fit the width or height of dst. + * + * On output_srcs precommit, wlr_mirror::ready is emitted. The compositor may + * call wlr_mirror_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_request_box called with box outside of src + * wlr_mirror_destroy + */ + +/** + * Immutable over session. + */ +struct wlr_mirror_params { + /** + * Render the src cursor on dst. + */ + bool overlay_cursor; + + /** + * srcs to send wlr_mirror::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_state; +struct wlr_mirror { + struct { + /** + * Ready to render a frame. Handler should call wlr_mirror_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_state *state; +}; + +/** + * Create a mirror session. + * + * Compositor must stop rendering on dst immediately after this. + */ +struct wlr_mirror *wlr_mirror_create(struct wlr_mirror_params *params); + +/** + * Destroy a mirror session. + * + * Compositor may resume rendering on dst. + */ +void wlr_mirror_destroy(struct wlr_mirror *mirror); + +/** + * Request a blank frame on dst. + * + * Should be invoked during the wlr_mirror::events::ready handler. + */ +void wlr_mirror_request_blank(struct wlr_mirror *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::events::ready handler. + */ +void wlr_mirror_request_box(struct wlr_mirror *mirror, + struct wlr_output *output_src, struct wlr_box box); + +/** + * Output is in use as a dst by another mirror session. + */ +bool wlr_mirror_v1_output_is_dst(struct wlr_output *output); + +#endif + diff --git a/include/wlr/util/addon.h b/include/wlr/util/addon.h index c64200cf9..608174820 100644 --- a/include/wlr/util/addon.h +++ b/include/wlr/util/addon.h @@ -41,4 +41,7 @@ void wlr_addon_finish(struct wlr_addon *addon); struct wlr_addon *wlr_addon_find(struct wlr_addon_set *set, const void *owner, const struct wlr_addon_interface *impl); +void wlr_addon_find_all(struct wl_array *all, struct wlr_addon_set *set, + const struct wlr_addon_interface *impl); + #endif diff --git a/types/meson.build b/types/meson.build index bcf1073b4..f8a49f545 100644 --- a/types/meson.build +++ b/types/meson.build @@ -55,6 +55,7 @@ wlr_files += files( 'wlr_layer_shell_v1.c', 'wlr_linux_dmabuf_v1.c', 'wlr_matrix.c', + 'wlr_mirror.c', 'wlr_output_damage.c', 'wlr_output_layout.c', 'wlr_output_management_v1.c', diff --git a/types/wlr_mirror.c b/types/wlr_mirror.c new file mode 100644 index 000000000..149e7ef4d --- /dev/null +++ b/types/wlr_mirror.c @@ -0,0 +1,538 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct wlr_mirror_output_src { + struct wl_list link; + + struct wlr_mirror_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_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_state { + struct wlr_mirror *mirror; + + struct wlr_mirror_params params; + + struct wlr_addon output_dst_addon; + + struct wlr_output *output_src; // mutable + struct wlr_output *output_dst; + + struct wl_list m_output_srcs; // wlr_mirror_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_destroy. + // During emission, wlr_mirror_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 wlr_mirror_stats stats; +}; + +/** + * 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 scaled to fit the width or height of dst. It will be rotated + * from src to dst. + */ +static void calculate_dst_box(struct wlr_box *box_dst, + 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); + + if (width_dst_rotated * height_src_rotated > height_dst_rotated * width_src_rotated) { + // expand to dst height + width_scaled = ((double) 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 = ((double) height_src_rotated) * width_dst_rotated / width_src_rotated; + } + if (src_rotated) { + box_dst->width = round(height_scaled); + box_dst->height = round(width_scaled); + } else { + box_dst->width = round(width_scaled); + box_dst->height = round(height_scaled); + } + box_dst->x = round((((double) width_dst_rotated) - width_scaled) / 2); + box_dst->y = round((((double) height_dst_rotated) - height_scaled) / 2); +} + +/** + * Produce a transformation matrix that un-transforms from src and transforms to dst. + */ +static void calculate_render_matrix(float mat[static 9], struct wlr_box *box_dst, + enum wl_output_transform transform_src, float transform_matrix_dst[static 9]) { + // account for the rotated dimensions of dst + struct wlr_box box_rotated = *box_dst; + rotate_v_h(&box_rotated.width, &box_rotated.height, transform_src, + box_rotated.width, box_rotated.height); + + // both transforms + wlr_matrix_project_box(mat, &box_rotated, wlr_output_transform_invert(transform_src), + 0.0, transform_matrix_dst); +} + +static void schedule_frame_dst(struct wlr_mirror_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_destroy if no other srcs remain. + */ +static void remove_output_src(struct wlr_mirror_output_src *src) { + struct wlr_mirror_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_destroy(state->mirror); + } +} + +static void output_src_handle_precommit(struct wl_listener *listener, void *data) { + struct wlr_mirror_output_src *m_output_src = + wl_container_of(listener, m_output_src, precommit); + struct wlr_mirror_state *state = m_output_src->state; + struct wlr_mirror *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_output_src *m_output_src = wl_container_of(listener, m_output_src, commit); + struct wlr_mirror_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_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_box box_dst = {0}; + calculate_dst_box(&box_dst, + output_src->transform, output_dst->transform, + box_src.width, box_src.height, + output_dst->width, output_dst->height); + + // transform from src to dst + float mat[9]; + calculate_render_matrix(mat, &box_dst, output_src->transform, output_dst->transform_matrix); + + // 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_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_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_state *state = wl_container_of(listener, state, output_dst_enable); + struct wlr_mirror *mirror = state->mirror; + + if (!state->output_dst->enabled) { + wlr_log(WLR_DEBUG, "Mirror dst '%s' disabled", state->output_dst->name); + wlr_mirror_destroy(mirror); + } +} + +static void output_dst_addon_handle_destroy(struct wlr_addon *addon) { + struct wlr_mirror_state *state = wl_container_of(addon, state, output_dst_addon); + struct wlr_mirror *mirror = state->mirror; + + wlr_log(WLR_DEBUG, "Mirror dst '%s' destroyed", state->output_dst->name); + + wlr_mirror_destroy(mirror); +} + +static const struct wlr_addon_interface output_dst_addon_impl = { + .name = "wlr_mirror_output_dst", + .destroy = output_dst_addon_handle_destroy, +}; + +struct wlr_mirror *wlr_mirror_create(struct wlr_mirror_params *params) { + if (!params->output_dst->enabled) { + wlr_log(WLR_ERROR, "Mirror dst '%s' not enabled", params->output_dst->name); + return NULL; + } + if (wlr_mirror_v1_output_is_dst(params->output_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 *mirror = calloc(1, sizeof(struct wlr_mirror)); + mirror->state = calloc(1, sizeof(struct wlr_mirror_state)); + struct wlr_mirror_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_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; + + 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_output_src *m_output_src = + calloc(1, sizeof(struct wlr_mirror_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); + + wlr_addon_init(&state->output_dst_addon, &state->output_dst->addons, mirror, + &output_dst_addon_impl); + + return mirror; +} + +void wlr_mirror_destroy(struct wlr_mirror *mirror) { + if (!mirror) { + return; + } + struct wlr_mirror_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); + + // all src output events + struct wlr_mirror_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 + wlr_addon_finish(&state->output_dst_addon); + + // 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_request_blank(struct wlr_mirror *mirror) { + struct wlr_mirror_state *state = mirror->state; + + state->needs_blank = true; + + schedule_frame_dst(state); + + mirror->state->stats.requested_blanks++; +} + +void wlr_mirror_request_box(struct wlr_mirror *mirror, + struct wlr_output *output_src, struct wlr_box box) { + struct wlr_mirror_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_destroy(mirror); + return; + } + + // listen for a commit on the specified output only + struct wlr_mirror_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++; +} + +bool wlr_mirror_v1_output_is_dst(struct wlr_output *output) { + struct wl_array addons; + wlr_addon_find_all(&addons, &output->addons, &output_dst_addon_impl); + bool is_dst = addons.size > 0; + wl_array_release(&addons); + return is_dst; +} + diff --git a/util/addon.c b/util/addon.c index 754666910..e2be46229 100644 --- a/util/addon.c +++ b/util/addon.c @@ -52,3 +52,16 @@ struct wlr_addon *wlr_addon_find(struct wlr_addon_set *set, const void *owner, } return NULL; } + +void wlr_addon_find_all(struct wl_array *all, struct wlr_addon_set *set, + const struct wlr_addon_interface *impl) { + wl_array_init(all); + struct wlr_addon *addon; + wl_list_for_each(addon, &set->addons, link) { + if (addon->impl == impl) { + struct wlr_addon **addon_ptr = wl_array_add(all, sizeof(addon_ptr)); + *addon_ptr = addon; + } + } +} +