[mutter] Add remote desktop and screen cast functionality



commit 97f2c7c16137355e3c3a64e6e80b7d5512d3b587
Author: Jonas Ådahl <jadahl gmail com>
Date:   Wed Jun 21 14:23:44 2017 +0800

    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

 .gitignore                                         |    2 +
 configure.ac                                       |   12 +
 data/org.gnome.mutter.gschema.xml.in               |    4 +
 src/Makefile.am                                    |   52 ++
 src/backends/meta-backend-private.h                |    7 +
 src/backends/meta-backend.c                        |   84 +++-
 src/backends/meta-dbus-session-watcher.c           |  235 ++++++++
 src/backends/meta-dbus-session-watcher.h           |   52 ++
 src/backends/meta-remote-desktop-session.c         |  444 +++++++++++++++
 src/backends/meta-remote-desktop-session.h         |   49 ++
 src/backends/meta-remote-desktop.c                 |  237 ++++++++
 src/backends/meta-remote-desktop.h                 |   45 ++
 src/backends/meta-screen-cast-monitor-stream-src.c |  181 ++++++
 src/backends/meta-screen-cast-monitor-stream-src.h |   41 ++
 src/backends/meta-screen-cast-monitor-stream.c     |  181 ++++++
 src/backends/meta-screen-cast-monitor-stream.h     |   47 ++
 src/backends/meta-screen-cast-session.c            |  280 +++++++++
 src/backends/meta-screen-cast-session.h            |   50 ++
 src/backends/meta-screen-cast-stream-src.c         |  597 ++++++++++++++++++++
 src/backends/meta-screen-cast-stream-src.h         |   55 ++
 src/backends/meta-screen-cast-stream.c             |  229 ++++++++
 src/backends/meta-screen-cast-stream.h             |   52 ++
 src/backends/meta-screen-cast.c                    |  271 +++++++++
 src/backends/meta-screen-cast.h                    |   40 ++
 src/backends/meta-settings-private.h               |    2 +
 src/backends/meta-settings.c                       |    4 +
 src/org.gnome.Mutter.RemoteDesktop.xml             |  103 ++++
 src/org.gnome.Mutter.ScreenCast.xml                |  107 ++++
 28 files changed, 3462 insertions(+), 1 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 56fb267..321d773 100644
--- a/.gitignore
+++ b/.gitignore
@@ -71,6 +71,8 @@ src/stamp-meta-enum-types.h
 src/meta-dbus-display-config.[ch]
 src/meta-dbus-idle-monitor.[ch]
 src/meta-dbus-login1.[ch]
+src/meta-dbus-remote-desktop.[ch]
+src/meta-dbus-screen-cast.[ch]
 src/gtk-primary-selection-protocol.c
 src/gtk-primary-selection-server-protocol.h
 src/gtk-shell-protocol.c
diff --git a/configure.ac b/configure.ac
index f70d894..af00563 100644
--- a/configure.ac
+++ b/configure.ac
@@ -238,6 +238,17 @@ else
   fi
 fi
 
+AC_ARG_ENABLE(remote-desktop,
+  AS_HELP_STRING([--enable-remote-desktop], [enable support for remote desktop and screen cast]),
+  enable_remote_desktop=yes,
+  enable_remote_desktop=no
+)
+AS_IF([test "$enable_remote_desktop" = "yes"], [
+  MUTTER_PC_MODULES="$MUTTER_PC_MODULES libpipewire-0.1 >= 0.1.4"
+  AC_DEFINE([HAVE_REMOTE_DESKTOP],[1], [Defined if EGLDevice support is enabled])
+])
+AM_CONDITIONAL([HAVE_REMOTE_DESKTOP],[test "$enable_remote_desktop" = "yes"])
+
 INTROSPECTION_VERSION=0.9.5
 GOBJECT_INTROSPECTION_CHECK([$INTROSPECTION_VERSION])
 
@@ -513,6 +524,7 @@ mutter-$VERSION
        Wayland:                  ${have_wayland}
        Native (KMS) backend:     ${have_native_backend}
        EGLDevice:                ${enable_egl_device}
+       Remote desktop:           ${enable_remote_desktop}
 "
 
 
diff --git a/data/org.gnome.mutter.gschema.xml.in b/data/org.gnome.mutter.gschema.xml.in
index 6cbd9c1..fb1cf73 100644
--- a/data/org.gnome.mutter.gschema.xml.in
+++ b/data/org.gnome.mutter.gschema.xml.in
@@ -120,6 +120,10 @@
                                         framebuffers instead of window content,
                                         to manage HiDPI monitors. Does not
                                         require a restart.
+        • “remote-desktop”            — enables remote desktop support. To support
+                                        remote desktop with screen sharing,
+                                        "screen-cast" must also be enabled.
+        • “screen-cast”               — enables screen cast support.
       </description>
     </key>
 
diff --git a/src/Makefile.am b/src/Makefile.am
index d88539b..6aef50d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -53,6 +53,13 @@ mutter_built_sources = \
        meta-enum-types.c                       \
        $(NULL)
 
+if HAVE_REMOTE_DESKTOP
+mutter_built_sources += \
+       $(dbus_remote_desktop_built_sources)    \
+       $(dbus_screen_cast_built_sources)       \
+       $(NULL)
+endif
+
 if HAVE_WAYLAND
 mutter_built_sources += \
        pointer-gestures-unstable-v1-protocol.c                         \
@@ -313,6 +320,29 @@ libmutter_@LIBMUTTER_API_VERSION@_la_SOURCES =     \
        x11/mutter-Xatomtype.h                  \
        $(NULL)
 
+if HAVE_REMOTE_DESKTOP
+libmutter_@LIBMUTTER_API_VERSION@_la_SOURCES +=        \
+       backends/meta-dbus-session-watcher.c    \
+       backends/meta-dbus-session-watcher.h    \
+       backends/meta-remote-desktop.c          \
+       backends/meta-remote-desktop.h          \
+       backends/meta-remote-desktop-session.c  \
+       backends/meta-remote-desktop-session.h  \
+       backends/meta-screen-cast.c             \
+       backends/meta-screen-cast.h             \
+       backends/meta-screen-cast-monitor-stream.c      \
+       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-session.c     \
+       backends/meta-screen-cast-session.h     \
+       backends/meta-screen-cast-stream.c      \
+       backends/meta-screen-cast-stream.h      \
+       backends/meta-screen-cast-stream-src.c  \
+       backends/meta-screen-cast-stream-src.h  \
+       $(NULL)
+endif
+
 if HAVE_WAYLAND
 libmutter_@LIBMUTTER_API_VERSION@_la_SOURCES +=        \
        compositor/meta-surface-actor-wayland.c \
@@ -581,6 +611,8 @@ EXTRA_DIST +=                                       \
        org.freedesktop.login1.xml              \
        org.gnome.Mutter.DisplayConfig.xml      \
        org.gnome.Mutter.IdleMonitor.xml        \
+       org.gnome.Mutter.RemoteDesktop.xml      \
+       org.gnome.Mutter.ScreenCast.xml \
        backends/native/gen-default-modes.py    \
        $(NULL)
 
@@ -629,6 +661,26 @@ $(dbus_idle_built_sources) : Makefile.am org.gnome.Mutter.IdleMonitor.xml
                --c-generate-autocleanup all                                            \
                $(srcdir)/org.gnome.Mutter.IdleMonitor.xml
 
+if HAVE_REMOTE_DESKTOP
+dbus_remote_desktop_built_sources = meta-dbus-remote-desktop.c meta-dbus-remote-desktop.h
+
+$(dbus_remote_desktop_built_sources) : Makefile.am org.gnome.Mutter.RemoteDesktop.xml
+       $(AM_V_GEN)gdbus-codegen                                                        \
+               --interface-prefix org.gnome.Mutter                                     \
+               --c-namespace MetaDBus                                                  \
+               --generate-c-code meta-dbus-remote-desktop                              \
+               $(srcdir)/org.gnome.Mutter.RemoteDesktop.xml
+
+dbus_screen_cast_built_sources = meta-dbus-screen-cast.c meta-dbus-screen-cast.h
+
+$(dbus_screen_cast_built_sources) : Makefile.am org.gnome.Mutter.ScreenCast.xml
+       $(AM_V_GEN)gdbus-codegen                                                        \
+               --interface-prefix org.gnome.Mutter                                     \
+               --c-namespace MetaDBus                                                  \
+               --generate-c-code meta-dbus-screen-cast                                 \
+               $(srcdir)/org.gnome.Mutter.ScreenCast.xml
+endif
+
 dbus_login1_built_sources = meta-dbus-login1.c meta-dbus-login1.h
 
 $(dbus_login1_built_sources) : Makefile.am org.freedesktop.login1.xml
diff --git a/src/backends/meta-backend-private.h b/src/backends/meta-backend-private.h
index bf6d0c3..47aa291 100644
--- a/src/backends/meta-backend-private.h
+++ b/src/backends/meta-backend-private.h
@@ -38,6 +38,9 @@
 #include "meta-input-settings-private.h"
 #include "backends/meta-egl.h"
 #include "backends/meta-pointer-constraint.h"
+#ifdef HAVE_REMOTE_DESKTOP
+#include "backends/meta-remote-desktop.h"
+#endif
 #include "backends/meta-renderer.h"
 #include "backends/meta-settings-private.h"
 #include "core/util-private.h"
@@ -120,6 +123,10 @@ MetaRenderer * meta_backend_get_renderer (MetaBackend *backend);
 MetaEgl * meta_backend_get_egl (MetaBackend *backend);
 MetaSettings * meta_backend_get_settings (MetaBackend *backend);
 
+#ifdef HAVE_REMOTE_DESKTOP
+MetaRemoteDesktop * meta_backend_get_remote_desktop (MetaBackend *backend);
+#endif
+
 gboolean meta_backend_grab_device (MetaBackend *backend,
                                    int          device_id,
                                    uint32_t     timestamp);
diff --git a/src/backends/meta-backend.c b/src/backends/meta-backend.c
index 66a7313..ba293b6 100644
--- a/src/backends/meta-backend.c
+++ b/src/backends/meta-backend.c
@@ -29,13 +29,19 @@
 #include <clutter/clutter-mutter.h>
 #include <meta/meta-backend.h>
 #include <meta/main.h>
+#include <meta/util.h>
 #include "meta-backend-private.h"
 #include "meta-input-settings-private.h"
-
 #include "backends/x11/meta-backend-x11.h"
 #include "meta-cursor-tracker-private.h"
 #include "meta-stage.h"
 
+#ifdef HAVE_REMOTE_DESKTOP
+#include "backends/meta-dbus-session-watcher.h"
+#include "backends/meta-screen-cast.h"
+#include "backends/meta-remote-desktop.h"
+#endif
+
 #ifdef HAVE_NATIVE_BACKEND
 #include "backends/native/meta-backend-native.h"
 #endif
@@ -85,6 +91,11 @@ struct _MetaBackendPrivate
   MetaRenderer *renderer;
   MetaEgl *egl;
   MetaSettings *settings;
+#ifdef HAVE_REMOTE_DESKTOP
+  MetaDbusSessionWatcher *dbus_session_watcher;
+  MetaScreenCast *screen_cast;
+  MetaRemoteDesktop *remote_desktop;
+#endif
 
   ClutterBackend *clutter_backend;
   ClutterActor *stage;
@@ -117,6 +128,11 @@ meta_backend_finalize (GObject *object)
   g_clear_object (&priv->monitor_manager);
   g_clear_object (&priv->orientation_manager);
   g_clear_object (&priv->input_settings);
+#ifdef HAVE_REMOTE_DESKTOP
+  g_clear_object (&priv->remote_desktop);
+  g_clear_object (&priv->screen_cast);
+  g_clear_object (&priv->dbus_session_watcher);
+#endif
 
   if (priv->device_update_idle_id)
     g_source_remove (priv->device_update_idle_id);
@@ -387,6 +403,28 @@ meta_backend_create_input_settings (MetaBackend *backend)
   return META_BACKEND_GET_CLASS (backend)->create_input_settings (backend);
 }
 
