diff --git a/src/backends/meta-screen-cast-session.c b/src/backends/meta-screen-cast-session.c
index 10c37ee88..3925738d4 100644
--- a/src/backends/meta-screen-cast-session.c
+++ b/src/backends/meta-screen-cast-session.c
@@ -30,6 +30,7 @@
#include "backends/meta-screen-cast-area-stream.h"
#include "backends/meta-screen-cast-monitor-stream.h"
#include "backends/meta-screen-cast-stream.h"
+#include "backends/meta-screen-cast-virtual-stream.h"
#include "backends/meta-screen-cast-window-stream.h"
#include "core/display-private.h"
@@ -600,6 +601,83 @@ handle_record_area (MetaDBusScreenCastSession *skeleton,
return TRUE;
}
+static gboolean
+handle_record_virtual (MetaDBusScreenCastSession *skeleton,
+ GDBusMethodInvocation *invocation,
+ GVariant *properties_variant)
+{
+ MetaScreenCastSession *session = META_SCREEN_CAST_SESSION (skeleton);
+ GDBusInterfaceSkeleton *interface_skeleton;
+ GDBusConnection *connection;
+ MetaScreenCastCursorMode cursor_mode;
+ gboolean is_platform;
+ MetaScreenCastFlag flags;
+ g_autoptr (GError) error = NULL;
+ MetaScreenCastVirtualStream *virtual_stream;
+ MetaScreenCastStream *stream;
+ char *stream_path;
+
+ if (!check_permission (session, invocation))
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_ACCESS_DENIED,
+ "Permission denied");
+ 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;
+ }
+ }
+
+ if (!g_variant_lookup (properties_variant, "is-platform", "b", &is_platform))
+ is_platform = FALSE;
+
+ interface_skeleton = G_DBUS_INTERFACE_SKELETON (skeleton);
+ connection = g_dbus_interface_skeleton_get_connection (interface_skeleton);
+
+ flags = META_SCREEN_CAST_FLAG_NONE;
+ if (is_platform)
+ flags |= META_SCREEN_CAST_FLAG_IS_PLATFORM;
+
+ virtual_stream = meta_screen_cast_virtual_stream_new (session,
+ connection,
+ cursor_mode,
+ flags,
+ &error);
+ if (!virtual_stream)
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Failed to record virtual: %s",
+ error->message);
+ return TRUE;
+ }
+
+ stream = META_SCREEN_CAST_STREAM (virtual_stream);
+ stream_path = meta_screen_cast_stream_get_object_path (stream);
+
+ session->streams = g_list_append (session->streams, stream);
+
+ g_signal_connect (stream, "closed", G_CALLBACK (on_stream_closed), session);
+
+ meta_dbus_screen_cast_session_complete_record_virtual (skeleton,
+ invocation,
+ stream_path);
+
+ return TRUE;
+}
+
static void
meta_screen_cast_session_init_iface (MetaDBusScreenCastSessionIface *iface)
{
@@ -608,6 +686,7 @@ meta_screen_cast_session_init_iface (MetaDBusScreenCastSessionIface *iface)
iface->handle_record_monitor = handle_record_monitor;
iface->handle_record_window = handle_record_window;
iface->handle_record_area = handle_record_area;
+ iface->handle_record_virtual = handle_record_virtual;
}
static void
diff --git a/src/backends/meta-screen-cast-virtual-stream-src.c b/src/backends/meta-screen-cast-virtual-stream-src.c
new file mode 100644
index 000000000..47a917da9
--- /dev/null
+++ b/src/backends/meta-screen-cast-virtual-stream-src.c
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) 2021 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include "backends/meta-screen-cast-virtual-stream-src.h"
+
+#include "backends/meta-crtc-mode.h"
+#include "backends/meta-cursor-tracker-private.h"
+#include "backends/meta-screen-cast-session.h"
+#include "backends/meta-stage-private.h"
+#include "backends/meta-virtual-monitor.h"
+#include "core/boxes-private.h"
+
+struct _MetaScreenCastVirtualStreamSrc
+{
+ MetaScreenCastStreamSrc parent;
+
+ MetaVirtualMonitor *virtual_monitor;
+
+ gboolean cursor_bitmap_invalid;
+ gboolean hw_cursor_inhibited;
+
+ MetaStageWatch *watch;
+
+ gulong position_invalidated_handler_id;
+ gulong cursor_changed_handler_id;
+
+ gulong monitors_changed_handler_id;
+};
+
+static void
+hw_cursor_inhibitor_iface_init (MetaHwCursorInhibitorInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (MetaScreenCastVirtualStreamSrc,
+ meta_screen_cast_virtual_stream_src,
+ META_TYPE_SCREEN_CAST_STREAM_SRC,
+ G_IMPLEMENT_INTERFACE (META_TYPE_HW_CURSOR_INHIBITOR,
+ hw_cursor_inhibitor_iface_init))
+
+static gboolean
+meta_screen_cast_virtual_stream_src_get_specs (MetaScreenCastStreamSrc *src,
+ int *width,
+ int *height,
+ float *frame_rate)
+{
+ return FALSE;
+}
+
+static MetaBackend *
+backend_from_src (MetaScreenCastStreamSrc *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 ClutterStageView *
+view_from_src (MetaScreenCastStreamSrc *src)
+{
+ MetaScreenCastVirtualStreamSrc *virtual_src =
+ META_SCREEN_CAST_VIRTUAL_STREAM_SRC (src);
+ MetaVirtualMonitor *virtual_monitor = virtual_src->virtual_monitor;
+ MetaCrtc *crtc = meta_virtual_monitor_get_crtc (virtual_monitor);
+ MetaRenderer *renderer = meta_backend_get_renderer (backend_from_src (src));
+ MetaRendererView *view = meta_renderer_get_view_for_crtc (renderer, crtc);
+
+ return CLUTTER_STAGE_VIEW (view);
+}
+
+static ClutterStage *
+stage_from_src (MetaScreenCastStreamSrc *src)
+{
+ return CLUTTER_STAGE (meta_backend_get_stage (backend_from_src (src)));
+}
+
+static gboolean
+is_redraw_queued (MetaScreenCastVirtualStreamSrc *virtual_src)
+{
+ MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (virtual_src);
+
+ return clutter_stage_is_redraw_queued_on_view (stage_from_src (src),
+ view_from_src (src));
+}
+
+ClutterStageView *
+meta_screen_cast_virtual_stream_src_get_view (MetaScreenCastVirtualStreamSrc *virtual_src)
+{
+ return view_from_src (META_SCREEN_CAST_STREAM_SRC (virtual_src));
+}
+
+static void
+sync_cursor_state (MetaScreenCastVirtualStreamSrc *virtual_src)
+{
+ MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (virtual_src);
+ MetaScreenCastRecordFlag flags;
+
+ if (is_redraw_queued (virtual_src))
+ return;
+
+ if (meta_screen_cast_stream_src_pending_follow_up_frame (src))
+ return;
+
+ flags = META_SCREEN_CAST_RECORD_FLAG_CURSOR_ONLY;
+ meta_screen_cast_stream_src_maybe_record_frame (src, flags);
+}
+
+static void
+pointer_position_invalidated (MetaCursorTracker *cursor_tracker,
+ MetaScreenCastVirtualStreamSrc *virtual_src)
+{
+ sync_cursor_state (virtual_src);
+}
+
+static void
+cursor_changed (MetaCursorTracker *cursor_tracker,
+ MetaScreenCastVirtualStreamSrc *virtual_src)
+{
+ virtual_src->cursor_bitmap_invalid = TRUE;
+ sync_cursor_state (virtual_src);
+}
+
+static void
+inhibit_hw_cursor (MetaScreenCastVirtualStreamSrc *virtual_src)
+{
+ MetaHwCursorInhibitor *inhibitor;
+ MetaBackend *backend;
+
+ g_return_if_fail (!virtual_src->hw_cursor_inhibited);
+
+ backend = backend_from_src (META_SCREEN_CAST_STREAM_SRC (virtual_src));
+ inhibitor = META_HW_CURSOR_INHIBITOR (virtual_src);
+ meta_backend_add_hw_cursor_inhibitor (backend, inhibitor);
+
+ virtual_src->hw_cursor_inhibited = TRUE;
+}
+
+static void
+uninhibit_hw_cursor (MetaScreenCastVirtualStreamSrc *virtual_src)
+{
+ MetaHwCursorInhibitor *inhibitor;
+ MetaBackend *backend;
+
+ g_return_if_fail (virtual_src->hw_cursor_inhibited);
+
+ backend = backend_from_src (META_SCREEN_CAST_STREAM_SRC (virtual_src));
+ inhibitor = META_HW_CURSOR_INHIBITOR (virtual_src);
+ meta_backend_remove_hw_cursor_inhibitor (backend, inhibitor);
+
+ virtual_src->hw_cursor_inhibited = FALSE;
+}
+
+static void
+actors_painted (MetaStage *stage,
+ ClutterStageView *view,
+ ClutterPaintContext *paint_context,
+ gpointer user_data)
+{
+ MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (user_data);
+ MetaScreenCastRecordFlag flags;
+
+ flags = META_SCREEN_CAST_RECORD_FLAG_NONE;
+ meta_screen_cast_stream_src_maybe_record_frame (src, flags);
+}
+
+static void
+add_watch (MetaScreenCastVirtualStreamSrc *virtual_src)
+{
+ MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (virtual_src);
+ MetaStage *meta_stage = META_STAGE (stage_from_src (src));
+
+ g_return_if_fail (!virtual_src->watch);
+
+ virtual_src->watch = meta_stage_watch_view (meta_stage,
+ view_from_src (src),
+ META_STAGE_WATCH_AFTER_PAINT,
+ actors_painted,
+ virtual_src);
+}
+
+static void
+on_monitors_changed (MetaMonitorManager *monitor_manager,
+ MetaScreenCastVirtualStreamSrc *virtual_src)
+{
+ MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (virtual_src);
+ MetaStage *stage = META_STAGE (stage_from_src (src));
+
+ meta_stage_remove_watch (stage, virtual_src->watch);
+ virtual_src->watch = NULL;
+ add_watch (virtual_src);
+}
+
+static void
+init_record_callbacks (MetaScreenCastVirtualStreamSrc *virtual_src)
+{
+ MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (virtual_src);
+ MetaScreenCastStream *stream = meta_screen_cast_stream_src_get_stream (src);
+ MetaBackend *backend = backend_from_src (src);
+ MetaMonitorManager *monitor_manager =
+ meta_backend_get_monitor_manager (backend);
+ MetaCursorTracker *cursor_tracker =
+ meta_backend_get_cursor_tracker (backend);
+
+ switch (meta_screen_cast_stream_get_cursor_mode (stream))
+ {
+ case META_SCREEN_CAST_CURSOR_MODE_METADATA:
+ virtual_src->position_invalidated_handler_id =
+ g_signal_connect_after (cursor_tracker, "position-invalidated",
+ G_CALLBACK (pointer_position_invalidated),
+ virtual_src);
+ virtual_src->cursor_changed_handler_id =
+ g_signal_connect_after (cursor_tracker, "cursor-changed",
+ G_CALLBACK (cursor_changed),
+ virtual_src);
+ G_GNUC_FALLTHROUGH;
+ case META_SCREEN_CAST_CURSOR_MODE_EMBEDDED:
+ case META_SCREEN_CAST_CURSOR_MODE_HIDDEN:
+ add_watch (virtual_src);
+ break;
+ }
+
+ if (meta_screen_cast_stream_get_cursor_mode (stream) ==
+ META_SCREEN_CAST_CURSOR_MODE_EMBEDDED)
+ inhibit_hw_cursor (virtual_src);
+
+ virtual_src->monitors_changed_handler_id =
+ g_signal_connect (monitor_manager, "monitors-changed-internal",
+ G_CALLBACK (on_monitors_changed),
+ virtual_src);
+}
+
+static void
+meta_screen_cast_virtual_stream_src_enable (MetaScreenCastStreamSrc *src)
+{
+ MetaScreenCastVirtualStreamSrc *virtual_src =
+ META_SCREEN_CAST_VIRTUAL_STREAM_SRC (src);
+ MetaScreenCastStream *stream = meta_screen_cast_stream_src_get_stream (src);
+ MetaBackend *backend = backend_from_src (src);
+ MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend);
+
+ switch (meta_screen_cast_stream_get_cursor_mode (stream))
+ {
+ case META_SCREEN_CAST_CURSOR_MODE_METADATA:
+ case META_SCREEN_CAST_CURSOR_MODE_EMBEDDED:
+ meta_cursor_tracker_track_position (cursor_tracker);
+ break;
+ case META_SCREEN_CAST_CURSOR_MODE_HIDDEN:
+ break;
+ }
+
+ init_record_callbacks (virtual_src);
+ clutter_actor_queue_redraw_with_clip (CLUTTER_ACTOR (stage_from_src (src)),
+ NULL);
+ clutter_stage_schedule_update (stage_from_src (src));
+}
+
+static void
+meta_screen_cast_virtual_stream_src_disable (MetaScreenCastStreamSrc *src)
+{
+ MetaScreenCastVirtualStreamSrc *virtual_src =
+ META_SCREEN_CAST_VIRTUAL_STREAM_SRC (src);
+ MetaScreenCastStream *stream = meta_screen_cast_stream_src_get_stream (src);
+ MetaBackend *backend = backend_from_src (src);
+ MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend);
+ MetaMonitorManager *monitor_manager =
+ meta_backend_get_monitor_manager (backend);
+
+ if (virtual_src->hw_cursor_inhibited)
+ uninhibit_hw_cursor (virtual_src);
+
+ if (virtual_src->watch)
+ {
+ meta_stage_remove_watch (META_STAGE (stage_from_src (src)),
+ virtual_src->watch);
+ virtual_src->watch = NULL;
+ }
+
+ g_clear_signal_handler (&virtual_src->position_invalidated_handler_id,
+ cursor_tracker);
+ g_clear_signal_handler (&virtual_src->cursor_changed_handler_id,
+ cursor_tracker);
+
+ g_clear_signal_handler (&virtual_src->monitors_changed_handler_id,
+ monitor_manager);
+
+ switch (meta_screen_cast_stream_get_cursor_mode (stream))
+ {
+ case META_SCREEN_CAST_CURSOR_MODE_METADATA:
+ case META_SCREEN_CAST_CURSOR_MODE_EMBEDDED:
+ meta_cursor_tracker_untrack_position (cursor_tracker);
+ break;
+ case META_SCREEN_CAST_CURSOR_MODE_HIDDEN:
+ break;
+ }
+
+ g_clear_object (&virtual_src->virtual_monitor);
+}
+
+static gboolean
+meta_screen_cast_virtual_stream_src_record_to_buffer (MetaScreenCastStreamSrc *src,
+ int width,
+ int height,
+ int stride,
+ uint8_t *data,
+ GError **error)
+{
+ clutter_stage_capture_view_into (stage_from_src (src),
+ view_from_src (src),
+ NULL,
+ data,
+ stride);
+
+ return TRUE;
+}
+
+static gboolean
+meta_screen_cast_virtual_stream_src_record_to_framebuffer (MetaScreenCastStreamSrc *src,
+ CoglFramebuffer *framebuffer,
+ GError **error)
+{
+ ClutterStageView *view;
+ CoglFramebuffer *view_framebuffer;
+
+ view = view_from_src (src);
+ view_framebuffer = clutter_stage_view_get_framebuffer (view);
+ if (!cogl_blit_framebuffer (view_framebuffer,
+ framebuffer,
+ 0, 0,
+ 0, 0,
+ cogl_framebuffer_get_width (view_framebuffer),
+ cogl_framebuffer_get_height (view_framebuffer),
+ error))
+ return FALSE;
+
+ cogl_framebuffer_flush (framebuffer);
+ return TRUE;
+}
+
+static void
+meta_screen_cast_virtual_stream_record_follow_up (MetaScreenCastStreamSrc *src)
+{
+ MetaRectangle damage;
+
+ clutter_stage_view_get_layout (view_from_src (src), &damage);
+ damage.width = 1;
+ damage.height = 1;
+
+ clutter_actor_queue_redraw_with_clip (CLUTTER_ACTOR (stage_from_src (src)),
+ &damage);
+}
+
+static gboolean
+is_cursor_in_stream (MetaScreenCastVirtualStreamSrc *virtual_src)
+{
+ MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (virtual_src);
+ MetaBackend *backend = backend_from_src (src);
+ MetaCursorRenderer *cursor_renderer =
+ meta_backend_get_cursor_renderer (backend);
+ ClutterStageView *stage_view = view_from_src (src);
+ MetaRectangle view_layout;
+ graphene_rect_t view_rect;
+ MetaCursorSprite *cursor_sprite;
+
+ clutter_stage_view_get_layout (stage_view, &view_layout);
+ view_rect = meta_rectangle_to_graphene_rect (&view_layout);
+
+ cursor_sprite = meta_cursor_renderer_get_cursor (cursor_renderer);
+ if (cursor_sprite)
+ {
+ graphene_rect_t cursor_rect;
+
+ cursor_rect = meta_cursor_renderer_calculate_rect (cursor_renderer,
+ cursor_sprite);
+ return graphene_rect_intersection (&cursor_rect, &view_rect, NULL);
+ }
+ else
+ {
+ MetaCursorTracker *cursor_tracker =
+ meta_backend_get_cursor_tracker (backend);
+ graphene_point_t cursor_position;
+
+ meta_cursor_tracker_get_pointer (cursor_tracker, &cursor_position, NULL);
+ return graphene_rect_contains_point (&view_rect,
+ &cursor_position);
+ }
+}
+
+static void
+meta_screen_cast_virtual_stream_src_set_cursor_metadata (MetaScreenCastStreamSrc *src,
+ struct spa_meta_cursor *spa_meta_cursor)
+{
+ MetaScreenCastVirtualStreamSrc *virtual_src =
+ META_SCREEN_CAST_VIRTUAL_STREAM_SRC (src);
+ MetaBackend *backend = backend_from_src (src);
+ MetaCursorRenderer *cursor_renderer =
+ meta_backend_get_cursor_renderer (backend);
+ MetaCursorTracker *cursor_tracker =
+ meta_backend_get_cursor_tracker (backend);
+ MetaCursorSprite *cursor_sprite;
+ ClutterStageView *stage_view;
+ MetaRectangle view_layout;
+ float view_scale;
+ graphene_rect_t view_rect;
+ graphene_point_t cursor_position;
+ int x, y;
+
+ cursor_sprite = meta_cursor_renderer_get_cursor (cursor_renderer);
+
+ if (!meta_cursor_tracker_get_pointer_visible (cursor_tracker) ||
+ !is_cursor_in_stream (virtual_src))
+ {
+ meta_screen_cast_stream_src_unset_cursor_metadata (src,
+ spa_meta_cursor);
+ return;
+ }
+
+ stage_view = view_from_src (src);
+ clutter_stage_view_get_layout (stage_view, &view_layout);
+ view_rect = meta_rectangle_to_graphene_rect (&view_layout);
+ view_scale = clutter_stage_view_get_scale (stage_view);
+
+ meta_cursor_tracker_get_pointer (cursor_tracker, &cursor_position, NULL);
+ cursor_position.x -= view_rect.origin.x;
+ cursor_position.y -= view_rect.origin.y;
+ cursor_position.x *= view_scale;
+ cursor_position.y *= view_scale;
+
+ x = (int) roundf (cursor_position.x);
+ y = (int) roundf (cursor_position.y);
+
+ if (virtual_src->cursor_bitmap_invalid)
+ {
+ if (cursor_sprite)
+ {
+ float cursor_scale;
+ float scale;
+
+ cursor_scale = meta_cursor_sprite_get_texture_scale (cursor_sprite);
+ scale = view_scale * cursor_scale;
+ 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);
+ }
+
+ virtual_src->cursor_bitmap_invalid = FALSE;
+ }
+ else
+ {
+ meta_screen_cast_stream_src_set_cursor_position_metadata (src,
+ spa_meta_cursor,
+ x, y);
+ }
+}
+
+static MetaVirtualMonitor *
+create_virtual_monitor (MetaScreenCastVirtualStreamSrc *virtual_src,
+ struct spa_video_info_raw *video_format,
+ GError **error)
+{
+ MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (virtual_src);
+ MetaBackend *backend = backend_from_src (src);
+ MetaMonitorManager *monitor_manager =
+ meta_backend_get_monitor_manager (backend);
+ static int virtual_monitor_src_seq = 0;
+ int width, height;
+ float refresh_rate;
+ g_autofree char *serial = NULL;
+ g_autoptr (MetaVirtualMonitorInfo) info = NULL;
+
+ width = video_format->size.width;
+ height = video_format->size.height;
+ refresh_rate = ((float) video_format->max_framerate.num /
+ video_format->max_framerate.denom);
+ serial = g_strdup_printf ("0x%.6x", ++virtual_monitor_src_seq);
+ info = meta_virtual_monitor_info_new (width, height, refresh_rate,
+ "MetaVendor",
+ "Virtual remote monitor",
+ serial);
+ return meta_monitor_manager_create_virtual_monitor (monitor_manager,
+ info,
+ error);
+}
+
+static void
+ensure_virtual_monitor (MetaScreenCastVirtualStreamSrc *virtual_src,
+ struct spa_video_info_raw *video_format)
+{
+ MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (virtual_src);
+ MetaBackend *backend = backend_from_src (src);
+ MetaMonitorManager *monitor_manager =
+ meta_backend_get_monitor_manager (backend);
+ g_autoptr (GError) error = NULL;
+ MetaVirtualMonitor *virtual_monitor;
+
+ virtual_monitor = virtual_src->virtual_monitor;
+ if (virtual_monitor)
+ {
+ MetaCrtcMode *crtc_mode =
+ meta_virtual_monitor_get_crtc_mode (virtual_monitor);
+ const MetaCrtcModeInfo *mode_info = meta_crtc_mode_get_info (crtc_mode);
+
+ if (mode_info->width == video_format->size.width &&
+ mode_info->height == video_format->size.height)
+ return;
+
+ g_clear_object (&virtual_src->virtual_monitor);
+ }
+
+ virtual_monitor = create_virtual_monitor (virtual_src, video_format, &error);
+ if (!virtual_monitor)
+ {
+ MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (virtual_src);
+
+ g_warning ("Failed to create virtual monitor with size %dx%d: %s",
+ video_format->size.width, video_format->size.height,
+ error->message);
+ meta_screen_cast_stream_src_close (src);
+ return;
+ }
+ virtual_src->virtual_monitor = virtual_monitor;
+
+ meta_monitor_manager_reload (monitor_manager);
+}
+
+static void
+meta_screen_cast_virtual_stream_src_notify_params_updated (MetaScreenCastStreamSrc *src,
+ struct spa_video_info_raw *video_format)
+{
+ MetaScreenCastVirtualStreamSrc *virtual_src =
+ META_SCREEN_CAST_VIRTUAL_STREAM_SRC (src);
+
+ ensure_virtual_monitor (virtual_src, video_format);
+}
+
+MetaScreenCastVirtualStreamSrc *
+meta_screen_cast_virtual_stream_src_new (MetaScreenCastVirtualStream *virtual_stream,
+ GError **error)
+{
+ return g_initable_new (META_TYPE_SCREEN_CAST_VIRTUAL_STREAM_SRC, NULL, error,
+ "stream", virtual_stream,
+ NULL);
+}
+
+static gboolean
+meta_screen_cast_virtual_stream_src_is_cursor_inhibited (MetaHwCursorInhibitor *inhibitor)
+{
+ MetaScreenCastVirtualStreamSrc *virtual_src =
+ META_SCREEN_CAST_VIRTUAL_STREAM_SRC (inhibitor);
+
+ return is_cursor_in_stream (virtual_src);
+}
+
+static void
+hw_cursor_inhibitor_iface_init (MetaHwCursorInhibitorInterface *iface)
+{
+ iface->is_cursor_inhibited =
+ meta_screen_cast_virtual_stream_src_is_cursor_inhibited;
+}
+
+static void
+meta_screen_cast_virtual_stream_src_init (MetaScreenCastVirtualStreamSrc *virtual_src)
+{
+}
+
+static void
+meta_screen_cast_virtual_stream_src_class_init (MetaScreenCastVirtualStreamSrcClass *klass)
+{
+ MetaScreenCastStreamSrcClass *src_class =
+ META_SCREEN_CAST_STREAM_SRC_CLASS (klass);
+
+ src_class->get_specs = meta_screen_cast_virtual_stream_src_get_specs;
+ src_class->enable = meta_screen_cast_virtual_stream_src_enable;
+ src_class->disable = meta_screen_cast_virtual_stream_src_disable;
+ src_class->record_to_buffer =
+ meta_screen_cast_virtual_stream_src_record_to_buffer;
+ src_class->record_to_framebuffer =
+ meta_screen_cast_virtual_stream_src_record_to_framebuffer;
+ src_class->record_follow_up =
+ meta_screen_cast_virtual_stream_record_follow_up;
+ src_class->set_cursor_metadata =
+ meta_screen_cast_virtual_stream_src_set_cursor_metadata;
+ src_class->notify_params_updated =
+ meta_screen_cast_virtual_stream_src_notify_params_updated;
+}
diff --git a/src/backends/meta-screen-cast-virtual-stream-src.h b/src/backends/meta-screen-cast-virtual-stream-src.h
new file mode 100644
index 000000000..a891166bd
--- /dev/null
+++ b/src/backends/meta-screen-cast-virtual-stream-src.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef META_SCREEN_CAST_VIRTUAL_STREAM_SRC_H
+#define META_SCREEN_CAST_VIRTUAL_STREAM_SRC_H
+
+#include "backends/meta-screen-cast-stream-src.h"
+#include "backends/meta-screen-cast-virtual-stream.h"
+
+#define META_TYPE_SCREEN_CAST_VIRTUAL_STREAM_SRC (meta_screen_cast_virtual_stream_src_get_type ())
+G_DECLARE_FINAL_TYPE (MetaScreenCastVirtualStreamSrc,
+ meta_screen_cast_virtual_stream_src,
+ META, SCREEN_CAST_VIRTUAL_STREAM_SRC,
+ MetaScreenCastStreamSrc)
+
+MetaScreenCastVirtualStreamSrc * meta_screen_cast_virtual_stream_src_new (MetaScreenCastVirtualStream *virtual_stream,
+ GError **error);
+
+ClutterStageView * meta_screen_cast_virtual_stream_src_get_view (MetaScreenCastVirtualStreamSrc *virtual_src);
+
+#endif /* META_SCREEN_CAST_VIRTUAL_STREAM_SRC_H */
diff --git a/src/backends/meta-screen-cast-virtual-stream.c b/src/backends/meta-screen-cast-virtual-stream.c
new file mode 100644
index 000000000..34dd2a00c
--- /dev/null
+++ b/src/backends/meta-screen-cast-virtual-stream.c
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2021 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include "backends/meta-screen-cast-virtual-stream.h"
+
+#include "backends/meta-screen-cast-virtual-stream-src.h"
+#include "backends/meta-virtual-monitor.h"
+
+
+struct _MetaScreenCastVirtualStream
+{
+ MetaScreenCastStream parent;
+};
+
+G_DEFINE_TYPE (MetaScreenCastVirtualStream,
+ meta_screen_cast_virtual_stream,
+ META_TYPE_SCREEN_CAST_STREAM)
+
+MetaScreenCastVirtualStream *
+meta_screen_cast_virtual_stream_new (MetaScreenCastSession *session,
+ GDBusConnection *connection,
+ MetaScreenCastCursorMode cursor_mode,
+ MetaScreenCastFlag flags,
+ GError **error)
+{
+ MetaScreenCastVirtualStream *virtual_stream;
+
+ virtual_stream = g_initable_new (META_TYPE_SCREEN_CAST_VIRTUAL_STREAM,
+ NULL,
+ error,
+ "session", session,
+ "connection", connection,
+ "cursor-mode", cursor_mode,
+ "flags", flags,
+ NULL);
+ if (!virtual_stream)
+ return NULL;
+
+ return virtual_stream;
+}
+
+static MetaScreenCastStreamSrc *
+meta_screen_cast_virtual_stream_create_src (MetaScreenCastStream *stream,
+ GError **error)
+{
+ MetaScreenCastVirtualStream *virtual_stream =
+ META_SCREEN_CAST_VIRTUAL_STREAM (stream);
+ MetaScreenCastVirtualStreamSrc *virtual_stream_src;
+
+ virtual_stream_src = meta_screen_cast_virtual_stream_src_new (virtual_stream,
+ error);
+ if (!virtual_stream_src)
+ return NULL;
+
+ return META_SCREEN_CAST_STREAM_SRC (virtual_stream_src);
+}
+
+static void
+meta_screen_cast_virtual_stream_set_parameters (MetaScreenCastStream *stream,
+ GVariantBuilder *parameters_builder)
+{
+}
+
+static gboolean
+meta_screen_cast_virtual_stream_transform_position (MetaScreenCastStream *stream,
+ double stream_x,
+ double stream_y,
+ double *x,
+ double *y)
+{
+ MetaScreenCastStreamSrc *src = meta_screen_cast_stream_get_src (stream);
+ MetaScreenCastVirtualStreamSrc *virtual_src =
+ META_SCREEN_CAST_VIRTUAL_STREAM_SRC (src);
+ ClutterStageView *view;
+ MetaRectangle view_layout;
+
+ view = meta_screen_cast_virtual_stream_src_get_view (virtual_src);
+ if (!view)
+ return FALSE;
+
+ clutter_stage_view_get_layout (view, &view_layout);
+ *x = stream_x + view_layout.x;
+ *y = stream_y + view_layout.y;
+
+ return TRUE;
+}
+
+static void
+meta_screen_cast_virtual_stream_init (MetaScreenCastVirtualStream *virtual_stream)
+{
+}
+
+static void
+meta_screen_cast_virtual_stream_class_init (MetaScreenCastVirtualStreamClass *klass)
+{
+ MetaScreenCastStreamClass *stream_class =
+ META_SCREEN_CAST_STREAM_CLASS (klass);
+
+ stream_class->create_src = meta_screen_cast_virtual_stream_create_src;
+ stream_class->set_parameters = meta_screen_cast_virtual_stream_set_parameters;
+ stream_class->transform_position = meta_screen_cast_virtual_stream_transform_position;
+}
diff --git a/src/backends/meta-screen-cast-virtual-stream.h b/src/backends/meta-screen-cast-virtual-stream.h
new file mode 100644
index 000000000..422db5e26
--- /dev/null
+++ b/src/backends/meta-screen-cast-virtual-stream.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifndef META_SCREEN_CAST_VIRTUAL_STREAM_H
+#define META_SCREEN_CAST_VIRTUAL_STREAM_H
+
+#include "backends/meta-screen-cast-stream.h"
+
+#define META_TYPE_SCREEN_CAST_VIRTUAL_STREAM (meta_screen_cast_virtual_stream_get_type ())
+G_DECLARE_FINAL_TYPE (MetaScreenCastVirtualStream,
+ meta_screen_cast_virtual_stream,
+ META, SCREEN_CAST_VIRTUAL_STREAM,
+ MetaScreenCastStream)
+
+MetaScreenCastVirtualStream * meta_screen_cast_virtual_stream_new (MetaScreenCastSession *session,
+ GDBusConnection *connection,
+ MetaScreenCastCursorMode cursor_mode,
+ MetaScreenCastFlag flags,
+ GError **error);
+
+MetaVirtualMonitor * meta_screen_cast_virtual_stream_get_virtual_monitor (MetaScreenCastVirtualStream *virtual_stream);
+
+#endif /* META_SCREEN_CAST_VIRTUAL_STREAM_H */
diff --git a/src/backends/meta-screen-cast.h b/src/backends/meta-screen-cast.h
index e2ea5a5e4..be297e28d 100644
--- a/src/backends/meta-screen-cast.h
+++ b/src/backends/meta-screen-cast.h
@@ -41,6 +41,7 @@ typedef enum _MetaScreenCastFlag
{
META_SCREEN_CAST_FLAG_NONE = 0,
META_SCREEN_CAST_FLAG_IS_RECORDING = 1 << 0,
+ META_SCREEN_CAST_FLAG_IS_PLATFORM = 1 << 1,
} MetaScreenCastFlag;
#define META_TYPE_SCREEN_CAST (meta_screen_cast_get_type ())
diff --git a/src/meson.build b/src/meson.build
index fe3d1ccaf..459a56315 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -480,6 +480,10 @@ if have_remote_desktop
'backends/meta-screen-cast-monitor-stream.h',
'backends/meta-screen-cast-monitor-stream-src.c',
'backends/meta-screen-cast-monitor-stream-src.h',
+ 'backends/meta-screen-cast-virtual-stream-src.c',
+ 'backends/meta-screen-cast-virtual-stream-src.h',
+ 'backends/meta-screen-cast-virtual-stream.c',
+ 'backends/meta-screen-cast-virtual-stream.h',
'backends/meta-screen-cast-window-stream-src.c',
'backends/meta-screen-cast-window-stream-src.h',
'backends/meta-screen-cast-window-stream.c',
diff --git a/src/org.gnome.Mutter.ScreenCast.xml b/src/org.gnome.Mutter.ScreenCast.xml
index 07ab402eb..d9f1f4435 100644
--- a/src/org.gnome.Mutter.ScreenCast.xml
+++ b/src/org.gnome.Mutter.ScreenCast.xml
@@ -153,6 +153,35 @@
+
+
+
+
+
+