[byzanz] Split recording into its own object
- From: Benjamin Otte <otte src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [byzanz] Split recording into its own object
- Date: Wed, 26 Aug 2009 10:22:22 +0000 (UTC)
commit 7697bf484847210269bebe129f2bec41dbc4c376
Author: Benjamin Otte <otte gnome org>
Date: Thu Aug 20 15:30:10 2009 +0200
Split recording into its own object
move the code that does recording from byzanzsession.c and split it into
logical objects:
- ByzanzRecorder
The base object for recording. It keeps track of the layers it records
and emits an "image" signal whenever a new image should be recorded.
- ByzanzLayer
Base class for the layers of a recording. Supposed to keep track of
the region of changes to the image it is recording and doing the
actual rendering. Two layers are implemented:
- ByzanzLayerWindow
The bottommost layer. It does the recording of the actual window using
the XDamage extension.
- ByzanzLayerCursor
The topmost layer. Keeps track of the mouse cursor.
.gitignore | 2 +
configure.ac | 6 +-
gifenc/gifenc.c | 16 ++
gifenc/gifenc.h | 2 +
src/Makefile.am | 21 +++
src/byzanzlayer.c | 116 +++++++++++++++
src/byzanzlayer.h | 60 ++++++++
src/byzanzlayercursor.c | 220 ++++++++++++++++++++++++++++
src/byzanzlayercursor.h | 58 ++++++++
src/byzanzlayerwindow.c | 162 +++++++++++++++++++++
src/byzanzlayerwindow.h | 52 +++++++
src/byzanzmarshal.list | 1 +
src/byzanzrecorder.c | 367 +++++++++++++++++++++++++++++++++++++++++++++++
src/byzanzrecorder.h | 76 ++++++++++
src/byzanzsession.c | 335 +++++++------------------------------------
15 files changed, 1208 insertions(+), 286 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 23a0718..c689136 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,4 +44,6 @@ src/ByzanzApplet.server.in
src/byzanz-applet
src/byzanz-record
src/byzanz.schemas
+src/byzanzmarshal.c
+src/byzanzmarshal.h
diff --git a/configure.ac b/configure.ac
index 4fa576e..76fe01f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -107,10 +107,8 @@ PKG_CHECK_MODULES(GTHREAD, xdamage >= $XDAMAGE_REQ gthread-2.0 >= $GTHREAD_REQ)
PKG_CHECK_MODULES(APPLET, libpanelapplet-2.0 >= $APPLET_REQ)
-AC_PATH_PROG(GCONFTOOL, gconftool-2, no)
-if test x"$GCONFTOOL" = xno; then
- AC_MSG_ERROR([gconftool-2 executable not found in your path - should be installed with GConf])
-fi
+AC_PATH_PROG(GLIB_GENMARSHAL, glib-genmarshal)
+AC_PATH_PROG(GCONFTOOL, gconftool-2)
AM_GCONF_SOURCE_2
diff --git a/gifenc/gifenc.c b/gifenc/gifenc.c
index 27029f5..af80014 100644
--- a/gifenc/gifenc.c
+++ b/gifenc/gifenc.c
@@ -449,6 +449,22 @@ gifenc_free (Gifenc *enc)
return success;
}
+guint
+gifenc_get_width (Gifenc *gifenc)
+{
+ g_return_val_if_fail (gifenc != NULL, 0);
+
+ return gifenc->width;
+}
+
+guint
+gifenc_get_height (Gifenc *gifenc)
+{
+ g_return_val_if_fail (gifenc != NULL, 0);
+
+ return gifenc->height;
+}
+
/* Floyd-Steinman factors */
#define FACTOR0 (23)
#define FACTOR1 (79)
diff --git a/gifenc/gifenc.h b/gifenc/gifenc.h
index 0b5c602..531e1e6 100644
--- a/gifenc/gifenc.h
+++ b/gifenc/gifenc.h
@@ -88,6 +88,8 @@ gboolean gifenc_add_image (Gifenc * enc,
GError ** error);
gboolean gifenc_close (Gifenc * gifenc,
GError ** error);
+guint gifenc_get_width (Gifenc * gifenc);
+guint gifenc_get_height (Gifenc * gifenc);
void gifenc_dither_rgb (guint8 * target,
guint target_rowstride,
diff --git a/src/Makefile.am b/src/Makefile.am
index 44a02e9..ba157ae 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -3,7 +3,15 @@ bin_PROGRAMS = byzanz-record
libexec_PROGRAMS = byzanz-applet
man_MANS = byzanz-record.1
+BUILT_SOURCES = \
+ byzanzmarshal.h \
+ byzanzmarshal.c
+
noinst_HEADERS = \
+ byzanzlayer.h \
+ byzanzlayercursor.h \
+ byzanzlayerwindow.h \
+ byzanzrecorder.h \
byzanzsession.h \
byzanzselect.h \
paneldropdown.h \
@@ -11,6 +19,11 @@ noinst_HEADERS = \
screenshot-utils.h
libbyzanz_la_SOURCES = \
+ byzanzlayer.c \
+ byzanzlayercursor.c \
+ byzanzlayerwindow.c \
+ byzanzmarshal.c \
+ byzanzrecorder.c \
byzanzsession.c \
byzanzselect.c
@@ -33,6 +46,13 @@ byzanz_applet_SOURCES = \
byzanz_applet_CFLAGS = $(APPLET_CFLAGS) -I$(top_srcdir)/gifenc
byzanz_applet_LDADD = $(APPLET_LIBS) ./libbyzanz.la
+byzanzmarshal.h: byzanzmarshal.list
+ $(GLIB_GENMARSHAL) --prefix=byzanz_marshal $(srcdir)/byzanzmarshal.list --header > byzanzmarshal.h
+
+byzanzmarshal.c: byzanzmarshal.list
+ (echo "#include \"byzanzmarshal.h\""; \
+ $(GLIB_GENMARSHAL) --prefix=byzanz_marshal $(srcdir)/byzanzmarshal.list --body) > byzanzmarshal.c
+
schemasdir = @GCONF_SCHEMA_FILE_DIR@
schemas_in_files = byzanz.schemas.in
@@ -58,6 +78,7 @@ ui_DATA = byzanzapplet.xml
CLEANFILES = $(server_in_files) $(server_DATA) $(schemas_DATA)
EXTRA_DIST = \
+ byzanzmarshal.list \
ByzanzApplet.server.in.in \
$(man_MANS) \
$(ui_DATA) \
diff --git a/src/byzanzlayer.c b/src/byzanzlayer.c
new file mode 100644
index 0000000..b99378c
--- /dev/null
+++ b/src/byzanzlayer.c
@@ -0,0 +1,116 @@
+/* desktop session recorder
+ * Copyright (C) 2009 Benjamin Otte <otte gnome org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "byzanzlayer.h"
+
+#include <gdk/gdkx.h>
+
+#include "byzanzmarshal.h"
+
+enum {
+ PROP_0,
+ PROP_RECORDER,
+};
+
+G_DEFINE_ABSTRACT_TYPE (ByzanzLayer, byzanz_layer, G_TYPE_OBJECT)
+
+static void
+byzanz_layer_set_property (GObject *object, guint param_id, const GValue *value,
+ GParamSpec * pspec)
+{
+ ByzanzLayer *layer = BYZANZ_LAYER (object);
+
+ switch (param_id) {
+ case PROP_RECORDER:
+ layer->recorder = g_value_get_object (value);
+ g_assert (layer->recorder != NULL);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+byzanz_layer_get_property (GObject *object, guint param_id, GValue *value,
+ GParamSpec * pspec)
+{
+ ByzanzLayer *layer = BYZANZ_LAYER (object);
+
+ switch (param_id) {
+ case PROP_RECORDER:
+ g_value_set_object (value, layer->recorder);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+byzanz_layer_constructed (GObject *object)
+{
+ ByzanzLayer *layer = BYZANZ_LAYER (object);
+
+ byzanz_layer_invalidate (layer);
+
+ if (G_OBJECT_CLASS (byzanz_layer_parent_class)->constructed)
+ G_OBJECT_CLASS (byzanz_layer_parent_class)->constructed (object);
+}
+
+static void
+byzanz_layer_finalize (GObject *object)
+{
+ //ByzanzLayer *layer = BYZANZ_LAYER (object);
+
+ G_OBJECT_CLASS (byzanz_layer_parent_class)->finalize (object);
+}
+
+static void
+byzanz_layer_class_init (ByzanzLayerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = byzanz_layer_get_property;
+ object_class->set_property = byzanz_layer_set_property;
+ object_class->finalize = byzanz_layer_finalize;
+ object_class->constructed = byzanz_layer_constructed;
+
+ g_object_class_install_property (object_class, PROP_RECORDER,
+ g_param_spec_object ("recorder", "recorder", "recorder that manages us",
+ BYZANZ_TYPE_RECORDER, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+byzanz_layer_init (ByzanzLayer *layer)
+{
+}
+
+void
+byzanz_layer_invalidate (ByzanzLayer *layer)
+{
+ g_return_if_fail (BYZANZ_IS_LAYER (layer));
+
+ byzanz_recorder_queue_snapshot (layer->recorder);
+}
+
diff --git a/src/byzanzlayer.h b/src/byzanzlayer.h
new file mode 100644
index 0000000..163f816
--- /dev/null
+++ b/src/byzanzlayer.h
@@ -0,0 +1,60 @@
+/* desktop session recorder
+ * Copyright (C) 2009 Benjamin Otte <otte gnome org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <gdk/gdk.h>
+#include "byzanzrecorder.h"
+
+#ifndef __HAVE_BYZANZ_LAYER_H__
+#define __HAVE_BYZANZ_LAYER_H__
+
+typedef struct _ByzanzLayer ByzanzLayer;
+typedef struct _ByzanzLayerClass ByzanzLayerClass;
+
+#define BYZANZ_TYPE_LAYER (byzanz_layer_get_type())
+#define BYZANZ_IS_LAYER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), BYZANZ_TYPE_LAYER))
+#define BYZANZ_IS_LAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), BYZANZ_TYPE_LAYER))
+#define BYZANZ_LAYER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BYZANZ_TYPE_LAYER, ByzanzLayer))
+#define BYZANZ_LAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), BYZANZ_TYPE_LAYER, ByzanzLayerClass))
+#define BYZANZ_LAYER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), BYZANZ_TYPE_LAYER, ByzanzLayerClass))
+
+struct _ByzanzLayer {
+ GObject object;
+
+ /*< protected >*/
+ ByzanzRecorder * recorder; /* the recorder we are recording from (not keeping a reference to it) */
+};
+
+struct _ByzanzLayerClass {
+ GObjectClass object_class;
+
+ gboolean (* event) (ByzanzLayer * layer,
+ GdkXEvent * event);
+ GdkRegion * (* snapshot) (ByzanzLayer * layer);
+ void (* render) (ByzanzLayer * layer,
+ cairo_t * cr);
+};
+
+GType byzanz_layer_get_type (void) G_GNUC_CONST;
+
+
+/* protected API for subclasses */
+void byzanz_layer_invalidate (ByzanzLayer * layer);
+
+
+#endif /* __HAVE_BYZANZ_LAYER_H__ */
diff --git a/src/byzanzlayercursor.c b/src/byzanzlayercursor.c
new file mode 100644
index 0000000..a23abfe
--- /dev/null
+++ b/src/byzanzlayercursor.c
@@ -0,0 +1,220 @@
+/* desktop session recorder
+ * Copyright (C) 2009 Benjamin Otte <otte gnome org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "byzanzlayercursor.h"
+
+#include <gdk/gdkx.h>
+
+G_DEFINE_TYPE (ByzanzLayerCursor, byzanz_layer_cursor, BYZANZ_TYPE_LAYER)
+
+static void
+byzanz_layer_cursor_read_cursor (ByzanzLayerCursor *clayer)
+{
+ Display *dpy = gdk_x11_drawable_get_xdisplay (BYZANZ_LAYER (clayer)->recorder->window);
+
+ clayer->cursor_next = XFixesGetCursorImage (dpy);
+ if (clayer->cursor_next)
+ g_hash_table_insert (clayer->cursors, clayer->cursor_next, clayer->cursor_next);
+}
+
+static gboolean
+byzanz_layer_cursor_event (ByzanzLayer * layer,
+ GdkXEvent * gdkxevent)
+{
+ ByzanzLayerCursor *clayer = BYZANZ_LAYER_CURSOR (layer);
+ XFixesCursorNotifyEvent *event = gdkxevent;
+
+ if (event->type == layer->recorder->fixes_event_base + XFixesCursorNotify) {
+ XFixesCursorImage hack;
+
+ hack.cursor_serial = event->cursor_serial;
+ clayer->cursor_next = g_hash_table_lookup (clayer->cursors, &hack);
+ if (clayer->cursor_next == NULL)
+ byzanz_layer_cursor_read_cursor (clayer);
+ if (clayer->cursor_next != clayer->cursor)
+ byzanz_layer_invalidate (layer);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+byzanz_layer_cursor_poll (gpointer data)
+{
+ ByzanzLayerCursor *clayer = data;
+ int x, y;
+
+ gdk_window_get_pointer (BYZANZ_LAYER (clayer)->recorder->window, &x, &y, NULL);
+ if (x == clayer->cursor_x &&
+ y == clayer->cursor_y)
+ return TRUE;
+
+ clayer->poll_source = 0;
+ byzanz_layer_invalidate (BYZANZ_LAYER (clayer));
+ return FALSE;
+}
+
+static void
+byzanz_layer_cursor_setup_poll (ByzanzLayerCursor *clayer)
+{
+ if (clayer->poll_source != 0)
+ return;
+
+ /* FIXME: Is 10ms ok or is it too much? */
+ clayer->poll_source = g_timeout_add (10, byzanz_layer_cursor_poll, clayer);
+}
+
+static void
+byzanz_recorder_invalidate_cursor (GdkRegion *region, XFixesCursorImage *cursor, int x, int y)
+{
+ GdkRectangle cursor_rect;
+
+ if (cursor == NULL)
+ return;
+
+ cursor_rect.x = x - cursor->xhot;
+ cursor_rect.y = y - cursor->yhot;
+ cursor_rect.width = cursor->width;
+ cursor_rect.height = cursor->height;
+
+ gdk_region_union_with_rect (region, &cursor_rect);
+}
+
+static GdkRegion *
+byzanz_layer_cursor_snapshot (ByzanzLayer *layer)
+{
+ ByzanzLayerCursor *clayer = BYZANZ_LAYER_CURSOR (layer);
+ GdkRegion *region, *area;
+ int x, y;
+
+ gdk_window_get_pointer (layer->recorder->window, &x, &y, NULL);
+ if (x == clayer->cursor_x &&
+ y == clayer->cursor_y &&
+ clayer->cursor_next == clayer->cursor)
+ return NULL;
+
+ region = gdk_region_new ();
+ byzanz_recorder_invalidate_cursor (region, clayer->cursor, clayer->cursor_x, clayer->cursor_y);
+ byzanz_recorder_invalidate_cursor (region, clayer->cursor_next, x, y);
+ area = gdk_region_rectangle (&layer->recorder->area);
+ gdk_region_intersect (region, area);
+ gdk_region_destroy (area);
+
+ clayer->cursor = clayer->cursor_next;
+ clayer->cursor_x = x;
+ clayer->cursor_y = y;
+ byzanz_layer_cursor_setup_poll (clayer);
+
+ return region;
+}
+
+static void
+byzanz_layer_cursor_render (ByzanzLayer *layer,
+ cairo_t * cr)
+{
+ ByzanzLayerCursor *clayer = BYZANZ_LAYER_CURSOR (layer);
+ XFixesCursorImage *cursor = clayer->cursor;
+ cairo_surface_t *cursor_surface;
+
+ if (clayer->cursor == NULL)
+ return;
+
+ cursor_surface = cairo_image_surface_create_for_data ((guchar *) cursor->pixels,
+ CAIRO_FORMAT_ARGB32, cursor->width, cursor->height, cursor->width * 4);
+
+ cairo_save (cr);
+ cairo_translate (cr, clayer->cursor_x, clayer->cursor_y);
+ cairo_set_source_surface (cr, cursor_surface, -(double) cursor->xhot, -(double) cursor->yhot);
+ cairo_paint (cr);
+ cairo_restore (cr);
+
+ cairo_surface_destroy (cursor_surface);
+}
+
+static void
+byzanz_layer_cursor_finalize (GObject *object)
+{
+ ByzanzLayerCursor *clayer = BYZANZ_LAYER_CURSOR (object);
+ GdkWindow *window = BYZANZ_LAYER (object)->recorder->window;
+ Display *dpy = gdk_x11_drawable_get_xdisplay (window);
+
+ XFixesSelectCursorInput (dpy, gdk_x11_drawable_get_xid (window), 0);
+
+ g_hash_table_destroy (clayer->cursors);
+
+ if (clayer->poll_source != 0) {
+ g_source_remove (clayer->poll_source);
+ }
+
+ G_OBJECT_CLASS (byzanz_layer_cursor_parent_class)->finalize (object);
+}
+
+static void
+byzanz_layer_cursor_constructed (GObject *object)
+{
+ ByzanzLayerCursor *clayer = BYZANZ_LAYER_CURSOR (object);
+ GdkWindow *window = BYZANZ_LAYER (object)->recorder->window;
+ Display *dpy = gdk_x11_drawable_get_xdisplay (window);
+
+ XFixesSelectCursorInput (dpy, gdk_x11_drawable_get_xid (window), XFixesDisplayCursorNotifyMask);
+ byzanz_layer_cursor_read_cursor (clayer);
+ byzanz_layer_cursor_setup_poll (clayer);
+
+ G_OBJECT_CLASS (byzanz_layer_cursor_parent_class)->constructed (object);
+}
+
+static void
+byzanz_layer_cursor_class_init (ByzanzLayerCursorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ ByzanzLayerClass *layer_class = BYZANZ_LAYER_CLASS (klass);
+
+ object_class->constructed = byzanz_layer_cursor_constructed;
+ object_class->finalize = byzanz_layer_cursor_finalize;
+
+ layer_class->event = byzanz_layer_cursor_event;
+ layer_class->snapshot = byzanz_layer_cursor_snapshot;
+ layer_class->render = byzanz_layer_cursor_render;
+}
+
+static guint
+byzanz_cursor_hash (gconstpointer key)
+{
+ return (guint) ((const XFixesCursorImage *) key)->cursor_serial;
+}
+
+static gboolean
+byzanz_cursor_equal (gconstpointer c1, gconstpointer c2)
+{
+ return ((const XFixesCursorImage *) c1)->cursor_serial ==
+ ((const XFixesCursorImage *) c2)->cursor_serial;
+}
+
+static void
+byzanz_layer_cursor_init (ByzanzLayerCursor *clayer)
+{
+ clayer->cursors = g_hash_table_new_full (byzanz_cursor_hash,
+ byzanz_cursor_equal, NULL, (GDestroyNotify) XFree);
+}
+
diff --git a/src/byzanzlayercursor.h b/src/byzanzlayercursor.h
new file mode 100644
index 0000000..5873a64
--- /dev/null
+++ b/src/byzanzlayercursor.h
@@ -0,0 +1,58 @@
+/* desktop session recorder
+ * Copyright (C) 2009 Benjamin Otte <otte gnome org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "byzanzlayer.h"
+
+#include <X11/extensions/Xdamage.h>
+
+#ifndef __HAVE_BYZANZ_LAYER_CURSOR_H__
+#define __HAVE_BYZANZ_LAYER_CURSOR_H__
+
+typedef struct _ByzanzLayerCursor ByzanzLayerCursor;
+typedef struct _ByzanzLayerCursorClass ByzanzLayerCursorClass;
+
+#define BYZANZ_TYPE_LAYER_CURSOR (byzanz_layer_cursor_get_type())
+#define BYZANZ_IS_LAYER_CURSOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), BYZANZ_TYPE_LAYER_CURSOR))
+#define BYZANZ_IS_LAYER_CURSOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), BYZANZ_TYPE_LAYER_CURSOR))
+#define BYZANZ_LAYER_CURSOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BYZANZ_TYPE_LAYER_CURSOR, ByzanzLayerCursor))
+#define BYZANZ_LAYER_CURSOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), BYZANZ_TYPE_LAYER_CURSOR, ByzanzLayerCursorClass))
+#define BYZANZ_LAYER_CURSOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), BYZANZ_TYPE_LAYER_CURSOR, ByzanzLayerCursorClass))
+
+struct _ByzanzLayerCursor {
+ ByzanzLayer layer;
+
+ XFixesCursorImage * cursor_next; /* current active cursor */
+
+ XFixesCursorImage * cursor; /* last recorded cursor */
+ int cursor_x; /* last recorded X position of cursor */
+ int cursor_y; /* last recorded Y position of cursor */
+
+ GHashTable * cursors; /* all cursors we know about already */
+
+ guint poll_source; /* source used for querying mouse position */
+};
+
+struct _ByzanzLayerCursorClass {
+ ByzanzLayerClass layer_class;
+};
+
+GType byzanz_layer_cursor_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __HAVE_BYZANZ_LAYER_CURSOR_H__ */
diff --git a/src/byzanzlayerwindow.c b/src/byzanzlayerwindow.c
new file mode 100644
index 0000000..9095eb8
--- /dev/null
+++ b/src/byzanzlayerwindow.c
@@ -0,0 +1,162 @@
+/* desktop session recorder
+ * Copyright (C) 2009 Benjamin Otte <otte gnome org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "byzanzlayerwindow.h"
+
+#include <gdk/gdkx.h>
+
+G_DEFINE_TYPE (ByzanzLayerWindow, byzanz_layer_window, BYZANZ_TYPE_LAYER)
+
+static gboolean
+byzanz_layer_window_event (ByzanzLayer * layer,
+ GdkXEvent * gdkxevent)
+{
+ XDamageNotifyEvent *event = (XDamageNotifyEvent *) gdkxevent;
+ ByzanzLayerWindow *wlayer = BYZANZ_LAYER_WINDOW (layer);
+
+ if (event->type == layer->recorder->damage_event_base + XDamageNotify &&
+ event->damage == wlayer->damage) {
+ GdkRectangle rect;
+
+ rect.x = event->area.x;
+ rect.y = event->area.y;
+ rect.width = event->area.width;
+ rect.height = event->area.height;
+ if (gdk_rectangle_intersect (&rect, &layer->recorder->area, &rect)) {
+ gdk_region_union_with_rect (wlayer->invalid, &rect);
+ byzanz_layer_invalidate (layer);
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static XserverRegion
+byzanz_server_region_from_gdk (Display *dpy, GdkRegion *source)
+{
+ XserverRegion dest;
+ XRectangle *dest_rects;
+ GdkRectangle *source_rects;
+ int n_rectangles, i;
+
+ gdk_region_get_rectangles (source, &source_rects, &n_rectangles);
+ g_assert (n_rectangles);
+ dest_rects = g_new (XRectangle, n_rectangles);
+ for (i = 0; i < n_rectangles; i++) {
+ dest_rects[i].x = source_rects[i].x;
+ dest_rects[i].y = source_rects[i].y;
+ dest_rects[i].width = source_rects[i].width;
+ dest_rects[i].height = source_rects[i].height;
+ }
+ dest = XFixesCreateRegion (dpy, dest_rects, n_rectangles);
+ g_free (dest_rects);
+ g_free (source_rects);
+
+ return dest;
+}
+
+static GdkRegion *
+byzanz_layer_window_snapshot (ByzanzLayer *layer)
+{
+ Display *dpy = gdk_x11_drawable_get_xdisplay (layer->recorder->window);
+ ByzanzLayerWindow *wlayer = BYZANZ_LAYER_WINDOW (layer);
+ XserverRegion reg;
+ GdkRegion *region;
+
+ if (gdk_region_empty (wlayer->invalid))
+ return NULL;
+
+ reg = byzanz_server_region_from_gdk (dpy, wlayer->invalid);
+ XDamageSubtract (dpy, wlayer->damage, reg, reg);
+ XFixesDestroyRegion (dpy, reg);
+
+ region = wlayer->invalid;
+ wlayer->invalid = gdk_region_new ();
+ return region;
+}
+
+static void
+byzanz_cairo_set_source_window (cairo_t *cr, GdkWindow *window, double x, double y)
+{
+ cairo_t *tmp;
+
+ tmp = gdk_cairo_create (window);
+ cairo_set_source_surface (cr, cairo_get_target (tmp), x, y);
+ cairo_destroy (tmp);
+}
+
+static void
+byzanz_layer_window_render (ByzanzLayer *layer,
+ cairo_t * cr)
+{
+ byzanz_cairo_set_source_window (cr, layer->recorder->window, 0, 0);
+ cairo_paint (cr);
+}
+
+static void
+byzanz_layer_window_finalize (GObject *object)
+{
+ Display *dpy = gdk_x11_drawable_get_xdisplay (BYZANZ_LAYER (object)->recorder->window);
+ ByzanzLayerWindow *wlayer = BYZANZ_LAYER_WINDOW (object);
+
+ XDamageDestroy (dpy, wlayer->damage);
+ gdk_region_destroy (wlayer->invalid);
+
+ G_OBJECT_CLASS (byzanz_layer_window_parent_class)->finalize (object);
+}
+
+static void
+byzanz_layer_window_constructed (GObject *object)
+{
+ ByzanzLayer *layer = BYZANZ_LAYER (object);
+ GdkWindow *window = layer->recorder->window;
+ Display *dpy = gdk_x11_drawable_get_xdisplay (window);
+ ByzanzLayerWindow *wlayer = BYZANZ_LAYER_WINDOW (object);
+
+ wlayer->damage = XDamageCreate (dpy, gdk_x11_drawable_get_xid (window), XDamageReportDeltaRectangles);
+ gdk_region_union_with_rect (wlayer->invalid, &layer->recorder->area);
+
+ G_OBJECT_CLASS (byzanz_layer_window_parent_class)->constructed (object);
+}
+
+static void
+byzanz_layer_window_class_init (ByzanzLayerWindowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ ByzanzLayerClass *layer_class = BYZANZ_LAYER_CLASS (klass);
+
+ object_class->finalize = byzanz_layer_window_finalize;
+ object_class->constructed = byzanz_layer_window_constructed;
+
+ layer_class->event = byzanz_layer_window_event;
+ layer_class->snapshot = byzanz_layer_window_snapshot;
+ layer_class->render = byzanz_layer_window_render;
+}
+
+static void
+byzanz_layer_window_init (ByzanzLayerWindow *wlayer)
+{
+ wlayer->invalid = gdk_region_new ();
+}
+
diff --git a/src/byzanzlayerwindow.h b/src/byzanzlayerwindow.h
new file mode 100644
index 0000000..1620434
--- /dev/null
+++ b/src/byzanzlayerwindow.h
@@ -0,0 +1,52 @@
+/* desktop session recorder
+ * Copyright (C) 2009 Benjamin Otte <otte gnome org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "byzanzlayer.h"
+
+#include <X11/extensions/Xdamage.h>
+#include <X11/extensions/Xfixes.h>
+
+#ifndef __HAVE_BYZANZ_LAYER_WINDOW_H__
+#define __HAVE_BYZANZ_LAYER_WINDOW_H__
+
+typedef struct _ByzanzLayerWindow ByzanzLayerWindow;
+typedef struct _ByzanzLayerWindowClass ByzanzLayerWindowClass;
+
+#define BYZANZ_TYPE_LAYER_WINDOW (byzanz_layer_window_get_type())
+#define BYZANZ_IS_LAYER_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), BYZANZ_TYPE_LAYER_WINDOW))
+#define BYZANZ_IS_LAYER_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), BYZANZ_TYPE_LAYER_WINDOW))
+#define BYZANZ_LAYER_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BYZANZ_TYPE_LAYER_WINDOW, ByzanzLayerWindow))
+#define BYZANZ_LAYER_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), BYZANZ_TYPE_LAYER_WINDOW, ByzanzLayerWindowClass))
+#define BYZANZ_LAYER_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), BYZANZ_TYPE_LAYER_WINDOW, ByzanzLayerWindowClass))
+
+struct _ByzanzLayerWindow {
+ ByzanzLayer layer;
+
+ GdkRegion * invalid; /* TRUE if we need to repaint */
+ Damage damage; /* the Damage object */
+};
+
+struct _ByzanzLayerWindowClass {
+ ByzanzLayerClass layer_class;
+};
+
+GType byzanz_layer_window_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __HAVE_BYZANZ_LAYER_WINDOW_H__ */
diff --git a/src/byzanzmarshal.list b/src/byzanzmarshal.list
new file mode 100644
index 0000000..19d9a83
--- /dev/null
+++ b/src/byzanzmarshal.list
@@ -0,0 +1 @@
+VOID:POINTER,POINTER,POINTER
diff --git a/src/byzanzrecorder.c b/src/byzanzrecorder.c
new file mode 100644
index 0000000..691898d
--- /dev/null
+++ b/src/byzanzrecorder.c
@@ -0,0 +1,367 @@
+/* desktop session recorder
+ * Copyright (C) 2009 Benjamin Otte <otte gnome org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "byzanzrecorder.h"
+
+#include <gdk/gdkx.h>
+
+#include <X11/extensions/Xdamage.h>
+#include <X11/extensions/Xfixes.h>
+
+#include "byzanzlayer.h"
+#include "byzanzlayercursor.h"
+#include "byzanzlayerwindow.h"
+#include "byzanzmarshal.h"
+
+enum {
+ PROP_0,
+ PROP_WINDOW,
+ PROP_AREA,
+ PROP_RECORDING,
+};
+
+enum {
+ IMAGE,
+ LAST_SIGNAL
+};
+
+G_DEFINE_TYPE (ByzanzRecorder, byzanz_recorder, G_TYPE_OBJECT)
+static guint signals[LAST_SIGNAL] = { 0, };
+
+static GdkRegion *
+byzanz_recorder_get_invalid_region (ByzanzRecorder *recorder)
+{
+ GdkRegion *invalid, *layer_invalid;
+ GSequenceIter *iter;
+
+ invalid = gdk_region_new ();
+ for (iter = g_sequence_get_begin_iter (recorder->layers);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter)) {
+ ByzanzLayer *layer = g_sequence_get (iter);
+ ByzanzLayerClass *klass = BYZANZ_LAYER_GET_CLASS (layer);
+
+ layer_invalid = klass->snapshot (layer);
+ if (layer_invalid) {
+ gdk_region_union (invalid, layer_invalid);
+ gdk_region_destroy (layer_invalid);
+ }
+ }
+
+ return invalid;
+}
+
+static cairo_surface_t *
+byzanz_recorder_create_snapshot (ByzanzRecorder *recorder, const GdkRegion *invalid)
+{
+ GdkRectangle extents;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ GSequenceIter *iter;
+
+ gdk_region_get_clipbox (invalid, &extents);
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, extents.width, extents.height);
+ cairo_surface_set_device_offset (surface, -extents.x, -extents.y);
+
+ cr = cairo_create (surface);
+ gdk_cairo_region (cr, invalid);
+ cairo_clip (cr);
+
+ for (iter = g_sequence_get_begin_iter (recorder->layers);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter)) {
+ ByzanzLayer *layer = g_sequence_get (iter);
+ ByzanzLayerClass *klass = BYZANZ_LAYER_GET_CLASS (layer);
+
+ cairo_save (cr);
+ klass->render (layer, cr);
+ cairo_restore (cr);
+ }
+
+ cairo_destroy (cr);
+
+ /* adjust device offset here - the layers work in GdkScreen coordinates, the rest
+ * of the code works in coordinates realtive to the passed in area. */
+ cairo_surface_set_device_offset (surface,
+ recorder->area.x - extents.x, recorder->area.y - extents.y);
+ return surface;
+}
+
+static gboolean byzanz_recorder_snapshot (ByzanzRecorder *recorder);
+static gboolean
+byzanz_recorder_next_image (gpointer data)
+{
+ ByzanzRecorder *recorder = data;
+
+ recorder->next_image_source = 0;
+ byzanz_recorder_snapshot (recorder);
+ return FALSE;
+}
+
+static gboolean
+byzanz_recorder_snapshot (ByzanzRecorder *recorder)
+{
+ cairo_surface_t *surface;
+ GdkRegion *invalid;
+ GTimeVal tv;
+
+ if (!recorder->recording)
+ return FALSE;
+
+ if (recorder->next_image_source != 0)
+ return FALSE;
+
+ invalid = byzanz_recorder_get_invalid_region (recorder);
+ if (gdk_region_empty (invalid)) {
+ gdk_region_destroy (invalid);
+ return FALSE;
+ }
+
+ surface = byzanz_recorder_create_snapshot (recorder, invalid);
+ g_get_current_time (&tv);
+ gdk_region_offset (invalid, -recorder->area.x, -recorder->area.y);
+
+ g_signal_emit (recorder, signals[IMAGE], 0, surface, invalid, &tv);
+
+ cairo_surface_destroy (surface);
+ gdk_region_destroy (invalid);
+
+ recorder->next_image_source = gdk_threads_add_timeout_full (G_PRIORITY_HIGH_IDLE,
+ BYZANZ_RECORDER_FRAME_RATE_MS, byzanz_recorder_next_image, recorder, NULL);
+
+ return TRUE;
+}
+
+static GdkFilterReturn
+byzanz_recorder_filter_events (GdkXEvent *xevent, GdkEvent *event, gpointer data)
+{
+ ByzanzRecorder *recorder = data;
+ GSequenceIter *iter;
+ gboolean handled;
+
+ if (event->any.window != recorder->window)
+ return GDK_FILTER_CONTINUE;
+
+ handled = FALSE;
+ for (iter = g_sequence_get_begin_iter (recorder->layers);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter)) {
+ ByzanzLayer *layer = g_sequence_get (iter);
+ ByzanzLayerClass *klass = BYZANZ_LAYER_GET_CLASS (layer);
+
+ handled |= klass->event (layer, xevent);
+ }
+
+ return handled ? GDK_FILTER_REMOVE : GDK_FILTER_CONTINUE;
+}
+
+static gboolean
+byzanz_recorder_prepare (ByzanzRecorder *recorder)
+{
+ GdkDisplay *display;
+ Display *dpy;
+
+ display = gdk_display_get_default ();
+ dpy = gdk_x11_display_get_xdisplay (display);
+
+ if (!XDamageQueryExtension (dpy, &recorder->damage_event_base, &recorder->damage_error_base) ||
+ !XFixesQueryExtension (dpy, &recorder->fixes_event_base, &recorder->fixes_error_base))
+ return FALSE;
+ gdk_x11_register_standard_event_type (display,
+ recorder->damage_event_base + XDamageNotify, 1);
+ gdk_x11_register_standard_event_type (display,
+ recorder->fixes_event_base + XFixesCursorNotify, 1);
+
+ return TRUE;
+}
+
+static void
+byzanz_recorder_set_window (ByzanzRecorder *recorder, GdkWindow *window)
+{
+ g_assert (window != NULL);
+
+ if (!byzanz_recorder_prepare (recorder)) {
+ g_assert_not_reached ();
+ }
+
+ recorder->window = g_object_ref (window);
+ gdk_window_add_filter (window, byzanz_recorder_filter_events, recorder);
+}
+
+static void
+byzanz_recorder_set_property (GObject *object, guint param_id, const GValue *value,
+ GParamSpec * pspec)
+{
+ ByzanzRecorder *recorder = BYZANZ_RECORDER (object);
+
+ switch (param_id) {
+ case PROP_WINDOW:
+ byzanz_recorder_set_window (recorder, g_value_get_object (value));
+ break;
+ case PROP_AREA:
+ recorder->area = *(GdkRectangle *) g_value_get_boxed (value);
+ break;
+ case PROP_RECORDING:
+ byzanz_recorder_set_recording (recorder, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+byzanz_recorder_get_property (GObject *object, guint param_id, GValue *value,
+ GParamSpec * pspec)
+{
+ ByzanzRecorder *recorder = BYZANZ_RECORDER (object);
+
+ switch (param_id) {
+ case PROP_WINDOW:
+ g_value_set_object (value, recorder->window);
+ break;
+ case PROP_AREA:
+ g_value_set_boxed (value, &recorder->area);
+ break;
+ case PROP_RECORDING:
+ g_value_set_boolean (value, byzanz_recorder_get_recording (recorder));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+byzanz_recorder_constructed (GObject *object)
+{
+ ByzanzRecorder *recorder = BYZANZ_RECORDER (object);
+
+ g_sequence_append (recorder->layers,
+ g_object_new (BYZANZ_TYPE_LAYER_WINDOW, "recorder", recorder, NULL));
+ g_sequence_append (recorder->layers,
+ g_object_new (BYZANZ_TYPE_LAYER_CURSOR, "recorder", recorder, NULL));
+
+ if (G_OBJECT_CLASS (byzanz_recorder_parent_class)->constructed)
+ G_OBJECT_CLASS (byzanz_recorder_parent_class)->constructed (object);
+}
+
+static void
+byzanz_recorder_dispose (GObject *object)
+{
+ ByzanzRecorder *recorder = BYZANZ_RECORDER (object);
+
+ g_sequence_free (recorder->layers);
+
+ G_OBJECT_CLASS (byzanz_recorder_parent_class)->dispose (object);
+}
+
+static void
+byzanz_recorder_finalize (GObject *object)
+{
+ ByzanzRecorder *recorder = BYZANZ_RECORDER (object);
+
+ if (recorder->next_image_source)
+ g_source_remove (recorder->next_image_source);
+
+ gdk_window_remove_filter (recorder->window,
+ byzanz_recorder_filter_events, recorder);
+ g_object_unref (recorder->window);
+
+ G_OBJECT_CLASS (byzanz_recorder_parent_class)->finalize (object);
+}
+
+static void
+byzanz_recorder_class_init (ByzanzRecorderClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = byzanz_recorder_get_property;
+ object_class->set_property = byzanz_recorder_set_property;
+ object_class->dispose = byzanz_recorder_dispose;
+ object_class->finalize = byzanz_recorder_finalize;
+ object_class->constructed = byzanz_recorder_constructed;
+
+ g_object_class_install_property (object_class, PROP_WINDOW,
+ g_param_spec_object ("window", "window", "window to record from",
+ GDK_TYPE_WINDOW, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class, PROP_AREA,
+ g_param_spec_boxed ("area", "area", "recorded area",
+ GDK_TYPE_RECTANGLE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class, PROP_RECORDING,
+ g_param_spec_boolean ("recording", "recording", "TRUE when actively recording",
+ FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ signals[IMAGE] = g_signal_new ("image", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ByzanzRecorderClass, image), NULL, NULL,
+ byzanz_marshal_VOID__POINTER_POINTER_POINTER, G_TYPE_NONE, 3,
+ G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER);
+}
+
+static void
+byzanz_recorder_init (ByzanzRecorder *recorder)
+{
+ recorder->layers = g_sequence_new (g_object_unref);
+}
+
+ByzanzRecorder *
+byzanz_recorder_new (GdkWindow *window, GdkRectangle *area)
+{
+ g_return_val_if_fail (GDK_IS_WINDOW (window), NULL);
+ g_return_val_if_fail (area != NULL, NULL);
+
+ return g_object_new (BYZANZ_TYPE_RECORDER, "window", window, "area", area, NULL);
+}
+
+void
+byzanz_recorder_set_recording (ByzanzRecorder *recorder, gboolean recording)
+{
+ g_return_if_fail (BYZANZ_IS_RECORDER (recorder));
+
+ if (recorder->recording == recording)
+ return;
+
+ recorder->recording = recording;
+ if (recording)
+ byzanz_recorder_snapshot (recorder);
+}
+
+gboolean
+byzanz_recorder_get_recording (ByzanzRecorder *recorder)
+{
+ g_return_val_if_fail (BYZANZ_IS_RECORDER (recorder), FALSE);
+
+ return recorder->recording;
+}
+
+void
+byzanz_recorder_queue_snapshot (ByzanzRecorder *recorder)
+{
+ g_return_if_fail (BYZANZ_IS_RECORDER (recorder));
+
+ if (recorder->next_image_source == 0) {
+ recorder->next_image_source = gdk_threads_add_idle_full (G_PRIORITY_HIGH_IDLE,
+ byzanz_recorder_next_image, recorder, NULL);
+ }
+}
+
diff --git a/src/byzanzrecorder.h b/src/byzanzrecorder.h
new file mode 100644
index 0000000..d7e6cb7
--- /dev/null
+++ b/src/byzanzrecorder.h
@@ -0,0 +1,76 @@
+/* desktop session recorder
+ * Copyright (C) 2009 Benjamin Otte <otte gnome org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <gdk/gdk.h>
+
+#ifndef __HAVE_BYZANZ_RECORDER_H__
+#define __HAVE_BYZANZ_RECORDER_H__
+
+typedef struct _ByzanzRecorder ByzanzRecorder;
+typedef struct _ByzanzRecorderClass ByzanzRecorderClass;
+
+/* 25 fps */
+#define BYZANZ_RECORDER_FRAME_RATE_MS 1000 / 25
+
+#define BYZANZ_TYPE_RECORDER (byzanz_recorder_get_type())
+#define BYZANZ_IS_RECORDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), BYZANZ_TYPE_RECORDER))
+#define BYZANZ_IS_RECORDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), BYZANZ_TYPE_RECORDER))
+#define BYZANZ_RECORDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BYZANZ_TYPE_RECORDER, ByzanzRecorder))
+#define BYZANZ_RECORDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), BYZANZ_TYPE_RECORDER, ByzanzRecorderClass))
+#define BYZANZ_RECORDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), BYZANZ_TYPE_RECORDER, ByzanzRecorderClass))
+
+struct _ByzanzRecorder {
+ GObject object;
+
+ GdkWindow * window; /* window we are recording from */
+ GdkRectangle area; /* area of window that we record */
+ gboolean recording; /* wether we should be recording now */
+
+ int damage_event_base; /* base event for Damage extension */
+ int damage_error_base; /* base error for Damage extension */
+ int fixes_event_base; /* base event for Fixes extension */
+ int fixes_error_base; /* base error for Fixes extension */
+
+ GSequence * layers; /* sequence of ByzanzLayer, ordered by layer depth */
+
+ guint next_image_source; /* timer that fires when enough time after the last frame has elapsed */
+};
+
+struct _ByzanzRecorderClass {
+ GObjectClass object_class;
+
+ void (* image) (ByzanzRecorder * recorder,
+ cairo_surface_t * surface,
+ const GdkRegion * region,
+ const GTimeVal * tv);
+};
+
+GType byzanz_recorder_get_type (void) G_GNUC_CONST;
+
+ByzanzRecorder * byzanz_recorder_new (GdkWindow * window,
+ GdkRectangle * area);
+
+void byzanz_recorder_set_recording (ByzanzRecorder * recorder,
+ gboolean recording);
+gboolean byzanz_recorder_get_recording (ByzanzRecorder * recorder);
+
+void byzanz_recorder_queue_snapshot (ByzanzRecorder * recorder);
+
+
+#endif /* __HAVE_BYZANZ_RECORDER_H__ */
diff --git a/src/byzanzsession.c b/src/byzanzsession.c
index d4de0f3..46ad6cf 100644
--- a/src/byzanzsession.c
+++ b/src/byzanzsession.c
@@ -36,6 +36,8 @@
#include <gdk/gdkx.h>
#include <X11/extensions/Xdamage.h>
#include <X11/extensions/Xfixes.h>
+
+#include "byzanzrecorder.h"
#include "gifenc.h"
typedef enum {
@@ -60,22 +62,11 @@ typedef struct {
struct _ByzanzSession {
/*< private >*/
/* set by user - accessed ALSO by thread */
- GdkRectangle area; /* area of the screen we record */
gboolean loop; /* wether the resulting gif should loop */
guint frame_duration; /* minimum frame duration in msecs */
/* state */
SessionState state; /* state the session is in */
- guint timeout; /* signal id for timeout */
- GdkWindow * window; /* root window we record */
- Damage damage; /* the Damage object */
- XserverRegion damaged; /* the damaged region */
- XserverRegion tmp_region; /* temporary region to construct the damaged region */
- GHashTable * cursors; /* all the cursors */
- XFixesCursorImage * cursor; /* current cursor */
- gint cursor_x; /* last rendered x position of cursor */
- gint cursor_y; /* last rendered y position of cursor */
- GdkRectangle cursor_area; /* area occupied by cursor */
- GdkRegion * region; /* the region we need to record next time */
+ ByzanzRecorder * recorder; /* the recorder in use */
GThread * encoder; /* encoding thread */
/* accessed ALSO by thread */
gint encoder_running;/* TRUE while the encoder is running */
@@ -87,29 +78,10 @@ struct _ByzanzSession {
guint8 * data_full; /* palettized data of full image to compare additions to */
GdkRectangle relevant_data; /* relevant area to encode */
};
-#define IS_RECORDING_CURSOR(rec) ((rec)->cursors != NULL)
-
-/* XDamage needs these */
-static int dmg_event_base = 0;
-static int dmg_error_base = 0;
-/* XFixes needs these */
-static int fixes_event_base = 0;
-static int fixes_error_base = 0;
-
/*** JOB FUNCTIONS ***/
static void
-byzanz_cairo_set_source_window (cairo_t *cr, GdkWindow *window, double x, double y)
-{
- cairo_t *tmp;
-
- tmp = gdk_cairo_create (window);
- cairo_set_source_surface (cr, cairo_get_target (tmp), x, y);
- cairo_destroy (tmp);
-}
-
-static void
session_job_free (SessionJob *job)
{
if (job->image)
@@ -122,8 +94,8 @@ session_job_free (SessionJob *job)
/* UGH: This function takes ownership of region, but only if a job could be created */
static SessionJob *
-session_job_new (ByzanzSession *rec, SessionJobType type,
- const GTimeVal *tv, GdkRegion *region)
+session_job_new (ByzanzSession *rec, SessionJobType type, cairo_surface_t *surface,
+ const GTimeVal *tv, const GdkRegion *region)
{
SessionJob *job;
@@ -132,23 +104,11 @@ session_job_new (ByzanzSession *rec, SessionJobType type,
if (tv)
job->tv = *tv;
job->type = type;
- job->region = region;
- if (region != NULL) {
- cairo_t *cr;
- job->image = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
- rec->area.width, rec->area.height);
- if (type == SESSION_JOB_ENCODE) {
- Display *dpy = gdk_x11_drawable_get_xdisplay (rec->window);
- XDamageSubtract (dpy, rec->damage, rec->damaged, rec->damaged);
- XFixesSubtractRegion (dpy, rec->damaged, rec->damaged, rec->damaged);
- }
- cr = cairo_create (job->image);
- byzanz_cairo_set_source_window (cr, rec->window, -rec->area.x, -rec->area.y);
- gdk_region_offset (region, -rec->area.x, -rec->area.y);
- gdk_cairo_region (cr, region);
- cairo_paint (cr);
- cairo_destroy (cr);
- }
+ if (region)
+ job->region = gdk_region_copy (region);
+ if (surface)
+ job->image = cairo_surface_reference (surface);
+
return job;
}
@@ -159,24 +119,29 @@ byzanz_session_dither_region (ByzanzSession *rec, GdkRegion *region, cairo_surfa
{
GdkRectangle *rects;
GdkRegion *rev;
- int i, line, nrects;
+ int i, line, nrects, xoffset, yoffset;
guint8 transparent;
- guint stride;
- gpointer mem;
+ guint width, stride;
+ guint8 *mem;
GdkRectangle area;
+ double xod, yod;
+ width = gifenc_get_width (rec->gifenc);
transparent = gifenc_palette_get_alpha_index (rec->gifenc->palette);
gdk_region_get_clipbox (region, &rec->relevant_data);
/* dither changed pixels */
gdk_region_get_rectangles (region, &rects, &nrects);
rev = gdk_region_new ();
stride = cairo_image_surface_get_stride (surface);
+ cairo_surface_get_device_offset (surface, &xod, &yod);
+ xoffset = xod;
+ yoffset = yod;
for (i = 0; i < nrects; i++) {
- mem = cairo_image_surface_get_data (surface) + rects[i].x * 4
- + rects[i].y * stride;
+ mem = cairo_image_surface_get_data (surface) + (rects[i].x + xoffset) * 4
+ + (rects[i].y + yoffset) * stride;
if (gifenc_dither_rgb_with_full_image (
- rec->data + rec->area.width * rects[i].y + rects[i].x, rec->area.width,
- rec->data_full + rec->area.width * rects[i].y + rects[i].x, rec->area.width,
+ rec->data + width * rects[i].y + rects[i].x, width,
+ rec->data_full + width * rects[i].y + rects[i].x, width,
rec->gifenc->palette, mem, rects[i].width, rects[i].height, stride, &area)) {
area.x += rects[i].x;
area.y += rects[i].y;
@@ -195,7 +160,7 @@ byzanz_session_dither_region (ByzanzSession *rec, GdkRegion *region, cairo_surfa
gdk_region_get_rectangles (rev, &rects, &nrects);
for (i = 0; i < nrects; i++) {
for (line = 0; line < rects[i].height; line++) {
- memset (rec->data + rects[i].x + rec->area.width * (rects[i].y + line),
+ memset (rec->data + rects[i].x + width * (rects[i].y + line),
transparent, rects[i].width);
}
}
@@ -208,8 +173,11 @@ static void
byzanz_session_add_image (ByzanzSession *rec, const GTimeVal *tv)
{
glong msecs;
+ guint width;
+
+ width = gifenc_get_width (rec->gifenc);
if (rec->data == NULL) {
- guint count = rec->area.width * rec->area.height;
+ guint count = width * gifenc_get_height (rec->gifenc);
rec->data = g_malloc (count);
rec->data_full = g_malloc (count);
memset (rec->data_full,
@@ -226,8 +194,8 @@ byzanz_session_add_image (ByzanzSession *rec, const GTimeVal *tv)
if (rec->relevant_data.width > 0 && rec->relevant_data.height > 0) {
gifenc_add_image (rec->gifenc, rec->relevant_data.x, rec->relevant_data.y,
rec->relevant_data.width, rec->relevant_data.height, msecs,
- rec->data + rec->area.width * rec->relevant_data.y + rec->relevant_data.x,
- rec->area.width, NULL);
+ rec->data + width * rec->relevant_data.y + rec->relevant_data.x,
+ width, NULL);
rec->current = *tv;
}
}
@@ -238,7 +206,8 @@ byzanz_session_quantize (ByzanzSession *rec, cairo_surface_t *image)
GifencPalette *palette;
palette = gifenc_quantize_image (cairo_image_surface_get_data (image),
- rec->area.width, rec->area.height, cairo_image_surface_get_stride (image), TRUE, 255);
+ cairo_image_surface_get_width (image), cairo_image_surface_get_height (image),
+ cairo_image_surface_get_stride (image), TRUE, 255);
gifenc_initialize (rec->gifenc, palette, rec->loop, NULL);
}
@@ -298,160 +267,6 @@ byzanz_session_run_encoder (gpointer data)
/*** MAIN FUNCTIONS ***/
static void
-render_cursor_to_image (cairo_surface_t *image, XFixesCursorImage *cursor, gint x, gint y)
-{
- cairo_surface_t *cursor_surface;
- cairo_t *cr;
-
- cursor_surface = cairo_image_surface_create_for_data ((guchar *) cursor->pixels,
- CAIRO_FORMAT_ARGB32, cursor->width, cursor->height, cursor->width * 4);
- cr = cairo_create (image);
-
- cairo_translate (cr, x, y);
- cairo_set_source_surface (cr, cursor_surface, -(double) cursor->xhot, -(double) cursor->yhot);
- cairo_paint (cr);
-
- cairo_destroy (cr);
- cairo_surface_destroy (cursor_surface);
-}
-
-static guint
-cursor_hash (gconstpointer key)
-{
- return (guint) ((const XFixesCursorImage *) key)->cursor_serial;
-}
-
-static gboolean
-cursor_equal (gconstpointer c1, gconstpointer c2)
-{
- return ((const XFixesCursorImage *) c1)->cursor_serial ==
- ((const XFixesCursorImage *) c2)->cursor_serial;
-}
-
-static gboolean byzanz_session_timeout_cb (gpointer session);
-static void
-byzanz_session_queue_image (ByzanzSession *rec)
-{
- SessionJob *job;
- GTimeVal tv;
- gboolean render_cursor = FALSE;
-
- g_get_current_time (&tv);
- if (IS_RECORDING_CURSOR (rec)) {
- GdkRectangle cursor_rect;
- gdk_region_union_with_rect (rec->region, &rec->cursor_area);
- cursor_rect.x = rec->cursor_x - rec->cursor->xhot;
- cursor_rect.y = rec->cursor_y - rec->cursor->yhot;
- cursor_rect.width = rec->cursor->width;
- cursor_rect.height = rec->cursor->height;
- render_cursor = gdk_rectangle_intersect (&cursor_rect, &rec->area, &rec->cursor_area);
- gdk_region_union_with_rect (rec->region, &rec->cursor_area);
- } else {
- g_assert (!gdk_region_empty (rec->region));
- }
-
- if (!gdk_region_empty (rec->region)) {
- job = session_job_new (rec, SESSION_JOB_ENCODE, &tv, rec->region);
- if (job) {
- if (render_cursor)
- render_cursor_to_image (job->image, rec->cursor,
- rec->cursor_x - rec->area.x, rec->cursor_y - rec->area.y);
- g_async_queue_push (rec->jobs, job);
- //g_print ("pushing ENCODE\n");
- rec->region = gdk_region_new ();
- }
- }
-
- if (rec->timeout == 0) {
- rec->timeout = g_timeout_add (rec->frame_duration,
- byzanz_session_timeout_cb, rec);
- }
-}
-
-static gboolean
-byzanz_session_timeout_cb (gpointer session)
-{
- ByzanzSession *rec = session;
-
- if (IS_RECORDING_CURSOR (rec)) {
- gint x, y;
- gdk_window_get_pointer (rec->window, &x, &y, NULL);
- if (x == rec->cursor_x && y == rec->cursor_y && gdk_region_empty (rec->region))
- return TRUE;
- rec->cursor_x = x;
- rec->cursor_y = y;
- } else {
- if (gdk_region_empty (rec->region)) {
- rec->timeout = 0;
- return FALSE;
- }
- }
- byzanz_session_queue_image (rec);
- return TRUE;
-}
-
-static gboolean
-byzanz_session_idle_cb (gpointer session)
-{
- ByzanzSession *rec = session;
-
- g_assert (!gdk_region_empty (rec->region));
-
- rec->timeout = 0;
- byzanz_session_queue_image (rec);
- return FALSE;
-}
-
-static GdkFilterReturn
-byzanz_session_filter_events (GdkXEvent *xevent, GdkEvent *event, gpointer data)
-{
- ByzanzSession *rec = data;
- XDamageNotifyEvent *dev = (XDamageNotifyEvent *) xevent;
- Display *dpy;
-
- if (event->any.window != rec->window)
- return GDK_FILTER_CONTINUE;
-
- dev = (XDamageNotifyEvent *) xevent;
- dpy = gdk_x11_display_get_xdisplay (gdk_display_get_default ());
-
- if (dev->type == dmg_event_base + XDamageNotify &&
- dev->damage == rec->damage) {
- GdkRectangle rect;
-
- rect.x = dev->area.x;
- rect.y = dev->area.y;
- rect.width = dev->area.width;
- rect.height = dev->area.height;
- XFixesSetRegion (dpy, rec->tmp_region, &dev->area, 1);
- XFixesUnionRegion (dpy, rec->damaged, rec->damaged, rec->tmp_region);
- //XDamageSubtract (dpy, rec->damage, rec->damaged, None);
- //g_print ("-> %d %d %d %d\n", rect.x, rect.y, rect.width, rect.height);
- if (gdk_rectangle_intersect (&rect, &rec->area, &rect)) {
- gdk_region_union_with_rect (rec->region, &rect);
- if (rec->timeout == 0)
- rec->timeout = g_idle_add_full (G_PRIORITY_DEFAULT,
- byzanz_session_idle_cb, rec, NULL);
- }
- return GDK_FILTER_REMOVE;
- } else if (dev->type == fixes_event_base + XFixesCursorNotify) {
- XFixesCursorNotifyEvent *cevent = xevent;
- XFixesCursorImage hack;
-
- g_assert (IS_RECORDING_CURSOR (rec));
- hack.cursor_serial = cevent->cursor_serial;
- rec->cursor = g_hash_table_lookup (rec->cursors, &hack);
- if (rec->cursor == NULL) {
- rec->cursor = XFixesGetCursorImage (dpy);
- if (rec->cursor)
- g_hash_table_insert (rec->cursors, rec->cursor, rec->cursor);
- }
- return GDK_FILTER_REMOVE;
- }
- return GDK_FILTER_CONTINUE;
-}
-
-static void
byzanz_session_state_advance (ByzanzSession *session)
{
switch (session->state) {
@@ -502,6 +317,17 @@ byzanz_session_new (const gchar *filename, GdkWindow *window, GdkRectangle *area
return byzanz_session_new_fd (fd, window, area, loop, record_cursor);
}
+static void
+byzanz_session_recorder_image_cb (ByzanzRecorder * recorder,
+ cairo_surface_t * surface,
+ const GdkRegion * region,
+ const GTimeVal * tv,
+ ByzanzSession * session)
+{
+ SessionJob *job = session_job_new (session, SESSION_JOB_ENCODE, surface, tv, region);
+ g_async_queue_push (session->jobs, job);
+}
+
static gboolean
session_gifenc_write (gpointer closure, const guchar *data, gsize len, GError **error)
{
@@ -539,7 +365,6 @@ byzanz_session_new_fd (gint fd, GdkWindow *window, GdkRectangle *area,
gboolean loop, gboolean record_cursor)
{
ByzanzSession *session;
- Display *dpy;
GdkRectangle root_rect;
g_return_val_if_fail (area->x >= 0, NULL);
@@ -547,32 +372,18 @@ byzanz_session_new_fd (gint fd, GdkWindow *window, GdkRectangle *area,
g_return_val_if_fail (area->width > 0, NULL);
g_return_val_if_fail (area->height > 0, NULL);
- dpy = gdk_x11_display_get_xdisplay (gdk_display_get_default ());
- if (dmg_event_base == 0) {
- if (!XDamageQueryExtension (dpy, &dmg_event_base, &dmg_error_base) ||
- !XFixesQueryExtension (dpy, &fixes_event_base, &fixes_error_base))
- return NULL;
- gdk_x11_register_standard_event_type (gdk_display_get_default (),
- dmg_event_base + XDamageNotify, 1);
- gdk_x11_register_standard_event_type (gdk_display_get_default (),
- fixes_event_base + XFixesCursorNotify, 1);
- }
-
session = g_new0 (ByzanzSession, 1);
/* set user properties */
- session->area = *area;
session->loop = loop;
session->frame_duration = 1000 / 25;
/* prepare thread first, so we can easily error out on failure */
- session->window = window;
- g_object_ref (window);
root_rect.x = root_rect.y = 0;
- gdk_drawable_get_size (session->window,
+ gdk_drawable_get_size (window,
&root_rect.width, &root_rect.height);
- gdk_rectangle_intersect (&session->area, &root_rect, &session->area);
- session->gifenc = gifenc_new (session->area.width, session->area.height,
+ gdk_rectangle_intersect (area, &root_rect, &root_rect);
+ session->gifenc = gifenc_new (root_rect.width, root_rect.height,
session_gifenc_write, GINT_TO_POINTER (fd), session_gifenc_close);
if (!session->gifenc) {
g_free (session);
@@ -589,12 +400,9 @@ byzanz_session_new_fd (gint fd, GdkWindow *window, GdkRectangle *area,
return NULL;
}
- /* do setup work */
- session->damaged = XFixesCreateRegion (dpy, 0, 0);
- session->tmp_region = XFixesCreateRegion (dpy, 0, 0);
- if (record_cursor)
- session->cursors = g_hash_table_new_full (cursor_hash, cursor_equal,
- NULL, (GDestroyNotify) XFree);
+ session->recorder = byzanz_recorder_new (window, &root_rect);
+ g_signal_connect (session->recorder, "image",
+ G_CALLBACK (byzanz_session_recorder_image_cb), session);
session->state = SESSION_STATE_CREATED;
return session;
@@ -603,26 +411,10 @@ byzanz_session_new_fd (gint fd, GdkWindow *window, GdkRectangle *area,
void
byzanz_session_start (ByzanzSession *rec)
{
- Display *dpy;
-
g_return_if_fail (BYZANZ_IS_SESSION (rec));
g_return_if_fail (rec->state == SESSION_STATE_CREATED);
- dpy = gdk_x11_display_get_xdisplay (gdk_display_get_default ());
- rec->region = gdk_region_rectangle (&rec->area);
- gdk_window_add_filter (rec->window,
- byzanz_session_filter_events, rec);
- rec->damage = XDamageCreate (dpy, GDK_DRAWABLE_XID (rec->window),
- XDamageReportDeltaRectangles);
- if (rec->cursors) {
- XFixesSelectCursorInput (dpy, GDK_DRAWABLE_XID (rec->window),
- XFixesDisplayCursorNotifyMask);
- rec->cursor = XFixesGetCursorImage (dpy);
- if (rec->cursor)
- g_hash_table_insert (rec->cursors, rec->cursor, rec->cursor);
- gdk_window_get_pointer (rec->window, &rec->cursor_x, &rec->cursor_y, NULL);
- }
- /* byzanz_session_queue_image (rec); - we'll get a damage event anyway */
+ byzanz_recorder_set_recording (rec->recorder, TRUE);
rec->state = SESSION_STATE_RECORDING;
}
@@ -632,37 +424,23 @@ byzanz_session_stop (ByzanzSession *rec)
{
GTimeVal tv;
SessionJob *job;
- Display *dpy;
g_return_if_fail (BYZANZ_IS_SESSION (rec));
g_return_if_fail (rec->state == SESSION_STATE_RECORDING);
/* byzanz_session_queue_image (rec); - useless because last image would have a 0 time */
g_get_current_time (&tv);
- job = session_job_new (rec, SESSION_JOB_QUIT, &tv, NULL);
+ job = session_job_new (rec, SESSION_JOB_QUIT, NULL, &tv, NULL);
g_async_queue_push (rec->jobs, job);
- //g_print ("pushing QUIT\n");
- gdk_window_remove_filter (rec->window,
- byzanz_session_filter_events, rec);
- if (rec->timeout != 0) {
- if (!g_source_remove (rec->timeout))
- g_assert_not_reached ();
- rec->timeout = 0;
- }
- dpy = gdk_x11_display_get_xdisplay (gdk_display_get_default ());
- XDamageDestroy (dpy, rec->damage);
- if (IS_RECORDING_CURSOR (rec))
- XFixesSelectCursorInput (dpy, GDK_DRAWABLE_XID (rec->window),
- 0);
+ byzanz_recorder_set_recording (rec->recorder, FALSE);
+
rec->state = SESSION_STATE_STOPPED;
}
void
byzanz_session_destroy (ByzanzSession *rec)
{
- Display *dpy;
-
g_return_if_fail (BYZANZ_IS_SESSION (rec));
while (rec->state != SESSION_STATE_ERROR &&
@@ -672,15 +450,8 @@ byzanz_session_destroy (ByzanzSession *rec)
if (g_thread_join (rec->encoder) != rec)
g_assert_not_reached ();
- dpy = gdk_x11_display_get_xdisplay (gdk_display_get_default ());
- XFixesDestroyRegion (dpy, rec->damaged);
- XFixesDestroyRegion (dpy, rec->tmp_region);
- gdk_region_destroy (rec->region);
- if (IS_RECORDING_CURSOR (rec))
- g_hash_table_destroy (rec->cursors);
-
gifenc_free (rec->gifenc);
- g_object_unref (rec->window);
+ g_object_unref (rec->recorder);
g_assert (g_async_queue_length (rec->jobs) == 0);
g_async_queue_unref (rec->jobs);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]