From c4535fdf85587e5cdf3613fad764d878b2e57fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Wed, 4 Mar 2020 21:42:52 +0100 Subject: [PATCH] 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 +- .../meta-screen-cast-area-stream-src.c | 570 ++++++++++++++++++ .../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(-) create mode 100644 src/backends/meta-screen-cast-area-stream-src.c create mode 100644 src/backends/meta-screen-cast-area-stream-src.h create mode 100644 src/backends/meta-screen-cast-area-stream.c create mode 100644 src/backends/meta-screen-cast-area-stream.h 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 + +#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 + +#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 @@ + + + + + + + + + +