Add a built-in screencast recording facility
For development and demonstration purposes, it's neat to be able to record a screencast of gnome-shell without any external setup. Built-in recording can also give much better quality than is possible with a generic desktop recording, since we hook right into the paint loop. src/shell-recorder.[ch]: A general-purposes object to record a Clutter stage to a GStreamer stream. src/shell-recorder-src.[ch]: A simple GStreamer source element (similar to appsrc in the most recent versions of GStreamer) for injecting captured data into a GStreamer pipeline. src/test-recorder.c: Test program that records a simple animation. configure.ac src/Makefile.am: Add machinery to conditionally build ShellRecorder. tools/build/gnome-shell-build-setup.sh: Add gstreamer packages to the list of required packages for Fedora. js/ui/main.js: Hook up the recorder to a MetaScreen ::toggle-recording keybinding. http://bugzilla.gnome.org/show_bug.cgi?id=575290
This commit is contained in:
parent
288fb7a837
commit
afceea3fe6
2
.gitignore
vendored
2
.gitignore
vendored
@ -26,4 +26,6 @@ src/Makefile
|
|||||||
src/Makefile.in
|
src/Makefile.in
|
||||||
src/gnomeshell-taskpanel
|
src/gnomeshell-taskpanel
|
||||||
src/gnome-shell
|
src/gnome-shell
|
||||||
|
src/test-recorder
|
||||||
|
src/test-recorder.ogg
|
||||||
stamp-h1
|
stamp-h1
|
||||||
|
22
configure.ac
22
configure.ac
@ -18,7 +18,27 @@ AC_SUBST(GETTEXT_PACKAGE)
|
|||||||
AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE",
|
AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE",
|
||||||
[The prefix for our gettext translation domains.])
|
[The prefix for our gettext translation domains.])
|
||||||
|
|
||||||
PKG_CHECK_MODULES(MUTTER_PLUGIN, gtk+-2.0 dbus-glib-1 metacity-plugins gjs-gi-1.0)
|
PKG_PROG_PKG_CONFIG(0.16)
|
||||||
|
|
||||||
|
# We need at least this, since gst_plugin_register_static() was added
|
||||||
|
# in 0.10.16, but nothing older than 0.10.21 has been tested.
|
||||||
|
GSTREAMER_MIN_VERSION=0.10.16
|
||||||
|
|
||||||
|
recorder_modules=
|
||||||
|
build_recorder=false
|
||||||
|
AC_MSG_CHECKING([for GStreamer (needed for recording functionality)])
|
||||||
|
if $PKG_CONFIG --exists gstreamer-0.10 '>=' $GSTREAMER_MIN_VERSION ; then
|
||||||
|
AC_MSG_RESULT(yes)
|
||||||
|
build_recorder=true
|
||||||
|
recorder_modules="gstreamer-0.10 gstreamer-base-0.10 xfixes"
|
||||||
|
PKG_CHECK_MODULES(TEST_SHELL_RECORDER, $recorder_modules clutter-0.9)
|
||||||
|
else
|
||||||
|
AC_MSG_RESULT(no)
|
||||||
|
fi
|
||||||
|
|
||||||
|
AM_CONDITIONAL(BUILD_RECORDER, $build_recorder)
|
||||||
|
|
||||||
|
PKG_CHECK_MODULES(MUTTER_PLUGIN, gtk+-2.0 dbus-glib-1 metacity-plugins gjs-gi-1.0 $recorder_modules)
|
||||||
PKG_CHECK_MODULES(TIDY, clutter-0.9)
|
PKG_CHECK_MODULES(TIDY, clutter-0.9)
|
||||||
PKG_CHECK_MODULES(BIG, clutter-0.9 gtk+-2.0 librsvg-2.0)
|
PKG_CHECK_MODULES(BIG, clutter-0.9 gtk+-2.0 librsvg-2.0)
|
||||||
PKG_CHECK_MODULES(GDMUSER, dbus-glib-1 gtk+-2.0)
|
PKG_CHECK_MODULES(GDMUSER, dbus-glib-1 gtk+-2.0)
|
||||||
|
@ -20,6 +20,7 @@ let overlay = null;
|
|||||||
let overlayActive = false;
|
let overlayActive = false;
|
||||||
let runDialog = null;
|
let runDialog = null;
|
||||||
let wm = null;
|
let wm = null;
|
||||||
|
let recorder = null;
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
let global = Shell.Global.get();
|
let global = Shell.Global.get();
|
||||||
@ -71,6 +72,24 @@ function start() {
|
|||||||
show_overlay();
|
show_overlay();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
global.screen.connect('toggle-recording', function() {
|
||||||
|
if (recorder == null) {
|
||||||
|
// We have to initialize GStreamer first. This isn't done
|
||||||
|
// inside ShellRecorder to make it usable inside projects
|
||||||
|
// with other usage of GStreamer.
|
||||||
|
let Gst = imports.gi.Gst;
|
||||||
|
Gst.init(null, null);
|
||||||
|
recorder = new Shell.Recorder({ stage: global.stage });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recorder.is_recording()) {
|
||||||
|
recorder.pause();
|
||||||
|
} else {
|
||||||
|
recorder.record();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
display.connect('overlay-key', toggleOverlay);
|
display.connect('overlay-key', toggleOverlay);
|
||||||
global.connect('panel-main-menu', toggleOverlay);
|
global.connect('panel-main-menu', toggleOverlay);
|
||||||
|
|
||||||
|
@ -66,6 +66,32 @@ libgnome_shell_la_SOURCES = \
|
|||||||
|
|
||||||
# ClutterGLXTexturePixmap is currently not wrapped
|
# ClutterGLXTexturePixmap is currently not wrapped
|
||||||
non_gir_sources = shell-gtkwindow-actor.h
|
non_gir_sources = shell-gtkwindow-actor.h
|
||||||
|
|
||||||
|
shell_recorder_sources = \
|
||||||
|
shell-recorder.c \
|
||||||
|
shell-recorder.h \
|
||||||
|
shell-recorder-src.c \
|
||||||
|
shell-recorder-src.h
|
||||||
|
|
||||||
|
# Custom element is an internal detail
|
||||||
|
shell_recorder_non_gir_sources = \
|
||||||
|
shell-recorder-src.c \
|
||||||
|
shell-recorder-src.h
|
||||||
|
|
||||||
|
if BUILD_RECORDER
|
||||||
|
libgnome_shell_la_SOURCES += $(shell_recorder_sources)
|
||||||
|
non_gir_sources += $(shell_recorder_non_gir_sources)
|
||||||
|
|
||||||
|
noinst_PROGRAMS = test-recorder
|
||||||
|
|
||||||
|
test_recorder_CPPFLAGS = $(TEST_SHELL_RECORDER_CFLAGS)
|
||||||
|
test_recorder_LDADD = $(TEST_SHELL_RECORDER_LIBS)
|
||||||
|
|
||||||
|
test_recorder_SOURCES = \
|
||||||
|
$(shell_recorder_sources) \
|
||||||
|
test-recorder.c
|
||||||
|
endif BUILD_RECORDER
|
||||||
|
|
||||||
libgnome_shell_la_gir_sources = \
|
libgnome_shell_la_gir_sources = \
|
||||||
$(filter-out $(non_gir_sources), $(libgnome_shell_la_SOURCES))
|
$(filter-out $(non_gir_sources), $(libgnome_shell_la_SOURCES))
|
||||||
|
|
||||||
|
298
src/shell-recorder-src.c
Normal file
298
src/shell-recorder-src.c
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
#include <gst/base/gstpushsrc.h>
|
||||||
|
|
||||||
|
#include "shell-recorder-src.h"
|
||||||
|
|
||||||
|
struct _ShellRecorderSrc
|
||||||
|
{
|
||||||
|
GstPushSrc parent;
|
||||||
|
|
||||||
|
GMutex *mutex;
|
||||||
|
|
||||||
|
GstCaps *caps;
|
||||||
|
GAsyncQueue *queue;
|
||||||
|
gboolean closed;
|
||||||
|
guint memory_used;
|
||||||
|
guint memory_used_update_idle;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _ShellRecorderSrcClass
|
||||||
|
{
|
||||||
|
GstPushSrcClass parent_class;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_0,
|
||||||
|
PROP_CAPS,
|
||||||
|
PROP_MEMORY_USED
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Special marker value once the source is closed */
|
||||||
|
#define RECORDER_QUEUE_END ((GstBuffer *)1)
|
||||||
|
|
||||||
|
GST_BOILERPLATE(ShellRecorderSrc, shell_recorder_src, GstPushSrc, GST_TYPE_PUSH_SRC);
|
||||||
|
|
||||||
|
static void
|
||||||
|
shell_recorder_src_init (ShellRecorderSrc *src,
|
||||||
|
ShellRecorderSrcClass *klass)
|
||||||
|
{
|
||||||
|
src->queue = g_async_queue_new ();
|
||||||
|
src->mutex = g_mutex_new ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
shell_recorder_src_base_init (gpointer klass)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
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_mutex_unlock (src->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
if (src->closed)
|
||||||
|
return GST_FLOW_UNEXPECTED;
|
||||||
|
|
||||||
|
buffer = g_async_queue_pop (src->queue);
|
||||||
|
if (buffer == RECORDER_QUEUE_END)
|
||||||
|
{
|
||||||
|
/* Returning UNEXPECTED here will cause a EOS message to be sent */
|
||||||
|
src->closed = TRUE;
|
||||||
|
return GST_FLOW_UNEXPECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
shell_recorder_src_update_memory_used (src,
|
||||||
|
- (int)(GST_BUFFER_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);
|
||||||
|
|
||||||
|
if (src->memory_used_update_idle)
|
||||||
|
g_source_remove (src->memory_used_update_idle);
|
||||||
|
|
||||||
|
shell_recorder_src_set_caps (src, NULL);
|
||||||
|
g_async_queue_unref (src->queue);
|
||||||
|
|
||||||
|
g_mutex_free (src->mutex);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (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);
|
||||||
|
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;
|
||||||
|
|
||||||
|
push_src_class->create = shell_recorder_src_create;
|
||||||
|
|
||||||
|
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_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));
|
||||||
|
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>");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
gst_buffer_set_caps (buffer, src->caps);
|
||||||
|
shell_recorder_src_update_memory_used (src,
|
||||||
|
(int) (GST_BUFFER_SIZE(buffer) / 1024));
|
||||||
|
|
||||||
|
g_async_queue_push (src->queue, gst_buffer_ref (buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 stick a marker onto our own
|
||||||
|
* queue to send an event once everything has been pushed.
|
||||||
|
*/
|
||||||
|
g_async_queue_push (src->queue, RECORDER_QUEUE_END);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
39
src/shell-recorder-src.h
Normal file
39
src/shell-recorder-src.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#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__ */
|
1666
src/shell-recorder.c
Normal file
1666
src/shell-recorder.c
Normal file
File diff suppressed because it is too large
Load Diff
43
src/shell-recorder.h
Normal file
43
src/shell-recorder.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#ifndef __SHELL_RECORDER_H__
|
||||||
|
#define __SHELL_RECORDER_H__
|
||||||
|
|
||||||
|
#include <clutter/clutter.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:ShellRecorder
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
typedef struct _ShellRecorder ShellRecorder;
|
||||||
|
typedef struct _ShellRecorderClass ShellRecorderClass;
|
||||||
|
|
||||||
|
#define SHELL_TYPE_RECORDER (shell_recorder_get_type ())
|
||||||
|
#define SHELL_RECORDER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_RECORDER, ShellRecorder))
|
||||||
|
#define SHELL_RECORDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_RECORDER, ShellRecorderClass))
|
||||||
|
#define SHELL_IS_RECORDER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_RECORDER))
|
||||||
|
#define SHELL_IS_RECORDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_RECORDER))
|
||||||
|
#define SHELL_RECORDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_RECORDER, ShellRecorderClass))
|
||||||
|
|
||||||
|
GType shell_recorder_get_type (void) G_GNUC_CONST;
|
||||||
|
|
||||||
|
ShellRecorder *shell_recorder_new (ClutterStage *stage);
|
||||||
|
|
||||||
|
void shell_recorder_set_filename (ShellRecorder *recorder,
|
||||||
|
const char *filename);
|
||||||
|
void shell_recorder_set_pipeline (ShellRecorder *recorder,
|
||||||
|
const char *pipeline);
|
||||||
|
gboolean shell_recorder_record (ShellRecorder *recorder);
|
||||||
|
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__ */
|
95
src/test-recorder.c
Normal file
95
src/test-recorder.c
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
#include "shell-recorder.h"
|
||||||
|
#include <clutter/clutter.h>
|
||||||
|
#include <gst/gst.h>
|
||||||
|
|
||||||
|
/* Very simple test of the ShellRecorder class; shows some text strings
|
||||||
|
* moving around and records it.
|
||||||
|
*/
|
||||||
|
static ShellRecorder *recorder;
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
stop_recording_timeout (gpointer data)
|
||||||
|
{
|
||||||
|
shell_recorder_close (recorder);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_animation_completed (ClutterAnimation *animation)
|
||||||
|
{
|
||||||
|
g_timeout_add (1000, stop_recording_timeout, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main (int argc, char **argv)
|
||||||
|
{
|
||||||
|
ClutterActor *stage;
|
||||||
|
ClutterActor *text;
|
||||||
|
ClutterAnimation *animation;
|
||||||
|
ClutterColor red, green, blue;
|
||||||
|
|
||||||
|
g_thread_init (NULL);
|
||||||
|
gst_init (&argc, &argv);
|
||||||
|
clutter_init (&argc, &argv);
|
||||||
|
|
||||||
|
clutter_color_from_string (&red, "red");
|
||||||
|
clutter_color_from_string (&green, "green");
|
||||||
|
clutter_color_from_string (&blue, "blue");
|
||||||
|
stage = clutter_stage_get_default ();
|
||||||
|
|
||||||
|
text = g_object_new (CLUTTER_TYPE_TEXT,
|
||||||
|
"text", "Red",
|
||||||
|
"font-name", "Sans 40px",
|
||||||
|
"color", &red,
|
||||||
|
NULL);
|
||||||
|
clutter_container_add_actor (CLUTTER_CONTAINER (stage), text);
|
||||||
|
animation = clutter_actor_animate (text,
|
||||||
|
CLUTTER_EASE_IN_OUT_QUAD,
|
||||||
|
3000,
|
||||||
|
"x", 320,
|
||||||
|
"y", 240,
|
||||||
|
NULL);
|
||||||
|
g_signal_connect (animation, "completed",
|
||||||
|
G_CALLBACK (on_animation_completed), NULL);
|
||||||
|
|
||||||
|
text = g_object_new (CLUTTER_TYPE_TEXT,
|
||||||
|
"text", "Blue",
|
||||||
|
"font-name", "Sans 40px",
|
||||||
|
"color", &blue,
|
||||||
|
"x", 640,
|
||||||
|
"y", 0,
|
||||||
|
NULL);
|
||||||
|
clutter_actor_set_anchor_point_from_gravity (text, CLUTTER_GRAVITY_NORTH_EAST);
|
||||||
|
clutter_container_add_actor (CLUTTER_CONTAINER (stage), text);
|
||||||
|
animation = clutter_actor_animate (text,
|
||||||
|
CLUTTER_EASE_IN_OUT_QUAD,
|
||||||
|
3000,
|
||||||
|
"x", 320,
|
||||||
|
"y", 240,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
text = g_object_new (CLUTTER_TYPE_TEXT,
|
||||||
|
"text", "Green",
|
||||||
|
"font-name", "Sans 40px",
|
||||||
|
"color", &green,
|
||||||
|
"x", 0,
|
||||||
|
"y", 480,
|
||||||
|
NULL);
|
||||||
|
clutter_actor_set_anchor_point_from_gravity (text, CLUTTER_GRAVITY_SOUTH_WEST);
|
||||||
|
clutter_container_add_actor (CLUTTER_CONTAINER (stage), text);
|
||||||
|
animation = clutter_actor_animate (text,
|
||||||
|
CLUTTER_EASE_IN_OUT_QUAD,
|
||||||
|
3000,
|
||||||
|
"x", 320,
|
||||||
|
"y", 240,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
recorder = shell_recorder_new (CLUTTER_STAGE (stage));
|
||||||
|
shell_recorder_set_filename (recorder, "test-recorder.ogg");
|
||||||
|
|
||||||
|
clutter_actor_show (stage);
|
||||||
|
|
||||||
|
shell_recorder_record (recorder);
|
||||||
|
clutter_main ();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -71,6 +71,7 @@ if test x$system = xFedora ; then
|
|||||||
librsvg2-devel libwnck-devel mesa-libGL-devel python-devel readline-devel \
|
librsvg2-devel libwnck-devel mesa-libGL-devel python-devel readline-devel \
|
||||||
xulrunner-devel libXdamage-devel \
|
xulrunner-devel libXdamage-devel \
|
||||||
gdb glx-utils xorg-x11-apps xorg-x11-server-Xephyr xterm zenity \
|
gdb glx-utils xorg-x11-apps xorg-x11-server-Xephyr xterm zenity \
|
||||||
|
gstreamer-devel gstreamer-plugins-base gstreamer-plugins-good \
|
||||||
; do
|
; do
|
||||||
if ! rpm -q $pkg > /dev/null 2>&1; then
|
if ! rpm -q $pkg > /dev/null 2>&1; then
|
||||||
reqd="$pkg $reqd"
|
reqd="$pkg $reqd"
|
||||||
|
Loading…
Reference in New Issue
Block a user