+#ifdef HAVE_REMOTE_DESKTOP
+static gboolean
+is_screen_cast_enabled (MetaBackend *backend)
+{
+  MetaSettings *settings = meta_backend_get_settings (backend);
+
+  return meta_settings_is_experimental_feature_enabled (
+    settings,
+    META_EXPERIMENTAL_FEATURE_SCREEN_CAST);
+}
+
+static gboolean
+is_remote_desktop_enabled (MetaBackend *backend)
+{
+  MetaSettings *settings = meta_backend_get_settings (backend);
+
+  return meta_settings_is_experimental_feature_enabled (
+    settings,
+    META_EXPERIMENTAL_FEATURE_REMOTE_DESKTOP);
+}
+#endif /* HAVE_REMOTE_DESKTOP */
+
 static void
 meta_backend_real_post_init (MetaBackend *backend)
 {
@@ -419,6 +457,14 @@ meta_backend_real_post_init (MetaBackend *backend)
   priv->input_settings = meta_backend_create_input_settings (backend);
 
   center_pointer (backend);
+
+#ifdef HAVE_REMOTE_DESKTOP
+  priv->dbus_session_watcher = g_object_new (META_TYPE_DBUS_SESSION_WATCHER, NULL);
+  if (is_screen_cast_enabled (backend))
+    priv->screen_cast = meta_screen_cast_new (priv->dbus_session_watcher);
+  if (is_remote_desktop_enabled (backend))
+    priv->remote_desktop = meta_remote_desktop_new (priv->dbus_session_watcher);
+#endif /* HAVE_REMOTE_DESKTOP */
 }
 
 static MetaCursorRenderer *
@@ -503,6 +549,26 @@ meta_backend_class_init (MetaBackendClass *klass)
   stage_views_disabled = g_strcmp0 (mutter_stage_views, "0") == 0;
 }
 
