From 4e402b397267169e578005cd89bb5bbdaf70853b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Fri, 14 Dec 2018 17:47:57 +0100 Subject: [PATCH] screen-cast: Add 'cursor-mode' to allow decoupled cursor updates The 'cursor-mode', which currently is limited to RecordMonitor(), allows the user to either do screen casts where the cursor is hidden, embedded in the framebuffer, or sent as PipeWire stream metadata. The latter allows the user to get cursor updates sent, including the cursor sprite, without requiring a stage paint each frame. Currently this is done by using the cursor sprite texture, and either reading directly from, or drawing to an offscreen framebuffer which is read from instead, in case the texture is scaled. https://gitlab.gnome.org/GNOME/mutter/merge_requests/357 --- .gitlab-ci/Dockerfile | 3 + meson.build | 2 +- .../meta-screen-cast-monitor-stream-src.c | 416 +++++++++++++++++- .../meta-screen-cast-monitor-stream.c | 12 +- .../meta-screen-cast-monitor-stream.h | 11 +- src/backends/meta-screen-cast-session.c | 31 ++ src/backends/meta-screen-cast-stream-src.c | 128 ++++-- src/backends/meta-screen-cast-stream-src.h | 24 +- src/backends/meta-screen-cast-stream.c | 30 ++ src/backends/meta-screen-cast-stream.h | 2 + .../meta-screen-cast-window-stream-src.c | 4 +- src/backends/meta-screen-cast.h | 7 + src/org.gnome.Mutter.ScreenCast.xml | 12 +- 13 files changed, 625 insertions(+), 57 deletions(-) diff --git a/.gitlab-ci/Dockerfile b/.gitlab-ci/Dockerfile index 29db895f9..43cafa1ae 100644 --- a/.gitlab-ci/Dockerfile +++ b/.gitlab-ci/Dockerfile @@ -13,5 +13,8 @@ RUN dnf -y update && dnf -y upgrade && \ # Unpackaged versions dnf install -y https://copr-be.cloud.fedoraproject.org/results/jadahl/mutter-ci/fedora-29-x86_64/00836095-gsettings-desktop-schemas/gsettings-desktop-schemas-3.30.1-1.20181206git918efdd69be53.fc29.x86_64.rpm https://copr-be.cloud.fedoraproject.org/results/jadahl/mutter-ci/fedora-29-x86_64/00836095-gsettings-desktop-schemas/gsettings-desktop-schemas-devel-3.30.1-1.20181206git918efdd69be53.fc29.x86_64.rpm && \ + # Packages not yet in stable + dnf install -y https://kojipkgs.fedoraproject.org//packages/pipewire/0.2.5/1.fc29/x86_64/pipewire-0.2.5-1.fc29.x86_64.rpm https://kojipkgs.fedoraproject.org//packages/pipewire/0.2.5/1.fc29/x86_64/pipewire-devel-0.2.5-1.fc29.x86_64.rpm https://kojipkgs.fedoraproject.org//packages/pipewire/0.2.5/1.fc29/x86_64/pipewire-libs-0.2.5-1.fc29.x86_64.rpm && \ + dnf install -y intltool redhat-rpm-config make && \ dnf clean all diff --git a/meson.build b/meson.build index 8d8f4da7b..be1b829a3 100644 --- a/meson.build +++ b/meson.build @@ -43,7 +43,7 @@ libinput_req = '>= 1.4' gbm_req = '>= 10.3' # screen cast version requirements -libpipewire_req = '>= 0.2.2' +libpipewire_req = '>= 0.2.5' gnome = import('gnome') pkg = import('pkgconfig') diff --git a/src/backends/meta-screen-cast-monitor-stream-src.c b/src/backends/meta-screen-cast-monitor-stream-src.c index 2aad3aa0c..50ed6c519 100644 --- a/src/backends/meta-screen-cast-monitor-stream-src.c +++ b/src/backends/meta-screen-cast-monitor-stream-src.c @@ -24,23 +24,36 @@ #include "backends/meta-screen-cast-monitor-stream-src.h" +#include + #include "backends/meta-backend-private.h" +#include "backends/meta-cursor-tracker-private.h" #include "backends/meta-logical-monitor.h" #include "backends/meta-monitor.h" #include "backends/meta-screen-cast-monitor-stream.h" +#include "backends/meta-screen-cast-session.h" #include "clutter/clutter.h" #include "clutter/clutter-mutter.h" +#include "core/boxes-private.h" struct _MetaScreenCastMonitorStreamSrc { MetaScreenCastStreamSrc parent; gulong actors_painted_handler_id; + gulong paint_handler_id; + gulong cursor_moved_handler_id; + gulong cursor_changed_handler_id; }; -G_DEFINE_TYPE (MetaScreenCastMonitorStreamSrc, - meta_screen_cast_monitor_stream_src, - META_TYPE_SCREEN_CAST_STREAM_SRC) +static void +hw_cursor_inhibitor_iface_init (MetaHwCursorInhibitorInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (MetaScreenCastMonitorStreamSrc, + meta_screen_cast_monitor_stream_src, + META_TYPE_SCREEN_CAST_STREAM_SRC, + G_IMPLEMENT_INTERFACE (META_TYPE_HW_CURSOR_INHIBITOR, + hw_cursor_inhibitor_iface_init)) static ClutterStage * get_stage (MetaScreenCastMonitorStreamSrc *monitor_src) @@ -102,18 +115,163 @@ stage_painted (ClutterActor *actor, meta_screen_cast_stream_src_maybe_record_frame (src); } +static MetaBackend * +get_backend (MetaScreenCastMonitorStreamSrc *monitor_src) +{ + MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (monitor_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 gboolean +is_cursor_in_stream (MetaScreenCastMonitorStreamSrc *monitor_src) +{ + MetaBackend *backend = get_backend (monitor_src); + MetaCursorRenderer *cursor_renderer = + meta_backend_get_cursor_renderer (backend); + MetaMonitor *monitor; + MetaLogicalMonitor *logical_monitor; + MetaRectangle logical_monitor_layout; + ClutterRect logical_monitor_rect; + MetaCursorSprite *cursor_sprite; + + monitor = get_monitor (monitor_src); + logical_monitor = meta_monitor_get_logical_monitor (monitor); + logical_monitor_layout = meta_logical_monitor_get_layout (logical_monitor); + logical_monitor_rect = + meta_rectangle_to_clutter_rect (&logical_monitor_layout); + + cursor_sprite = meta_cursor_renderer_get_cursor (cursor_renderer); + if (cursor_sprite) + { + ClutterRect cursor_rect; + + cursor_rect = meta_cursor_renderer_calculate_rect (cursor_renderer, + cursor_sprite); + return clutter_rect_intersection (&cursor_rect, + &logical_monitor_rect, + NULL); + } + else + { + ClutterPoint cursor_position; + + cursor_position = meta_cursor_renderer_get_position (cursor_renderer); + return clutter_rect_contains_point (&logical_monitor_rect, + &cursor_position); + } +} + +static void +sync_cursor_state (MetaScreenCastMonitorStreamSrc *monitor_src) +{ + MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (monitor_src); + ClutterStage *stage = get_stage (monitor_src); + + if (!is_cursor_in_stream (monitor_src)) + return; + + if (clutter_stage_is_redraw_queued (stage)) + return; + + meta_screen_cast_stream_src_maybe_record_frame (src); +} + +static void +cursor_moved (MetaCursorTracker *cursor_tracker, + float x, + float y, + MetaScreenCastMonitorStreamSrc *monitor_src) +{ + sync_cursor_state (monitor_src); +} + +static void +cursor_changed (MetaCursorTracker *cursor_tracker, + MetaScreenCastMonitorStreamSrc *monitor_src) +{ + sync_cursor_state (monitor_src); +} + +static MetaCursorRenderer * +get_cursor_renderer (MetaScreenCastMonitorStreamSrc *monitor_src) +{ + MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (monitor_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); + MetaBackend *backend = meta_screen_cast_get_backend (screen_cast); + + return meta_backend_get_cursor_renderer (backend); +} + +static void +inhibit_hw_cursor (MetaScreenCastMonitorStreamSrc *monitor_src) +{ + MetaCursorRenderer *cursor_renderer; + MetaHwCursorInhibitor *inhibitor; + + cursor_renderer = get_cursor_renderer (monitor_src); + inhibitor = META_HW_CURSOR_INHIBITOR (monitor_src); + meta_cursor_renderer_add_hw_cursor_inhibitor (cursor_renderer, inhibitor); +} + +static void +uninhibit_hw_cursor (MetaScreenCastMonitorStreamSrc *monitor_src) +{ + MetaCursorRenderer *cursor_renderer; + MetaHwCursorInhibitor *inhibitor; + + cursor_renderer = get_cursor_renderer (monitor_src); + inhibitor = META_HW_CURSOR_INHIBITOR (monitor_src); + meta_cursor_renderer_remove_hw_cursor_inhibitor (cursor_renderer, inhibitor); +} + static void meta_screen_cast_monitor_stream_src_enable (MetaScreenCastStreamSrc *src) { MetaScreenCastMonitorStreamSrc *monitor_src = META_SCREEN_CAST_MONITOR_STREAM_SRC (src); + MetaBackend *backend = get_backend (monitor_src); + MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend); ClutterStage *stage; + MetaScreenCastStream *stream; + stream = meta_screen_cast_stream_src_get_stream (src); stage = get_stage (monitor_src); - monitor_src->actors_painted_handler_id = - g_signal_connect_after (stage, "actors-painted", - G_CALLBACK (stage_painted), - monitor_src); + + switch (meta_screen_cast_stream_get_cursor_mode (stream)) + { + case META_SCREEN_CAST_CURSOR_MODE_METADATA: + monitor_src->cursor_moved_handler_id = + g_signal_connect_after (cursor_tracker, "cursor-moved", + G_CALLBACK (cursor_moved), + monitor_src); + monitor_src->cursor_changed_handler_id = + g_signal_connect_after (cursor_tracker, "cursor-changed", + G_CALLBACK (cursor_changed), + monitor_src); + /* Intentional fall-through */ + case META_SCREEN_CAST_CURSOR_MODE_HIDDEN: + monitor_src->actors_painted_handler_id = + g_signal_connect (stage, "actors-painted", + G_CALLBACK (stage_painted), + monitor_src); + break; + case META_SCREEN_CAST_CURSOR_MODE_EMBEDDED: + inhibit_hw_cursor (monitor_src); + monitor_src->paint_handler_id = + g_signal_connect_after (stage, "paint", + G_CALLBACK (stage_painted), + monitor_src); + break; + } + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); } @@ -122,14 +280,43 @@ meta_screen_cast_monitor_stream_src_disable (MetaScreenCastStreamSrc *src) { MetaScreenCastMonitorStreamSrc *monitor_src = META_SCREEN_CAST_MONITOR_STREAM_SRC (src); + MetaBackend *backend = get_backend (monitor_src); + MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend); ClutterStage *stage; stage = get_stage (monitor_src); - g_signal_handler_disconnect (stage, monitor_src->actors_painted_handler_id); - monitor_src->actors_painted_handler_id = 0; + + if (monitor_src->actors_painted_handler_id) + { + g_signal_handler_disconnect (stage, + monitor_src->actors_painted_handler_id); + monitor_src->actors_painted_handler_id = 0; + } + + if (monitor_src->paint_handler_id) + { + g_signal_handler_disconnect (stage, + monitor_src->paint_handler_id); + monitor_src->paint_handler_id = 0; + uninhibit_hw_cursor (monitor_src); + } + + if (monitor_src->cursor_moved_handler_id) + { + g_signal_handler_disconnect (cursor_tracker, + monitor_src->cursor_moved_handler_id); + monitor_src->cursor_moved_handler_id = 0; + } + + if (monitor_src->cursor_changed_handler_id) + { + g_signal_handler_disconnect (cursor_tracker, + monitor_src->cursor_changed_handler_id); + monitor_src->cursor_changed_handler_id = 0; + } } -static void +static gboolean meta_screen_cast_monitor_stream_src_record_frame (MetaScreenCastStreamSrc *src, uint8_t *data) { @@ -140,9 +327,216 @@ meta_screen_cast_monitor_stream_src_record_frame (MetaScreenCastStreamSrc *src, MetaLogicalMonitor *logical_monitor; stage = get_stage (monitor_src); + if (!clutter_stage_is_redraw_queued (stage)) + return FALSE; + monitor = get_monitor (monitor_src); logical_monitor = meta_monitor_get_logical_monitor (monitor); clutter_stage_capture_into (stage, FALSE, &logical_monitor->rect, data); + + return TRUE; +} + +static gboolean +draw_cursor_sprite_via_offscreen (MetaScreenCastMonitorStreamSrc *monitor_src, + CoglTexture *cursor_texture, + int bitmap_width, + int bitmap_height, + uint32_t *bitmap_data, + GError **error) +{ + MetaBackend *backend = get_backend (monitor_src); + ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); + CoglContext *cogl_context = + clutter_backend_get_cogl_context (clutter_backend); + CoglTexture2D *bitmap_texture; + CoglOffscreen *offscreen; + CoglFramebuffer *fb; + CoglPipeline *pipeline; + CoglColor clear_color; + + bitmap_texture = cogl_texture_2d_new_with_size (cogl_context, + bitmap_width, bitmap_height); + cogl_primitive_texture_set_auto_mipmap (COGL_PRIMITIVE_TEXTURE (bitmap_texture), + FALSE); + if (!cogl_texture_allocate (COGL_TEXTURE (bitmap_texture), error)) + { + cogl_object_unref (bitmap_texture); + return FALSE; + } + + offscreen = cogl_offscreen_new_with_texture (COGL_TEXTURE (bitmap_texture)); + fb = COGL_FRAMEBUFFER (offscreen); + cogl_object_unref (bitmap_texture); + if (!cogl_framebuffer_allocate (fb, error)) + { + cogl_object_unref (fb); + return FALSE; + } + + pipeline = cogl_pipeline_new (cogl_context); + cogl_pipeline_set_layer_texture (pipeline, 0, cursor_texture); + cogl_pipeline_set_layer_filters (pipeline, 0, + COGL_PIPELINE_FILTER_LINEAR, + COGL_PIPELINE_FILTER_LINEAR); + cogl_color_init_from_4ub (&clear_color, 0, 0, 0, 0); + cogl_framebuffer_clear (fb, COGL_BUFFER_BIT_COLOR, &clear_color); + cogl_framebuffer_draw_rectangle (fb, pipeline, + -1, 1, 1, -1); + cogl_object_unref (pipeline); + + cogl_framebuffer_read_pixels (fb, + 0, 0, + bitmap_width, bitmap_height, + COGL_PIXEL_FORMAT_RGBA_8888_PRE, + (uint8_t *) bitmap_data); + cogl_object_unref (fb); + + return TRUE; +} + +static void +meta_screen_cast_monitor_stream_src_set_cursor_metadata (MetaScreenCastStreamSrc *src, + struct spa_meta_cursor *spa_meta_cursor) +{ + MetaScreenCastMonitorStreamSrc *monitor_src = + META_SCREEN_CAST_MONITOR_STREAM_SRC (src); + MetaBackend *backend = get_backend (monitor_src); + MetaCursorRenderer *cursor_renderer = + meta_backend_get_cursor_renderer (backend); + MetaRenderer *renderer = meta_backend_get_renderer (backend); + MetaSpaType *spa_type = meta_screen_cast_stream_src_get_spa_type (src); + GError *error = NULL; + MetaCursorSprite *cursor_sprite; + CoglTexture *cursor_texture; + MetaMonitor *monitor; + MetaLogicalMonitor *logical_monitor; + MetaRectangle logical_monitor_layout; + ClutterRect logical_monitor_rect; + MetaRendererView *view; + float view_scale; + ClutterPoint cursor_position; + struct spa_meta_bitmap *spa_meta_bitmap; + + cursor_sprite = meta_cursor_renderer_get_cursor (cursor_renderer); + if (cursor_sprite) + cursor_texture = meta_cursor_sprite_get_cogl_texture (cursor_sprite); + else + cursor_texture = NULL; + + if (!is_cursor_in_stream (monitor_src)) + { + spa_meta_cursor->id = 0; + return; + } + + monitor = get_monitor (monitor_src); + logical_monitor = meta_monitor_get_logical_monitor (monitor); + logical_monitor_layout = meta_logical_monitor_get_layout (logical_monitor); + logical_monitor_rect = + meta_rectangle_to_clutter_rect (&logical_monitor_layout); + + view = meta_renderer_get_view_from_logical_monitor (renderer, + logical_monitor); + if (view) + view_scale = clutter_stage_view_get_scale (CLUTTER_STAGE_VIEW (view)); + else + view_scale = 1.0; + + cursor_position = meta_cursor_renderer_get_position (cursor_renderer); + cursor_position.x -= logical_monitor_rect.origin.x; + cursor_position.y -= logical_monitor_rect.origin.y; + cursor_position.x *= view_scale; + cursor_position.y *= view_scale; + + spa_meta_cursor->id = 1; + spa_meta_cursor->position.x = (int32_t) roundf (cursor_position.x); + spa_meta_cursor->position.y = (int32_t) roundf (cursor_position.y); + spa_meta_cursor->bitmap_offset = sizeof (struct spa_meta_cursor); + + spa_meta_bitmap = SPA_MEMBER (spa_meta_cursor, + spa_meta_cursor->bitmap_offset, + struct spa_meta_bitmap); + spa_meta_bitmap->format = spa_type->video_format.RGBA; + spa_meta_bitmap->offset = sizeof (struct spa_meta_bitmap); + + if (cursor_texture) + { + float cursor_scale; + float bitmap_scale; + int hotspot_x, hotspot_y; + int texture_width, texture_height; + int bitmap_width, bitmap_height; + uint32_t *bitmap_data; + + cursor_scale = meta_cursor_sprite_get_texture_scale (cursor_sprite); + bitmap_scale = view_scale * cursor_scale; + + meta_cursor_sprite_get_hotspot (cursor_sprite, &hotspot_x, &hotspot_y); + spa_meta_cursor->hotspot.x = (int32_t) roundf (hotspot_x * bitmap_scale); + spa_meta_cursor->hotspot.y = (int32_t) roundf (hotspot_y * bitmap_scale); + + texture_width = cogl_texture_get_width (cursor_texture); + texture_height = cogl_texture_get_height (cursor_texture); + bitmap_width = texture_width * bitmap_scale; + bitmap_height = texture_height * bitmap_scale; + + spa_meta_bitmap->size.width = bitmap_width; + spa_meta_bitmap->size.height = bitmap_height; + spa_meta_bitmap->stride = bitmap_width * 4; + + bitmap_data = SPA_MEMBER (spa_meta_bitmap, + spa_meta_bitmap->offset, + uint32_t); + + if (texture_width == bitmap_width && + texture_height == bitmap_height) + { + cogl_texture_get_data (cursor_texture, + COGL_PIXEL_FORMAT_RGBA_8888_PRE, + texture_width * 4, + (uint8_t *) bitmap_data); + } + else + { + if (!draw_cursor_sprite_via_offscreen (monitor_src, + cursor_texture, + bitmap_width, + bitmap_height, + bitmap_data, + &error)) + { + g_warning ("Failed to draw cursor via offscreen: %s", + error->message); + g_error_free (error); + spa_meta_cursor->id = 0; + } + } + } + else + { + spa_meta_cursor->hotspot.x = 0; + spa_meta_cursor->hotspot.y = 0; + + *spa_meta_bitmap = (struct spa_meta_bitmap) { 0 }; + } +} + +static gboolean +meta_screen_cast_monitor_stream_src_is_cursor_sprite_inhibited (MetaHwCursorInhibitor *inhibitor, + MetaCursorSprite *cursor_sprite) +{ + MetaScreenCastMonitorStreamSrc *monitor_src = + META_SCREEN_CAST_MONITOR_STREAM_SRC (inhibitor); + + return is_cursor_in_stream (monitor_src); +} + +static void +hw_cursor_inhibitor_iface_init (MetaHwCursorInhibitorInterface *iface) +{ + iface->is_cursor_sprite_inhibited = + meta_screen_cast_monitor_stream_src_is_cursor_sprite_inhibited; } MetaScreenCastMonitorStreamSrc * @@ -169,4 +563,6 @@ meta_screen_cast_monitor_stream_src_class_init (MetaScreenCastMonitorStreamSrcCl src_class->enable = meta_screen_cast_monitor_stream_src_enable; src_class->disable = meta_screen_cast_monitor_stream_src_disable; src_class->record_frame = meta_screen_cast_monitor_stream_src_record_frame; + src_class->set_cursor_metadata = + meta_screen_cast_monitor_stream_src_set_cursor_metadata; } diff --git a/src/backends/meta-screen-cast-monitor-stream.c b/src/backends/meta-screen-cast-monitor-stream.c index a6bed1b52..33b9f026a 100644 --- a/src/backends/meta-screen-cast-monitor-stream.c +++ b/src/backends/meta-screen-cast-monitor-stream.c @@ -105,11 +105,12 @@ meta_screen_cast_monitor_stream_get_monitor (MetaScreenCastMonitorStream *monito } MetaScreenCastMonitorStream * -meta_screen_cast_monitor_stream_new (MetaScreenCastSession *session, - GDBusConnection *connection, - MetaMonitor *monitor, - ClutterStage *stage, - GError **error) +meta_screen_cast_monitor_stream_new (MetaScreenCastSession *session, + GDBusConnection *connection, + MetaMonitor *monitor, + ClutterStage *stage, + MetaScreenCastCursorMode cursor_mode, + GError **error) { MetaGpu *gpu = meta_monitor_get_gpu (monitor); MetaMonitorManager *monitor_manager = meta_gpu_get_monitor_manager (gpu); @@ -126,6 +127,7 @@ meta_screen_cast_monitor_stream_new (MetaScreenCastSession *session, error, "session", session, "connection", connection, + "cursor-mode", cursor_mode, "monitor", monitor, NULL); if (!monitor_stream) diff --git a/src/backends/meta-screen-cast-monitor-stream.h b/src/backends/meta-screen-cast-monitor-stream.h index 98f160c88..f8dc04181 100644 --- a/src/backends/meta-screen-cast-monitor-stream.h +++ b/src/backends/meta-screen-cast-monitor-stream.h @@ -35,11 +35,12 @@ G_DECLARE_FINAL_TYPE (MetaScreenCastMonitorStream, META, SCREEN_CAST_MONITOR_STREAM, MetaScreenCastStream) -MetaScreenCastMonitorStream * meta_screen_cast_monitor_stream_new (MetaScreenCastSession *session, - GDBusConnection *connection, - MetaMonitor *monitor, - ClutterStage *stage, - GError **error); +MetaScreenCastMonitorStream * meta_screen_cast_monitor_stream_new (MetaScreenCastSession *session, + GDBusConnection *connection, + MetaMonitor *monitor, + ClutterStage *stage, + MetaScreenCastCursorMode cursor_mode, + GError **error); ClutterStage * meta_screen_cast_monitor_stream_get_stage (MetaScreenCastMonitorStream *monitor_stream); diff --git a/src/backends/meta-screen-cast-session.c b/src/backends/meta-screen-cast-session.c index 6a8f2d328..45d403dca 100644 --- a/src/backends/meta-screen-cast-session.c +++ b/src/backends/meta-screen-cast-session.c @@ -262,6 +262,20 @@ on_stream_closed (MetaScreenCastStream *stream, meta_screen_cast_session_close (session); } +static gboolean +is_valid_cursor_mode (MetaScreenCastCursorMode cursor_mode) +{ + switch (cursor_mode) + { + case META_SCREEN_CAST_CURSOR_MODE_HIDDEN: + case META_SCREEN_CAST_CURSOR_MODE_EMBEDDED: + case META_SCREEN_CAST_CURSOR_MODE_METADATA: + return TRUE; + } + + return FALSE; +} + static gboolean handle_record_monitor (MetaDBusScreenCastSession *skeleton, GDBusMethodInvocation *invocation, @@ -275,6 +289,7 @@ handle_record_monitor (MetaDBusScreenCastSession *skeleton, MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); MetaMonitor *monitor; + MetaScreenCastCursorMode cursor_mode; ClutterStage *stage; GError *error = NULL; MetaScreenCastMonitorStream *monitor_stream; @@ -306,12 +321,28 @@ handle_record_monitor (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; + } + } + stage = CLUTTER_STAGE (meta_backend_get_stage (backend)); monitor_stream = meta_screen_cast_monitor_stream_new (session, connection, monitor, stage, + cursor_mode, &error); if (!monitor_stream) { diff --git a/src/backends/meta-screen-cast-stream-src.c b/src/backends/meta-screen-cast-stream-src.c index 04b84eb1d..7e11bd067 100644 --- a/src/backends/meta-screen-cast-stream-src.c +++ b/src/backends/meta-screen-cast-stream-src.c @@ -40,6 +40,10 @@ #define PRIVATE_OWNER_FROM_FIELD(TypeName, field_ptr, field_name) \ (TypeName *)((guint8 *)(field_ptr) - G_PRIVATE_OFFSET (TypeName, field_name)) +#define CURSOR_META_SIZE(width, height) \ + (sizeof (struct spa_meta_cursor) + \ + sizeof (struct spa_meta_bitmap) + width * height * 4) + enum { PROP_0, @@ -57,14 +61,6 @@ enum static guint signals[N_SIGNALS]; -typedef struct _MetaSpaType -{ - struct spa_type_media_type media_type; - struct spa_type_media_subtype media_subtype; - struct spa_type_format_video format_video; - struct spa_type_video_format video_format; -} MetaSpaType; - typedef struct _MetaPipeWireSource { GSource base; @@ -133,14 +129,68 @@ meta_screen_cast_stream_src_get_videocrop (MetaScreenCastStreamSrc *src, return FALSE; } -static void +static gboolean meta_screen_cast_stream_src_record_frame (MetaScreenCastStreamSrc *src, uint8_t *data) { MetaScreenCastStreamSrcClass *klass = META_SCREEN_CAST_STREAM_SRC_GET_CLASS (src); - klass->record_frame (src, data); + return klass->record_frame (src, data); +} + +static void +meta_screen_cast_stream_src_set_cursor_metadata (MetaScreenCastStreamSrc *src, + struct spa_meta_cursor *spa_meta_cursor) +{ + MetaScreenCastStreamSrcClass *klass = + META_SCREEN_CAST_STREAM_SRC_GET_CLASS (src); + + if (klass->set_cursor_metadata) + klass->set_cursor_metadata (src, spa_meta_cursor); +} + +MetaSpaType * +meta_screen_cast_stream_src_get_spa_type (MetaScreenCastStreamSrc *src) +{ + MetaScreenCastStreamSrcPrivate *priv = + meta_screen_cast_stream_src_get_instance_private (src); + + return &priv->spa_type; +} + +static void +add_cursor_metadata (MetaScreenCastStreamSrc *src, + struct spa_buffer *spa_buffer) +{ + MetaScreenCastStreamSrcPrivate *priv = + meta_screen_cast_stream_src_get_instance_private (src); + MetaSpaType *spa_type = &priv->spa_type; + struct spa_meta_cursor *spa_meta_cursor; + + spa_meta_cursor = spa_buffer_find_meta (spa_buffer, spa_type->meta_cursor); + if (spa_meta_cursor) + meta_screen_cast_stream_src_set_cursor_metadata (src, spa_meta_cursor); +} + +static void +maybe_record_cursor (MetaScreenCastStreamSrc *src, + struct spa_buffer *spa_buffer, + uint8_t *data) +{ + MetaScreenCastStream *stream = meta_screen_cast_stream_src_get_stream (src); + + switch (meta_screen_cast_stream_get_cursor_mode (stream)) + { + case META_SCREEN_CAST_CURSOR_MODE_HIDDEN: + case META_SCREEN_CAST_CURSOR_MODE_EMBEDDED: + return; + case META_SCREEN_CAST_CURSOR_MODE_METADATA: + add_cursor_metadata (src, spa_buffer); + return; + } + + g_assert_not_reached (); } void @@ -151,7 +201,6 @@ meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc *src) MetaRectangle crop_rect; struct pw_buffer *buffer; struct spa_buffer *spa_buffer; - struct spa_meta_video_crop *spa_meta_video_crop; uint8_t *map = NULL; uint8_t *data; uint64_t now_us; @@ -199,35 +248,45 @@ meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc *src) return; } - meta_screen_cast_stream_src_record_frame (src, data); - - /* Update VideoCrop if needed */ - spa_meta_video_crop = spa_buffer_find_meta (spa_buffer, priv->pipewire_type->meta.VideoCrop); - if (spa_meta_video_crop) + if (meta_screen_cast_stream_src_record_frame (src, data)) { - if (meta_screen_cast_stream_src_get_videocrop (src, &crop_rect)) + struct spa_meta_video_crop *spa_meta_video_crop; + + spa_buffer->datas[0].chunk->size = spa_buffer->datas[0].maxsize; + + /* Update VideoCrop if needed */ + spa_meta_video_crop = + spa_buffer_find_meta (spa_buffer, priv->pipewire_type->meta.VideoCrop); + if (spa_meta_video_crop) { - spa_meta_video_crop->x = crop_rect.x; - spa_meta_video_crop->y = crop_rect.y; - spa_meta_video_crop->width = crop_rect.width; - spa_meta_video_crop->height = crop_rect.height; - } - else - { - spa_meta_video_crop->x = 0; - spa_meta_video_crop->y = 0; - spa_meta_video_crop->width = priv->stream_width; - spa_meta_video_crop->height = priv->stream_height; + if (meta_screen_cast_stream_src_get_videocrop (src, &crop_rect)) + { + spa_meta_video_crop->x = crop_rect.x; + spa_meta_video_crop->y = crop_rect.y; + spa_meta_video_crop->width = crop_rect.width; + spa_meta_video_crop->height = crop_rect.height; + } + else + { + spa_meta_video_crop->x = 0; + spa_meta_video_crop->y = 0; + spa_meta_video_crop->width = priv->stream_width; + spa_meta_video_crop->height = priv->stream_height; + } } } + else + { + spa_buffer->datas[0].chunk->size = 0; + } + + maybe_record_cursor (src, spa_buffer, data); priv->last_frame_timestamp_us = now_us; if (map) munmap (map, spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset); - spa_buffer->datas[0].chunk->size = spa_buffer->datas[0].maxsize; - pw_stream_queue_buffer (priv->pipewire_stream, buffer); } @@ -314,7 +373,7 @@ on_stream_format_changed (void *data, uint8_t params_buffer[1024]; int32_t width, height, stride, size; struct spa_pod_builder pod_builder; - const struct spa_pod *params[2]; + const struct spa_pod *params[3]; const int bpp = 4; if (!format) @@ -348,6 +407,12 @@ on_stream_format_changed (void *data, ":", pipewire_type->param_meta.type, "I", pipewire_type->meta.VideoCrop, ":", pipewire_type->param_meta.size, "i", sizeof (struct spa_meta_video_crop)); + params[2] = spa_pod_builder_object ( + &pod_builder, + pipewire_type->param.idMeta, pipewire_type->param_meta.Meta, + ":", pipewire_type->param_meta.type, "I", priv->spa_type.meta_cursor, + ":", pipewire_type->param_meta.size, "i", CURSOR_META_SIZE (64, 64)); + pw_stream_finish_format (priv->pipewire_stream, 0, params, G_N_ELEMENTS (params)); } @@ -517,6 +582,7 @@ init_spa_type (MetaSpaType *type, spa_type_media_subtype_map (map, &type->media_subtype); spa_type_format_video_map (map, &type->format_video); spa_type_video_format_map (map, &type->video_format); + type->meta_cursor = spa_type_map_get_id(map, SPA_TYPE_META__Cursor); } static MetaPipeWireSource * diff --git a/src/backends/meta-screen-cast-stream-src.h b/src/backends/meta-screen-cast-stream-src.h index f3b3fd779..f2f96f213 100644 --- a/src/backends/meta-screen-cast-stream-src.h +++ b/src/backends/meta-screen-cast-stream-src.h @@ -24,10 +24,26 @@ #define META_SCREEN_CAST_STREAM_SRC_H #include +#include +#include +#include "backends/meta-backend-private.h" +#include "backends/meta-cursor-renderer.h" +#include "backends/meta-cursor.h" +#include "backends/meta-renderer.h" #include "clutter/clutter.h" +#include "cogl/cogl.h" #include "meta/boxes.h" +typedef struct _MetaSpaType +{ + struct spa_type_media_type media_type; + struct spa_type_media_subtype media_subtype; + struct spa_type_format_video format_video; + struct spa_type_video_format video_format; + uint32_t meta_cursor; +} MetaSpaType; + typedef struct _MetaScreenCastStream MetaScreenCastStream; #define META_TYPE_SCREEN_CAST_STREAM_SRC (meta_screen_cast_stream_src_get_type ()) @@ -46,14 +62,18 @@ struct _MetaScreenCastStreamSrcClass float *frame_rate); void (* enable) (MetaScreenCastStreamSrc *src); void (* disable) (MetaScreenCastStreamSrc *src); - void (* record_frame) (MetaScreenCastStreamSrc *src, - uint8_t *data); + gboolean (* record_frame) (MetaScreenCastStreamSrc *src, + uint8_t *data); gboolean (* get_videocrop) (MetaScreenCastStreamSrc *src, MetaRectangle *crop_rect); + void (* set_cursor_metadata) (MetaScreenCastStreamSrc *src, + struct spa_meta_cursor *spa_meta_cursor); }; void meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc *src); MetaScreenCastStream * meta_screen_cast_stream_src_get_stream (MetaScreenCastStreamSrc *src); +MetaSpaType * meta_screen_cast_stream_src_get_spa_type (MetaScreenCastStreamSrc *src); + #endif /* META_SCREEN_CAST_STREAM_SRC_H */ diff --git a/src/backends/meta-screen-cast-stream.c b/src/backends/meta-screen-cast-stream.c index 875ada01a..c14f8fd85 100644 --- a/src/backends/meta-screen-cast-stream.c +++ b/src/backends/meta-screen-cast-stream.c @@ -34,6 +34,7 @@ enum PROP_SESSION, PROP_CONNECTION, + PROP_CURSOR_MODE, }; enum @@ -52,6 +53,8 @@ typedef struct _MetaScreenCastStreamPrivate GDBusConnection *connection; char *object_path; + MetaScreenCastCursorMode cursor_mode; + MetaScreenCastStreamSrc *src; } MetaScreenCastStreamPrivate; @@ -164,6 +167,15 @@ meta_screen_cast_stream_transform_position (MetaScreenCastStream *stream, y); } +MetaScreenCastCursorMode +meta_screen_cast_stream_get_cursor_mode (MetaScreenCastStream *stream) +{ + MetaScreenCastStreamPrivate *priv = + meta_screen_cast_stream_get_instance_private (stream); + + return priv->cursor_mode; +} + static void meta_screen_cast_stream_set_property (GObject *object, guint prop_id, @@ -182,6 +194,9 @@ meta_screen_cast_stream_set_property (GObject *object, case PROP_CONNECTION: priv->connection = g_value_get_object (value); break; + case PROP_CURSOR_MODE: + priv->cursor_mode = g_value_get_uint (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -205,6 +220,9 @@ meta_screen_cast_stream_get_property (GObject *object, case PROP_CONNECTION: g_value_set_object (value, priv->connection); break; + case PROP_CURSOR_MODE: + g_value_set_uint (value, priv->cursor_mode); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -296,6 +314,18 @@ meta_screen_cast_stream_class_init (MetaScreenCastStreamClass *klass) G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_CURSOR_MODE, + g_param_spec_uint ("cursor-mode", + "cursor-mode", + "Cursor mode", + META_SCREEN_CAST_CURSOR_MODE_HIDDEN, + META_SCREEN_CAST_CURSOR_MODE_METADATA, + META_SCREEN_CAST_CURSOR_MODE_HIDDEN, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + signals[CLOSED] = g_signal_new ("closed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, diff --git a/src/backends/meta-screen-cast-stream.h b/src/backends/meta-screen-cast-stream.h index d394dbb4d..4fc10869d 100644 --- a/src/backends/meta-screen-cast-stream.h +++ b/src/backends/meta-screen-cast-stream.h @@ -65,4 +65,6 @@ void meta_screen_cast_stream_transform_position (MetaScreenCastStream *stream, double *x, double *y); +MetaScreenCastCursorMode meta_screen_cast_stream_get_cursor_mode (MetaScreenCastStream *stream); + #endif /* META_SCREEN_CAST_STREAM_H */ diff --git a/src/backends/meta-screen-cast-window-stream-src.c b/src/backends/meta-screen-cast-window-stream-src.c index c3f9cf5ca..32df9f127 100644 --- a/src/backends/meta-screen-cast-window-stream-src.c +++ b/src/backends/meta-screen-cast-window-stream-src.c @@ -207,7 +207,7 @@ meta_screen_cast_window_stream_src_disable (MetaScreenCastStreamSrc *src) meta_screen_cast_window_stream_src_stop (window_src); } -static void +static gboolean meta_screen_cast_window_stream_src_record_frame (MetaScreenCastStreamSrc *src, uint8_t *data) { @@ -215,6 +215,8 @@ meta_screen_cast_window_stream_src_record_frame (MetaScreenCastStreamSrc *src, META_SCREEN_CAST_WINDOW_STREAM_SRC (src); capture_into (window_src, data); + + return TRUE; } MetaScreenCastWindowStreamSrc * diff --git a/src/backends/meta-screen-cast.h b/src/backends/meta-screen-cast.h index a5450ae0e..994c40c53 100644 --- a/src/backends/meta-screen-cast.h +++ b/src/backends/meta-screen-cast.h @@ -30,6 +30,13 @@ #include "meta-dbus-screen-cast.h" +typedef enum _MetaScreenCastCursorMode +{ + META_SCREEN_CAST_CURSOR_MODE_HIDDEN = 0, + META_SCREEN_CAST_CURSOR_MODE_EMBEDDED = 1, + META_SCREEN_CAST_CURSOR_MODE_METADATA = 2, +} MetaScreenCastCursorMode; + #define META_TYPE_SCREEN_CAST (meta_screen_cast_get_type ()) G_DECLARE_FINAL_TYPE (MetaScreenCast, meta_screen_cast, META, SCREEN_CAST, diff --git a/src/org.gnome.Mutter.ScreenCast.xml b/src/org.gnome.Mutter.ScreenCast.xml index 3cd02b6cb..953809727 100644 --- a/src/org.gnome.Mutter.ScreenCast.xml +++ b/src/org.gnome.Mutter.ScreenCast.xml @@ -71,7 +71,15 @@ Record a single monitor. - Available @properties include: (none) + Available @properties include: + + * "cursor-mode" (u): Cursor mode. Default: 'hidden' (see below) + + Available cursor mode values: + + 0: hidden - cursor is not included in the stream + 1: embedded - cursor is included in the framebuffer + 2: metadata - cursor is included as metadata in the PipeWire stream --> @@ -84,7 +92,7 @@ @properties: Properties used determining what window to select @stream_path: Path to the new stream object - Record a single window. + Record a single window. The cursor will not be included. Available @properties include: