From 9a20271f900cfde225f5a2a98f9e93604ab89208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Thu, 24 Jan 2019 18:43:27 +0100 Subject: [PATCH] screen-cast-window-stream: Add support for cursor modes Make the RecordWindow method also understand the 'cursor-mode' property. For 'embedded' the cursor is drawn onto the pixel buffer using cairo, otherwise it works similarly to how RecordMonitor deals with it. https://gitlab.gnome.org/GNOME/mutter/merge_requests/413 --- src/backends/meta-screen-cast-session.c | 17 ++ .../meta-screen-cast-window-stream-src.c | 288 +++++++++++++++++- src/backends/meta-screen-cast-window-stream.c | 10 +- src/backends/meta-screen-cast-window-stream.h | 9 +- src/org.gnome.Mutter.ScreenCast.xml | 1 + 5 files changed, 312 insertions(+), 13 deletions(-) diff --git a/src/backends/meta-screen-cast-session.c b/src/backends/meta-screen-cast-session.c index 130bd17c9..a9b861e8d 100644 --- a/src/backends/meta-screen-cast-session.c +++ b/src/backends/meta-screen-cast-session.c @@ -383,6 +383,7 @@ handle_record_window (MetaDBusScreenCastSession *skeleton, GDBusInterfaceSkeleton *interface_skeleton; GDBusConnection *connection; MetaWindow *window; + MetaScreenCastCursorMode cursor_mode; GError *error = NULL; MetaDisplay *display; GVariant *window_id_variant = NULL; @@ -424,12 +425,28 @@ handle_record_window (MetaDBusScreenCastSession *skeleton, return TRUE; } + if (!g_variant_lookup (properties_variant, "cursor-mode", "u", &cursor_mode)) + { + cursor_mode = META_SCREEN_CAST_CURSOR_MODE_HIDDEN; + } + else + { + if (!is_valid_cursor_mode (cursor_mode)) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "Unknown cursor mode"); + return TRUE; + } + } + interface_skeleton = G_DBUS_INTERFACE_SKELETON (skeleton); connection = g_dbus_interface_skeleton_get_connection (interface_skeleton); window_stream = meta_screen_cast_window_stream_new (session, connection, window, + cursor_mode, &error); if (!window_stream) { diff --git a/src/backends/meta-screen-cast-window-stream-src.c b/src/backends/meta-screen-cast-window-stream-src.c index 0448b443f..dbf330420 100644 --- a/src/backends/meta-screen-cast-window-stream-src.c +++ b/src/backends/meta-screen-cast-window-stream-src.c @@ -23,6 +23,7 @@ #include "backends/meta-screen-cast-window-stream-src.h" #include "backends/meta-backend-private.h" +#include "backends/meta-screen-cast-session.h" #include "backends/meta-screen-cast-window.h" #include "backends/meta-screen-cast-window-stream.h" #include "compositor/meta-window-actor-private.h" @@ -33,14 +34,32 @@ struct _MetaScreenCastWindowStreamSrc MetaScreenCastWindow *screen_cast_window; + unsigned long screen_cast_window_before_paint_handler_id; unsigned long screen_cast_window_after_paint_handler_id; unsigned long screen_cast_window_destroyed_handler_id; + unsigned long cursor_moved_handler_id; + unsigned long cursor_changed_handler_id; + + gboolean actor_was_dirty; + gboolean cursor_bitmap_invalid; }; G_DEFINE_TYPE (MetaScreenCastWindowStreamSrc, meta_screen_cast_window_stream_src, META_TYPE_SCREEN_CAST_STREAM_SRC) +static MetaBackend * +get_backend (MetaScreenCastWindowStreamSrc *window_src) +{ + MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (window_src); + MetaScreenCastStream *stream = meta_screen_cast_stream_src_get_stream (src); + MetaScreenCastSession *session = meta_screen_cast_stream_get_session (stream); + MetaScreenCast *screen_cast = + meta_screen_cast_session_get_screen_cast (session); + + return meta_screen_cast_get_backend (screen_cast); +} + static MetaScreenCastWindowStream * get_window_stream (MetaScreenCastWindowStreamSrc *window_src) { @@ -83,11 +102,91 @@ get_stream_height (MetaScreenCastWindowStreamSrc *window_src) return meta_screen_cast_window_stream_get_height (window_stream); } +static void +maybe_draw_cursor_sprite (MetaScreenCastWindowStreamSrc *window_src, + uint8_t *data, + MetaRectangle *stream_rect) +{ + MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (window_src); + MetaBackend *backend = get_backend (window_src); + MetaCursorRenderer *cursor_renderer = + meta_backend_get_cursor_renderer (backend); + MetaCursorSprite *cursor_sprite; + CoglTexture *cursor_texture; + MetaScreenCastWindow *screen_cast_window; + ClutterPoint cursor_position; + ClutterPoint relative_cursor_position; + cairo_surface_t *cursor_surface; + uint8_t *cursor_surface_data; + GError *error = NULL; + cairo_surface_t *stream_surface; + int width, height; + float scale; + int hotspot_x, hotspot_y; + cairo_t *cr; + + cursor_sprite = meta_cursor_renderer_get_cursor (cursor_renderer); + if (!cursor_sprite) + return; + + cursor_texture = meta_cursor_sprite_get_cogl_texture (cursor_sprite); + if (!cursor_texture) + return; + + screen_cast_window = window_src->screen_cast_window; + cursor_position = meta_cursor_renderer_get_position (cursor_renderer); + if (!meta_screen_cast_window_transform_cursor_position (screen_cast_window, + cursor_sprite, + &cursor_position, + &scale, + &relative_cursor_position)) + return; + + meta_cursor_sprite_get_hotspot (cursor_sprite, &hotspot_x, &hotspot_y); + + width = cogl_texture_get_width (cursor_texture) * scale; + height = cogl_texture_get_height (cursor_texture) * scale; + cursor_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + width, height); + + cursor_surface_data = cairo_image_surface_get_data (cursor_surface); + if (!meta_screen_cast_stream_src_draw_cursor_into (src, + cursor_texture, + scale, + cursor_surface_data, + &error)) + { + g_warning ("Failed to draw cursor: %s", error->message); + g_error_free (error); + cairo_surface_destroy (cursor_surface); + return; + } + + stream_surface = + cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, + stream_rect->width, + stream_rect->height, + stream_rect->width * 4); + + cr = cairo_create (stream_surface); + cairo_surface_mark_dirty (cursor_surface); + cairo_surface_flush (cursor_surface); + cairo_set_source_surface (cr, cursor_surface, + relative_cursor_position.x - hotspot_x * scale, + relative_cursor_position.y - hotspot_y * scale); + cairo_paint (cr); + cairo_destroy (cr); + cairo_surface_destroy (stream_surface); + cairo_surface_destroy (cursor_surface); +} + static gboolean capture_into (MetaScreenCastWindowStreamSrc *window_src, uint8_t *data) { + MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (window_src); MetaRectangle stream_rect; + MetaScreenCastStream *stream; stream_rect.x = 0; stream_rect.y = 0; @@ -97,6 +196,17 @@ capture_into (MetaScreenCastWindowStreamSrc *window_src, meta_screen_cast_window_capture_into (window_src->screen_cast_window, &stream_rect, data); + stream = meta_screen_cast_stream_src_get_stream (src); + switch (meta_screen_cast_stream_get_cursor_mode (stream)) + { + case META_SCREEN_CAST_CURSOR_MODE_EMBEDDED: + maybe_draw_cursor_sprite (window_src, data, &stream_rect); + break; + case META_SCREEN_CAST_CURSOR_MODE_METADATA: + case META_SCREEN_CAST_CURSOR_MODE_HIDDEN: + break; + } + return TRUE; } @@ -139,9 +249,16 @@ static void meta_screen_cast_window_stream_src_stop (MetaScreenCastWindowStreamSrc *window_src) { + MetaBackend *backend = get_backend (window_src); + MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend); + if (!window_src->screen_cast_window) return; + if (window_src->screen_cast_window_before_paint_handler_id) + g_signal_handler_disconnect (window_src->screen_cast_window, + window_src->screen_cast_window_before_paint_handler_id); + window_src->screen_cast_window_before_paint_handler_id = 0; if (window_src->screen_cast_window_after_paint_handler_id) g_signal_handler_disconnect (window_src->screen_cast_window, @@ -152,23 +269,97 @@ meta_screen_cast_window_stream_src_stop (MetaScreenCastWindowStreamSrc *window_s g_signal_handler_disconnect (window_src->screen_cast_window, window_src->screen_cast_window_destroyed_handler_id); window_src->screen_cast_window_destroyed_handler_id = 0; + + if (window_src->cursor_moved_handler_id) + g_signal_handler_disconnect (cursor_tracker, + window_src->cursor_moved_handler_id); + window_src->cursor_moved_handler_id = 0; + + if (window_src->cursor_changed_handler_id) + g_signal_handler_disconnect (cursor_tracker, + window_src->cursor_changed_handler_id); + window_src->cursor_changed_handler_id = 0; } static void -screen_cast_window_after_paint (MetaScreenCastWindow *screen_cast_window, +screen_cast_window_before_paint (MetaScreenCastWindow *screen_cast_window, + MetaScreenCastWindowStreamSrc *window_src) +{ + window_src->actor_was_dirty = + meta_screen_cast_window_has_damage (screen_cast_window); +} + +static void +screen_cast_window_after_paint (MetaWindowActor *actor, MetaScreenCastWindowStreamSrc *window_src) +{ + if (window_src->actor_was_dirty) + { + MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (window_src); + + meta_screen_cast_stream_src_maybe_record_frame (src); + } +} + +static void +screen_cast_window_destroyed (MetaWindowActor *actor, + MetaScreenCastWindowStreamSrc *window_src) +{ + meta_screen_cast_window_stream_src_stop (window_src); + window_src->screen_cast_window = NULL; +} + +static gboolean +is_cursor_in_stream (MetaScreenCastWindowStreamSrc *window_src) +{ + MetaBackend *backend = get_backend (window_src); + MetaCursorRenderer *cursor_renderer = + meta_backend_get_cursor_renderer (backend); + MetaCursorSprite *cursor_sprite; + ClutterPoint cursor_position; + MetaScreenCastWindow *screen_cast_window; + + cursor_sprite = meta_cursor_renderer_get_cursor (cursor_renderer); + + cursor_position = meta_cursor_renderer_get_position (cursor_renderer); + + screen_cast_window = window_src->screen_cast_window; + return meta_screen_cast_window_transform_cursor_position (screen_cast_window, + cursor_sprite, + &cursor_position, + NULL, + NULL); +} + +static void +sync_cursor_state (MetaScreenCastWindowStreamSrc *window_src) { MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (window_src); + if (!is_cursor_in_stream (window_src)) + return; + + if (meta_screen_cast_window_has_damage (window_src->screen_cast_window)) + return; + meta_screen_cast_stream_src_maybe_record_frame (src); } static void -screen_cast_window_destroyed (MetaScreenCastWindow *screen_cast_window, - MetaScreenCastWindowStreamSrc *window_src) +cursor_moved (MetaCursorTracker *cursor_tracker, + float x, + float y, + MetaScreenCastWindowStreamSrc *window_src) { - meta_screen_cast_window_stream_src_stop (window_src); - window_src->screen_cast_window = NULL; + sync_cursor_state (window_src); +} + +static void +cursor_changed (MetaCursorTracker *cursor_tracker, + MetaScreenCastWindowStreamSrc *window_src) +{ + window_src->cursor_bitmap_invalid = TRUE; + sync_cursor_state (window_src); } static void @@ -176,7 +367,10 @@ meta_screen_cast_window_stream_src_enable (MetaScreenCastStreamSrc *src) { MetaScreenCastWindowStreamSrc *window_src = META_SCREEN_CAST_WINDOW_STREAM_SRC (src); + MetaBackend *backend = get_backend (window_src); + MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend); MetaWindowActor *window_actor; + MetaScreenCastStream *stream; window_actor = meta_window_actor_from_window (get_window (window_src)); if (!window_actor) @@ -184,6 +378,11 @@ meta_screen_cast_window_stream_src_enable (MetaScreenCastStreamSrc *src) window_src->screen_cast_window = META_SCREEN_CAST_WINDOW (window_actor); + window_src->screen_cast_window_before_paint_handler_id = + g_signal_connect (window_src->screen_cast_window, + "paint", + G_CALLBACK (screen_cast_window_before_paint), + window_src); window_src->screen_cast_window_after_paint_handler_id = g_signal_connect_after (window_src->screen_cast_window, "paint", @@ -195,6 +394,24 @@ meta_screen_cast_window_stream_src_enable (MetaScreenCastStreamSrc *src) "destroy", G_CALLBACK (screen_cast_window_destroyed), window_src); + + stream = meta_screen_cast_stream_src_get_stream (src); + switch (meta_screen_cast_stream_get_cursor_mode (stream)) + { + case META_SCREEN_CAST_CURSOR_MODE_METADATA: + case META_SCREEN_CAST_CURSOR_MODE_EMBEDDED: + window_src->cursor_moved_handler_id = + g_signal_connect_after (cursor_tracker, "cursor-moved", + G_CALLBACK (cursor_moved), + window_src); + window_src->cursor_changed_handler_id = + g_signal_connect_after (cursor_tracker, "cursor-changed", + G_CALLBACK (cursor_changed), + window_src); + break; + case META_SCREEN_CAST_CURSOR_MODE_HIDDEN: + break; + } } static void @@ -218,6 +435,65 @@ meta_screen_cast_window_stream_src_record_frame (MetaScreenCastStreamSrc *src, return TRUE; } +static void +meta_screen_cast_window_stream_src_set_cursor_metadata (MetaScreenCastStreamSrc *src, + struct spa_meta_cursor *spa_meta_cursor) +{ + MetaScreenCastWindowStreamSrc *window_src = + META_SCREEN_CAST_WINDOW_STREAM_SRC (src); + MetaBackend *backend = get_backend (window_src); + MetaCursorRenderer *cursor_renderer = + meta_backend_get_cursor_renderer (backend); + MetaScreenCastWindow *screen_cast_window = window_src->screen_cast_window; + MetaCursorSprite *cursor_sprite; + ClutterPoint cursor_position; + float scale; + ClutterPoint relative_cursor_position; + int x, y; + + cursor_sprite = meta_cursor_renderer_get_cursor (cursor_renderer); + cursor_position = meta_cursor_renderer_get_position (cursor_renderer); + + if (!meta_screen_cast_window_transform_cursor_position (screen_cast_window, + cursor_sprite, + &cursor_position, + &scale, + &relative_cursor_position)) + { + meta_screen_cast_stream_src_unset_cursor_metadata (src, + spa_meta_cursor); + return; + } + + x = (int) roundf (relative_cursor_position.x); + y = (int) roundf (relative_cursor_position.y); + + if (window_src->cursor_bitmap_invalid) + { + if (cursor_sprite) + { + meta_screen_cast_stream_src_set_cursor_sprite_metadata (src, + spa_meta_cursor, + cursor_sprite, + x, y, + scale); + } + else + { + meta_screen_cast_stream_src_set_empty_cursor_sprite_metadata (src, + spa_meta_cursor, + x, y); + } + window_src->cursor_bitmap_invalid = FALSE; + } + else + { + meta_screen_cast_stream_src_set_cursor_position_metadata (src, + spa_meta_cursor, + x, y); + } +} + MetaScreenCastWindowStreamSrc * meta_screen_cast_window_stream_src_new (MetaScreenCastWindowStream *window_stream, GError **error) @@ -230,6 +506,7 @@ meta_screen_cast_window_stream_src_new (MetaScreenCastWindowStream *window_stre static void meta_screen_cast_window_stream_src_init (MetaScreenCastWindowStreamSrc *window_src) { + window_src->cursor_bitmap_invalid = TRUE; } static void @@ -243,4 +520,5 @@ meta_screen_cast_window_stream_src_class_init (MetaScreenCastWindowStreamSrcClas src_class->disable = meta_screen_cast_window_stream_src_disable; src_class->record_frame = meta_screen_cast_window_stream_src_record_frame; src_class->get_videocrop = meta_screen_cast_window_stream_src_get_videocrop; + src_class->set_cursor_metadata = meta_screen_cast_window_stream_src_set_cursor_metadata; } diff --git a/src/backends/meta-screen-cast-window-stream.c b/src/backends/meta-screen-cast-window-stream.c index 19645540c..50d1806cd 100644 --- a/src/backends/meta-screen-cast-window-stream.c +++ b/src/backends/meta-screen-cast-window-stream.c @@ -80,16 +80,18 @@ meta_screen_cast_window_stream_get_height (MetaScreenCastWindowStream *window_st } MetaScreenCastWindowStream * -meta_screen_cast_window_stream_new (MetaScreenCastSession *session, - GDBusConnection *connection, - MetaWindow *window, - GError **error) +meta_screen_cast_window_stream_new (MetaScreenCastSession *session, + GDBusConnection *connection, + MetaWindow *window, + MetaScreenCastCursorMode cursor_mode, + GError **error) { return g_initable_new (META_TYPE_SCREEN_CAST_WINDOW_STREAM, NULL, error, "session", session, "connection", connection, + "cursor-mode", cursor_mode, "window", window, NULL); } diff --git a/src/backends/meta-screen-cast-window-stream.h b/src/backends/meta-screen-cast-window-stream.h index 3799be98a..6eae74f1c 100644 --- a/src/backends/meta-screen-cast-window-stream.h +++ b/src/backends/meta-screen-cast-window-stream.h @@ -32,10 +32,11 @@ G_DECLARE_FINAL_TYPE (MetaScreenCastWindowStream, META, SCREEN_CAST_WINDOW_STREAM, MetaScreenCastStream) -MetaScreenCastWindowStream * meta_screen_cast_window_stream_new (MetaScreenCastSession *session, - GDBusConnection *connection, - MetaWindow *window, - GError **error); +MetaScreenCastWindowStream * meta_screen_cast_window_stream_new (MetaScreenCastSession *session, + GDBusConnection *connection, + MetaWindow *window, + MetaScreenCastCursorMode cursor_mode, + GError **error); MetaWindow * meta_screen_cast_window_stream_get_window (MetaScreenCastWindowStream *window_stream); int meta_screen_cast_window_stream_get_width (MetaScreenCastWindowStream *window_stream); diff --git a/src/org.gnome.Mutter.ScreenCast.xml b/src/org.gnome.Mutter.ScreenCast.xml index 953809727..8ec2a1919 100644 --- a/src/org.gnome.Mutter.ScreenCast.xml +++ b/src/org.gnome.Mutter.ScreenCast.xml @@ -97,6 +97,7 @@ Available @properties include: * "window-id" (t): Id of the window to record. + * "cursor-mode" (u): Cursor mode. Default: 'hidden' (see RecordMonitor) -->