+static void
+experimental_features_changed (MetaSettings            *settings,
+                               MetaExperimentalFeature  old_experimental_features,
+                               MetaBackend             *backend)
+{
+#ifdef HAVE_REMOTE_DESKTOP
+  MetaBackendPrivate *priv = meta_backend_get_instance_private (backend);
+
+  if (is_screen_cast_enabled (backend) && !priv->screen_cast)
+    priv->screen_cast = meta_screen_cast_new (priv->dbus_session_watcher);
+  else if (!is_screen_cast_enabled (backend))
+    g_clear_object (&priv->screen_cast);
+
+  if (is_remote_desktop_enabled (backend) && !priv->remote_desktop)
+    priv->remote_desktop = meta_remote_desktop_new (priv->dbus_session_watcher);
+  else if (!is_remote_desktop_enabled (backend))
+    g_clear_object (&priv->remote_desktop);
+#endif /* HAVE_REMOTE_DESKTOP */
+}
+
 static gboolean
 meta_backend_initable_init (GInitable     *initable,
                             GCancellable  *cancellable,
@@ -512,6 +578,9 @@ meta_backend_initable_init (GInitable     *initable,
   MetaBackendPrivate *priv = meta_backend_get_instance_private (backend);
 
   priv->settings = meta_settings_new (backend);
+  g_signal_connect (priv->settings, "experimental-features-changed",
+                    G_CALLBACK (experimental_features_changed),
+                    backend);
 
   priv->egl = g_object_new (META_TYPE_EGL, NULL);
 
@@ -641,6 +710,19 @@ meta_backend_get_settings (MetaBackend *backend)
   return priv->settings;
 }
 
+#ifdef HAVE_REMOTE_DESKTOP
+/**
+ * meta_backend_get_remote_desktop: (skip)
+ */
+MetaRemoteDesktop *
+meta_backend_get_remote_desktop (MetaBackend *backend)
+{
+  MetaBackendPrivate *priv = meta_backend_get_instance_private (backend);
+
+  return priv->remote_desktop;
+}
+#endif /* HAVE_REMOTE_DESKTOP */
+
 /**
  * meta_backend_grab_device: (skip)
  */
diff --git a/src/backends/meta-dbus-session-watcher.c b/src/backends/meta-dbus-session-watcher.c
new file mode 100644
index 0000000..22718e6
--- /dev/null
+++ b/src/backends/meta-dbus-session-watcher.c
@@ -0,0 +1,235 @@
+/* -*- 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-dbus-session-watcher.h"
+
+#include <gio/gio.h>
+
+enum
+{
+  SESSION_SIGNAL_SESSION_CLOSED,
+
+  N_SESSION_SIGNALS
+};
+
+static guint session_signals[N_SESSION_SIGNALS];
+
+G_DEFINE_INTERFACE (MetaDbusSession, meta_dbus_session, G_TYPE_OBJECT)
+
+struct _MetaDbusSessionWatcher
+{
+  GObject parent;
+
+  GHashTable *clients;
+};
+
+G_DEFINE_TYPE (MetaDbusSessionWatcher,
+               meta_dbus_session_watcher,
+               G_TYPE_OBJECT)
+
+typedef struct _MetaDbusSessionClient
+{
+  MetaDbusSessionWatcher *session_watcher;
+  MetaDbusSession *session;
+  char *dbus_name;
+  guint name_watcher_id;
+  GList *sessions;
+} MetaDbusSessionClient;
+
+static void
+meta_dbus_session_client_vanished (MetaDbusSession *session)
+{
+  META_DBUS_SESSION_GET_IFACE (session)->client_vanished (session);
+}
+
+static void
+meta_dbus_session_client_destroy (MetaDbusSessionClient *client)
+{
+  while (TRUE)
+    {
+      GList *l;
+      MetaDbusSession *session;
+
+      l = client->sessions;
+      if (!l)
+        break;
+
+      session = l->data;
+
+      /*
+       * This will invoke on_session_closed which removes the session from the
+       * list.
+       */
+      meta_dbus_session_client_vanished (session);
+    }
+
+  if (client->name_watcher_id)
+    g_bus_unwatch_name (client->name_watcher_id);
+
+  g_free (client->dbus_name);
+  g_free (client);
+}
+
+static void
+meta_dbus_session_watcher_destroy_client (MetaDbusSessionWatcher *session_watcher,
+                                          MetaDbusSessionClient  *client)
+{
+  g_hash_table_remove (session_watcher->clients, client->dbus_name);
+}
+
+static void
+name_vanished_callback (GDBusConnection *connection,
+                        const char      *name,
+                        gpointer         user_data)
+{
+  MetaDbusSessionClient *client = user_data;
+
+  g_warning ("D-Bus client with active sessions vanished");
+
+  client->name_watcher_id = 0;
+
+  meta_dbus_session_watcher_destroy_client (client->session_watcher, client);
+}
+
+static MetaDbusSessionClient *
+meta_dbus_session_client_new (MetaDbusSessionWatcher *session_watcher,
+                              MetaDbusSession        *session,
+                              const char             *dbus_name)
+{
+  GDBusInterfaceSkeleton *interface_skeleton =
+    G_DBUS_INTERFACE_SKELETON (session);
+  GDBusConnection *connection =
+    g_dbus_interface_skeleton_get_connection (interface_skeleton);
+  MetaDbusSessionClient *client;
+
+  client = g_new0 (MetaDbusSessionClient, 1);
+  client->session_watcher = session_watcher;
+  client->session = session;
+  client->dbus_name = g_strdup (dbus_name);
+
+  client->name_watcher_id =
+    g_bus_watch_name_on_connection (connection,
+                                    dbus_name,
+                                    G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                    NULL,
+                                    name_vanished_callback,
+                                    client,
+                                    NULL);
+
+  return client;
+}
+
+static void
+on_session_closed (MetaDbusSession       *session,
+                   MetaDbusSessionClient *client)
+{
+  client->sessions = g_list_remove (client->sessions, session);
+
+  if (!client->sessions)
+    meta_dbus_session_watcher_destroy_client (client->session_watcher, client);
+}
+
+static void
+meta_dbus_session_client_add_session (MetaDbusSessionClient *client,
+                                      MetaDbusSession       *session)
+{
+  client->sessions = g_list_append (client->sessions, session);
+
+  g_signal_connect (session, "session-closed",
+                    G_CALLBACK (on_session_closed),
+                    client);
+}
+
+static MetaDbusSessionClient *
+meta_dbus_session_watcher_get_client (MetaDbusSessionWatcher *session_watcher,
+                                      const char             *dbus_name)
+{
+  return g_hash_table_lookup (session_watcher->clients, dbus_name);
+}
+
+void
+meta_dbus_session_watcher_watch_session (MetaDbusSessionWatcher *session_watcher,
+                                         const char             *client_dbus_name,
+                                         MetaDbusSession        *session)
+{
+  MetaDbusSessionClient *client;
+
+  client = meta_dbus_session_watcher_get_client (session_watcher,
+                                                 client_dbus_name);
+  if (!client)
+    {
+      client = meta_dbus_session_client_new (session_watcher,
+                                             session,
+                                             client_dbus_name);
+      g_hash_table_insert (session_watcher->clients,
+                           g_strdup (client_dbus_name),
+                           client);
+    }
+
+  meta_dbus_session_client_add_session (client, session);
+}
+
+void
+meta_dbus_session_notify_closed (MetaDbusSession *session)
+{
+  g_signal_emit (session, session_signals[SESSION_SIGNAL_SESSION_CLOSED], 0);
+}
+
+static void
+meta_dbus_session_default_init (MetaDbusSessionInterface *iface)
+{
+  session_signals[SESSION_SIGNAL_SESSION_CLOSED] =
+    g_signal_new ("session-closed",
+                  G_TYPE_FROM_INTERFACE (iface),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 0);
+}
+
+static void
+meta_dbus_session_watcher_finalize (GObject *object)
+{
+  MetaDbusSessionWatcher *session_watcher = META_DBUS_SESSION_WATCHER (object);
+
+  g_hash_table_destroy (session_watcher->clients);
+}
+
+static void
+meta_dbus_session_watcher_init (MetaDbusSessionWatcher *session_watcher)
+{
+  session_watcher->clients =
+    g_hash_table_new_full (g_str_hash,
+                           g_str_equal,
+                           g_free,
+                           (GDestroyNotify) meta_dbus_session_client_destroy);
+}
+
+static void
+meta_dbus_session_watcher_class_init (MetaDbusSessionWatcherClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = meta_dbus_session_watcher_finalize;
+}
diff --git a/src/backends/meta-dbus-session-watcher.h b/src/backends/meta-dbus-session-watcher.h
new file mode 100644
index 0000000..06d3e1f
--- /dev/null
+++ b/src/backends/meta-dbus-session-watcher.h
@@ -0,0 +1,52 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 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.
+ *
+ */
+
+#ifndef META_DBUS_SESSION_WATCHER_H
+#define META_DBUS_SESSION_WATCHER_H
+
+#include <glib-object.h>
+
+#define META_TYPE_DBUS_SESSION (meta_dbus_session_get_type ())
+G_DECLARE_INTERFACE (MetaDbusSession, meta_dbus_session,
+                     META, DBUS_SESSION,
+                     GObject)
+
+struct _MetaDbusSessionInterface
+{
+  GTypeInterface parent_iface;
+
+  void (* client_vanished) (MetaDbusSession *session);
+};
+
+#define META_TYPE_DBUS_SESSION_WATCHER (meta_dbus_session_watcher_get_type ())
+G_DECLARE_FINAL_TYPE (MetaDbusSessionWatcher,
+                      meta_dbus_session_watcher,
+                      META, DBUS_SESSION_WATCHER,
+                      GObject)
+
+void meta_dbus_session_watcher_watch_session (MetaDbusSessionWatcher *session_watcher,
+                                              const char             *client_dbus_name,
+                                              MetaDbusSession        *session);
+
+void meta_dbus_session_notify_closed (MetaDbusSession *session);
+
+#endif /* META_DBUS_SESSION_WATCHER_H */
diff --git a/src/backends/meta-remote-desktop-session.c b/src/backends/meta-remote-desktop-session.c
new file mode 100644
index 0000000..c8fd2da
--- /dev/null
+++ b/src/backends/meta-remote-desktop-session.c
@@ -0,0 +1,444 @@
+/* -*- 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-remote-desktop-session.h"
+
+#include <linux/input.h>
+#include <xkbcommon/xkbcommon.h>
+
+#include "backends/meta-dbus-session-watcher.h"
+#include "backends/meta-screen-cast-session.h"
+#include "backends/native/meta-backend-native.h"
+#include "backends/x11/meta-backend-x11.h"
+#include "cogl/cogl.h"
+#include "meta/meta-backend.h"
+#include "meta/errors.h"
+#include "meta-dbus-remote-desktop.h"
+
+#define META_REMOTE_DESKTOP_SESSION_DBUS_PATH "/org/gnome/Mutter/RemoteDesktop/Session"
+
+struct _MetaRemoteDesktopSession
+{
+  MetaDBusRemoteDesktopSessionSkeleton parent;
+
+  char *session_id;
+  char *object_path;
+
+  MetaScreenCastSession *screen_cast_session;
+  gulong screen_cast_session_closed_handler_id;
+
+  ClutterVirtualInputDevice *virtual_pointer;
+  ClutterVirtualInputDevice *virtual_keyboard;
+};
+
+static void
+meta_remote_desktop_session_init_iface (MetaDBusRemoteDesktopSessionIface *iface);
+
+static void
+meta_dbus_session_init_iface (MetaDbusSessionInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (MetaRemoteDesktopSession,
+                         meta_remote_desktop_session,
+                         META_DBUS_TYPE_REMOTE_DESKTOP_SESSION_SKELETON,
+                         G_IMPLEMENT_INTERFACE (META_DBUS_TYPE_REMOTE_DESKTOP_SESSION,
+                                                meta_remote_desktop_session_init_iface)
+                         G_IMPLEMENT_INTERFACE (META_TYPE_DBUS_SESSION,
+                                                meta_dbus_session_init_iface))
+
+static gboolean
+meta_remote_desktop_session_is_running (MetaRemoteDesktopSession *session)
+{
+  return !!session->virtual_pointer;
+}
+
+static gboolean
+meta_remote_desktop_session_start (MetaRemoteDesktopSession *session,
+                                   GError                  **error)
+{
+  ClutterDeviceManager *device_manager =
+    clutter_device_manager_get_default ();
+
+  g_assert (!session->virtual_pointer && !session->virtual_keyboard);
+
+  if (session->screen_cast_session)
+    {
+      if (!meta_screen_cast_session_start (session->screen_cast_session, error))
+        return FALSE;
+    }
+
+  session->virtual_pointer =
+    clutter_device_manager_create_virtual_device (device_manager,
+                                                  CLUTTER_POINTER_DEVICE);
+  session->virtual_keyboard =
+    clutter_device_manager_create_virtual_device (device_manager,
+                                                  CLUTTER_KEYBOARD_DEVICE);
+
+  return TRUE;
+}
+
+void
+meta_remote_desktop_session_close (MetaRemoteDesktopSession *session)
+{
+  MetaDBusRemoteDesktopSession *skeleton =
+    META_DBUS_REMOTE_DESKTOP_SESSION (session);
+
+  if (session->screen_cast_session)
+    {
+      g_signal_handler_disconnect (session->screen_cast_session,
+                                   session->screen_cast_session_closed_handler_id);
+      meta_screen_cast_session_close (session->screen_cast_session);
+      session->screen_cast_session = NULL;
+    }
+
+  g_clear_object (&session->virtual_pointer);
+  g_clear_object (&session->virtual_keyboard);
+
+  meta_dbus_session_notify_closed (META_DBUS_SESSION (session));
+  meta_dbus_remote_desktop_session_emit_closed (skeleton);
+  g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (session));
+
+  g_object_unref (session);
+}
+
+char *
+meta_remote_desktop_session_get_object_path (MetaRemoteDesktopSession *session)
+{
+  return session->object_path;
+}
+
+char *
+meta_remote_desktop_session_get_session_id (MetaRemoteDesktopSession *session)
+{
+  return session->session_id;
+}
+
+static void
+on_screen_cast_session_closed (MetaScreenCastSession    *screen_cast_session,
+                               MetaRemoteDesktopSession *session)
+{
+  session->screen_cast_session = NULL;
+  meta_remote_desktop_session_close (session);
+}
+
+gboolean
+meta_remote_desktop_session_register_screen_cast (MetaRemoteDesktopSession  *session,
+                                                  MetaScreenCastSession     *screen_cast_session,
+                                                  GError                   **error)
+{
+  if (session->screen_cast_session)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Remote desktop session already have an associated "
+                   "screen cast session");
+      return FALSE;
+    }
+
+  session->screen_cast_session = screen_cast_session;
+  session->screen_cast_session_closed_handler_id =
+    g_signal_connect (screen_cast_session, "session-closed",
+                      G_CALLBACK (on_screen_cast_session_closed),
+                      session);
+
+  return TRUE;
+}
+
+MetaRemoteDesktopSession *
+meta_remote_desktop_session_new (MetaRemoteDesktop  *remote_desktop,
+                                 GError            **error)
+{
+  GDBusInterfaceSkeleton *interface_skeleton;
+  MetaRemoteDesktopSession *session;
+  GDBusConnection *connection;
+
+  session = g_object_new (META_TYPE_REMOTE_DESKTOP_SESSION, NULL);
+
+  interface_skeleton = G_DBUS_INTERFACE_SKELETON (session);
+  connection = meta_remote_desktop_get_connection (remote_desktop);
+  if (!g_dbus_interface_skeleton_export (interface_skeleton,
+                                         connection,
+                                         session->object_path,
+                                         error))
+    {
+      g_object_unref (session);
+      return NULL;
+    }
+
+  return session;
+}
+
+static gboolean
+handle_start (MetaDBusRemoteDesktopSession *skeleton,
+              GDBusMethodInvocation        *invocation)
+{
+  MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
+  GError *error = NULL;
+
+  if (!meta_remote_desktop_session_start (session, &error))
+    {
+      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+                                             G_DBUS_ERROR_FAILED,
+                                             "Failed to start remote desktop: %s",
+                                             error->message);
+      g_error_free (error);
+
+      meta_remote_desktop_session_close (session);
+
+      return TRUE;
+    }
+
+  meta_dbus_remote_desktop_session_complete_start (skeleton, invocation);
+
+  return TRUE;
+}
+
+static gboolean
+handle_stop (MetaDBusRemoteDesktopSession *skeleton,
+             GDBusMethodInvocation        *invocation)
+{
+  MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
+
+  meta_remote_desktop_session_close (session);
+
+  meta_dbus_remote_desktop_session_complete_stop (skeleton, invocation);
+
+  return TRUE;
+}
+
+static gboolean
+handle_notify_keyboard_keysym (MetaDBusRemoteDesktopSession *skeleton,
+                               GDBusMethodInvocation        *invocation,
+                               unsigned int                  keysym,
+                               gboolean                      pressed)
+{
+  MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
+
+  ClutterKeyState state;
+
+  if (pressed)
+    state = CLUTTER_KEY_STATE_PRESSED;
+  else
+    state = CLUTTER_KEY_STATE_RELEASED;
+
+  clutter_virtual_input_device_notify_keyval (session->virtual_keyboard,
+                                              CLUTTER_CURRENT_TIME,
+                                              keysym,
+                                              state);
+
+  meta_dbus_remote_desktop_session_complete_notify_keyboard_keysym (skeleton,
+                                                                    invocation);
+  return TRUE;
+}
+
+/* Translation taken from the clutter evdev backend. */
+static int
+translate_to_clutter_button (int button)
+{
+  switch (button)
+    {
+    case BTN_LEFT:
+      return CLUTTER_BUTTON_PRIMARY;
+    case BTN_RIGHT:
+      return CLUTTER_BUTTON_SECONDARY;
+    case BTN_MIDDLE:
+      return CLUTTER_BUTTON_MIDDLE;
+    default:
+      /*
+       * For compatibility reasons, all additional buttons go after the old
+       * 4-7 scroll ones.
+       */
+      return button - (BTN_LEFT - 1) + 4;
+    }
+}
+
+static gboolean
+handle_notify_pointer_button (MetaDBusRemoteDesktopSession *skeleton,
+                              GDBusMethodInvocation        *invocation,
+                              int                           button_code,
+                              gboolean                      pressed)
+{
+  MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
+  uint32_t button;
+  ClutterButtonState state;
+
+  button = translate_to_clutter_button (button_code);
+
+  if (pressed)
+    state = CLUTTER_BUTTON_STATE_PRESSED;
+  else
+    state = CLUTTER_BUTTON_STATE_RELEASED;
+
+  clutter_virtual_input_device_notify_button (session->virtual_pointer,
+                                              CLUTTER_CURRENT_TIME,
+                                              button,
+                                              state);
+
+  meta_dbus_remote_desktop_session_complete_notify_pointer_button (skeleton,
+                                                                   invocation);
+
+  return TRUE;
+}
+
+static ClutterScrollDirection
+discrete_steps_to_scroll_direction (unsigned int axis,
+                                    int          steps)
+{
+  if (axis == 0 && steps < 0)
+    return CLUTTER_SCROLL_UP;
+  if (axis == 0 && steps > 0)
+    return CLUTTER_SCROLL_DOWN;
+  if (axis == 1 && steps < 0)
+    return CLUTTER_SCROLL_LEFT;
+  if (axis == 1 && steps > 0)
+    return CLUTTER_SCROLL_RIGHT;
+
+  g_assert_not_reached ();
+}
+
+static gboolean
+handle_notify_pointer_axis_discrete (MetaDBusRemoteDesktopSession *skeleton,
+                                     GDBusMethodInvocation        *invocation,
+                                     unsigned int                  axis,
+                                     int                           steps)
+{
+  MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
+  ClutterScrollDirection direction;
+
+  if (axis <= 1)
+    {
+      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+                                             G_DBUS_ERROR_FAILED,
+                                             "Invalid axis value");
+      return TRUE;
+    }
+
+  if (steps == 0)
+    {
+      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+                                             G_DBUS_ERROR_FAILED,
+                                             "Invalid axis steps value");
+      return TRUE;
+    }
+
+  if (steps != -1 || steps != 1)
+    g_warning ("Multiple steps at at once not yet implemented, treating as one.");
+
+  /*
+   * We don't have the actual scroll source, but only know they should be
+   * considered as discrete steps. The device that produces such scroll events
+   * is the scroll wheel, so pretend that is the scroll source.
+   */
+  direction = discrete_steps_to_scroll_direction (axis, steps);
+  clutter_virtual_input_device_notify_discrete_scroll (session->virtual_pointer,
+                                                       CLUTTER_CURRENT_TIME,
+                                                       direction,
+                                                       CLUTTER_SCROLL_SOURCE_WHEEL);
+
+  meta_dbus_remote_desktop_session_complete_notify_pointer_axis_discrete (skeleton,
+                                                                          invocation);
+
+  return TRUE;
+}
+
+static gboolean
+handle_notify_pointer_motion_absolute (MetaDBusRemoteDesktopSession *skeleton,
+                                       GDBusMethodInvocation        *invocation,
+                                       const char                   *stream_path,
+                                       double                        x,
+                                       double                        y)
+{
+  MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
+
+  clutter_virtual_input_device_notify_absolute_motion (session->virtual_pointer,
+                                                       CLUTTER_CURRENT_TIME,
+                                                       x, y);
+
+  meta_dbus_remote_desktop_session_complete_notify_pointer_motion_absolute (skeleton,
+                                                                            invocation);
+
+  return TRUE;
+}
+
+static void
+meta_remote_desktop_session_init_iface (MetaDBusRemoteDesktopSessionIface *iface)
+{
+  iface->handle_start = handle_start;
+  iface->handle_stop = handle_stop;
+  iface->handle_notify_keyboard_keysym = handle_notify_keyboard_keysym;
+  iface->handle_notify_pointer_button = handle_notify_pointer_button;
+  iface->handle_notify_pointer_axis_discrete = handle_notify_pointer_axis_discrete;
+  iface->handle_notify_pointer_motion_absolute = handle_notify_pointer_motion_absolute;
+}
+
+static void
+meta_remote_desktop_session_client_vanished (MetaDbusSession *dbus_session)
+{
+  meta_remote_desktop_session_close (META_REMOTE_DESKTOP_SESSION (dbus_session));
+}
+
+static void
+meta_dbus_session_init_iface (MetaDbusSessionInterface *iface)
+{
+  iface->client_vanished = meta_remote_desktop_session_client_vanished;
+}
+
+static void
+meta_remote_desktop_session_finalize (GObject *object)
+{
+  MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (object);
+
+  g_assert (!meta_remote_desktop_session_is_running (session));
+
+  g_free (session->session_id);
+  g_free (session->object_path);
+
+  G_OBJECT_CLASS (meta_remote_desktop_session_parent_class)->finalize (object);
+}
+
+static void
+meta_remote_desktop_session_init (MetaRemoteDesktopSession *session)
+{
+  MetaDBusRemoteDesktopSession *skeleton =
+    META_DBUS_REMOTE_DESKTOP_SESSION  (session);
+  GRand *rand;
+  static unsigned int global_session_number = 0;
+
+  rand = g_rand_new ();
+  session->session_id = meta_generate_random_id (rand, 32);
+  g_rand_free (rand);
+
+  meta_dbus_remote_desktop_session_set_session_id (skeleton, session->session_id);
+
+  session->object_path =
+    g_strdup_printf (META_REMOTE_DESKTOP_SESSION_DBUS_PATH "/u%u",
+                     ++global_session_number);
+}
+
+static void
+meta_remote_desktop_session_class_init (MetaRemoteDesktopSessionClass *klass)
+{
+
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = meta_remote_desktop_session_finalize;
+}
diff --git a/src/backends/meta-remote-desktop-session.h b/src/backends/meta-remote-desktop-session.h
new file mode 100644
index 0000000..a406a0f
--- /dev/null
+++ b/src/backends/meta-remote-desktop-session.h
@@ -0,0 +1,49 @@
+/* -*- 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.
+ *
+ */
+
+#ifndef META_REMOTE_DESKTOP_SESSION_H
+#define META_REMOTE_DESKTOP_SESSION_H
+
+#include <glib-object.h>
+
+#include "backends/meta-remote-desktop.h"
+#include "backends/meta-screen-cast-session.h"
+
+#define META_TYPE_REMOTE_DESKTOP_SESSION (meta_remote_desktop_session_get_type ())
+G_DECLARE_FINAL_TYPE (MetaRemoteDesktopSession, meta_remote_desktop_session,
+                      META, REMOTE_DESKTOP_SESSION,
+                      MetaDBusRemoteDesktopSessionSkeleton)
+
+char * meta_remote_desktop_session_get_object_path (MetaRemoteDesktopSession *session);
+
+char * meta_remote_desktop_session_get_session_id (MetaRemoteDesktopSession *session);
+
+gboolean meta_remote_desktop_session_register_screen_cast (MetaRemoteDesktopSession  *session,
+                                                           MetaScreenCastSession     *screen_cast_session,
+                                                           GError                   **error);
+
+void meta_remote_desktop_session_close (MetaRemoteDesktopSession *session);
+
+MetaRemoteDesktopSession * meta_remote_desktop_session_new (MetaRemoteDesktop  *remote_desktop,
+                                                            GError            **error);
+
+#endif /* META_REMOTE_DESKTOP_SESSION_H */
diff --git a/src/backends/meta-remote-desktop.c b/src/backends/meta-remote-desktop.c
new file mode 100644
index 0000000..4817276
--- /dev/null
+++ b/src/backends/meta-remote-desktop.c
@@ -0,0 +1,237 @@
+/* -*- 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.
+ *
+ */
+
+#define _GNU_SOURCE
+
+#include "config.h"
+
+#include "backends/meta-remote-desktop.h"
+
+#include <errno.h>
+#include <glib.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "meta-dbus-remote-desktop.h"
+#include "backends/meta-backend-private.h"
+#include "backends/meta-cursor-renderer.h"
+#include "backends/meta-remote-desktop-session.h"
+#include "backends/native/meta-cursor-renderer-native.h"
+#include "meta/errors.h"
+#include "meta/meta-backend.h"
+
+#define META_REMOTE_DESKTOP_DBUS_SERVICE "org.gnome.Mutter.RemoteDesktop"
+#define META_REMOTE_DESKTOP_DBUS_PATH "/org/gnome/Mutter/RemoteDesktop"
+
+struct _MetaRemoteDesktop
+{
+  MetaDBusRemoteDesktopSkeleton parent;
+
+  int dbus_name_id;
+
+  GHashTable *sessions;
+
+  MetaDbusSessionWatcher *session_watcher;
+};
+
+static void
+meta_remote_desktop_init_iface (MetaDBusRemoteDesktopIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (MetaRemoteDesktop,
+                         meta_remote_desktop,
+                         META_DBUS_TYPE_REMOTE_DESKTOP_SKELETON,
+                         G_IMPLEMENT_INTERFACE (META_DBUS_TYPE_REMOTE_DESKTOP,
+                                                meta_remote_desktop_init_iface));
+
+GDBusConnection *
+meta_remote_desktop_get_connection (MetaRemoteDesktop *remote_desktop)
+{
+  GDBusInterfaceSkeleton *interface_skeleton =
+    G_DBUS_INTERFACE_SKELETON (remote_desktop);
+
+  return g_dbus_interface_skeleton_get_connection (interface_skeleton);
+}
+
+MetaRemoteDesktopSession *
+meta_remote_desktop_get_session (MetaRemoteDesktop *remote_desktop,
+                                 const char        *session_id)
+{
+  return g_hash_table_lookup (remote_desktop->sessions, session_id);
+}
+
+static void
+on_session_closed (MetaRemoteDesktopSession *session,
+                   MetaRemoteDesktop        *remote_desktop)
+{
+  char *session_id;
+
+  session_id = meta_remote_desktop_session_get_session_id (session);
+  g_hash_table_remove (remote_desktop->sessions, session_id);
+}
+
+static gboolean
+handle_create_session (MetaDBusRemoteDesktop *skeleton,
+                       GDBusMethodInvocation *invocation)
+{
+  MetaRemoteDesktop *remote_desktop = META_REMOTE_DESKTOP (skeleton);
+  MetaRemoteDesktopSession *session;
+  GError *error = NULL;
+  char *session_id;
+  char *session_path;
+  const char *client_dbus_name;
+
+  session = meta_remote_desktop_session_new (remote_desktop,
+                                             &error);
+  if (!session)
+    {
+      g_warning ("Failed to create remote desktop session: %s",
+                 error->message);
+
+      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+                                             G_DBUS_ERROR_FAILED,
+                                             "Failed to create session: %s",
+                                             error->message);
+      g_error_free (error);
+
+      return TRUE;
+    }
+
+  session_id = meta_remote_desktop_session_get_session_id (session);
+  g_hash_table_insert (remote_desktop->sessions,
+                       session_id,
+                       session);
+
+  client_dbus_name = g_dbus_method_invocation_get_sender (invocation);
+  meta_dbus_session_watcher_watch_session (remote_desktop->session_watcher,
+                                           client_dbus_name,
+                                           META_DBUS_SESSION (session));
+
+  session_path = meta_remote_desktop_session_get_object_path (session);
+  meta_dbus_remote_desktop_complete_create_session (skeleton,
+                                                    invocation,
+                                                    session_path);
+
+  g_signal_connect (session, "session-closed",
+                    G_CALLBACK (on_session_closed),
+                    remote_desktop);
+
+  return TRUE;
+}
+
+static void
+meta_remote_desktop_init_iface (MetaDBusRemoteDesktopIface *iface)
+{
+  iface->handle_create_session = handle_create_session;
+}
+
+static void
+on_bus_acquired (GDBusConnection *connection,
+                 const char      *name,
+                 gpointer         user_data)
+{
+  MetaRemoteDesktop *remote_desktop = user_data;
+  GDBusInterfaceSkeleton *interface_skeleton =
+    G_DBUS_INTERFACE_SKELETON (remote_desktop);
+  GError *error = NULL;
+
+  if (!g_dbus_interface_skeleton_export (interface_skeleton,
+                                         connection,
+                                         META_REMOTE_DESKTOP_DBUS_PATH,
+                                         &error))
+    g_warning ("Failed to export remote desktop object: %s\n", error->message);
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+                  const char      *name,
+                  gpointer         user_data)
+{
+  g_info ("Acquired name %s\n", name);
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+              const char      *name,
+              gpointer         user_data)
+{
+  g_warning ("Lost or failed to acquire name %s\n", name);
+}
+
+static void
+meta_remote_desktop_constructed (GObject *object)
+{
+  MetaRemoteDesktop *remote_desktop = META_REMOTE_DESKTOP (object);
+
+  remote_desktop->dbus_name_id =
+    g_bus_own_name (G_BUS_TYPE_SESSION,
+                    META_REMOTE_DESKTOP_DBUS_SERVICE,
+                    G_BUS_NAME_OWNER_FLAGS_NONE,
+                    on_bus_acquired,
+                    on_name_acquired,
+                    on_name_lost,
+                    remote_desktop,
+                    NULL);
+}
+
+static void
+meta_remote_desktop_finalize (GObject *object)
+{
+  MetaRemoteDesktop *remote_desktop = META_REMOTE_DESKTOP (object);
+  GList *sessions;
+
+  if (remote_desktop->dbus_name_id != 0)
+    g_bus_unown_name (remote_desktop->dbus_name_id);
+
+  sessions = g_list_copy (g_hash_table_get_values (remote_desktop->sessions));
+  g_list_free_full (sessions,
+                    (GDestroyNotify) meta_remote_desktop_session_close);
+  g_hash_table_destroy (remote_desktop->sessions);
+
+  G_OBJECT_CLASS (meta_remote_desktop_parent_class)->finalize (object);
+}
+
+MetaRemoteDesktop *
+meta_remote_desktop_new (MetaDbusSessionWatcher *session_watcher)
+{
+  MetaRemoteDesktop *remote_desktop;
+
+  remote_desktop = g_object_new (META_TYPE_REMOTE_DESKTOP, NULL);
+  remote_desktop->session_watcher = session_watcher;
+
+  return remote_desktop;
+}
+
+static void
+meta_remote_desktop_init (MetaRemoteDesktop *remote_desktop)
+{
+  remote_desktop->sessions = g_hash_table_new (g_str_hash, g_str_equal);
+}
+
+static void
+meta_remote_desktop_class_init (MetaRemoteDesktopClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = meta_remote_desktop_constructed;
+  object_class->finalize = meta_remote_desktop_finalize;
+}
diff --git a/src/backends/meta-remote-desktop.h b/src/backends/meta-remote-desktop.h
new file mode 100644
index 0000000..9ce89f9
--- /dev/null
+++ b/src/backends/meta-remote-desktop.h
@@ -0,0 +1,45 @@
+/* -*- 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.
+ *
+ */
+
+#ifndef META_REMOTE_DESKTOP_H
+#define META_REMOTE_DESKTOP_H
+
+#include <glib-object.h>
+
+#include "backends/meta-dbus-session-watcher.h"
+#include "meta-dbus-remote-desktop.h"
+
+typedef struct _MetaRemoteDesktopSession MetaRemoteDesktopSession;
+
+#define META_TYPE_REMOTE_DESKTOP (meta_remote_desktop_get_type ())
+G_DECLARE_FINAL_TYPE (MetaRemoteDesktop, meta_remote_desktop,
+                      META, REMOTE_DESKTOP,
+                      MetaDBusRemoteDesktopSkeleton)
+
+MetaRemoteDesktopSession * meta_remote_desktop_get_session (MetaRemoteDesktop *remote_desktop,
+                                                            const char        *session_id);
+
+GDBusConnection * meta_remote_desktop_get_connection (MetaRemoteDesktop *remote_desktop);
+
+MetaRemoteDesktop * meta_remote_desktop_new (MetaDbusSessionWatcher *session_watcher);
+
+#endif /* META_REMOTE_DESKTOP_H */
diff --git a/src/backends/meta-screen-cast-monitor-stream-src.c 
b/src/backends/meta-screen-cast-monitor-stream-src.c
new file mode 100644
index 0000000..dd9196e
--- /dev/null
+++ b/src/backends/meta-screen-cast-monitor-stream-src.c
@@ -0,0 +1,181 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 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-monitor-stream-src.h"
+
+#include "backends/meta-backend-private.h"
+#include "backends/meta-screen-cast-monitor-stream.h"
+#include "backends/meta-logical-monitor.h"
+#include "backends/meta-monitor.h"
+#include "clutter/clutter.h"
+#include "clutter/clutter-mutter.h"
+
+struct _MetaScreenCastMonitorStreamSrc
+{
+  MetaScreenCastStreamSrc parent;
+
+  gulong stage_painted_handler_id;
+};
+
+G_DEFINE_TYPE (MetaScreenCastMonitorStreamSrc,
+               meta_screen_cast_monitor_stream_src,
+               META_TYPE_SCREEN_CAST_STREAM_SRC)
+
+static ClutterStage *
+get_stage (MetaScreenCastMonitorStreamSrc *monitor_src)
+{
+  MetaScreenCastStreamSrc *src;
+  MetaScreenCastStream *stream;
+  MetaScreenCastMonitorStream *monitor_stream;
+
+  src = META_SCREEN_CAST_STREAM_SRC (monitor_src);
+  stream = meta_screen_cast_stream_src_get_stream (src);
+  monitor_stream = META_SCREEN_CAST_MONITOR_STREAM (stream);
+
+  return meta_screen_cast_monitor_stream_get_stage (monitor_stream);
+}
+
+static MetaMonitor *
+get_monitor (MetaScreenCastMonitorStreamSrc *monitor_src)
+{
+  MetaScreenCastStreamSrc *src;
+  MetaScreenCastStream *stream;
+  MetaScreenCastMonitorStream *monitor_stream;
+
+  src = META_SCREEN_CAST_STREAM_SRC (monitor_src);
+  stream = meta_screen_cast_stream_src_get_stream (src);
+  monitor_stream = META_SCREEN_CAST_MONITOR_STREAM (stream);
+
+  return meta_screen_cast_monitor_stream_get_monitor (monitor_stream);
+}
+
+static void
+meta_screen_cast_monitor_stream_src_get_specs (MetaScreenCastStreamSrc *src,
+                                               int                     *width,
+                                               int                     *height,
+                                               float                   *frame_rate)
+{
+  MetaScreenCastMonitorStreamSrc *monitor_src =
+    META_SCREEN_CAST_MONITOR_STREAM_SRC (src);
+  MetaMonitor *monitor;
+  MetaLogicalMonitor *logical_monitor;
+  float scale;
+  MetaMonitorMode *mode;
+
+  monitor = get_monitor (monitor_src);
+  logical_monitor = meta_monitor_get_logical_monitor (monitor);
+  mode = meta_monitor_get_current_mode (monitor);
+
+  scale = logical_monitor->scale;
+  *width = (int) roundf (logical_monitor->rect.width * scale);
+  *height = (int) roundf (logical_monitor->rect.height * scale);
+  *frame_rate = meta_monitor_mode_get_refresh_rate (mode);
+}
+
+static void
+meta_screen_cast_monitor_stream_src_record_frame (MetaScreenCastStreamSrc *src,
+                                                  uint8_t                 *data)
+{
+  MetaScreenCastMonitorStreamSrc *monitor_src =
+    META_SCREEN_CAST_MONITOR_STREAM_SRC (src);
+  ClutterStage *stage;
+  MetaMonitor *monitor;
+  MetaLogicalMonitor *logical_monitor;
+
+  stage = get_stage (monitor_src);
+  monitor = get_monitor (monitor_src);
+  logical_monitor = meta_monitor_get_logical_monitor (monitor);
+  clutter_stage_capture_into (stage, FALSE, &logical_monitor->rect, data);
+}
+
+static void
+stage_painted (ClutterActor                   *actor,
+               MetaScreenCastMonitorStreamSrc *monitor_src)
+{
+  MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (monitor_src);
+
+  meta_screen_cast_stream_src_maybe_record_frame (src);
+}
+
+MetaScreenCastMonitorStreamSrc *
+meta_screen_cast_monitor_stream_src_new (MetaScreenCastMonitorStream  *monitor_stream,
+                                         const char                   *stream_id,
+                                         GError                      **error)
+{
+  return g_initable_new (META_TYPE_SCREEN_CAST_MONITOR_STREAM_SRC,
+                         NULL,
+                         error,
+                         "stream-id", stream_id,
+                         "stream", monitor_stream,
+                         NULL);
+}
+
+static void
+meta_screen_cast_monitor_stream_src_constructed (GObject *object)
+{
+  MetaScreenCastMonitorStreamSrc *monitor_src =
+    META_SCREEN_CAST_MONITOR_STREAM_SRC (object);
+  ClutterStage *stage;
+
+  stage = get_stage (monitor_src);
+  monitor_src->stage_painted_handler_id =
+    g_signal_connect_after (stage, "paint",
+                            G_CALLBACK (stage_painted),
+                            monitor_src);
+  clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
+}
+
+static void
+meta_screen_cast_monitor_stream_src_finalize (GObject *object)
+{
+  MetaScreenCastMonitorStreamSrc *monitor_src =
+    META_SCREEN_CAST_MONITOR_STREAM_SRC (object);
+  GObjectClass *parent_class =
+    G_OBJECT_CLASS (meta_screen_cast_monitor_stream_src_parent_class);
+  ClutterStage *stage;
+
+  stage = get_stage (monitor_src);
+  g_signal_handler_disconnect (stage, monitor_src->stage_painted_handler_id);
+
+  parent_class->finalize (object);
+}
+
+static void
+meta_screen_cast_monitor_stream_src_init (MetaScreenCastMonitorStreamSrc *monitor_src)
+{
+}
+
+static void
+meta_screen_cast_monitor_stream_src_class_init (MetaScreenCastMonitorStreamSrcClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  MetaScreenCastStreamSrcClass *src_class =
+    META_SCREEN_CAST_STREAM_SRC_CLASS (klass);
+
+  object_class->constructed = meta_screen_cast_monitor_stream_src_constructed;
+  object_class->finalize = meta_screen_cast_monitor_stream_src_finalize;
+
+  src_class->get_specs = meta_screen_cast_monitor_stream_src_get_specs;
+  src_class->record_frame = meta_screen_cast_monitor_stream_src_record_frame;
+}
diff --git a/src/backends/meta-screen-cast-monitor-stream-src.h 
b/src/backends/meta-screen-cast-monitor-stream-src.h
new file mode 100644
index 0000000..26a0d91
--- /dev/null
+++ b/src/backends/meta-screen-cast-monitor-stream-src.h
@@ -0,0 +1,41 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 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.
+ *
+ */
+
+#ifndef META_SCREEN_CAST_MONITOR_STREAM_SRC_H
+#define META_SCREEN_CAST_MONITOR_STREAM_SRC_H
+
+#include "backends/meta-monitor-manager-private.h"
+#include "backends/meta-screen-cast-stream-src.h"
+
+typedef struct _MetaScreenCastMonitorStream MetaScreenCastMonitorStream;
+
+#define META_TYPE_SCREEN_CAST_MONITOR_STREAM_SRC (meta_screen_cast_monitor_stream_src_get_type ())
+G_DECLARE_FINAL_TYPE (MetaScreenCastMonitorStreamSrc,
+                      meta_screen_cast_monitor_stream_src,
+                      META, SCREEN_CAST_MONITOR_STREAM_SRC,
+                      MetaScreenCastStreamSrc)
+
+MetaScreenCastMonitorStreamSrc * meta_screen_cast_monitor_stream_src_new (MetaScreenCastMonitorStream 
*monitor_stream,
+                                                                          const char                  
*stream_id,
+                                                                          GError                     
**error);
+
+#endif /* META_SCREEN_CAST_MONITOR_STREAM_SRC_H */
diff --git a/src/backends/meta-screen-cast-monitor-stream.c b/src/backends/meta-screen-cast-monitor-stream.c
new file mode 100644
index 0000000..e97a173
--- /dev/null
+++ b/src/backends/meta-screen-cast-monitor-stream.c
@@ -0,0 +1,181 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 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-monitor-stream.h"
+
+#include "backends/meta-logical-monitor.h"
+#include "backends/meta-screen-cast-monitor-stream-src.h"
+
+struct _MetaScreenCastMonitorStream
+{
+  MetaScreenCastStream parent;
+
+  ClutterStage *stage;
+
+  MetaMonitor *monitor;
+  MetaLogicalMonitor *logical_monitor;
+};
+
+G_DEFINE_TYPE (MetaScreenCastMonitorStream,
+               meta_screen_cast_monitor_stream,
+               META_TYPE_SCREEN_CAST_STREAM)
+
+static gboolean
+update_monitor (MetaScreenCastMonitorStream *monitor_stream,
+                MetaMonitor                 *new_monitor)
+{
+  MetaLogicalMonitor *new_logical_monitor;
+
+  new_logical_monitor = meta_monitor_get_logical_monitor (new_monitor);
+  if (!new_logical_monitor)
+    return FALSE;
+
+  if (!meta_rectangle_equal (&new_logical_monitor->rect,
+                             &monitor_stream->logical_monitor->rect))
+    return FALSE;
+
+  g_set_object (&monitor_stream->monitor, new_monitor);
+  g_set_object (&monitor_stream->logical_monitor, new_logical_monitor);
+
+  return TRUE;
+}
+
+static void
+on_monitors_changed (MetaMonitorManager          *monitor_manager,
+                     MetaScreenCastMonitorStream *monitor_stream)
+{
+  MetaMonitor *new_monitor = NULL;
+  GList *monitors;
+  GList *l;
+
+  monitors = meta_monitor_manager_get_monitors (monitor_manager);
+  for (l = monitors; l; l = l->next)
+    {
+      MetaMonitor *other_monitor = l->data;
+
+      if (meta_monitor_is_same_as (monitor_stream->monitor, other_monitor))
+        {
+          new_monitor = other_monitor;
+          break;
+        }
+    }
+
+  if (!new_monitor || !update_monitor (monitor_stream, new_monitor))
+    meta_screen_cast_stream_close (META_SCREEN_CAST_STREAM (monitor_stream));
+}
+
+ClutterStage *
+meta_screen_cast_monitor_stream_get_stage (MetaScreenCastMonitorStream *monitor_stream)
+{
+  return monitor_stream->stage;
+}
+
+MetaMonitor *
+meta_screen_cast_monitor_stream_get_monitor (MetaScreenCastMonitorStream *monitor_stream)
+{
+  return monitor_stream->monitor;
+}
+
+MetaScreenCastMonitorStream *
+meta_screen_cast_monitor_stream_new (GDBusConnection     *connection,
+                                     MetaMonitorManager  *monitor_manager,
+                                     MetaMonitor         *monitor,
+                                     ClutterStage        *stage,
+                                     GError             **error)
+{
+  MetaScreenCastMonitorStream *monitor_stream;
+  MetaLogicalMonitor *logical_monitor;
+
+  logical_monitor = meta_monitor_get_logical_monitor (monitor);
+  if (!logical_monitor)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Monitor not active");
+      return NULL;
+    }
+
+  monitor_stream = g_initable_new (META_TYPE_SCREEN_CAST_MONITOR_STREAM,
+                                   NULL,
+                                   error,
+                                   "connection", connection,
+                                   NULL);
+  if (!monitor_stream)
+    return NULL;
+
+  g_set_object (&monitor_stream->monitor, monitor);
+  g_set_object (&monitor_stream->logical_monitor, logical_monitor);
+  monitor_stream->stage = stage;
+
+  g_signal_connect_object (monitor_manager, "monitors-changed",
+                           G_CALLBACK (on_monitors_changed),
+                           monitor_stream, 0);
+
+  return monitor_stream;
+}
+
+static MetaScreenCastStreamSrc *
+meta_screen_cast_monitor_stream_create_src (MetaScreenCastStream  *stream,
+                                            const char            *stream_id,
+                                            GError               **error)
+{
+  MetaScreenCastMonitorStream *monitor_stream =
+    META_SCREEN_CAST_MONITOR_STREAM (stream);
+  MetaScreenCastMonitorStreamSrc *monitor_stream_src;
+
+  monitor_stream_src = meta_screen_cast_monitor_stream_src_new (monitor_stream,
+                                                                stream_id,
+                                                                error);
+  if (!monitor_stream_src)
+    return NULL;
+
+  return META_SCREEN_CAST_STREAM_SRC (monitor_stream_src);
+}
+
+static void
+meta_screen_cast_monitor_stream_finalize (GObject *object)
+{
+  MetaScreenCastMonitorStream *monitor_stream =
+    META_SCREEN_CAST_MONITOR_STREAM (object);
+
+  g_clear_object (&monitor_stream->monitor);
+  g_clear_object (&monitor_stream->logical_monitor);
+
+  G_OBJECT_CLASS (meta_screen_cast_monitor_stream_parent_class)->finalize (object);
+}
+
+static void
+meta_screen_cast_monitor_stream_init (MetaScreenCastMonitorStream *monitor_stream)
+{
+}
+
+static void
+meta_screen_cast_monitor_stream_class_init (MetaScreenCastMonitorStreamClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  MetaScreenCastStreamClass *stream_class =
+    META_SCREEN_CAST_STREAM_CLASS (klass);
+
+  object_class->finalize = meta_screen_cast_monitor_stream_finalize;
+
+  stream_class->create_src = meta_screen_cast_monitor_stream_create_src;
+}
diff --git a/src/backends/meta-screen-cast-monitor-stream.h b/src/backends/meta-screen-cast-monitor-stream.h
new file mode 100644
index 0000000..fbf3c77
--- /dev/null
+++ b/src/backends/meta-screen-cast-monitor-stream.h
@@ -0,0 +1,47 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 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.
+ *
+ */
+
+#ifndef META_SCREEN_CAST_MONITOR_STREAM_H
+#define META_SCREEN_CAST_MONITOR_STREAM_H
+
+#include <glib-object.h>
+
+#include "backends/meta-monitor-manager-private.h"
+#include "backends/meta-screen-cast-stream.h"
+
+#define META_TYPE_SCREEN_CAST_MONITOR_STREAM (meta_screen_cast_monitor_stream_get_type ())
+G_DECLARE_FINAL_TYPE (MetaScreenCastMonitorStream,
+                      meta_screen_cast_monitor_stream,
+                      META, SCREEN_CAST_MONITOR_STREAM,
+                      MetaScreenCastStream)
+
+MetaScreenCastMonitorStream * meta_screen_cast_monitor_stream_new (GDBusConnection    *connection,
+                                                                  MetaMonitorManager *monitor_manager,
+                                                                   MetaMonitor        *monitor,
+                                                                   ClutterStage       *stage,
+                                                                   GError            **error);
+
+ClutterStage * meta_screen_cast_monitor_stream_get_stage (MetaScreenCastMonitorStream *monitor_stream);
+
+MetaMonitor * meta_screen_cast_monitor_stream_get_monitor (MetaScreenCastMonitorStream *monitor_stream);
+
+#endif /* META_SCREEN_CAST_MONITOR_STREAM_H */
diff --git a/src/backends/meta-screen-cast-session.c b/src/backends/meta-screen-cast-session.c
new file mode 100644
index 0000000..e53f78d
--- /dev/null
+++ b/src/backends/meta-screen-cast-session.c
@@ -0,0 +1,280 @@
+/* -*- 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-session.h"
+
+#include "backends/meta-backend-private.h"
+#include "backends/meta-dbus-session-watcher.h"
+#include "backends/meta-screen-cast-monitor-stream.h"
+#include "backends/meta-screen-cast-stream.h"
+
+#define META_SCREEN_CAST_SESSION_DBUS_PATH "/org/gnome/Mutter/ScreenCast/Session"
+
+struct _MetaScreenCastSession
+{
+  MetaDBusScreenCastSessionSkeleton parent;
+
+  MetaScreenCastSessionType session_type;
+  char *object_path;
+
+  GList *streams;
+};
+
+static void
+meta_screen_cast_session_init_iface (MetaDBusScreenCastSessionIface *iface);
+
+static void
+meta_dbus_session_init_iface (MetaDbusSessionInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (MetaScreenCastSession,
+                         meta_screen_cast_session,
+                         META_DBUS_TYPE_SCREEN_CAST_SESSION_SKELETON,
+                         G_IMPLEMENT_INTERFACE (META_DBUS_TYPE_SCREEN_CAST_SESSION,
+                                                meta_screen_cast_session_init_iface)
+                         G_IMPLEMENT_INTERFACE (META_TYPE_DBUS_SESSION,
+                                                meta_dbus_session_init_iface))
+
+gboolean
+meta_screen_cast_session_start (MetaScreenCastSession  *session,
+                                GError                **error)
+{
+  GList *l;
+
+  for (l = session->streams; l; l = l->next)
+    {
+      MetaScreenCastStream *stream = l->data;
+
+      if (!meta_screen_cast_stream_start (stream, error))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+void
+meta_screen_cast_session_close (MetaScreenCastSession *session)
+{
+  MetaDBusScreenCastSession *skeleton = META_DBUS_SCREEN_CAST_SESSION (session);
+
+  g_list_free_full (session->streams, g_object_unref);
+
+  meta_dbus_session_notify_closed (META_DBUS_SESSION (session));
+
+  switch (session->session_type)
+    {
+    case META_SCREEN_CAST_SESSION_TYPE_NORMAL:
+      meta_dbus_screen_cast_session_emit_closed (skeleton);
+      break;
+    case META_SCREEN_CAST_SESSION_TYPE_REMOTE_DESKTOP:
+      break;
+    }
+
+  g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (session));
+
+  g_object_unref (session);
+}
+
+char *
+meta_screen_cast_session_get_object_path (MetaScreenCastSession *session)
+{
+  return session->object_path;
+}
+
+static gboolean
+handle_start (MetaDBusScreenCastSession *skeleton,
+              GDBusMethodInvocation     *invocation)
+{
+  g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+                                         G_DBUS_ERROR_FAILED,
+                                         "Stand alone screen casting not yet implemented");
+
+  return TRUE;
+}
+
+static gboolean
+handle_stop (MetaDBusScreenCastSession *skeleton,
+             GDBusMethodInvocation     *invocation)
+{
+  g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+                                         G_DBUS_ERROR_FAILED,
+                                         "Stand alone screen casting not yet implemented");
+
+  return TRUE;
+}
+
+static void
+on_stream_closed (MetaScreenCastStream  *stream,
+                  MetaScreenCastSession *session)
+{
+  meta_screen_cast_session_close (session);
+}
+
+static gboolean
+handle_record_monitor (MetaDBusScreenCastSession *skeleton,
+                       GDBusMethodInvocation     *invocation,
+                       const char                *connector,
+                       GVariant                  *properties_variant)
+{
+  MetaScreenCastSession *session = META_SCREEN_CAST_SESSION (skeleton);
+  GDBusInterfaceSkeleton *interface_skeleton;
+  GDBusConnection *connection;
+  MetaBackend *backend = meta_get_backend ();
+  MetaMonitorManager *monitor_manager =
+    meta_backend_get_monitor_manager (backend);
+  MetaMonitor *monitor;
+  ClutterStage *stage;
+  GError *error = NULL;
+  MetaScreenCastMonitorStream *monitor_stream;
+  MetaScreenCastStream *stream;
+  char *stream_path;
+
+  interface_skeleton = G_DBUS_INTERFACE_SKELETON (skeleton);
+  connection = g_dbus_interface_skeleton_get_connection (interface_skeleton);
+
+  if (g_str_equal (connector, ""))
+    monitor = meta_monitor_manager_get_primary_monitor (monitor_manager);
+  else
+    monitor = meta_monitor_manager_get_monitor_from_connector (monitor_manager,
+                                                               connector);
+
+  if (!monitor)
+    {
+      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+                                             G_DBUS_ERROR_FAILED,
+                                             "Unknown monitor");
+      return TRUE;
+    }
+
+  stage = CLUTTER_STAGE (meta_backend_get_stage (backend));
+
+  monitor_stream = meta_screen_cast_monitor_stream_new (connection,
+                                                        monitor_manager,
+                                                        monitor,
+                                                        stage,
+                                                        &error);
+  if (!monitor_stream)
+    {
+      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+                                             G_DBUS_ERROR_FAILED,
+                                             "Failed to record monitor: %s",
+                                             error->message);
+      g_error_free (error);
+      return TRUE;
+    }
+
+  stream = META_SCREEN_CAST_STREAM (monitor_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_monitor (skeleton,
+                                                         invocation,
+                                                         stream_path);
+
+  return TRUE;
+}
+
+static gboolean
+handle_record_window (MetaDBusScreenCastSession *skeleton,
+                      GDBusMethodInvocation     *invocation,
+                      GVariant                  *properties_variant)
+{
+  g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+                                         G_DBUS_ERROR_FAILED,
+                                         "Recording a window not yet supported");
+  return TRUE;
+}
+
+static void
+meta_screen_cast_session_init_iface (MetaDBusScreenCastSessionIface *iface)
+{
+  iface->handle_start = handle_start;
+  iface->handle_stop = handle_stop;
+  iface->handle_record_monitor = handle_record_monitor;
+  iface->handle_record_window = handle_record_window;
+}
+
+static void
+meta_screen_cast_session_client_vanished (MetaDbusSession *dbus_session)
+{
+  meta_screen_cast_session_close (META_SCREEN_CAST_SESSION (dbus_session));
+}
+
+static void
+meta_dbus_session_init_iface (MetaDbusSessionInterface *iface)
+{
+  iface->client_vanished = meta_screen_cast_session_client_vanished;
+}
+
+MetaScreenCastSession *
+meta_screen_cast_session_new (MetaScreenCast             *screen_cast,
+                              MetaScreenCastSessionType   session_type,
+                              GError                    **error)
+{
+  GDBusInterfaceSkeleton *interface_skeleton;
+  MetaScreenCastSession *session;
+  GDBusConnection *connection;
+  static unsigned int global_session_number = 0;
+
+  session = g_object_new (META_TYPE_SCREEN_CAST_SESSION, NULL);
+  session->session_type = session_type;
+  session->object_path =
+    g_strdup_printf (META_SCREEN_CAST_SESSION_DBUS_PATH "/u%u",
+                     ++global_session_number);
+
+  interface_skeleton = G_DBUS_INTERFACE_SKELETON (session);
+  connection = meta_screen_cast_get_connection (screen_cast);
+  if (!g_dbus_interface_skeleton_export (interface_skeleton,
+                                         connection,
+                                         session->object_path,
+                                         error))
+    return NULL;
+
+  return session;
+}
+
+static void
+meta_screen_cast_session_finalize (GObject *object)
+{
+  MetaScreenCastSession *session = META_SCREEN_CAST_SESSION (object);
+
+  g_free (session->object_path);
+
+  G_OBJECT_CLASS (meta_screen_cast_session_parent_class)->finalize (object);
+}
+
+static void
+meta_screen_cast_session_init (MetaScreenCastSession *session)
+{
+}
+
+static void
+meta_screen_cast_session_class_init (MetaScreenCastSessionClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = meta_screen_cast_session_finalize;
+}
diff --git a/src/backends/meta-screen-cast-session.h b/src/backends/meta-screen-cast-session.h
new file mode 100644
index 0000000..cf8d8e2
--- /dev/null
+++ b/src/backends/meta-screen-cast-session.h
@@ -0,0 +1,50 @@
+/* -*- 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.
+ *
+ */
+
+#ifndef META_SCREEN_CAST_SESSION_H
+#define META_SCREEN_CAST_SESSION_H
+
+#include "backends/meta-screen-cast.h"
+
+typedef enum _MetaScreenCastSessionType
+{
+  META_SCREEN_CAST_SESSION_TYPE_NORMAL,
+  META_SCREEN_CAST_SESSION_TYPE_REMOTE_DESKTOP,
+} MetaScreenCastSessionType;
+
+#define META_TYPE_SCREEN_CAST_SESSION (meta_screen_cast_session_get_type ())
+G_DECLARE_FINAL_TYPE (MetaScreenCastSession, meta_screen_cast_session,
+                      META, SCREEN_CAST_SESSION,
+                      MetaDBusScreenCastSessionSkeleton)
+
+char * meta_screen_cast_session_get_object_path (MetaScreenCastSession *session);
+
+MetaScreenCastSession * meta_screen_cast_session_new (MetaScreenCast             *screen_cast,
+                                                      MetaScreenCastSessionType   session_type,
+                                                      GError                    **error);
+
+gboolean meta_screen_cast_session_start (MetaScreenCastSession  *session,
+                                         GError                **error);
+
+void meta_screen_cast_session_close (MetaScreenCastSession *session);
+
+#endif /* META_SCREEN_CAST_SESSION_H */
diff --git a/src/backends/meta-screen-cast-stream-src.c b/src/backends/meta-screen-cast-stream-src.c
new file mode 100644
index 0000000..5e64846
--- /dev/null
+++ b/src/backends/meta-screen-cast-stream-src.c
@@ -0,0 +1,597 @@
+/* -*- 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));
+}
diff --git a/src/backends/meta-screen-cast-stream-src.h b/src/backends/meta-screen-cast-stream-src.h
new file mode 100644
index 0000000..7e7ecb5
--- /dev/null
+++ b/src/backends/meta-screen-cast-stream-src.h
@@ -0,0 +1,55 @@
+/* -*- 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.
+ *
+ */
+
+#ifndef META_SCREEN_CAST_STREAM_SRC_H
+#define META_SCREEN_CAST_STREAM_SRC_H
+
+#include <glib-object.h>
+
+#include "clutter/clutter.h"
+#include "mutter/meta/boxes.h"
+
+typedef struct _MetaScreenCastStream MetaScreenCastStream;
+
+#define META_TYPE_SCREEN_CAST_STREAM_SRC (meta_screen_cast_stream_src_get_type ())
+G_DECLARE_DERIVABLE_TYPE (MetaScreenCastStreamSrc,
+                          meta_screen_cast_stream_src,
+                          META, SCREEN_CAST_STREAM_SRC,
+                          GObject)
+
+struct _MetaScreenCastStreamSrcClass
+{
+  GObjectClass parent_class;
+
+  void (* get_specs) (MetaScreenCastStreamSrc *src,
+                      int                     *width,
+                      int                     *height,
+                      float                   *frame_rate);
+  void (* record_frame) (MetaScreenCastStreamSrc *src,
+                         uint8_t                 *data);
+};
+
+void meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc *src);
+
+MetaScreenCastStream * meta_screen_cast_stream_src_get_stream (MetaScreenCastStreamSrc *src);
+
+#endif /* META_SCREEN_CAST_STREAM_SRC_H */
diff --git a/src/backends/meta-screen-cast-stream.c b/src/backends/meta-screen-cast-stream.c
new file mode 100644
index 0000000..8d7a6df
--- /dev/null
+++ b/src/backends/meta-screen-cast-stream.c
@@ -0,0 +1,229 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 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.h"
+
+#define META_SCREEN_CAST_STREAM_DBUS_PATH "/org/gnome/Mutter/ScreenCast/Stream"
+
+enum
+{
+  PROP_0,
+
+  PROP_CONNECTION,
+};
+
+enum
+{
+  CLOSED,
+
+  N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+typedef struct _MetaScreenCastStreamPrivate
+{
+  GDBusConnection *connection;
+  char *object_path;
+
+  MetaScreenCastStreamSrc *src;
+} MetaScreenCastStreamPrivate;
+
+static void
+meta_screen_cast_stream_init_initable_iface (GInitableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (MetaScreenCastStream,
+                         meta_screen_cast_stream,
+                         META_DBUS_TYPE_SCREEN_CAST_STREAM_SKELETON,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                meta_screen_cast_stream_init_initable_iface)
+                         G_ADD_PRIVATE (MetaScreenCastStream))
+
+static MetaScreenCastStreamSrc *
+meta_screen_cast_stream_create_src (MetaScreenCastStream  *stream,
+                                    const char            *stream_id,
+                                    GError               **error)
+{
+  return META_SCREEN_CAST_STREAM_GET_CLASS (stream)->create_src (stream,
+                                                                 stream_id,
+                                                                 error);
+}
+
+gboolean
+meta_screen_cast_stream_start (MetaScreenCastStream  *stream,
+                               GError               **error)
+{
+  MetaDBusScreenCastStream *skeleton = META_DBUS_SCREEN_CAST_STREAM (stream);
+  MetaScreenCastStreamPrivate *priv =
+    meta_screen_cast_stream_get_instance_private (stream);
+  g_autofree char *stream_id = NULL;
+  MetaScreenCastStreamSrc *src;
+  static unsigned int global_stream_id = 0;
+
+  stream_id = g_strdup_printf ("%u", ++global_stream_id);
+  src = meta_screen_cast_stream_create_src (stream, stream_id, error);
+  if (!src)
+    return FALSE;
+
+  priv->src = src;
+
+  meta_dbus_screen_cast_stream_emit_pipewire_stream_added (skeleton, stream_id);
+
+  return TRUE;
+}
+
+void
+meta_screen_cast_stream_close (MetaScreenCastStream *stream)
+{
+  MetaScreenCastStreamPrivate *priv =
+    meta_screen_cast_stream_get_instance_private (stream);
+
+  g_clear_object (&priv->src);
+
+  g_signal_emit (stream, signals[CLOSED], 0);
+}
+
+char *
+meta_screen_cast_stream_get_object_path (MetaScreenCastStream *stream)
+{
+  MetaScreenCastStreamPrivate *priv =
+    meta_screen_cast_stream_get_instance_private (stream);
+
+  return priv->object_path;
+}
+
+static void
+meta_screen_cast_stream_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+  MetaScreenCastStream *stream = META_SCREEN_CAST_STREAM (object);
+  MetaScreenCastStreamPrivate *priv =
+    meta_screen_cast_stream_get_instance_private (stream);
+
+  switch (prop_id)
+    {
+    case PROP_CONNECTION:
+      priv->connection = g_value_get_object (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+meta_screen_cast_stream_get_property (GObject    *object,
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+  MetaScreenCastStream *stream = META_SCREEN_CAST_STREAM (object);
+  MetaScreenCastStreamPrivate *priv =
+    meta_screen_cast_stream_get_instance_private (stream);
+
+  switch (prop_id)
+    {
+    case PROP_CONNECTION:
+      g_value_set_object (value, priv->connection);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+meta_screen_cast_stream_finalize (GObject *object)
+{
+  MetaScreenCastStream *stream = META_SCREEN_CAST_STREAM (object);
+  MetaScreenCastStreamPrivate *priv =
+    meta_screen_cast_stream_get_instance_private (stream);
+
+  if (priv->src)
+    meta_screen_cast_stream_close (stream);
+
+  g_clear_pointer (&priv->object_path, g_free);
+
+  G_OBJECT_CLASS (meta_screen_cast_stream_parent_class)->finalize (object);
+}
+
+static gboolean
+meta_screen_cast_stream_initable_init (GInitable     *initable,
+                                       GCancellable  *cancellable,
+                                       GError       **error)
+{
+  MetaScreenCastStream *stream = META_SCREEN_CAST_STREAM (initable);
+  MetaScreenCastStreamPrivate *priv =
+    meta_screen_cast_stream_get_instance_private (stream);
+  static unsigned int global_stream_number = 0;
+
+  priv->object_path =
+    g_strdup_printf (META_SCREEN_CAST_STREAM_DBUS_PATH "/u%u",
+                     ++global_stream_number);
+  if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (stream),
+                                         priv->connection,
+                                         priv->object_path,
+                                         error))
+    return FALSE;
+
+  return TRUE;
+}
+
+static void
+meta_screen_cast_stream_init_initable_iface (GInitableIface *iface)
+{
+  iface->init = meta_screen_cast_stream_initable_init;
+}
+
+static void
+meta_screen_cast_stream_init (MetaScreenCastStream *stream)
+{
+}
+
+static void
+meta_screen_cast_stream_class_init (MetaScreenCastStreamClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = meta_screen_cast_stream_finalize;
+  object_class->set_property = meta_screen_cast_stream_set_property;
+  object_class->get_property = meta_screen_cast_stream_get_property;
+
+  g_object_class_install_property (object_class,
+                                   PROP_CONNECTION,
+                                   g_param_spec_object ("connection",
+                                                        "connection",
+                                                        "GDBus connection",
+                                                        G_TYPE_DBUS_CONNECTION,
+                                                        G_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_STRINGS));
+
+  signals[CLOSED] = g_signal_new ("closed",
+                                  G_TYPE_FROM_CLASS (klass),
+                                  G_SIGNAL_RUN_LAST,
+                                  0,
+                                  NULL, NULL, NULL,
+                                  G_TYPE_NONE, 0);
+}
diff --git a/src/backends/meta-screen-cast-stream.h b/src/backends/meta-screen-cast-stream.h
new file mode 100644
index 0000000..b8366ae
--- /dev/null
+++ b/src/backends/meta-screen-cast-stream.h
@@ -0,0 +1,52 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 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.
+ *
+ */
+
+#ifndef META_SCREEN_CAST_STREAM_H
+#define META_SCREEN_CAST_STREAM_H
+
+#include <glib-object.h>
+
+#include "backends/meta-screen-cast-stream-src.h"
+#include "meta-dbus-screen-cast.h"
+
+#define META_TYPE_SCREEN_CAST_STREAM (meta_screen_cast_stream_get_type ())
+G_DECLARE_DERIVABLE_TYPE (MetaScreenCastStream, meta_screen_cast_stream,
+                          META, SCREEN_CAST_STREAM,
+                          MetaDBusScreenCastStreamSkeleton)
+
+struct _MetaScreenCastStreamClass
+{
+  MetaDBusScreenCastStreamSkeletonClass parent_class;
+
+  MetaScreenCastStreamSrc * (* create_src) (MetaScreenCastStream *stream,
+                                            const char           *stream_id,
+                                            GError              **error);
+};
+
+gboolean meta_screen_cast_stream_start (MetaScreenCastStream *stream,
+                                        GError              **error);
+
+void meta_screen_cast_stream_close (MetaScreenCastStream *stream);
+
+char * meta_screen_cast_stream_get_object_path (MetaScreenCastStream *stream);
+
+#endif /* META_SCREEN_CAST_STREAM_H */
diff --git a/src/backends/meta-screen-cast.c b/src/backends/meta-screen-cast.c
new file mode 100644
index 0000000..558ad18
--- /dev/null
+++ b/src/backends/meta-screen-cast.c
@@ -0,0 +1,271 @@
+/* -*- 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.h"
+
+#include <pipewire/pipewire.h>
+
+#include "backends/meta-backend-private.h"
+#include "backends/meta-screen-cast-session.h"
+#include "backends/meta-remote-desktop-session.h"
+
+#define META_SCREEN_CAST_DBUS_SERVICE "org.gnome.Mutter.ScreenCast"
+#define META_SCREEN_CAST_DBUS_PATH "/org/gnome/Mutter/ScreenCast"
+
+struct _MetaScreenCast
+{
+  MetaDBusScreenCastSkeleton parent;
+
+  int dbus_name_id;
+
+  GList *sessions;
+
+  MetaDbusSessionWatcher *session_watcher;
+};
+
+static void
+meta_screen_cast_init_iface (MetaDBusScreenCastIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (MetaScreenCast, meta_screen_cast,
+                         META_DBUS_TYPE_SCREEN_CAST_SKELETON,
+                         G_IMPLEMENT_INTERFACE (META_DBUS_TYPE_SCREEN_CAST,
+                                                meta_screen_cast_init_iface))
+
+GDBusConnection *
+meta_screen_cast_get_connection (MetaScreenCast *screen_cast)
+{
+  GDBusInterfaceSkeleton *interface_skeleton =
+    G_DBUS_INTERFACE_SKELETON (screen_cast);
+
+  return g_dbus_interface_skeleton_get_connection (interface_skeleton);
+}
+
+static gboolean
+register_remote_desktop_screen_cast_session (MetaScreenCastSession  *session,
+                                             const char             *remote_desktop_session_id,
+                                             GError                **error)
+{
+  MetaBackend *backend = meta_get_backend ();
+  MetaRemoteDesktop *remote_desktop = meta_backend_get_remote_desktop (backend);
+  MetaRemoteDesktopSession *remote_desktop_session;
+
+  remote_desktop_session =
+    meta_remote_desktop_get_session (remote_desktop, remote_desktop_session_id);
+  if (!remote_desktop_session)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "No remote desktop session found");
+      return FALSE;
+    }
+
+  if (!meta_remote_desktop_session_register_screen_cast (remote_desktop_session,
+                                                         session,
+                                                         error))
+    return FALSE;
+
+  return TRUE;
+}
+
+static void
+on_session_closed (MetaScreenCastSession *session,
+                   MetaScreenCast        *screen_cast)
+{
+  screen_cast->sessions = g_list_remove (screen_cast->sessions, session);
+}
+
+static gboolean
+handle_create_session (MetaDBusScreenCast    *skeleton,
+                       GDBusMethodInvocation *invocation,
+                       GVariant              *properties)
+{
+  MetaScreenCast *screen_cast = META_SCREEN_CAST (skeleton);
+  MetaScreenCastSession *session;
+  GError *error = NULL;
+  const char *session_path;
+  const char *client_dbus_name;
+  char *remote_desktop_session_id = NULL;
+  MetaScreenCastSessionType session_type;
+
+  g_variant_lookup (properties, "remote-desktop-session-id", "s",
+                    &remote_desktop_session_id);
+
+  if (remote_desktop_session_id)
+    session_type = META_SCREEN_CAST_SESSION_TYPE_REMOTE_DESKTOP;
+  else
+    session_type = META_SCREEN_CAST_SESSION_TYPE_NORMAL;
+
+  session = meta_screen_cast_session_new (screen_cast, session_type, &error);
+  if (!session)
+    {
+      g_warning ("Failed to create screen cast session: %s",
+                 error->message);
+
+      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+                                             G_DBUS_ERROR_FAILED,
+                                             "Failed to create session: %s",
+                                             error->message);
+      g_error_free (error);
+
+      return TRUE;
+    }
+
+  if (remote_desktop_session_id)
+    {
+      if (!register_remote_desktop_screen_cast_session (session,
+                                                        remote_desktop_session_id,
+                                                        &error))
+        {
+          g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+                                                 G_DBUS_ERROR_FAILED,
+                                                 "%s", error->message);
+          g_error_free (error);
+          g_object_unref (session);
+          return TRUE;
+        }
+    }
+
+  client_dbus_name = g_dbus_method_invocation_get_sender (invocation);
+  meta_dbus_session_watcher_watch_session (screen_cast->session_watcher,
+                                           client_dbus_name,
+                                           META_DBUS_SESSION (session));
+
+  session_path = meta_screen_cast_session_get_object_path (session);
+  meta_dbus_screen_cast_complete_create_session (skeleton,
+                                                 invocation,
+                                                 session_path);
+
+  screen_cast->sessions = g_list_append (screen_cast->sessions, session);
+
+  g_signal_connect (session, "session-closed",
+                    G_CALLBACK (on_session_closed),
+                    screen_cast);
+
+  return TRUE;
+}
+
+static void
+meta_screen_cast_init_iface (MetaDBusScreenCastIface *iface)
+{
+  iface->handle_create_session = handle_create_session;
+}
+
+static void
+on_bus_acquired (GDBusConnection *connection,
+                 const char      *name,
+                 gpointer         user_data)
+{
+  MetaScreenCast *screen_cast = user_data;
+  GDBusInterfaceSkeleton *interface_skeleton =
+    G_DBUS_INTERFACE_SKELETON (screen_cast);
+  GError *error = NULL;
+
+  if (!g_dbus_interface_skeleton_export (interface_skeleton,
+                                         connection,
+                                         META_SCREEN_CAST_DBUS_PATH,
+                                         &error))
+    g_warning ("Failed to export remote desktop object: %s\n", error->message);
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+                  const char      *name,
+                  gpointer         user_data)
+{
+  g_info ("Acquired name %s\n", name);
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+              const char      *name,
+              gpointer         user_data)
+{
+  g_warning ("Lost or failed to acquire name %s\n", name);
+}
+
+static void
+meta_screen_cast_constructed (GObject *object)
+{
+  MetaScreenCast *screen_cast = META_SCREEN_CAST (object);
+
+  screen_cast->dbus_name_id =
+    g_bus_own_name (G_BUS_TYPE_SESSION,
+                    META_SCREEN_CAST_DBUS_SERVICE,
+                    G_BUS_NAME_OWNER_FLAGS_NONE,
+                    on_bus_acquired,
+                    on_name_acquired,
+                    on_name_lost,
+                    screen_cast,
+                    NULL);
+}
+
+static void
+meta_screen_cast_finalize (GObject *object)
+{
+  MetaScreenCast *screen_cast = META_SCREEN_CAST (object);
+
+  if (screen_cast->dbus_name_id)
+    g_bus_unown_name (screen_cast->dbus_name_id);
+
+  while (screen_cast->sessions)
+    {
+      MetaScreenCastSession *session = screen_cast->sessions->data;
+
+      meta_screen_cast_session_close (session);
+    }
+
+  G_OBJECT_CLASS (meta_screen_cast_parent_class)->finalize (object);
+}
+
+MetaScreenCast *
+meta_screen_cast_new (MetaDbusSessionWatcher *session_watcher)
+{
+  MetaScreenCast *screen_cast;
+
+  screen_cast = g_object_new (META_TYPE_SCREEN_CAST, NULL);
+  screen_cast->session_watcher = session_watcher;
+
+  return screen_cast;
+}
+
+
+static void
+meta_screen_cast_init (MetaScreenCast *screen_cast)
+{
+  static gboolean is_pipewire_initialized = FALSE;
+
+  if (!is_pipewire_initialized)
+    {
+      pw_init (NULL, NULL);
+      is_pipewire_initialized = TRUE;
+    }
+}
+
+static void
+meta_screen_cast_class_init (MetaScreenCastClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = meta_screen_cast_constructed;
+  object_class->finalize = meta_screen_cast_finalize;
+}
diff --git a/src/backends/meta-screen-cast.h b/src/backends/meta-screen-cast.h
new file mode 100644
index 0000000..a4fb8dd
--- /dev/null
+++ b/src/backends/meta-screen-cast.h
@@ -0,0 +1,40 @@
+/* -*- 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.
+ *
+ */
+
+#ifndef META_SCREEN_CAST_H
+#define META_SCREEN_CAST_H
+
+#include <glib-object.h>
+
+#include "backends/meta-dbus-session-watcher.h"
+#include "meta-dbus-screen-cast.h"
+
+#define META_TYPE_SCREEN_CAST (meta_screen_cast_get_type ())
+G_DECLARE_FINAL_TYPE (MetaScreenCast, meta_screen_cast,
+                      META, SCREEN_CAST,
+                      MetaDBusScreenCastSkeleton)
+
+GDBusConnection * meta_screen_cast_get_connection (MetaScreenCast *screen_cast);
+
+MetaScreenCast * meta_screen_cast_new (MetaDbusSessionWatcher *session_watcher);
+
+#endif /* META_SCREEN_CAST_H */
diff --git a/src/backends/meta-settings-private.h b/src/backends/meta-settings-private.h
index 494112b..1507807 100644
--- a/src/backends/meta-settings-private.h
+++ b/src/backends/meta-settings-private.h
@@ -31,6 +31,8 @@ typedef enum _MetaExperimentalFeature
 {
   META_EXPERIMENTAL_FEATURE_NONE = 0,
   META_EXPERIMENTAL_FEATURE_SCALE_MONITOR_FRAMEBUFFER = (1 << 0),
+  META_EXPERIMENTAL_FEATURE_SCREEN_CAST = (1 << 1),
+  META_EXPERIMENTAL_FEATURE_REMOTE_DESKTOP  = (1 << 2),
 } MetaExperimentalFeature;
 
 #define META_TYPE_SETTINGS (meta_settings_get_type ())
diff --git a/src/backends/meta-settings.c b/src/backends/meta-settings.c
index b7c4a96..cf66334 100644
--- a/src/backends/meta-settings.c
+++ b/src/backends/meta-settings.c
@@ -262,6 +262,10 @@ experimental_features_handler (GVariant *features_variant,
       /* So far no experimental features defined. */
       if (g_str_equal (feature, "scale-monitor-framebuffer"))
         features |= META_EXPERIMENTAL_FEATURE_SCALE_MONITOR_FRAMEBUFFER;
+      else if (g_str_equal (feature, "screen-cast"))
+        features |= META_EXPERIMENTAL_FEATURE_SCREEN_CAST;
+      else if (g_str_equal (feature, "remote-desktop"))
+        features |= META_EXPERIMENTAL_FEATURE_REMOTE_DESKTOP;
       else
         g_info ("Unknown experimental feature '%s'\n", feature);
     }
diff --git a/src/org.gnome.Mutter.RemoteDesktop.xml b/src/org.gnome.Mutter.RemoteDesktop.xml
new file mode 100644
index 0000000..7807f16
--- /dev/null
+++ b/src/org.gnome.Mutter.RemoteDesktop.xml
@@ -0,0 +1,103 @@
+<!DOCTYPE node PUBLIC
+'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
+'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
+<node>
+
+  <!--
+      org.gnome.Mutter.RemoteDesktop:
+      @short_description: Remote desktop interface
+  -->
+  <interface name="org.gnome.Mutter.RemoteDesktop">
+
+    <!--
+       CreateSession:
+       @session_path: Path to the new session object
+    -->
+    <method name="CreateSession">
+      <arg name="session_path" type="o" direction="out" />
+    </method>
+
+  </interface>
+
+  <!--
+       org.gnome.Mutter.RemoteDesktop.Session:
+       @short_description: Remote desktop session
+  -->
+  <interface name="org.gnome.Mutter.RemoteDesktop.Session">
+
+    <!--
+       SessionId:
+
+       An identification string used for identifying a remote desktop session.
+       It can be used to associate screen cast sessions with a remote desktop session.
+    -->
+    <property name="SessionId" type="s" access="read" />
+
+    <!--
+       Start:
+
+       Start the remote desktop session
+    -->
+    <method name="Start" />
+
+    <!--
+       Stop:
+
+       Stop the remote desktop session
+    -->
+    <method name="Stop" />
+
+    <!--
+       Closed:
+
+       The session has closed.
+
+       A session doesn't have to have been started before it may be closed.
+       After it being closed, it can no longer be used.
+    -->
+    <signal name="Closed" />
+
+    <!--
+       NotifyKeyboardKeysym:
+
+       A key identified by a keysym was pressed or released
+     -->
+    <method name="NotifyKeyboardKeysym">
+      <arg name="keysym" type="u" direction="in" />
+      <arg name="state" type="b" direction="in" />
+    </method>
+
+    <!--
+       NotifyPointerButton:
+
+       A pointer button was pressed or released
+     -->
+    <method name="NotifyPointerButton">
+      <arg name="button" type="i" direction="in" />
+      <arg name="state" type="b" direction="in" />
+    </method>
+
+    <!--
+       NotifyPointerAxisDiscrete:
+
+       A discrete pointer axis event notification
+     -->
+    <method name="NotifyPointerAxisDiscrete">
+      <arg name="axis" type="u" direction="in" />
+      <arg name="steps" type="i" direction="in" />
+    </method>
+
+    <!--
+       NotifyPointerMotionAbsolute:
+
+       A absolute pointer motion event notification
+     -->
+    <method name="NotifyPointerMotionAbsolute">
+      <arg name="stream" type="s" direction="in" />
+      <arg name="x" type="d" direction="in" />
+      <arg name="y" type="d" direction="in" />
+    </method>
+
+  </interface>
+
+</node>
diff --git a/src/org.gnome.Mutter.ScreenCast.xml b/src/org.gnome.Mutter.ScreenCast.xml
new file mode 100644
index 0000000..bab3fea
--- /dev/null
+++ b/src/org.gnome.Mutter.ScreenCast.xml
@@ -0,0 +1,107 @@
+<!DOCTYPE node PUBLIC
+'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
+'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
+<node>
+
+  <!--
+      org.gnome.Mutter.ScreenCast:
+      @short_description: Screen cast interface
+  -->
+  <interface name="org.gnome.Mutter.ScreenCast">
+
+    <!--
+       CreateSession:
+       @properties: Properties
+       @session_path: Path to the new session object
+
+       * "remote-desktop-session-id" (s): The ID of a remote desktop session.
+                                          Remote desktop driven screen casts
+                                          are started and stopped by the remote
+                                          desktop session.
+    -->
+    <method name="CreateSession">
+      <arg name="properties" type="a{sv}" direction="in" />
+      <arg name="session_path" type="o" direction="out" />
+    </method>
+
+  </interface>
+
+  <!--
+       org.gnome.Mutter.ScreenCast.Session:
+       @short_description: Screen cast session
+  -->
+  <interface name="org.gnome.Mutter.ScreenCast.Session">
+
+    <!--
+       Start:
+
+       Start the screen cast session
+    -->
+    <method name="Start" />
+
+    <!--
+       Stop:
+
+       Stop the screen cast session
+    -->
+    <method name="Stop" />
+
+    <!--
+       Closed:
+
+       The session has closed.
+    -->
+    <signal name="Closed" />
+
+    <!--
+       RecordMonitor:
+       @connector: Connector of the monitor to record
+       @properties: Properties
+       @stream_path: Path to the new stream object
+
+       Record a single monitor.
+
+       Available @properties include: (none)
+    -->
+    <method name="RecordMonitor">
+      <arg name="connector" type="s" direction="in" />
+      <arg name="properties" type="a{sv}" direction="in" />
+      <arg name="stream_path" type="o" direction="out" />
+    </method>
+
+    <!--
+       RecordWindow:
+       @properties: Properties used determining what window to select
+       @stream_path: Path to the new stream object
+
+       Record a single window.
+
+       Available @properties include: (none)
+    -->
+    <method name="RecordWindow">
+      <arg name="properties" type="a{sv}" direction="in" />
+      <arg name="stream_path" type="o" direction="out" />
+    </method>
+  </interface>
+
+  <!--
+       org.gnome.Mutter.ScreenCast.Stream:
+       @short_description: Screen cast stream
+  -->
+  <interface name="org.gnome.Mutter.ScreenCast.Stream">
+
+    <!--
+       PipeWireStreamAdded:
+       @short_description: Pipewire stream added
+
+       A signal emitted when PipeWire stream for the screen cast stream has
+       been created. The @node_id corresponds to the PipeWire stream node.
+    -->
+    <signal name="PipeWireStreamAdded">
+      <annotation name="org.gtk.GDBus.C.Name" value="pipewire-stream-added"/>
+      <arg name="stream_id" type="s" direction="out" />
+    </signal>
+
+  </interface>
+
+</node>


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