Commit 325aabd4 authored by Benjamin Berg's avatar Benjamin Berg
Browse files

Add basic screencasting implementation

This somewhat works at this point, but it is rough, and will need fixing
and cleanup in places.
parent 0284f817
......@@ -2,6 +2,7 @@ project('gnome-screencast', 'c', version: '0.1.0',
meson_version: '>= 0.40.0',
)
gnome = import('gnome')
i18n = import('i18n')
config_h = configuration_data()
......
/* gnome-screencast-window.c
*
* Copyright 2018 Benjamin Berg
* Copyright 2018 Benjamin Berg <bberg@redhat.com>
*
* 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
......@@ -18,30 +18,143 @@
#include "gnome-screencast-config.h"
#include "gnome-screencast-window.h"
#include "screencast-sink-list.h"
#include "screencast-sink-row.h"
#include "screencast-meta-provider.h"
#include "screencast-wfd-p2p-registry.h"
#include "screencast-portal.h"
struct _GnomeScreencastWindow
{
GtkApplicationWindow parent_instance;
GtkApplicationWindow parent_instance;
ScreencastMetaProvider *meta_provider;
ScreencastWFDP2PRegistry *wfd_p2p_registry;
ScreencastPortal *portal;
GCancellable *cancellable;
/* Template widgets */
GtkHeaderBar *header_bar;
GtkLabel *label;
GtkStack *step_stack;
ScreencastSinkList *find_sink_list;
};
G_DEFINE_TYPE (GnomeScreencastWindow, gnome_screencast_window, GTK_TYPE_APPLICATION_WINDOW)
static void
find_sink_list_row_activated_cb (GnomeScreencastWindow *self, ScreencastSinkRow *row, ScreencastSinkList *sink_list)
{
ScreencastSink *sink;
g_autoptr(ScreencastSink) streaming_sink = NULL;
if (!self->portal)
{
g_warning ("Cannot start streaming right now as we don't have a portal!");
return;
}
g_assert (SCREENCAST_IS_SINK_ROW (row));
sink = screencast_sink_row_get_sink (row);
streaming_sink = screencast_sink_start_stream (sink, self->portal);
/* XXX: leak streaming_sink intentionally for now */
g_steal_pointer (&streaming_sink);
}
static void
gnome_screencast_window_finalize (GObject *obj)
{
GnomeScreencastWindow *self = GNOME_SCREENCAST_WINDOW (obj);
g_clear_object (&self->meta_provider);
g_clear_object (&self->wfd_p2p_registry);
G_OBJECT_CLASS (gnome_screencast_window_parent_class)->finalize (obj);
}
static void
gnome_screencast_window_class_init (GnomeScreencastWindowClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = gnome_screencast_window_finalize;
SCREENCAST_TYPE_SINK_LIST;
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/screencast/gnome-screencast-window.ui");
gtk_widget_class_bind_template_child (widget_class, GnomeScreencastWindow, header_bar);
gtk_widget_class_bind_template_child (widget_class, GnomeScreencastWindow, label);
gtk_widget_class_bind_template_child (widget_class, GnomeScreencastWindow, step_stack);
gtk_widget_class_bind_template_child (widget_class, GnomeScreencastWindow, find_sink_list);
}
static void
screencast_portal_init_async_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GstPipeline *pipeline;
GstElement *src, *dst;
GnomeScreencastWindow *window;
g_autoptr(GError) error = NULL;
if (!g_async_initable_init_finish (G_ASYNC_INITABLE (source_object), res, &error))
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
g_warning ("Error initing screencast portal: %s", error->message);
/* XXX: This is fatal! */
window = GNOME_SCREENCAST_WINDOW (user_data);
gtk_widget_destroy (GTK_WIDGET (window));
}
return;
}
window = GNOME_SCREENCAST_WINDOW (user_data);
window->portal = SCREENCAST_PORTAL (source_object);
/* Try starting a gstreamer pipeline */
pipeline = gst_pipeline_new ("pipewire to internal sink");
src = screencast_portal_get_source (window->portal);
gst_bin_add (GST_BIN(pipeline), src);
/* convert = gst_element_factory_make ("videoconvert", "test convert"); */
/* gst_bin_add (GST_BIN(pipeline), convert); */
dst = gst_element_factory_make ("intervideosink", "inter video sink");
gst_bin_add (GST_BIN(pipeline), dst);
gst_element_link_many (src, dst, NULL);
gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);
}
static void
gnome_screencast_window_init (GnomeScreencastWindow *self)
{
ScreencastPortal *portal;
gtk_widget_init_template (GTK_WIDGET (self));
self->meta_provider = screencast_meta_provider_new ();
self->wfd_p2p_registry = screencast_wfd_p2p_registry_new (self->meta_provider);
screencast_sink_list_set_provider (self->find_sink_list, SCREENCAST_PROVIDER (self->meta_provider));
g_signal_connect_object (self->find_sink_list,
"row-activated",
(GCallback) find_sink_list_row_activated_cb,
self,
G_CONNECT_SWAPPED);
self->cancellable = g_cancellable_new ();
portal = screencast_portal_new ();
g_async_initable_init_async (G_ASYNC_INITABLE (portal),
G_PRIORITY_LOW,
self->cancellable,
screencast_portal_init_async_cb,
self);
}
/* gnome-screencast-window.h
*
* Copyright 2018 Benjamin Berg
* Copyright 2018 Benjamin Berg <bberg@redhat.com>
*
* 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
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.0 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<template class="GnomeScreencastWindow" parent="GtkApplicationWindow">
<property name="default-width">600</property>
<property name="default-height">300</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="header_bar">
<property name="can_focus">False</property>
<property name="default_width">600</property>
<property name="default_height">300</property>
<child>
<object class="GtkStack" id="step_stack">
<property name="visible">True</property>
<property name="show-close-button">True</property>
<property name="title">Hello, World!</property>
<property name="can_focus">False</property>
<property name="transition_type">slide-left-right</property>
<property name="interpolate_size">True</property>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hscrollbar_policy">never</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">18</property>
<property name="margin_right">18</property>
<property name="margin_top">32</property>
<property name="margin_bottom">32</property>
<property name="hexpand">True</property>
<property name="orientation">vertical</property>
<property name="spacing">16</property>
<child>
<object class="GtkBox" id="sinks_header">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Available Video Sinks</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinner" id="find_spinner">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="ScreencastSinkList" id="find_sink_list">
<property name="height_request">150</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
</object>
</child>
<child type="label_item">
<placeholder/>
</child>
<style>
<class name="view"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">select</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">32</property>
<property name="margin_bottom">32</property>
<property name="hexpand">True</property>
<property name="orientation">vertical</property>
<property name="spacing">16</property>
<child>
<object class="GtkBox" id="sinks_header1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Connecting</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinner" id="connect_spinner">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">connect</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="label">
<property name="label">Hello, World!</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="header_bar">
<property name="visible">True</property>
<attributes>
<attribute name="weight" value="bold"/>
<attribute name="scale" value="2"/>
</attributes>
<property name="can_focus">False</property>
<property name="title">Screen Casting</property>
<property name="show_close_button">True</property>
</object>
</child>
</template>
</interface>
......@@ -2,5 +2,6 @@
<gresources>
<gresource prefix="/org/gnome/screencast">
<file>gnome-screencast-window.ui</file>
<file>screencast-sink-row.ui</file>
</gresource>
</gresources>
......@@ -17,7 +17,7 @@
*/
#include <glib/gi18n.h>
#include <gst/gst.h>
#include "gnome-screencast-config.h"
#include "gnome-screencast-window.h"
......@@ -59,6 +59,8 @@ main (int argc,
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
textdomain (GETTEXT_PACKAGE);
gst_init (&argc, &argv);
/*
* Create a new GtkApplication. The application manages our main loop,
* application windows, integration with the window manager/compositor, and
......
subdir('wfd')
gnome_screencast_sources = [
'main.c',
'gnome-screencast-window.c',
'screencast-sink-list.c',
'screencast-sink-row.c',
'screencast-sink.c',
'screencast-provider.c',
'screencast-meta-sink.c',
'screencast-meta-provider.c',
'screencast-portal.c',
'screencast-wfd-p2p-sink.c',
'screencast-wfd-p2p-provider.c',
'screencast-wfd-p2p-registry.c',
]
enum_headers = files('screencast-sink.h')
gnome_screencast_sources += gnome.mkenums_simple(
'screencast-enum-types',
sources: enum_headers,
)
gnome_screencast_deps = [
dependency('gio-2.0', version: '>= 2.50'),
dependency('gtk+-3.0', version: '>= 3.22'),
dependency('libnm', version: '>= 1.15'),
dependency('gstreamer-1.0', version: '>= 1.14'),
]
gnome = import('gnome')
gnome_screencast_deps += wfd_server_deps
gnome_screencast_sources += gnome.compile_resources('gnome-screencast-resources',
'gnome-screencast.gresource.xml',
c_name: 'gnome_screencast'
)
executable('gnome-screencast', gnome_screencast_sources,
executable('gnome-screencast',
gnome_screencast_sources,
dependencies: gnome_screencast_deps,
install: true,
link_with: wfd_server,
)
/* screencast-meta-provider.c
*
* Copyright 2018 Benjamin Berg <bberg@redhat.com>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "gnome-screencast-config.h"
#include "screencast-meta-provider.h"
#include "screencast-meta-sink.h"
struct _ScreencastMetaProvider
{
GObject parent_instance;
GHashTable *deduplicate;
GPtrArray *sinks;
GPtrArray *providers;
};
static void screencast_meta_provider_provider_iface_init (ScreencastProviderIface *iface);
static GList * screencast_meta_provider_provider_get_sinks (ScreencastProvider *provider);
G_DEFINE_TYPE_EXTENDED (ScreencastMetaProvider, screencast_meta_provider, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (SCREENCAST_TYPE_PROVIDER,
screencast_meta_provider_provider_iface_init);
)
static void
deduplicate_add_meta_sink (ScreencastMetaProvider *meta_provider, ScreencastMetaSink *meta_sink)
{
g_autoptr(GPtrArray) meta_matches = NULL;
g_object_get (meta_sink, "matches", &meta_matches, NULL);
g_assert (meta_matches != NULL);
for (gint i = 0; i < meta_matches->len; i++)
{
gchar *match;
match = meta_matches->pdata[i];
g_hash_table_insert (meta_provider->deduplicate, g_strdup (match), meta_sink);
}
}
static void
provider_sink_added_cb (ScreencastMetaProvider *meta_provider, ScreencastSink *sink, ScreencastProvider *provider)
{
g_autoptr(GPtrArray) sink_matches = NULL;
g_autoptr(GPtrArray) meta_sinks = NULL;
ScreencastMetaSink *meta_sink;
gchar *match;
g_object_get (sink, "matches", &sink_matches, NULL);
g_assert (sink_matches != NULL);
meta_sinks = g_ptr_array_new ();