Move screencasting into a separate service process

Move the screencasting into a separate D-Bus service process, using
PipeWire instead of Clutter API. The service is implemented in
Javascript using the dbusService.js helper, and implements the same API
as was done by screencast.js and the corresponding C code.

https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1372
This commit is contained in:
Jonas Ådahl
2020-04-23 20:46:44 +02:00
parent a9b803f075
commit 2b0731ab81
19 changed files with 719 additions and 2340 deletions

View File

@@ -163,15 +163,6 @@ libshell_private_sources = [
'shell-app-cache.c',
]
if enable_recorder
libshell_sources += ['shell-recorder.c']
libshell_public_headers += ['shell-recorder.h']
libshell_private_sources += ['shell-recorder-src.c']
libshell_private_headers += ['shell-recorder-src.h']
endif
libshell_enums = gnome.mkenums_simple('shell-enum-types',
sources: libshell_public_headers
)

View File

@@ -1,425 +0,0 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
#include "config.h"
#define GST_USE_UNSTABLE_API
#include <gst/base/gstpushsrc.h>
#include "shell-recorder-src.h"
struct _ShellRecorderSrc
{
GstPushSrc parent;
GMutex mutex;
GstCaps *caps;
GMutex queue_lock;
GCond queue_cond;
GQueue *queue;
gboolean eos;
gboolean flushing;
guint memory_used;
guint memory_used_update_idle;
};
struct _ShellRecorderSrcClass
{
GstPushSrcClass parent_class;
};
enum {
PROP_0,
PROP_CAPS,
PROP_MEMORY_USED
};
#define shell_recorder_src_parent_class parent_class
G_DEFINE_TYPE(ShellRecorderSrc, shell_recorder_src, GST_TYPE_PUSH_SRC);
static void
shell_recorder_src_init (ShellRecorderSrc *src)
{
gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME);
gst_base_src_set_live (GST_BASE_SRC (src), TRUE);
src->queue = g_queue_new ();
g_mutex_init (&src->mutex);
g_mutex_init (&src->queue_lock);
g_cond_init (&src->queue_cond);
}
static gboolean
shell_recorder_src_memory_used_update_idle (gpointer data)
{
ShellRecorderSrc *src = data;
g_mutex_lock (&src->mutex);
src->memory_used_update_idle = 0;
g_mutex_unlock (&src->mutex);
g_object_notify (G_OBJECT (src), "memory-used");
return FALSE;
}
/* The memory_used property is used to monitor buffer usage,
* so we marshal notification back to the main loop thread.
*/
static void
shell_recorder_src_update_memory_used (ShellRecorderSrc *src,
int delta)
{
g_mutex_lock (&src->mutex);
src->memory_used += delta;
if (src->memory_used_update_idle == 0)
{
src->memory_used_update_idle = g_idle_add (shell_recorder_src_memory_used_update_idle, src);
g_source_set_name_by_id (src->memory_used_update_idle, "[gnome-shell] shell_recorder_src_memory_used_update_idle");
}
g_mutex_unlock (&src->mutex);
}
/* _negotiate() is called when we have to decide on a format. We
* use the configured format */
static gboolean
shell_recorder_src_negotiate (GstBaseSrc * base_src)
{
ShellRecorderSrc *src = SHELL_RECORDER_SRC (base_src);
gboolean result;
result = gst_base_src_set_caps (base_src, src->caps);
return result;
}
static gboolean
shell_recorder_src_unlock (GstBaseSrc * base_src)
{
ShellRecorderSrc *src = SHELL_RECORDER_SRC (base_src);
g_mutex_lock (&src->queue_lock);
src->flushing = TRUE;
g_cond_signal (&src->queue_cond);
g_mutex_unlock (&src->queue_lock);
return TRUE;
}
static gboolean
shell_recorder_src_unlock_stop (GstBaseSrc * base_src)
{
ShellRecorderSrc *src = SHELL_RECORDER_SRC (base_src);
g_mutex_lock (&src->queue_lock);
src->flushing = FALSE;
g_cond_signal (&src->queue_cond);
g_mutex_unlock (&src->queue_lock);
return TRUE;
}
static gboolean
shell_recorder_src_start (GstBaseSrc * base_src)
{
ShellRecorderSrc *src = SHELL_RECORDER_SRC (base_src);
g_mutex_lock (&src->queue_lock);
src->flushing = FALSE;
src->eos = FALSE;
g_cond_signal (&src->queue_cond);
g_mutex_unlock (&src->queue_lock);
return TRUE;
}
static gboolean
shell_recorder_src_stop (GstBaseSrc * base_src)
{
ShellRecorderSrc *src = SHELL_RECORDER_SRC (base_src);
g_mutex_lock (&src->queue_lock);
src->flushing = TRUE;
src->eos = FALSE;
g_queue_foreach (src->queue, (GFunc) gst_buffer_unref, NULL);
g_queue_clear (src->queue);
g_cond_signal (&src->queue_cond);
g_mutex_unlock (&src->queue_lock);
return TRUE;
}
static gboolean
shell_recorder_src_send_event (GstElement * element, GstEvent * event)
{
ShellRecorderSrc *src = SHELL_RECORDER_SRC (element);
gboolean res;
if (GST_EVENT_TYPE (event) == GST_EVENT_EOS)
{
shell_recorder_src_close (src);
gst_event_unref (event);
res = TRUE;
}
else
{
res = GST_CALL_PARENT_WITH_DEFAULT (GST_ELEMENT_CLASS, send_event, (element,
event), FALSE);
}
return res;
}
/* The create() virtual function is responsible for returning the next buffer.
* We just pop buffers off of the queue and block if necessary.
*/
static GstFlowReturn
shell_recorder_src_create (GstPushSrc *push_src,
GstBuffer **buffer_out)
{
ShellRecorderSrc *src = SHELL_RECORDER_SRC (push_src);
GstBuffer *buffer;
g_mutex_lock (&src->queue_lock);
while (TRUE) {
/* int the flushing state we just return FLUSHING */
if (src->flushing) {
g_mutex_unlock (&src->queue_lock);
return GST_FLOW_FLUSHING;
}
buffer = g_queue_pop_head (src->queue);
/* we have a buffer, exit the loop to handle it */
if (buffer != NULL)
break;
/* no buffer, check EOS */
if (src->eos) {
g_mutex_unlock (&src->queue_lock);
return GST_FLOW_EOS;
}
/* wait for something to happen and try again */
g_cond_wait (&src->queue_cond, &src->queue_lock);
}
g_mutex_unlock (&src->queue_lock);
shell_recorder_src_update_memory_used (src,
- (int)(gst_buffer_get_size(buffer) / 1024));
*buffer_out = buffer;
return GST_FLOW_OK;
}
static void
shell_recorder_src_set_caps (ShellRecorderSrc *src,
const GstCaps *caps)
{
if (caps == src->caps)
return;
if (src->caps != NULL)
{
gst_caps_unref (src->caps);
src->caps = NULL;
}
if (caps)
{
/* The capabilities will be negotated with the downstream element
* and set on the pad when the first buffer is pushed.
*/
src->caps = gst_caps_copy (caps);
}
else
src->caps = NULL;
}
static void
shell_recorder_src_finalize (GObject *object)
{
ShellRecorderSrc *src = SHELL_RECORDER_SRC (object);
g_clear_handle_id (&src->memory_used_update_idle, g_source_remove);
shell_recorder_src_set_caps (src, NULL);
g_queue_free_full (src->queue, (GDestroyNotify) gst_buffer_unref);
g_mutex_clear (&src->mutex);
g_mutex_clear (&src->queue_lock);
g_cond_clear (&src->queue_cond);
G_OBJECT_CLASS (shell_recorder_src_parent_class)->finalize (object);
}
static void
shell_recorder_src_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
ShellRecorderSrc *src = SHELL_RECORDER_SRC (object);
switch (prop_id)
{
case PROP_CAPS:
shell_recorder_src_set_caps (src, gst_value_get_caps (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
shell_recorder_src_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
ShellRecorderSrc *src = SHELL_RECORDER_SRC (object);
switch (prop_id)
{
case PROP_CAPS:
gst_value_set_caps (value, src->caps);
break;
case PROP_MEMORY_USED:
g_mutex_lock (&src->mutex);
g_value_set_uint (value, src->memory_used);
g_mutex_unlock (&src->mutex);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
shell_recorder_src_class_init (ShellRecorderSrcClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GstBaseSrcClass *base_src_class = GST_BASE_SRC_CLASS (klass);
GstPushSrcClass *push_src_class = GST_PUSH_SRC_CLASS (klass);
static GstStaticPadTemplate src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
object_class->finalize = shell_recorder_src_finalize;
object_class->set_property = shell_recorder_src_set_property;
object_class->get_property = shell_recorder_src_get_property;
g_object_class_install_property (object_class,
PROP_CAPS,
g_param_spec_boxed ("caps",
"Caps",
"Fixed GstCaps for the source",
GST_TYPE_CAPS,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MEMORY_USED,
g_param_spec_uint ("memory-used",
"Memory Used",
"Memory currently used by the queue (in kB)",
0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&src_template));
gst_element_class_set_details_simple (element_class,
"ShellRecorderSrc",
"Generic/Src",
"Feed screen capture data to a pipeline",
"Owen Taylor <otaylor@redhat.com>");
element_class->send_event = shell_recorder_src_send_event;
base_src_class->negotiate = shell_recorder_src_negotiate;
base_src_class->unlock = shell_recorder_src_unlock;
base_src_class->unlock_stop = shell_recorder_src_unlock_stop;
base_src_class->start = shell_recorder_src_start;
base_src_class->stop = shell_recorder_src_stop;
push_src_class->create = shell_recorder_src_create;
}
/**
* shell_recorder_src_add_buffer:
*
* Adds a buffer to the internal queue to be pushed out at the next opportunity.
* There is no flow control, so arbitrary amounts of memory may be used by
* the buffers on the queue. The buffer contents must match the #GstCaps
* set in the :caps property.
*/
void
shell_recorder_src_add_buffer (ShellRecorderSrc *src,
GstBuffer *buffer)
{
g_return_if_fail (SHELL_IS_RECORDER_SRC (src));
g_return_if_fail (src->caps != NULL);
shell_recorder_src_update_memory_used (src,
(int)(gst_buffer_get_size(buffer) / 1024));
g_mutex_lock (&src->queue_lock);
g_queue_push_tail (src->queue, gst_buffer_ref (buffer));
g_cond_signal (&src->queue_cond);
g_mutex_unlock (&src->queue_lock);
}
/**
* shell_recorder_src_close:
*
* Indicates the end of the input stream. Once all previously added buffers have
* been pushed out an end-of-stream message will be sent.
*/
void
shell_recorder_src_close (ShellRecorderSrc *src)
{
/* We can't send a message to the source immediately or buffers that haven't
* been pushed yet will be discarded. Instead mark ourselves EOS, which will
* make us send an event once everything has been pushed.
*/
g_mutex_lock (&src->queue_lock);
src->eos = TRUE;
g_cond_signal (&src->queue_cond);
g_mutex_unlock (&src->queue_lock);
}
static gboolean
plugin_init (GstPlugin *plugin)
{
gst_element_register(plugin, "shellrecordersrc", GST_RANK_NONE,
SHELL_TYPE_RECORDER_SRC);
return TRUE;
}
/**
* shell_recorder_src_register:
*
* Registers a plugin holding our single element to use privately in
* this application. Can safely be called multiple times.
*/
void
shell_recorder_src_register (void)
{
static gboolean registered = FALSE;
if (registered)
return;
gst_plugin_register_static (GST_VERSION_MAJOR, GST_VERSION_MINOR,
"shellrecorder",
"Plugin for ShellRecorder",
plugin_init,
"0.1",
"LGPL",
"gnome-shell", "gnome-shell", "http://live.gnome.org/GnomeShell");
registered = TRUE;
}

View File

@@ -1,40 +0,0 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
#ifndef __SHELL_RECORDER_SRC_H__
#define __SHELL_RECORDER_SRC_H__
#include <gst/gst.h>
G_BEGIN_DECLS
/**
* ShellRecorderSrc:
*
* shellrecordersrc a custom source element is pretty much like a very
* simple version of the stander GStreamer 'appsrc' element, without
* any of the provisions for seeking, generating data on demand,
* etc. In both cases, the application supplies the buffers and the
* element pushes them into the pipeline. The main reason for not using
* appsrc is that it wasn't a supported element until gstreamer 0.10.22,
* and as of 2009-03, many systems still have 0.10.21.
*/
typedef struct _ShellRecorderSrc ShellRecorderSrc;
typedef struct _ShellRecorderSrcClass ShellRecorderSrcClass;
#define SHELL_TYPE_RECORDER_SRC (shell_recorder_src_get_type ())
#define SHELL_RECORDER_SRC(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_RECORDER_SRC, ShellRecorderSrc))
#define SHELL_RECORDER_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_RECORDER_SRC, ShellRecorderSrcClass))
#define SHELL_IS_RECORDER_SRC(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_RECORDER_SRC))
#define SHELL_IS_RECORDER_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_RECORDER_SRC))
#define SHELL_RECORDER_SRC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_RECORDER_SRC, ShellRecorderSrcClass))
GType shell_recorder_src_get_type (void) G_GNUC_CONST;
void shell_recorder_src_register (void);
void shell_recorder_src_add_buffer (ShellRecorderSrc *src,
GstBuffer *buffer);
void shell_recorder_src_close (ShellRecorderSrc *src);
G_END_DECLS
#endif /* __SHELL_RECORDER_SRC_H__ */

File diff suppressed because it is too large Load Diff

View File

@@ -1,45 +0,0 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
#ifndef __SHELL_RECORDER_H__
#define __SHELL_RECORDER_H__
#include <clutter/clutter.h>
G_BEGIN_DECLS
/**
* SECTION:shell-recorder
* @short_description: Record from a #ClutterStage
*
* The #ShellRecorder object is used to make recordings ("screencasts")
* of a #ClutterStage. Recording is done via #GStreamer. The default is
* to encode as a Theora movie and write it to a file in the current
* directory named after the date, but the encoding and output can
* be configured.
*/
#define SHELL_TYPE_RECORDER (shell_recorder_get_type ())
G_DECLARE_FINAL_TYPE (ShellRecorder, shell_recorder, SHELL, RECORDER, GObject)
ShellRecorder *shell_recorder_new (ClutterStage *stage);
void shell_recorder_set_framerate (ShellRecorder *recorder,
int framerate);
void shell_recorder_set_file_template (ShellRecorder *recorder,
const char *file_template);
void shell_recorder_set_pipeline (ShellRecorder *recorder,
const char *pipeline);
void shell_recorder_set_draw_cursor (ShellRecorder *recorder,
gboolean draw_cursor);
void shell_recorder_set_area (ShellRecorder *recorder,
int x,
int y,
int width,
int height);
gboolean shell_recorder_record (ShellRecorder *recorder,
char **filename_used);
void shell_recorder_close (ShellRecorder *recorder);
void shell_recorder_pause (ShellRecorder *recorder);
gboolean shell_recorder_is_recording (ShellRecorder *recorder);
G_END_DECLS
#endif /* __SHELL_RECORDER_H__ */