1
0
Fork 0
mutter-performance-source/src/backends/meta-screen-cast-stream-src.c

598 lines
20 KiB
C
Raw Normal View History

Add remote desktop and screen cast functionality This commit adds basic screen casting and remote desktoping functionalty. This works by exposing two D-Bus API services: org.gnome.Mutter.ScreenCast and org.gnome.Mutter.RemoteDesktop. The remote desktop API is used to create remote desktop sessions. For each session, a D-Bus object is created, and an application can manage the session by sending messages to the session object. A remote desktop session the user to emit input events using the D-Bus methods on the session object. To get framebuffer content, the application should create an associated screen cast session. The screen cast API is used to create screen cast sessions. One can so far either create stand-alone screen cast sessions, or a screen cast session associated with a remote desktop session. A remote desktop associated screen cast session is managed by the remote desktop session. So far only remote desktop managed screen cast sessions are implemented. Each screen cast session may have one or more streams. A screen cast stream is a stream of buffers of some part of the compositor content. So far API exists for creating streams of monitors and windows, but only monitor streams are implemented. When a screen cast session is started, the one PipeWire stream is created for each screen cast stream created for the session. When this has happened, a PipeWireStreamAdded signal is emitted on the stream object, passing a unique identifier. The application may use this identifier to find the associated stream being advertised by the PipeWire daemon. The remote desktop and screen cast functionality must be explicitly be enabled at ./configure time by passing --enable-remote-desktop to ./configure. Doing this will build both screen cast and remote desktop support. To actually enable the screen casting and remote desktop, the user must enable the experimental feature. See org.gnome.mutter.experimental-features. https://bugzilla.gnome.org/show_bug.cgi?id=784199
2017-06-21 06:23:44 +00:00
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2015-2017 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-stream-src.h"
#include <errno.h>
#include <pipewire/pipewire.h>
#include <spa/format-builder.h>
#include <spa/format-utils.h>
#include <spa/props.h>
#include <spa/type-map.h>
#include <spa/video/format-utils.h>
#include <stdint.h>
#include <sys/mman.h>
#include "backends/meta-screen-cast-stream.h"
#include "clutter/clutter-mutter.h"
#include "core/meta-fraction.h"
#include "meta/boxes.h"
#define PRIVATE_OWNER_FROM_FIELD(TypeName, field_ptr, field_name) \
(TypeName *)((guint8 *)(field_ptr) - G_PRIVATE_OFFSET (TypeName, field_name))
enum
{
PROP_0,
PROP_STREAM,
PROP_STREAM_ID,
};
typedef struct _MetaSpaType
{
uint32_t format;
uint32_t props;
struct spa_type_meta meta;
struct spa_type_data data;
struct spa_type_media_type media_type;
struct spa_type_media_subtype media_subtype;
struct spa_type_format_video format_video;
struct spa_type_video_format video_format;
} MetaSpaType;
typedef struct _MetaPipeWireSource
{
GSource base;
struct pw_loop *pipewire_loop;
} MetaPipeWireSource;
typedef struct _MetaScreenCastStreamSrcPrivate
{
MetaScreenCastStream *stream;
char *stream_id;
struct pw_core *pipewire_core;
struct pw_remote *pipewire_remote;
struct pw_type *pipewire_type;
MetaPipeWireSource *pipewire_source;
struct spa_hook pipewire_remote_listener;
struct pw_stream *pipewire_stream;
struct spa_hook pipewire_stream_listener;
MetaSpaType spa_type;
uint8_t params_buffer[1024];
struct spa_video_info_raw video_format;
uint64_t last_frame_timestamp_us;
} MetaScreenCastStreamSrcPrivate;
static void
meta_screen_cast_stream_src_init_initable_iface (GInitableIface *iface);
G_DEFINE_TYPE_WITH_CODE (MetaScreenCastStreamSrc,
meta_screen_cast_stream_src,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
meta_screen_cast_stream_src_init_initable_iface)
G_ADD_PRIVATE (MetaScreenCastStreamSrc))
#define PROP(f, key, type, ...) \
SPA_POD_PROP (f, key, 0, type, 1, __VA_ARGS__)
#define PROP_U_MM(f, key, type, ...) \
SPA_POD_PROP (f, key, (SPA_POD_PROP_FLAG_UNSET | \
SPA_POD_PROP_RANGE_MIN_MAX), \
type, 3, __VA_ARGS__)
static void
meta_screen_cast_stream_src_get_specs (MetaScreenCastStreamSrc *src,
int *width,
int *height,
float *frame_rate)
{
MetaScreenCastStreamSrcClass *klass =
META_SCREEN_CAST_STREAM_SRC_GET_CLASS (src);
klass->get_specs (src, width, height, frame_rate);
}
static void
meta_screen_cast_stream_src_record_frame (MetaScreenCastStreamSrc *src,
uint8_t *data)
{
MetaScreenCastStreamSrcClass *klass =
META_SCREEN_CAST_STREAM_SRC_GET_CLASS (src);
klass->record_frame (src, data);
}
void
meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc *src)
{
MetaScreenCastStreamSrcPrivate *priv =
meta_screen_cast_stream_src_get_instance_private (src);
uint32_t buffer_id;
struct spa_buffer *buffer;
uint8_t *map = NULL;
uint8_t *data;
uint64_t now_us;
now_us = g_get_monotonic_time ();
if (priv->last_frame_timestamp_us != 0 &&
(now_us - priv->last_frame_timestamp_us <
((1000000 * priv->video_format.max_framerate.denom) /
priv->video_format.max_framerate.num)))
return;
if (!priv->pipewire_stream)
return;
buffer_id = pw_stream_get_empty_buffer (priv->pipewire_stream);
if (buffer_id == SPA_ID_INVALID)
return;
buffer = pw_stream_peek_buffer (priv->pipewire_stream, buffer_id);
if (buffer->datas[0].type == priv->spa_type.data.MemFd)
{
map = mmap (NULL, buffer->datas[0].maxsize + buffer->datas[0].mapoffset,
PROT_READ | PROT_WRITE, MAP_SHARED,
buffer->datas[0].fd, 0);
if (map == MAP_FAILED)
{
g_warning ("Failed to mmap pipewire stream buffer: %s\n",
strerror (errno));
return;
}
data = SPA_MEMBER (map, buffer->datas[0].mapoffset, uint8_t);
}
else if (buffer->datas[0].type == priv->spa_type.data.MemPtr)
{
data = buffer->datas[0].data;
}
else
{
return;
}
meta_screen_cast_stream_src_record_frame (src, data);
priv->last_frame_timestamp_us = now_us;
if (map)
munmap (map, buffer->datas[0].maxsize + buffer->datas[0].mapoffset);
pw_stream_send_buffer (priv->pipewire_stream, buffer_id);
}
static void
on_stream_state_changed (void *data,
enum pw_stream_state old,
enum pw_stream_state state,
const char *error_message)
{
}
static void
on_stream_format_changed (void *data,
struct spa_format *format)
{
MetaScreenCastStreamSrc *src = data;
MetaScreenCastStreamSrcPrivate *priv =
meta_screen_cast_stream_src_get_instance_private (src);
struct pw_type *pipewire_type = priv->pipewire_type;
struct spa_type_param_alloc_buffers *param_alloc_buffers;
struct spa_pod_builder pod_builder = { NULL };
struct spa_pod_frame object_frame;
struct spa_pod_frame prop_frame;
struct spa_param *params[1];
const int bpp = 4;
if (!format)
{
pw_stream_finish_format (priv->pipewire_stream, SPA_RESULT_OK, NULL, 0);
return;
}
spa_format_video_raw_parse (format,
&priv->video_format,
&priv->spa_type.format_video);
spa_pod_builder_init (&pod_builder,
priv->params_buffer,
sizeof (priv->params_buffer));
param_alloc_buffers = &pipewire_type->param_alloc_buffers;
spa_pod_builder_object (&pod_builder, &object_frame, 0,
param_alloc_buffers->Buffers,
PROP (&prop_frame, param_alloc_buffers->size,
SPA_POD_TYPE_INT,
(priv->video_format.size.width *
priv->video_format.size.height *
bpp)),
PROP (&prop_frame, param_alloc_buffers->stride,
SPA_POD_TYPE_INT,
priv->video_format.size.width * bpp),
PROP_U_MM (&prop_frame, param_alloc_buffers->buffers,
SPA_POD_TYPE_INT,
16, 2, 16),
PROP (&prop_frame, param_alloc_buffers->align,
SPA_POD_TYPE_INT,
16));
params[0] = SPA_POD_BUILDER_DEREF (&pod_builder, object_frame.ref,
struct spa_param);
pw_stream_finish_format (priv->pipewire_stream, SPA_RESULT_OK,
params, G_N_ELEMENTS (params));
}
static const struct pw_stream_events stream_events = {
PW_VERSION_STREAM_EVENTS,
.state_changed = on_stream_state_changed,
.format_changed = on_stream_format_changed,
};
static struct pw_stream *
create_pipewire_stream (MetaScreenCastStreamSrc *src,
GError **error)
{
MetaScreenCastStreamSrcPrivate *priv =
meta_screen_cast_stream_src_get_instance_private (src);
struct pw_stream *pipewire_stream;
const struct spa_format *format;
uint8_t buffer[1024];
struct spa_pod_builder pod_builder =
SPA_POD_BUILDER_INIT (buffer, sizeof (buffer));
struct spa_pod_frame format_frame;
struct spa_pod_frame prop_frame;
MetaSpaType *spa_type = &priv->spa_type;
int width, height;
float frame_rate;
MetaFraction frame_rate_fraction;
pipewire_stream = pw_stream_new (priv->pipewire_remote,
"meta-screen-cast-src",
NULL);
meta_screen_cast_stream_src_get_specs (src, &width, &height, &frame_rate);
frame_rate_fraction = meta_fraction_from_double (frame_rate);
spa_pod_builder_format (&pod_builder, &format_frame,
spa_type->format,
spa_type->media_type.video,
spa_type->media_subtype.raw,
PROP (&prop_frame,
spa_type->format_video.format,
SPA_POD_TYPE_ID, spa_type->video_format.BGRx),
PROP (&prop_frame,
spa_type->format_video.size,
SPA_POD_TYPE_RECTANGLE,
width, height),
PROP (&prop_frame,
spa_type->format_video.framerate,
SPA_POD_TYPE_FRACTION,
0, 1),
PROP_U_MM (&prop_frame,
spa_type->format_video.max_framerate,
SPA_POD_TYPE_FRACTION,
frame_rate_fraction.num,
frame_rate_fraction.denom,
1, 1,
frame_rate_fraction.num,
frame_rate_fraction.denom));
format = SPA_POD_BUILDER_DEREF (&pod_builder, format_frame.ref, struct spa_format);
pw_stream_add_listener (pipewire_stream,
&priv->pipewire_stream_listener,
&stream_events,
src);
if (!pw_stream_connect (pipewire_stream,
PW_DIRECTION_OUTPUT,
PW_STREAM_MODE_BUFFER,
NULL,
PW_STREAM_FLAG_NONE,
1, &format))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Could not connect");
return NULL;
}
return pipewire_stream;
}
static void
on_state_changed (void *data,
enum pw_remote_state old,
enum pw_remote_state state,
const char *error_message)
{
MetaScreenCastStreamSrc *src = data;
MetaScreenCastStreamSrcPrivate *priv =
meta_screen_cast_stream_src_get_instance_private (src);
struct pw_stream *pipewire_stream;
GError *error = NULL;
switch (state)
{
case PW_REMOTE_STATE_ERROR:
g_warning ("pipewire remote error: %s\n", error_message);
break;
case PW_REMOTE_STATE_CONNECTED:
pipewire_stream = create_pipewire_stream (src, &error);
if (!pipewire_stream)
{
g_warning ("Could not create pipewire stream: %s", error->message);
g_error_free (error);
}
else
{
priv->pipewire_stream = pipewire_stream;
}
break;
case PW_REMOTE_STATE_UNCONNECTED:
case PW_REMOTE_STATE_CONNECTING:
break;
}
}
static gboolean
pipewire_loop_source_prepare (GSource *base,
int *timeout)
{
*timeout = -1;
return FALSE;
}
static gboolean
pipewire_loop_source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
MetaPipeWireSource *pipewire_source = (MetaPipeWireSource *) source;
int result;
result = pw_loop_iterate (pipewire_source->pipewire_loop, 0);
if (result == SPA_RESULT_ERRNO)
g_warning ("pipewire_loop_iterate failed: %s", strerror (errno));
else if (result != SPA_RESULT_OK)
g_warning ("pipewire_loop_iterate failed: %d", result);
return TRUE;
}
static void
pipewire_loop_source_finalize (GSource *source)
{
MetaPipeWireSource *pipewire_source = (MetaPipeWireSource *) source;
pw_loop_leave (pipewire_source->pipewire_loop);
pw_loop_destroy (pipewire_source->pipewire_loop);
}
static GSourceFuncs pipewire_source_funcs =
{
pipewire_loop_source_prepare,
NULL,
pipewire_loop_source_dispatch,
pipewire_loop_source_finalize
};
static void
init_spa_type (MetaSpaType *type,
struct spa_type_map *map)
{
type->format = spa_type_map_get_id (map, SPA_TYPE__Format);
type->props = spa_type_map_get_id (map, SPA_TYPE__Props);
spa_type_meta_map (map, &type->meta);
spa_type_data_map (map, &type->data);
spa_type_media_type_map (map, &type->media_type);
spa_type_media_subtype_map (map, &type->media_subtype);
spa_type_format_video_map (map, &type->format_video);
spa_type_video_format_map (map, &type->video_format);
}
static MetaPipeWireSource *
create_pipewire_source (void)
{
MetaPipeWireSource *pipewire_source;
pipewire_source =
(MetaPipeWireSource *) g_source_new (&pipewire_source_funcs,
sizeof (MetaPipeWireSource));
pipewire_source->pipewire_loop = pw_loop_new (NULL);
g_source_add_unix_fd (&pipewire_source->base,
pw_loop_get_fd (pipewire_source->pipewire_loop),
G_IO_IN | G_IO_ERR);
pw_loop_enter (pipewire_source->pipewire_loop);
g_source_attach (&pipewire_source->base, NULL);
return pipewire_source;
}
static const struct pw_remote_events remote_events = {
PW_VERSION_REMOTE_EVENTS,
.state_changed = on_state_changed,
};
static gboolean
meta_screen_cast_stream_src_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (initable);
MetaScreenCastStreamSrcPrivate *priv =
meta_screen_cast_stream_src_get_instance_private (src);
priv->pipewire_source = create_pipewire_source ();
priv->pipewire_core = pw_core_new (priv->pipewire_source->pipewire_loop,
NULL);
if (!priv->pipewire_core)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create pipewire core");
return FALSE;
}
priv->pipewire_remote = pw_remote_new (priv->pipewire_core, NULL);
if (!priv->pipewire_remote)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Couldn't creat pipewire remote");
return FALSE;
}
pw_remote_add_listener (priv->pipewire_remote,
&priv->pipewire_remote_listener,
&remote_events,
src);
priv->pipewire_type = pw_core_get_type (priv->pipewire_core);
init_spa_type (&priv->spa_type, priv->pipewire_type->map);
if (pw_remote_connect (priv->pipewire_remote) != 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Couldn't connect pipewire remote");
return FALSE;
}
return TRUE;
}
static void
meta_screen_cast_stream_src_init_initable_iface (GInitableIface *iface)
{
iface->init = meta_screen_cast_stream_src_initable_init;
}
MetaScreenCastStream *
meta_screen_cast_stream_src_get_stream (MetaScreenCastStreamSrc *src)
{
MetaScreenCastStreamSrcPrivate *priv =
meta_screen_cast_stream_src_get_instance_private (src);
return priv->stream;
}
static void
meta_screen_cast_stream_src_finalize (GObject *object)
{
MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (object);
MetaScreenCastStreamSrcPrivate *priv =
meta_screen_cast_stream_src_get_instance_private (src);
g_clear_pointer (&priv->pipewire_stream, (GDestroyNotify) pw_stream_destroy);
pw_remote_destroy (priv->pipewire_remote);
pw_core_destroy (priv->pipewire_core);
g_source_destroy (&priv->pipewire_source->base);
g_clear_pointer (&priv->stream_id, g_free);
G_OBJECT_CLASS (meta_screen_cast_stream_src_parent_class)->finalize (object);
}
static void
meta_screen_cast_stream_src_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (object);
MetaScreenCastStreamSrcPrivate *priv =
meta_screen_cast_stream_src_get_instance_private (src);
switch (prop_id)
{
case PROP_STREAM:
priv->stream = g_value_get_object (value);
break;;
case PROP_STREAM_ID:
priv->stream_id = g_strdup (g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
meta_screen_cast_stream_src_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (object);
MetaScreenCastStreamSrcPrivate *priv =
meta_screen_cast_stream_src_get_instance_private (src);
switch (prop_id)
{
case PROP_STREAM:
g_value_set_object (value, priv->stream);
break;
case PROP_STREAM_ID:
g_value_set_string (value, priv->stream_id);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
meta_screen_cast_stream_src_init (MetaScreenCastStreamSrc *src)
{
}
static void
meta_screen_cast_stream_src_class_init (MetaScreenCastStreamSrcClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = meta_screen_cast_stream_src_finalize;
object_class->set_property = meta_screen_cast_stream_src_set_property;
object_class->get_property = meta_screen_cast_stream_src_get_property;
g_object_class_install_property (object_class,
PROP_STREAM,
g_param_spec_object ("stream",
"stream",
"MetaScreenCastStream",
META_TYPE_SCREEN_CAST_STREAM,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_STREAM_ID,
g_param_spec_string ("stream-id",
"stream-id",
"Unique stream ID",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
}