diff --git a/.gitignore b/.gitignore index 56fb267d2..321d7732f 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 f70d894fc..af005637d 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 6cbd9c1b5..fb1cf73c2 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. diff --git a/src/Makefile.am b/src/Makefile.am index d88539b04..6aef50de8 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 bf6d0c329..47aa291c8 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 66a731318..ba293b62d 100644 --- a/src/backends/meta-backend.c +++ b/src/backends/meta-backend.c @@ -29,13 +29,19 @@ #include #include #include +#include #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 000000000..22718e6c2 --- /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 + +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 000000000..06d3e1ff9 --- /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 + +#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 000000000..c8fd2da3d --- /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 +#include + +#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 000000000..a406a0ffe --- /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 + +#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 000000000..481727627 --- /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 +#include +#include +#include +#include + +#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 000000000..9ce89f992 --- /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 + +#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 000000000..dd9196e5e --- /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 000000000..26a0d9150 --- /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 000000000..e97a17356 --- /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 000000000..fbf3c77c3 --- /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 + +#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 000000000..e53f78dc5 --- /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 000000000..cf8d8e293 --- /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 000000000..5e6484672 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 000000000..7e7ecb585 --- /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 + +#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 000000000..8d7a6dfcc --- /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 000000000..b8366aebb --- /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 + +#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 000000000..558ad181b --- /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 + +#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 000000000..a4fb8dd45 --- /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 + +#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 494112bf7..1507807ea 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 b7c4a96d4..cf66334f1 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 000000000..7807f1608 --- /dev/null +++ b/src/org.gnome.Mutter.RemoteDesktop.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/org.gnome.Mutter.ScreenCast.xml b/src/org.gnome.Mutter.ScreenCast.xml new file mode 100644 index 000000000..bab3fea20 --- /dev/null +++ b/src/org.gnome.Mutter.ScreenCast.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +