[mutter] screen-cast: Add RecordArea for screen cast arbitrary area



commit c4535fdf85587e5cdf3613fad764d878b2e57fb4
Author: Jonas Ådahl <jadahl gmail com>
Date:   Wed Mar 4 21:42:52 2020 +0100

    screen-cast: Add RecordArea for screen cast arbitrary area
    
    It takes coordinates in stage coordinate space, and will result in
    a screen cast stream consisting of that area, but scaled up by the scale
    factor of the view that overlaps with the area and has the highest scale
    factor.
    
    https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1207

 clutter/clutter/clutter-mutter.h                |   3 +
 clutter/clutter/clutter-stage-private.h         |   2 -
 clutter/clutter/clutter-stage.c                 |   9 +-
 src/backends/meta-screen-cast-area-stream-src.c | 570 ++++++++++++++++++++++++
 src/backends/meta-screen-cast-area-stream-src.h |  37 ++
 src/backends/meta-screen-cast-area-stream.c     | 177 ++++++++
 src/backends/meta-screen-cast-area-stream.h     |  48 ++
 src/backends/meta-screen-cast-session.c         |  86 ++++
 src/meson.build                                 |   4 +
 src/org.gnome.Mutter.ScreenCast.xml             |  33 ++
 10 files changed, 964 insertions(+), 5 deletions(-)
---
diff --git a/clutter/clutter/clutter-mutter.h b/clutter/clutter/clutter-mutter.h
index 7506e74da..d5dbf89e6 100644
--- a/clutter/clutter/clutter-mutter.h
+++ b/clutter/clutter/clutter-mutter.h
@@ -36,6 +36,9 @@
 #include "cogl/clutter-stage-cogl.h"
 #include "clutter/x11/clutter-backend-x11.h"
 
+CLUTTER_EXPORT
+GList * clutter_stage_peek_stage_views (ClutterStage *stage);
+
 CLUTTER_EXPORT
 void clutter_set_custom_backend_func (ClutterBackend *(* func) (void));
 
diff --git a/clutter/clutter/clutter-stage-private.h b/clutter/clutter/clutter-stage-private.h
index c8c1ef34a..db2ee4baf 100644
--- a/clutter/clutter/clutter-stage-private.h
+++ b/clutter/clutter/clutter-stage-private.h
@@ -139,8 +139,6 @@ void            _clutter_stage_presented                (ClutterStage      *stag
                                                          CoglFrameEvent     frame_event,
                                                          ClutterFrameInfo  *frame_info);
 
-GList *         _clutter_stage_peek_stage_views         (ClutterStage *stage);
-
 void            clutter_stage_queue_actor_relayout      (ClutterStage *stage,
                                                          ClutterActor *actor);
 
diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c
index c97d9bd9a..d656ec2e9 100644
--- a/clutter/clutter/clutter-stage.c
+++ b/clutter/clutter/clutter-stage.c
@@ -552,7 +552,7 @@ clutter_stage_add_redraw_clip (ClutterStage          *stage,
 {
   GList *l;
 
-  for (l = _clutter_stage_peek_stage_views (stage); l; l = l->next)
+  for (l = clutter_stage_peek_stage_views (stage); l; l = l->next)
     {
       ClutterStageView *view = l->data;
 
@@ -1573,7 +1573,7 @@ is_full_stage_redraw_queued (ClutterStage *stage)
 {
   GList *l;
 
-  for (l = _clutter_stage_peek_stage_views (stage); l; l = l->next)
+  for (l = clutter_stage_peek_stage_views (stage); l; l = l->next)
     {
       ClutterStageView *view = l->data;
 
@@ -4402,8 +4402,11 @@ clutter_stage_thaw_updates (ClutterStage *stage)
     }
 }
 
+/**
+ * clutter_stage_peek_stage_views: (skip)
+ */
 GList *
-_clutter_stage_peek_stage_views (ClutterStage *stage)
+clutter_stage_peek_stage_views (ClutterStage *stage)
 {
   ClutterStagePrivate *priv = stage->priv;
 
diff --git a/src/backends/meta-screen-cast-area-stream-src.c b/src/backends/meta-screen-cast-area-stream-src.c
new file mode 100644
index 000000000..1fe94d448
--- /dev/null
+++ b/src/backends/meta-screen-cast-area-stream-src.c
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2020 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-area-stream-src.h"
+
+#include <spa/buffer/meta.h>
+
+#include "backends/meta-backend-private.h"
+#include "backends/meta-cursor-tracker-private.h"
+#include "backends/meta-screen-cast-area-stream.h"
+#include "backends/meta-screen-cast-session.h"
+#include "backends/meta-stage-private.h"
+#include "clutter/clutter.h"
+#include "clutter/clutter-mutter.h"
+#include "core/boxes-private.h"
+
+struct _MetaScreenCastAreaStreamSrc
+{
+  MetaScreenCastStreamSrc parent;
+
+  gboolean cursor_bitmap_invalid;
+  gboolean hw_cursor_inhibited;
+
+  GList *watches;
+
+  gulong cursor_moved_handler_id;
+  gulong cursor_changed_handler_id;
+
+  guint maybe_record_idle_id;
+};
+
+static void
+hw_cursor_inhibitor_iface_init (MetaHwCursorInhibitorInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (MetaScreenCastAreaStreamSrc,
+                         meta_screen_cast_area_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 (MetaScreenCastAreaStreamSrc *area_src)
+{
+  MetaScreenCastStreamSrc *src;
+  MetaScreenCastStream *stream;
+  MetaScreenCastAreaStream *area_stream;
+
+  src = META_SCREEN_CAST_STREAM_SRC (area_src);
+  stream = meta_screen_cast_stream_src_get_stream (src);
+  area_stream = META_SCREEN_CAST_AREA_STREAM (stream);
+
+  return meta_screen_cast_area_stream_get_stage (area_stream);
+}
+
+static MetaBackend *
+get_backend (MetaScreenCastAreaStreamSrc *area_src)
+{
+  MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (area_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 MetaCursorRenderer *
+get_cursor_renderer (MetaScreenCastAreaStreamSrc *area_src)
+{
+  MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (area_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
+meta_screen_cast_area_stream_src_get_specs (MetaScreenCastStreamSrc *src,
+                                            int                     *width,
+                                            int                     *height,
+                                            float                   *frame_rate)
+{
+  MetaScreenCastStream *stream = meta_screen_cast_stream_src_get_stream (src);
+  MetaScreenCastAreaStream *area_stream = META_SCREEN_CAST_AREA_STREAM (stream);
+  MetaRectangle *area;
+  float scale;
+
+  area = meta_screen_cast_area_stream_get_area (area_stream);
+  scale = meta_screen_cast_area_stream_get_scale (area_stream);
+
+  *width = (int) roundf (area->width * scale);
+  *height = (int) roundf (area->height * scale);
+  *frame_rate = 60.0;
+}
+
+static gboolean
+is_cursor_in_stream (MetaScreenCastAreaStreamSrc *area_src)
+{
+  MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (area_src);
+  MetaScreenCastStream *stream = meta_screen_cast_stream_src_get_stream (src);
+  MetaScreenCastAreaStream *area_stream = META_SCREEN_CAST_AREA_STREAM (stream);
+  MetaBackend *backend = get_backend (area_src);
+  MetaCursorRenderer *cursor_renderer =
+    meta_backend_get_cursor_renderer (backend);
+  MetaRectangle *area;
+  graphene_rect_t area_rect;
+  MetaCursorSprite *cursor_sprite;
+
+  area = meta_screen_cast_area_stream_get_area (area_stream);
+  area_rect = meta_rectangle_to_graphene_rect (area);
+
+  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, &area_rect, NULL);
+    }
+  else
+    {
+      graphene_point_t cursor_position;
+
+      cursor_position = meta_cursor_renderer_get_position (cursor_renderer);
+      return graphene_rect_contains_point (&area_rect, &cursor_position);
+    }
+}
+
+static void
+sync_cursor_state (MetaScreenCastAreaStreamSrc *area_src)
+{
+  MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (area_src);
+  ClutterStage *stage = get_stage (area_src);
+
+  if (!is_cursor_in_stream (area_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,
+              MetaScreenCastAreaStreamSrc *area_src)
+{
+  sync_cursor_state (area_src);
+}
+
+static void
+cursor_changed (MetaCursorTracker           *cursor_tracker,
+                MetaScreenCastAreaStreamSrc *area_src)
+{
+  area_src->cursor_bitmap_invalid = TRUE;
+  sync_cursor_state (area_src);
+}
+
+static void
+inhibit_hw_cursor (MetaScreenCastAreaStreamSrc *area_src)
+{
+  MetaCursorRenderer *cursor_renderer;
+  MetaHwCursorInhibitor *inhibitor;
+
+  g_return_if_fail (!area_src->hw_cursor_inhibited);
+
+  cursor_renderer = get_cursor_renderer (area_src);
+  inhibitor = META_HW_CURSOR_INHIBITOR (area_src);
+  meta_cursor_renderer_add_hw_cursor_inhibitor (cursor_renderer, inhibitor);
+
+  area_src->hw_cursor_inhibited = TRUE;
+}
+
+static void
+uninhibit_hw_cursor (MetaScreenCastAreaStreamSrc *area_src)
+{
+  MetaCursorRenderer *cursor_renderer;
+  MetaHwCursorInhibitor *inhibitor;
+
+  g_return_if_fail (area_src->hw_cursor_inhibited);
+
+  cursor_renderer = get_cursor_renderer (area_src);
+  inhibitor = META_HW_CURSOR_INHIBITOR (area_src);
+  meta_cursor_renderer_remove_hw_cursor_inhibitor (cursor_renderer, inhibitor);
+
+  area_src->hw_cursor_inhibited = FALSE;
+}
+
+static gboolean
+maybe_record_frame_on_idle (gpointer user_data)
+{
+  MetaScreenCastAreaStreamSrc *area_src =
+    META_SCREEN_CAST_AREA_STREAM_SRC (user_data);
+  MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (area_src);
+
+  area_src->maybe_record_idle_id = 0;
+
+  meta_screen_cast_stream_src_maybe_record_frame (src);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+stage_painted (MetaStage           *stage,
+               ClutterStageView    *view,
+               ClutterPaintContext *paint_context,
+               gpointer             user_data)
+{
+  MetaScreenCastAreaStreamSrc *area_src =
+    META_SCREEN_CAST_AREA_STREAM_SRC (user_data);
+  MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (area_src);
+  MetaScreenCastStream *stream = meta_screen_cast_stream_src_get_stream (src);
+  MetaScreenCastAreaStream *area_stream = META_SCREEN_CAST_AREA_STREAM (stream);
+  const cairo_region_t *redraw_clip;
+  MetaRectangle *area;
+
+  if (area_src->maybe_record_idle_id)
+    return;
+
+  area = meta_screen_cast_area_stream_get_area (area_stream);
+  redraw_clip = clutter_paint_context_get_redraw_clip (paint_context);
+
+  if (redraw_clip)
+    {
+      switch (cairo_region_contains_rectangle (redraw_clip, area))
+        {
+        case CAIRO_REGION_OVERLAP_IN:
+        case CAIRO_REGION_OVERLAP_PART:
+          break;
+        case CAIRO_REGION_OVERLAP_OUT:
+          return;
+        }
+    }
+
+  area_src->maybe_record_idle_id = g_idle_add (maybe_record_frame_on_idle, src);
+}
+
+static void
+add_view_painted_watches (MetaScreenCastAreaStreamSrc *area_src,
+                          MetaStageWatchPhase          watch_phase)
+{
+  MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (area_src);
+  MetaScreenCastStream *stream = meta_screen_cast_stream_src_get_stream (src);
+  MetaScreenCastAreaStream *area_stream = META_SCREEN_CAST_AREA_STREAM (stream);
+  MetaBackend *backend = get_backend (area_src);
+  MetaRenderer *renderer = meta_backend_get_renderer (backend);
+  ClutterStage *stage;
+  MetaStage *meta_stage;
+  MetaRectangle *area;
+  GList *l;
+
+  stage = get_stage (area_src);
+  meta_stage = META_STAGE (stage);
+  area = meta_screen_cast_area_stream_get_area (area_stream);
+
+  for (l = meta_renderer_get_views (renderer); l; l = l->next)
+    {
+      MetaRendererView *view = l->data;
+      MetaRectangle view_layout;
+
+      clutter_stage_view_get_layout (CLUTTER_STAGE_VIEW (view), &view_layout);
+      if (meta_rectangle_overlap (area, &view_layout))
+        {
+          MetaStageWatch *watch;
+
+          watch = meta_stage_watch_view (meta_stage,
+                                         CLUTTER_STAGE_VIEW (view),
+                                         watch_phase,
+                                         stage_painted,
+                                         area_src);
+
+          area_src->watches = g_list_prepend (area_src->watches, watch);
+        }
+    }
+}
+
+static void
+meta_screen_cast_area_stream_src_enable (MetaScreenCastStreamSrc *src)
+{
+  MetaScreenCastAreaStreamSrc *area_src =
+    META_SCREEN_CAST_AREA_STREAM_SRC (src);
+  MetaBackend *backend = get_backend (area_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 (area_src);
+
+  switch (meta_screen_cast_stream_get_cursor_mode (stream))
+    {
+    case META_SCREEN_CAST_CURSOR_MODE_METADATA:
+      area_src->cursor_moved_handler_id =
+        g_signal_connect_after (cursor_tracker, "cursor-moved",
+                                G_CALLBACK (cursor_moved),
+                                area_src);
+      area_src->cursor_changed_handler_id =
+        g_signal_connect_after (cursor_tracker, "cursor-changed",
+                                G_CALLBACK (cursor_changed),
+                                area_src);
+      G_GNUC_FALLTHROUGH;
+    case META_SCREEN_CAST_CURSOR_MODE_HIDDEN:
+      add_view_painted_watches (area_src,
+                                META_STAGE_WATCH_AFTER_ACTOR_PAINT);
+      break;
+    case META_SCREEN_CAST_CURSOR_MODE_EMBEDDED:
+      inhibit_hw_cursor (area_src);
+      add_view_painted_watches (area_src,
+                                META_STAGE_WATCH_AFTER_ACTOR_PAINT);
+      break;
+    }
+
+  clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
+}
+
+static void
+meta_screen_cast_area_stream_src_disable (MetaScreenCastStreamSrc *src)
+{
+  MetaScreenCastAreaStreamSrc *area_src =
+    META_SCREEN_CAST_AREA_STREAM_SRC (src);
+  MetaBackend *backend = get_backend (area_src);
+  MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend);
+  ClutterStage *stage;
+  MetaStage *meta_stage;
+  GList *l;
+
+  stage = get_stage (area_src);
+  meta_stage = META_STAGE (stage);
+
+  for (l = area_src->watches; l; l = l->next)
+    {
+      MetaStageWatch *watch = l->data;
+
+      meta_stage_remove_watch (meta_stage, watch);
+    }
+  g_clear_pointer (&area_src->watches, g_list_free);
+
+  if (area_src->hw_cursor_inhibited)
+    uninhibit_hw_cursor (area_src);
+
+  g_clear_signal_handler (&area_src->cursor_moved_handler_id,
+                          cursor_tracker);
+  g_clear_signal_handler (&area_src->cursor_changed_handler_id,
+                          cursor_tracker);
+
+  g_clear_handle_id (&area_src->maybe_record_idle_id, g_source_remove);
+}
+
+static gboolean
+meta_screen_cast_area_stream_src_record_frame (MetaScreenCastStreamSrc *src,
+                                               uint8_t                 *data)
+{
+  MetaScreenCastAreaStreamSrc *area_src =
+    META_SCREEN_CAST_AREA_STREAM_SRC (src);
+  MetaScreenCastStream *stream = meta_screen_cast_stream_src_get_stream (src);
+  MetaScreenCastAreaStream *area_stream = META_SCREEN_CAST_AREA_STREAM (stream);
+  ClutterStage *stage;
+  MetaRectangle *area;
+  float scale;
+  int stride;
+  ClutterPaintFlag paint_flags = CLUTTER_PAINT_FLAG_NONE;
+  g_autoptr (GError) error = NULL;
+
+  stage = get_stage (area_src);
+  area = meta_screen_cast_area_stream_get_area (area_stream);
+  scale = meta_screen_cast_area_stream_get_scale (area_stream);
+  stride = meta_screen_cast_stream_src_get_stride (src);
+
+  switch (meta_screen_cast_stream_get_cursor_mode (stream))
+    {
+    case META_SCREEN_CAST_CURSOR_MODE_METADATA:
+    case META_SCREEN_CAST_CURSOR_MODE_HIDDEN:
+      paint_flags |= CLUTTER_PAINT_FLAG_NO_CURSORS;
+      break;
+    case META_SCREEN_CAST_CURSOR_MODE_EMBEDDED:
+      break;
+    }
+
+  if (!clutter_stage_paint_to_buffer (stage, area, scale,
+                                      data,
+                                      stride,
+                                      CLUTTER_CAIRO_FORMAT_ARGB32,
+                                      paint_flags,
+                                      &error))
+    {
+      g_warning ("Failed to record area: %s", error->message);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+meta_screen_cast_area_stream_src_blit_to_framebuffer (MetaScreenCastStreamSrc *src,
+                                                      CoglFramebuffer         *framebuffer)
+{
+  MetaScreenCastAreaStreamSrc *area_src =
+    META_SCREEN_CAST_AREA_STREAM_SRC (src);
+  MetaScreenCastStream *stream = meta_screen_cast_stream_src_get_stream (src);
+  MetaScreenCastAreaStream *area_stream = META_SCREEN_CAST_AREA_STREAM (stream);
+  MetaBackend *backend = get_backend (area_src);
+  ClutterStage *stage;
+  MetaRectangle *area;
+  float scale;
+  ClutterPaintFlag paint_flags = CLUTTER_PAINT_FLAG_NONE;
+
+  stage = CLUTTER_STAGE (meta_backend_get_stage (backend));
+  area = meta_screen_cast_area_stream_get_area (area_stream);
+  scale = meta_screen_cast_area_stream_get_scale (area_stream);
+
+  switch (meta_screen_cast_stream_get_cursor_mode (stream))
+    {
+    case META_SCREEN_CAST_CURSOR_MODE_METADATA:
+    case META_SCREEN_CAST_CURSOR_MODE_HIDDEN:
+      paint_flags |= CLUTTER_PAINT_FLAG_NO_CURSORS;
+      break;
+    case META_SCREEN_CAST_CURSOR_MODE_EMBEDDED:
+      break;
+    }
+  clutter_stage_paint_to_framebuffer (stage, framebuffer,
+                                      area, scale,
+                                      paint_flags);
+
+  cogl_framebuffer_finish (framebuffer);
+
+  return TRUE;
+}
+
+static void
+meta_screen_cast_area_stream_src_set_cursor_metadata (MetaScreenCastStreamSrc *src,
+                                                      struct spa_meta_cursor  *spa_meta_cursor)
+{
+  MetaScreenCastAreaStreamSrc *area_src =
+    META_SCREEN_CAST_AREA_STREAM_SRC (src);
+  MetaScreenCastStream *stream = meta_screen_cast_stream_src_get_stream (src);
+  MetaScreenCastAreaStream *area_stream = META_SCREEN_CAST_AREA_STREAM (stream);
+  MetaBackend *backend = get_backend (area_src);
+  MetaCursorRenderer *cursor_renderer =
+    meta_backend_get_cursor_renderer (backend);
+  MetaCursorSprite *cursor_sprite;
+  MetaRectangle *area;
+  float scale;
+  graphene_point_t cursor_position;
+  int x, y;
+
+  cursor_sprite = meta_cursor_renderer_get_cursor (cursor_renderer);
+
+  if (!is_cursor_in_stream (area_src))
+    {
+      meta_screen_cast_stream_src_unset_cursor_metadata (src,
+                                                         spa_meta_cursor);
+      return;
+    }
+
+  area = meta_screen_cast_area_stream_get_area (area_stream);
+  scale = meta_screen_cast_area_stream_get_scale (area_stream);
+
+  cursor_position = meta_cursor_renderer_get_position (cursor_renderer);
+  cursor_position.x -= area->x;
+  cursor_position.y -= area->y;
+  cursor_position.x *= scale;
+  cursor_position.y *= scale;
+
+  x = (int) roundf (cursor_position.x);
+  y = (int) roundf (cursor_position.y);
+
+  if (area_src->cursor_bitmap_invalid)
+    {
+      if (cursor_sprite)
+        {
+          float cursor_scale;
+          float metadata_scale;
+
+          cursor_scale = meta_cursor_sprite_get_texture_scale (cursor_sprite);
+          metadata_scale = scale * cursor_scale;
+          meta_screen_cast_stream_src_set_cursor_sprite_metadata (src,
+                                                                  spa_meta_cursor,
+                                                                  cursor_sprite,
+                                                                  x, y,
+                                                                  metadata_scale);
+        }
+      else
+        {
+          meta_screen_cast_stream_src_set_empty_cursor_sprite_metadata (src,
+                                                                        spa_meta_cursor,
+                                                                        x, y);
+        }
+
+      area_src->cursor_bitmap_invalid = FALSE;
+    }
+  else
+    {
+      meta_screen_cast_stream_src_set_cursor_position_metadata (src,
+                                                                spa_meta_cursor,
+                                                                x, y);
+    }
+}
+
+static gboolean
+meta_screen_cast_area_stream_src_is_cursor_sprite_inhibited (MetaHwCursorInhibitor *inhibitor,
+                                                             MetaCursorSprite      *cursor_sprite)
+{
+  MetaScreenCastAreaStreamSrc *area_src =
+    META_SCREEN_CAST_AREA_STREAM_SRC (inhibitor);
+
+  return is_cursor_in_stream (area_src);
+}
+
+static void
+hw_cursor_inhibitor_iface_init (MetaHwCursorInhibitorInterface *iface)
+{
+  iface->is_cursor_sprite_inhibited =
+    meta_screen_cast_area_stream_src_is_cursor_sprite_inhibited;
+}
+
+MetaScreenCastAreaStreamSrc *
+meta_screen_cast_area_stream_src_new (MetaScreenCastAreaStream  *area_stream,
+                                      GError                   **error)
+{
+  return g_initable_new (META_TYPE_SCREEN_CAST_AREA_STREAM_SRC, NULL, error,
+                         "stream", area_stream,
+                         NULL);
+}
+
+static void
+meta_screen_cast_area_stream_src_init (MetaScreenCastAreaStreamSrc *area_src)
+{
+  area_src->cursor_bitmap_invalid = TRUE;
+}
+
+static void
+meta_screen_cast_area_stream_src_class_init (MetaScreenCastAreaStreamSrcClass *klass)
+{
+  MetaScreenCastStreamSrcClass *src_class =
+    META_SCREEN_CAST_STREAM_SRC_CLASS (klass);
+
+  src_class->get_specs = meta_screen_cast_area_stream_src_get_specs;
+  src_class->enable = meta_screen_cast_area_stream_src_enable;
+  src_class->disable = meta_screen_cast_area_stream_src_disable;
+  src_class->record_frame = meta_screen_cast_area_stream_src_record_frame;
+  src_class->blit_to_framebuffer =
+    meta_screen_cast_area_stream_src_blit_to_framebuffer;
+  src_class->set_cursor_metadata =
+    meta_screen_cast_area_stream_src_set_cursor_metadata;
+}
diff --git a/src/backends/meta-screen-cast-area-stream-src.h b/src/backends/meta-screen-cast-area-stream-src.h
new file mode 100644
index 000000000..72261642c
--- /dev/null
+++ b/src/backends/meta-screen-cast-area-stream-src.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 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_AREA_STREAM_SRC_H
+#define META_SCREEN_CAST_AREA_STREAM_SRC_H
+
+#include "backends/meta-screen-cast-stream-src.h"
+
+typedef struct _MetaScreenCastAreaStream MetaScreenCastAreaStream;
+
+#define META_TYPE_SCREEN_CAST_AREA_STREAM_SRC (meta_screen_cast_area_stream_src_get_type ())
+G_DECLARE_FINAL_TYPE (MetaScreenCastAreaStreamSrc,
+                      meta_screen_cast_area_stream_src,
+                      META, SCREEN_CAST_AREA_STREAM_SRC,
+                      MetaScreenCastStreamSrc)
+
+MetaScreenCastAreaStreamSrc * meta_screen_cast_area_stream_src_new (MetaScreenCastAreaStream  *area_stream,
+                                                                    GError                   **error);
+
+#endif /* META_SCREEN_CAST_AREA_STREAM_SRC_H */
diff --git a/src/backends/meta-screen-cast-area-stream.c b/src/backends/meta-screen-cast-area-stream.c
new file mode 100644
index 000000000..98883b2fc
--- /dev/null
+++ b/src/backends/meta-screen-cast-area-stream.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2020 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-area-stream.h"
+
+#include "backends/meta-screen-cast-area-stream-src.h"
+
+struct _MetaScreenCastAreaStream
+{
+  MetaScreenCastStream parent;
+
+  ClutterStage *stage;
+
+  MetaRectangle area;
+  float scale;
+};
+
+G_DEFINE_TYPE (MetaScreenCastAreaStream,
+               meta_screen_cast_area_stream,
+               META_TYPE_SCREEN_CAST_STREAM)
+
+ClutterStage *
+meta_screen_cast_area_stream_get_stage (MetaScreenCastAreaStream *area_stream)
+{
+  return area_stream->stage;
+}
+
+MetaRectangle *
+meta_screen_cast_area_stream_get_area (MetaScreenCastAreaStream *area_stream)
+{
+  return &area_stream->area;
+}
+
+float
+meta_screen_cast_area_stream_get_scale (MetaScreenCastAreaStream *area_stream)
+{
+  return area_stream->scale;
+}
+
+static gboolean
+calculate_scale (ClutterStage  *stage,
+                 MetaRectangle *area,
+                 float         *out_scale)
+{
+  GList *l;
+  float scale = 0.0;
+
+  for (l = clutter_stage_peek_stage_views (stage); l; l = l->next)
+    {
+      ClutterStageView *stage_view = l->data;
+      MetaRectangle view_layout;
+
+      clutter_stage_view_get_layout (stage_view, &view_layout);
+      if (meta_rectangle_overlap (area, &view_layout))
+        scale = MAX (clutter_stage_view_get_scale (stage_view), scale);
+    }
+
+  if (scale == 0.0)
+    return FALSE;
+
+  *out_scale = scale;
+  return TRUE;
+}
+
+MetaScreenCastAreaStream *
+meta_screen_cast_area_stream_new (MetaScreenCastSession     *session,
+                                  GDBusConnection           *connection,
+                                  MetaRectangle             *area,
+                                  ClutterStage              *stage,
+                                  MetaScreenCastCursorMode   cursor_mode,
+                                  GError                   **error)
+{
+  MetaScreenCastAreaStream *area_stream;
+  float scale;
+
+  if (!calculate_scale (stage, area, &scale))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Area is off-screen");
+      return NULL;
+    }
+
+  area_stream = g_initable_new (META_TYPE_SCREEN_CAST_AREA_STREAM,
+                                NULL,
+                                error,
+                                "session", session,
+                                "connection", connection,
+                                "cursor-mode", cursor_mode,
+                                NULL);
+  if (!area_stream)
+    return NULL;
+
+  area_stream->area = *area;
+  area_stream->scale = scale;
+  area_stream->stage = stage;
+
+  return area_stream;
+}
+
+static MetaScreenCastStreamSrc *
+meta_screen_cast_area_stream_create_src (MetaScreenCastStream  *stream,
+                                         GError               **error)
+{
+  MetaScreenCastAreaStream *area_stream =
+    META_SCREEN_CAST_AREA_STREAM (stream);
+  MetaScreenCastAreaStreamSrc *area_stream_src;
+
+  area_stream_src = meta_screen_cast_area_stream_src_new (area_stream,
+                                                          error);
+  if (!area_stream_src)
+    return NULL;
+
+  return META_SCREEN_CAST_STREAM_SRC (area_stream_src);
+}
+
+static void
+meta_screen_cast_area_stream_set_parameters (MetaScreenCastStream *stream,
+                                             GVariantBuilder      *parameters_builder)
+{
+  MetaScreenCastAreaStream *area_stream =
+    META_SCREEN_CAST_AREA_STREAM (stream);
+
+  g_variant_builder_add (parameters_builder, "{sv}",
+                         "size",
+                         g_variant_new ("(ii)",
+                                        area_stream->area.width,
+                                        area_stream->area.height));
+}
+
+static void
+meta_screen_cast_area_stream_transform_position (MetaScreenCastStream *stream,
+                                                 double                stream_x,
+                                                 double                stream_y,
+                                                 double               *x,
+                                                 double               *y)
+{
+  MetaScreenCastAreaStream *area_stream =
+    META_SCREEN_CAST_AREA_STREAM (stream);
+
+  *x = area_stream->area.x + (int) roundf (stream_x / area_stream->scale);
+  *y = area_stream->area.y + (int) roundf (stream_y / area_stream->scale);
+}
+
+static void
+meta_screen_cast_area_stream_init (MetaScreenCastAreaStream *area_stream)
+{
+}
+
+static void
+meta_screen_cast_area_stream_class_init (MetaScreenCastAreaStreamClass *klass)
+{
+  MetaScreenCastStreamClass *stream_class =
+    META_SCREEN_CAST_STREAM_CLASS (klass);
+
+  stream_class->create_src = meta_screen_cast_area_stream_create_src;
+  stream_class->set_parameters = meta_screen_cast_area_stream_set_parameters;
+  stream_class->transform_position = meta_screen_cast_area_stream_transform_position;
+}
diff --git a/src/backends/meta-screen-cast-area-stream.h b/src/backends/meta-screen-cast-area-stream.h
new file mode 100644
index 000000000..47d1079e0
--- /dev/null
+++ b/src/backends/meta-screen-cast-area-stream.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 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_AREA_STREAM_H
+#define META_SCREEN_CAST_AREA_STREAM_H
+
+#include <glib-object.h>
+
+#include "backends/meta-screen-cast-stream.h"
+#include "backends/meta-screen-cast.h"
+
+#define META_TYPE_SCREEN_CAST_AREA_STREAM (meta_screen_cast_area_stream_get_type ())
+G_DECLARE_FINAL_TYPE (MetaScreenCastAreaStream,
+                      meta_screen_cast_area_stream,
+                      META, SCREEN_CAST_AREA_STREAM,
+                      MetaScreenCastStream)
+
+MetaScreenCastAreaStream * meta_screen_cast_area_stream_new (MetaScreenCastSession     *session,
+                                                             GDBusConnection           *connection,
+                                                             MetaRectangle             *area,
+                                                             ClutterStage              *stage,
+                                                             MetaScreenCastCursorMode   cursor_mode,
+                                                             GError                   **error);
+
+ClutterStage * meta_screen_cast_area_stream_get_stage (MetaScreenCastAreaStream *area_stream);
+
+MetaRectangle * meta_screen_cast_area_stream_get_area (MetaScreenCastAreaStream *area_stream);
+
+float meta_screen_cast_area_stream_get_scale (MetaScreenCastAreaStream *area_stream);
+
+#endif /* META_SCREEN_CAST_AREA_STREAM_H */
diff --git a/src/backends/meta-screen-cast-session.c b/src/backends/meta-screen-cast-session.c
index 7a1b8e07a..377dafd1f 100644
--- a/src/backends/meta-screen-cast-session.c
+++ b/src/backends/meta-screen-cast-session.c
@@ -27,6 +27,7 @@
 #include "backends/meta-backend-private.h"
 #include "backends/meta-dbus-session-watcher.h"
 #include "backends/meta-remote-access-controller-private.h"
+#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-window-stream.h"
@@ -485,6 +486,90 @@ handle_record_window (MetaDBusScreenCastSession *skeleton,
   return TRUE;
 }
 
+static gboolean
+handle_record_area (MetaDBusScreenCastSession *skeleton,
+                    GDBusMethodInvocation     *invocation,
+                    int                        x,
+                    int                        y,
+                    int                        width,
+                    int                        height,
+                    GVariant                  *properties_variant)
+{
+  MetaScreenCastSession *session = META_SCREEN_CAST_SESSION (skeleton);
+  GDBusInterfaceSkeleton *interface_skeleton;
+  GDBusConnection *connection;
+  MetaBackend *backend;
+  ClutterStage *stage;
+  MetaScreenCastCursorMode cursor_mode;
+  g_autoptr (GError) error = NULL;
+  MetaRectangle rect;
+  MetaScreenCastAreaStream *area_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;
+        }
+    }
+
+  interface_skeleton = G_DBUS_INTERFACE_SKELETON (skeleton);
+  connection = g_dbus_interface_skeleton_get_connection (interface_skeleton);
+  backend = meta_screen_cast_get_backend (session->screen_cast);
+  stage = CLUTTER_STAGE (meta_backend_get_stage (backend));
+
+  rect = (MetaRectangle) {
+    .x = x,
+    .y = y,
+    .width = width,
+    .height = height
+  };
+  area_stream = meta_screen_cast_area_stream_new (session,
+                                                  connection,
+                                                  &rect,
+                                                  stage,
+                                                  cursor_mode,
+                                                  &error);
+  if (!area_stream)
+    {
+      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+                                             G_DBUS_ERROR_FAILED,
+                                             "Failed to record area: %s",
+                                             error->message);
+      return TRUE;
+    }
+
+  stream = META_SCREEN_CAST_STREAM (area_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_area (skeleton,
+                                                      invocation,
+                                                      stream_path);
+
+  return TRUE;
+}
+
 static void
 meta_screen_cast_session_init_iface (MetaDBusScreenCastSessionIface *iface)
 {
@@ -492,6 +577,7 @@ meta_screen_cast_session_init_iface (MetaDBusScreenCastSessionIface *iface)
   iface->handle_stop = handle_stop;
   iface->handle_record_monitor = handle_record_monitor;
   iface->handle_record_window = handle_record_window;
+  iface->handle_record_area = handle_record_area;
 }
 
 static void
diff --git a/src/meson.build b/src/meson.build
index ea337214c..39eb637ee 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -458,6 +458,10 @@ if have_remote_desktop
     'backends/meta-remote-desktop-session.h',
     'backends/meta-screen-cast.c',
     'backends/meta-screen-cast.h',
+    'backends/meta-screen-cast-area-stream.c',
+    'backends/meta-screen-cast-area-stream.h',
+    'backends/meta-screen-cast-area-stream-src.c',
+    'backends/meta-screen-cast-area-stream-src.h',
     'backends/meta-screen-cast-monitor-stream.c',
     'backends/meta-screen-cast-monitor-stream.h',
     'backends/meta-screen-cast-monitor-stream-src.c',
diff --git a/src/org.gnome.Mutter.ScreenCast.xml b/src/org.gnome.Mutter.ScreenCast.xml
index 1d9fa598e..e20f5e5c6 100644
--- a/src/org.gnome.Mutter.ScreenCast.xml
+++ b/src/org.gnome.Mutter.ScreenCast.xml
@@ -111,6 +111,39 @@
       <arg name="properties" type="a{sv}" direction="in" />
       <arg name="stream_path" type="o" direction="out" />
     </method>
+
+    <!--
+       RecordArea:
+       @x: X position of the recorded area
+       @y: Y position of the recorded area
+       @width: width of the recorded area
+       @height: height of the recorded area
+       @properties: Properties
+       @stream_path: Path to the new stream object
+
+       Record an area of the stage. The coordinates are in stage coordinates.
+       The size of the stream does not necessarily match the size of the
+       recorded area, and will depend on DPI scale of the affected monitors.
+
+       Available @properties include:
+
+       * "cursor-mode" (u): Cursor mode. Default: 'hidden' (see below)
+                            Available since API version 2.
+
+       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
+    -->
+    <method name="RecordArea">
+      <arg name="x" type="i" direction="in" />
+      <arg name="y" type="i" direction="in" />
+      <arg name="width" type="i" direction="in" />
+      <arg name="height" type="i" direction="in" />
+      <arg name="properties" type="a{sv}" direction="in" />
+      <arg name="stream_path" type="o" direction="out" />
+    </method>
   </interface>
 
   <!--



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]