summaryrefslogtreecommitdiff
path: root/savers
diff options
context:
space:
mode:
Diffstat (limited to 'savers')
-rw-r--r--savers/Makefile.am142
-rw-r--r--savers/cosmos-slideshow.desktop.in.in10
-rw-r--r--savers/floaters.c1269
-rw-r--r--savers/floaters.c.orig1256
-rw-r--r--savers/footlogo-floaters.desktop.in.in10
-rw-r--r--savers/gs-theme-engine-marshal.list1
-rw-r--r--savers/gs-theme-engine.c226
-rw-r--r--savers/gs-theme-engine.c.orig216
-rw-r--r--savers/gs-theme-engine.h88
-rw-r--r--savers/gs-theme-engine.h.orig88
-rw-r--r--savers/gs-theme-window.c214
-rw-r--r--savers/gs-theme-window.c.orig208
-rw-r--r--savers/gs-theme-window.h72
-rw-r--r--savers/gs-theme-window.h.orig72
-rw-r--r--savers/gste-popsquares.c622
-rw-r--r--savers/gste-popsquares.c.orig563
-rw-r--r--savers/gste-popsquares.h57
-rw-r--r--savers/gste-popsquares.h.orig57
-rw-r--r--savers/gste-slideshow.c1082
-rw-r--r--savers/gste-slideshow.c.orig1001
-rw-r--r--savers/gste-slideshow.h69
-rw-r--r--savers/gste-slideshow.h.orig69
-rw-r--r--savers/personal-slideshow.desktop.in.in10
-rw-r--r--savers/popsquares.c73
-rw-r--r--savers/popsquares.c.orig72
-rw-r--r--savers/popsquares.desktop.in.in10
-rw-r--r--savers/slideshow.c143
-rw-r--r--savers/slideshow.c.orig127
-rw-r--r--savers/xdg-user-dir-lookup.c168
-rw-r--r--savers/xdg-user-dir-lookup.c.orig168
-rw-r--r--savers/xdg-user-dir-lookup.h28
31 files changed, 8191 insertions, 0 deletions
diff --git a/savers/Makefile.am b/savers/Makefile.am
new file mode 100644
index 0000000..298177e
--- /dev/null
+++ b/savers/Makefile.am
@@ -0,0 +1,142 @@
+## We require new-style dependency handling.
+AUTOMAKE_OPTIONS = 1.7
+
+NULL =
+
+INCLUDES = \
+ -I. \
+ -I$(srcdir) \
+ $(MATE_SCREENSAVER_SAVER_CFLAGS) \
+ $(DISABLE_DEPRECATED_CFLAGS) \
+ -DLIBEXECDIR=\""$(libexecdir)"\" \
+ -DMATELOCALEDIR=\""$(datadir)/locale"\" \
+ -DDATADIR=\""$(datadir)"\" \
+ $(WARN_CFLAGS) \
+ $(NULL)
+
+DESKTOP_IN_IN_FILES = \
+ cosmos-slideshow.desktop.in.in \
+ footlogo-floaters.desktop.in.in \
+ personal-slideshow.desktop.in.in \
+ popsquares.desktop.in.in \
+ $(NULL)
+
+cosmos-slideshow.desktop.in: cosmos-slideshow.desktop.in.in Makefile
+ $(edit) $< >$@
+footlogo-floaters.desktop.in: footlogo-floaters.desktop.in.in Makefile
+ $(edit) $< >$@
+personal-slideshow.desktop.in: personal-slideshow.desktop.in.in Makefile
+ $(edit) $< >$@
+popsquares.desktop.in: popsquares.desktop.in.in Makefile
+ $(edit) $< >$@
+
+@INTLTOOL_DESKTOP_RULE@
+
+edit = sed \
+ -e 's|@SLIDESHOW_COSMOS_DIR[@]|$(SLIDESHOW_COSMOS_DIR)|g' \
+ -e 's|@FLOATERS_FOOT_LOGO_IMAGE[@]|$(FLOATERS_FOOT_LOGO_IMAGE)|g' \
+ -e 's|@SAVERDIR[@]|$(privlibexecdir)|g'
+
+themesdir = $(datadir)/applications/screensavers
+themes_in_files = \
+ cosmos-slideshow.desktop.in \
+ footlogo-floaters.desktop.in \
+ personal-slideshow.desktop.in \
+ popsquares.desktop.in \
+ $(NULL)
+themes_DATA = $(themes_in_files:.desktop.in=.desktop)
+
+BUILT_SOURCES = \
+ gs-theme-engine-marshal.c \
+ gs-theme-engine-marshal.h \
+ $(NULL)
+
+gs-theme-engine-marshal.c: gs-theme-engine-marshal.list
+ echo "#include \"gs-theme-engine-marshal.h\"" > $@ && \
+ @GLIB_GENMARSHAL@ $< --prefix=gs_theme_engine_marshal --body >> $@
+
+gs-theme-engine-marshal.h: gs-theme-engine-marshal.list
+ @GLIB_GENMARSHAL@ $< --prefix=gs_theme_engine_marshal --header > $@
+
+noinst_LIBRARIES = \
+ libgs-theme-engine.a
+
+libgs_theme_engine_a_CPPFLAGS = \
+ -DDATADIR=\""$(datadir)"\" \
+ $(NULL)
+
+libgs_theme_engine_a_SOURCES = \
+ $(BUILT_SOURCES) \
+ gs-theme-window.h \
+ gs-theme-window.c \
+ gs-theme-engine.c \
+ gs-theme-engine.h \
+ $(NULL)
+
+saverdir = $(libexecdir)/mate-screensaver
+saver_PROGRAMS = \
+ floaters \
+ popsquares \
+ slideshow \
+ $(NULL)
+
+floaters_SOURCES = \
+ floaters.c \
+ $(NULL)
+
+floaters_LDADD = \
+ libgs-theme-engine.a \
+ $(MATE_SCREENSAVER_SAVER_LIBS) \
+ -lm \
+ $(NULL)
+
+popsquares_SOURCES = \
+ gste-popsquares.c \
+ gste-popsquares.h \
+ popsquares.c \
+ $(NULL)
+
+popsquares_LDADD = \
+ libgs-theme-engine.a \
+ $(MATE_SCREENSAVER_SAVER_LIBS) \
+ $(NULL)
+
+slideshow_SOURCES = \
+ gste-slideshow.c \
+ gste-slideshow.h \
+ xdg-user-dir-lookup.c \
+ xdg-user-dir-lookup.h \
+ slideshow.c \
+ $(NULL)
+
+slideshow_LDADD = \
+ libgs-theme-engine.a \
+ $(MATE_SCREENSAVER_SAVER_LIBS) \
+ $(NULL)
+
+EXTRA_DIST = \
+ gs-theme-engine-marshal.list \
+ $(DESKTOP_IN_IN_FILES) \
+ $(themes_in_files) \
+ $(NULL)
+
+CLEANFILES = \
+ $(BUILT_SOURCES) \
+ cosmos-slideshow.desktop.in \
+ footlogo-floaters.desktop.in \
+ popsquares.desktop.in \
+ personal-slideshow.desktop.in \
+ $(NULL)
+
+DISTCLEANFILES = \
+ $(themes_DATA) \
+ $(NULL)
+
+MAINTAINERCLEANFILES = \
+ *~ \
+ Makefile.in \
+ cosmos-slideshow.desktop \
+ footlogo-floaters.desktop \
+ personal-slideshow.desktop \
+ popsquares.desktop \
+ $(NULL)
diff --git a/savers/cosmos-slideshow.desktop.in.in b/savers/cosmos-slideshow.desktop.in.in
new file mode 100644
index 0000000..cc51661
--- /dev/null
+++ b/savers/cosmos-slideshow.desktop.in.in
@@ -0,0 +1,10 @@
+[Desktop Entry]
+_Name=Cosmos
+_Comment=Display a slideshow of pictures of the cosmos
+Exec=@SAVERDIR@/slideshow --location=@SLIDESHOW_COSMOS_DIR@
+TryExec=@SAVERDIR@/slideshow
+StartupNotify=false
+Terminal=false
+Type=Application
+Categories=MATE;Screensaver;
+OnlyShowIn=MATE;
diff --git a/savers/floaters.c b/savers/floaters.c
new file mode 100644
index 0000000..8052e95
--- /dev/null
+++ b/savers/floaters.c
@@ -0,0 +1,1269 @@
+/*
+ * Copyright (C) 2005 Ray Strode <[email protected]>,
+ * Matthias Clasen <[email protected]>,
+ * Søren Sandmann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Originally written by: Ray Strode <[email protected]>
+ *
+ * Later contributions by: Matthias Clasen <[email protected]>
+ * Søren Sandmann <[email protected]>
+ */
+
+#include "config.h"
+
+#include <math.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <time.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+
+#include <gtk/gtk.h>
+
+#include "gs-theme-window.h"
+
+#ifndef trunc
+#define trunc(x) (((x) > 0.0) ? floor((x)) : -floor(-(x)))
+#endif
+
+#ifndef OPTIMAL_FRAME_RATE
+#define OPTIMAL_FRAME_RATE (25.0)
+#endif
+
+#ifndef STAT_PRINT_FREQUENCY
+#define STAT_PRINT_FREQUENCY (2000)
+#endif
+
+#ifndef FLOATER_MAX_SIZE
+#define FLOATER_MAX_SIZE (128.0)
+#endif
+
+#ifndef FLOATER_MIN_SIZE
+#define FLOATER_MIN_SIZE (16.0)
+#endif
+#ifndef FLOATER_DEFAULT_COUNT
+#define FLOATER_DEFAULT_COUNT (5)
+#endif
+
+#ifndef SMALL_ANGLE
+#define SMALL_ANGLE (0.025 * G_PI)
+#endif
+
+#ifndef BIG_ANGLE
+#define BIG_ANGLE (0.125 * G_PI)
+#endif
+
+#ifndef GAMMA
+#define GAMMA 2.2
+#endif
+
+static gboolean should_show_paths = FALSE;
+static gboolean should_do_rotations = FALSE;
+static gboolean should_print_stats = FALSE;
+static gint max_floater_count = FLOATER_DEFAULT_COUNT;
+static gchar *geometry = NULL;
+static gchar **filenames = NULL;
+
+static GOptionEntry options[] =
+{
+ {
+ "show-paths", 'p', 0, G_OPTION_ARG_NONE, &should_show_paths,
+ N_("Show paths that images follow"), NULL
+ },
+
+ {
+ "do-rotations", 'r', 0, G_OPTION_ARG_NONE, &should_do_rotations,
+ N_("Occasionally rotate images as they move"), NULL
+ },
+
+ {
+ "print-stats", 's', 0, G_OPTION_ARG_NONE, &should_print_stats,
+ N_("Print out frame rate and other statistics"), NULL
+ },
+
+ {
+ "number-of-images", 'n', 0, G_OPTION_ARG_INT, &max_floater_count,
+ N_("The maximum number of images to keep on screen"), N_("MAX_IMAGES")
+ },
+
+ {
+ "geometry", 0, 0, G_OPTION_ARG_STRING, &geometry,
+ N_("The initial size and position of window"), N_("WIDTHxHEIGHT+X+Y")
+ },
+
+ {
+ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames,
+ N_("The source image to use"), NULL
+ },
+
+ {NULL}
+};
+
+
+typedef struct _Point Point;
+typedef struct _Path Path;
+typedef struct _Rectangle Rectangle;
+typedef struct _ScreenSaverFloater ScreenSaverFloater;
+typedef struct _CachedSource CachedSource;
+typedef struct _ScreenSaver ScreenSaver;
+
+struct _Point
+{
+ gdouble x, y;
+};
+
+struct _Path
+{
+ Point start_point;
+ Point start_control_point;
+ Point end_control_point;
+ Point end_point;
+
+ gdouble x_linear_coefficient,
+ y_linear_coefficient;
+
+ gdouble x_quadratic_coefficient,
+ y_quadratic_coefficient;
+
+ gdouble x_cubic_coefficient,
+ y_cubic_coefficient;
+
+ gdouble duration;
+};
+
+struct _CachedSource
+{
+ cairo_pattern_t *pattern;
+ gint width, height;
+};
+
+struct _Rectangle
+{
+ Point top_left_point;
+ Point bottom_right_point;
+};
+
+struct _ScreenSaverFloater
+{
+ GdkRectangle bounds;
+
+ Point start_position;
+ Point position;
+
+ gdouble scale;
+ gdouble opacity;
+
+ Path *path;
+ gdouble path_start_time;
+ gdouble path_start_scale;
+ gdouble path_end_scale;
+
+ gdouble angle;
+ gdouble angle_increment;
+};
+
+struct _ScreenSaver
+{
+ GtkWidget *drawing_area;
+ Rectangle canvas_rectangle;
+ GHashTable *cached_sources;
+
+ char *filename;
+
+ gdouble first_update_time;
+
+ gdouble last_calculated_stats_time,
+ current_calculated_stats_time;
+ gint update_count, frame_count;
+
+ gdouble updates_per_second;
+ gdouble frames_per_second;
+
+ guint state_update_timeout_id;
+ guint stats_update_timeout_id;
+
+ GList *floaters;
+ gint max_floater_count;
+
+ guint should_do_rotations: 1;
+ guint should_show_paths : 1;
+ guint draw_ops_pending : 1;
+};
+
+static Path *path_new (Point *start_point,
+ Point *start_control_point,
+ Point *end_control_point,
+ Point *end_point,
+ gdouble duration);
+static void path_free (Path *path);
+
+static ScreenSaverFloater *screen_saver_floater_new (ScreenSaver *screen_saver,
+ Point *position,
+ gdouble scale);
+static void screen_saver_floater_free (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater);
+static gboolean screen_saver_floater_is_off_canvas (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater);
+static gboolean screen_saver_floater_should_bubble_up (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble performance_ratio,
+ gdouble *duration);
+static gboolean screen_saver_floater_should_come_on_screen (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble performance_ratio,
+ gdouble *duration);
+static Point screen_saver_floater_get_position_from_time (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble time);
+static gdouble screen_saver_floater_get_scale_from_time (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble time);
+static gdouble screen_saver_floater_get_angle_from_time (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble time);
+
+static Path *screen_saver_floater_create_path_to_on_screen (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble duration);
+static Path *screen_saver_floater_create_path_to_bubble_up (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble duration);
+static Path *screen_saver_floater_create_path_to_random_point (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble duration);
+static Path *screen_saver_floater_create_path (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater);
+static void screen_saver_floater_update_state (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble time);
+
+static gboolean screen_saver_floater_do_draw (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ cairo_t *context);
+
+static CachedSource *cached_source_new (cairo_pattern_t *pattern,
+ gint width,
+ gint height);
+static void cached_source_free (CachedSource *source);
+
+static ScreenSaver *screen_saver_new (GtkDrawingArea *drawing_area,
+ const gchar *filename,
+ gint max_floater_count,
+ gboolean should_do_rotations,
+ gboolean should_show_paths);
+static void screen_saver_free (ScreenSaver *screen_saver);
+static gdouble screen_saver_get_timestamp (ScreenSaver *screen_saver);
+static void screen_saver_get_initial_state (ScreenSaver *screen_saver);
+static void screen_saver_update_state (ScreenSaver *screen_saver,
+ gdouble time);
+static gboolean screen_saver_do_update_state (ScreenSaver *screen_saver);
+static gboolean screen_saver_do_update_stats (ScreenSaver *screen_saver);
+static gdouble screen_saver_get_updates_per_second (ScreenSaver *screen_saver);
+static gdouble screen_saver_get_frames_per_second (ScreenSaver *screen_saver);
+static gdouble screen_saver_get_image_cache_usage (ScreenSaver *screen_saver);
+static void screen_saver_create_floaters (ScreenSaver *screen_saver);
+static void screen_saver_destroy_floaters (ScreenSaver *screen_saver);
+static void screen_saver_on_size_allocate (ScreenSaver *screen_saver,
+ GtkAllocation *allocation);
+static void screen_saver_on_expose_event (ScreenSaver *screen_saver,
+ GdkEventExpose *event);
+static gboolean do_print_screen_saver_stats (ScreenSaver *screen_saver);
+static GdkPixbuf *gamma_correct (const GdkPixbuf *input_pixbuf);
+
+static CachedSource*
+cached_source_new (cairo_pattern_t *pattern,
+ gint width,
+ gint height)
+{
+ CachedSource *source;
+
+ source = g_new (CachedSource, 1);
+ source->pattern = cairo_pattern_reference (pattern);
+ source->width = width;
+ source->height = height;
+
+ return source;
+}
+
+static void
+cached_source_free (CachedSource *source)
+{
+ if (source == NULL)
+ return;
+
+ cairo_pattern_destroy (source->pattern);
+
+ g_free (source);
+}
+
+static Path *
+path_new (Point *start_point,
+ Point *start_control_point,
+ Point *end_control_point,
+ Point *end_point,
+ gdouble duration)
+{
+ Path *path;
+
+ path = g_new (Path, 1);
+ path->start_point = *start_point;
+ path->start_control_point = *start_control_point;
+ path->end_control_point = *end_control_point;
+ path->end_point = *end_point;
+ path->duration = duration;
+
+ /* we precompute the coefficients to the cubic bezier curve here
+ * so that we don't have to do it repeatedly later The equation is:
+ *
+ * B(t) = A * t^3 + B * t^2 + C * t + start_point
+ */
+ path->x_linear_coefficient = 3 * (start_control_point->x - start_point->x);
+ path->x_quadratic_coefficient = 3 * (end_control_point->x -
+ start_control_point->x) -
+ path->x_linear_coefficient;
+ path->x_cubic_coefficient = end_point->x - start_point->x -
+ path->x_linear_coefficient -
+ path->x_quadratic_coefficient;
+
+ path->y_linear_coefficient = 3 * (start_control_point->y - start_point->y);
+ path->y_quadratic_coefficient = 3 * (end_control_point->y -
+ start_control_point->y) -
+ path->y_linear_coefficient;
+ path->y_cubic_coefficient = end_point->y - start_point->y -
+ path->y_linear_coefficient -
+ path->y_quadratic_coefficient;
+ return path;
+}
+
+static void
+path_free (Path *path)
+{
+ g_free (path);
+}
+
+static ScreenSaverFloater*
+screen_saver_floater_new (ScreenSaver *screen_saver,
+ Point *position,
+ gdouble scale)
+{
+ ScreenSaverFloater *floater;
+
+ floater = g_new (ScreenSaverFloater, 1);
+ floater->bounds.width = 0;
+ floater->start_position = *position;
+ floater->position = *position;
+ floater->scale = scale;
+ floater->opacity = pow (scale, 1.0 / GAMMA);
+ floater->path = NULL;
+ floater->path_start_time = 0.0;
+ floater->path_start_scale = 1.0;
+ floater->path_end_scale = 0.0;
+
+ floater->angle = 0.0;
+ floater->angle_increment = 0.0;
+
+ return floater;
+}
+
+void
+screen_saver_floater_free (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater)
+{
+ if (floater == NULL)
+ return;
+
+ path_free (floater->path);
+
+ g_free (floater);
+}
+
+static gboolean
+screen_saver_floater_is_off_canvas (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater)
+{
+ if ((floater->position.x < screen_saver->canvas_rectangle.top_left_point.x) ||
+ (floater->position.x > screen_saver->canvas_rectangle.bottom_right_point.x) ||
+ (floater->position.y < screen_saver->canvas_rectangle.top_left_point.y) ||
+ (floater->position.y > screen_saver->canvas_rectangle.bottom_right_point.y))
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+screen_saver_floater_should_come_on_screen (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble performance_ratio,
+ gdouble *duration)
+{
+
+ if (!screen_saver_floater_is_off_canvas (screen_saver, floater))
+ return FALSE;
+
+ if ((abs (performance_ratio - .5) >= G_MINDOUBLE) &&
+ (g_random_double () > .5))
+ {
+ if (duration)
+ *duration = g_random_double_range (3.0, 7.0);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+screen_saver_floater_should_bubble_up (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble performance_ratio,
+ gdouble *duration)
+{
+
+ if ((performance_ratio < .5) && (g_random_double () > .5))
+ {
+ if (duration)
+ *duration = performance_ratio * 30.0;
+
+ return TRUE;
+ }
+
+ if ((floater->scale < .3) && (g_random_double () > .6))
+ {
+ if (duration)
+ *duration = 30.0;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static Point
+screen_saver_floater_get_position_from_time (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble time)
+{
+ Point point;
+
+ time = time / floater->path->duration;
+
+ point.x = floater->path->x_cubic_coefficient * (time * time * time) +
+ floater->path->x_quadratic_coefficient * (time * time) +
+ floater->path->x_linear_coefficient * (time) +
+ floater->path->start_point.x;
+ point.y = floater->path->y_cubic_coefficient * (time * time * time) +
+ floater->path->y_quadratic_coefficient * (time * time) +
+ floater->path->y_linear_coefficient * (time) +
+ floater->path->start_point.y;
+
+ return point;
+}
+
+static gdouble
+screen_saver_floater_get_scale_from_time (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble time)
+{
+ gdouble completion_ratio, total_scale_growth, new_scale;
+
+ completion_ratio = time / floater->path->duration;
+ total_scale_growth = (floater->path_end_scale - floater->path_start_scale);
+ new_scale = floater->path_start_scale + total_scale_growth * completion_ratio;
+
+ return CLAMP (new_scale, 0.0, 1.0);
+}
+
+static gdouble
+screen_saver_floater_get_angle_from_time (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble time)
+{
+ gdouble completion_ratio;
+ gdouble total_rotation;
+
+ completion_ratio = time / floater->path->duration;
+ total_rotation = floater->angle_increment * floater->path->duration;
+
+ return floater->angle + total_rotation * completion_ratio;
+}
+
+static Path *
+screen_saver_floater_create_path_to_on_screen (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble duration)
+{
+ Point start_position, end_position, start_control_point, end_control_point;
+ start_position = floater->position;
+
+ end_position.x = g_random_double_range (.25, .75) *
+ (screen_saver->canvas_rectangle.top_left_point.x +
+ screen_saver->canvas_rectangle.bottom_right_point.x);
+ end_position.y = g_random_double_range (.25, .75) *
+ (screen_saver->canvas_rectangle.top_left_point.y +
+ screen_saver->canvas_rectangle.bottom_right_point.y);
+
+ start_control_point.x = start_position.x + .9 * (end_position.x - start_position.x);
+ start_control_point.y = start_position.y + .9 * (end_position.y - start_position.y);
+
+ end_control_point.x = start_position.x + 1.0 * (end_position.x - start_position.x);
+ end_control_point.y = start_position.y + 1.0 * (end_position.y - start_position.y);
+
+ return path_new (&start_position, &start_control_point, &end_control_point,
+ &end_position, duration);
+}
+
+static Path *
+screen_saver_floater_create_path_to_bubble_up (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble duration)
+{
+ Point start_position, end_position, start_control_point, end_control_point;
+
+ start_position = floater->position;
+ end_position.x = start_position.x;
+ end_position.y = screen_saver->canvas_rectangle.top_left_point.y - FLOATER_MAX_SIZE;
+ start_control_point.x = .5 * start_position.x;
+ start_control_point.y = .5 * start_position.y;
+ end_control_point.x = 1.5 * end_position.x;
+ end_control_point.y = .5 * end_position.y;
+
+ return path_new (&start_position, &start_control_point, &end_control_point,
+ &end_position, duration);
+}
+
+static Path *
+screen_saver_floater_create_path_to_random_point (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble duration)
+{
+ Point start_position, end_position, start_control_point, end_control_point;
+
+ start_position = floater->position;
+
+ end_position.x = start_position.x +
+ (g_random_double_range (-.5, .5) * 4 * FLOATER_MAX_SIZE);
+ end_position.y = start_position.y +
+ (g_random_double_range (-.5, .5) * 4 * FLOATER_MAX_SIZE);
+
+ start_control_point.x = start_position.x + .95 * (end_position.x - start_position.x);
+ start_control_point.y = start_position.y + .95 * (end_position.y - start_position.y);
+
+ end_control_point.x = start_position.x + 1.0 * (end_position.x - start_position.x);
+ end_control_point.y = start_position.y + 1.0 * (end_position.y - start_position.y);
+
+ return path_new (&start_position, &start_control_point, &end_control_point,
+ &end_position, duration);
+}
+
+static Path *
+screen_saver_floater_create_path (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater)
+{
+ gdouble performance_ratio;
+ gdouble duration;
+
+ performance_ratio =
+ screen_saver_get_frames_per_second (screen_saver) / OPTIMAL_FRAME_RATE;
+
+ if (abs (performance_ratio) <= G_MINDOUBLE)
+ performance_ratio = 1.0;
+
+ if (screen_saver_floater_should_bubble_up (screen_saver, floater, performance_ratio, &duration))
+ return screen_saver_floater_create_path_to_bubble_up (screen_saver, floater, duration);
+
+ if (screen_saver_floater_should_come_on_screen (screen_saver, floater, performance_ratio, &duration))
+ return screen_saver_floater_create_path_to_on_screen (screen_saver, floater, duration);
+
+ return screen_saver_floater_create_path_to_random_point (screen_saver, floater,
+ g_random_double_range (3.0, 7.0));
+}
+
+static void
+screen_saver_floater_update_state (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble time)
+{
+ gdouble performance_ratio;
+
+ performance_ratio =
+ screen_saver_get_frames_per_second (screen_saver) / OPTIMAL_FRAME_RATE;
+
+ if (floater->path == NULL)
+ {
+ floater->path = screen_saver_floater_create_path (screen_saver, floater);
+ floater->path_start_time = time;
+
+ floater->path_start_scale = floater->scale;
+
+ if (g_random_double () > .5)
+ floater->path_end_scale = g_random_double_range (0.10, performance_ratio);
+
+ /* poor man's distribution */
+ if (screen_saver->should_do_rotations &&
+ (g_random_double () < .75 * performance_ratio))
+ {
+ gint r;
+
+ r = g_random_int_range (0, 100);
+ if (r < 80)
+ floater->angle_increment = 0.0;
+ else if (r < 95)
+ floater->angle_increment = g_random_double_range (-SMALL_ANGLE, SMALL_ANGLE);
+ else
+ floater->angle_increment = g_random_double_range (-BIG_ANGLE, BIG_ANGLE);
+ }
+ }
+
+ if (time < (floater->path_start_time + floater->path->duration))
+ {
+ gdouble path_time;
+
+ path_time = time - floater->path_start_time;
+
+ floater->position =
+ screen_saver_floater_get_position_from_time (screen_saver, floater,
+ path_time);
+ floater->scale =
+ screen_saver_floater_get_scale_from_time (screen_saver, floater, path_time);
+
+ floater->angle =
+ screen_saver_floater_get_angle_from_time (screen_saver, floater, path_time);
+
+ floater->opacity = pow (floater->scale, 1.0 / GAMMA);
+ }
+ else
+ {
+ path_free (floater->path);
+
+ floater->path = NULL;
+ floater->path_start_time = 0.0;
+ }
+}
+
+static GdkPixbuf *
+gamma_correct (const GdkPixbuf *input_pixbuf)
+{
+ gint x, y, width, height, rowstride;
+ GdkPixbuf *output_pixbuf;
+ guchar *pixels;
+
+ output_pixbuf = gdk_pixbuf_copy (input_pixbuf);
+ pixels = gdk_pixbuf_get_pixels (output_pixbuf);
+
+ width = gdk_pixbuf_get_width (output_pixbuf);
+ height = gdk_pixbuf_get_height (output_pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (output_pixbuf);
+
+ for (y = 0; y < height; y++)
+ for (x = 0; x < width; x++)
+ {
+ guchar *alpha_channel;
+ guchar opacity;
+
+ alpha_channel = pixels + y * (rowstride / 4) + x + 3;
+ opacity = (guchar) (255 * pow ((*alpha_channel / 255.0), 1.0 / GAMMA));
+
+ *alpha_channel = opacity;
+ }
+
+ return output_pixbuf;
+}
+
+static gboolean
+screen_saver_floater_do_draw (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ cairo_t *context)
+{
+ gint size;
+ CachedSource *source;
+
+ size = CLAMP ((int) (FLOATER_MAX_SIZE * floater->scale),
+ FLOATER_MIN_SIZE, FLOATER_MAX_SIZE);
+
+ source = g_hash_table_lookup (screen_saver->cached_sources, GINT_TO_POINTER (size));
+
+ if (source == NULL)
+ {
+ GdkPixbuf *pixbuf;
+ GError *error;
+
+ pixbuf = NULL;
+ error = NULL;
+
+ pixbuf = gdk_pixbuf_new_from_file_at_size (screen_saver->filename, size, -1,
+ &error);
+ if (pixbuf == NULL)
+ {
+ g_assert (error != NULL);
+ g_printerr ("%s", _(error->message));
+ g_error_free (error);
+ return FALSE;
+ }
+
+ if (gdk_pixbuf_get_has_alpha (pixbuf))
+ gamma_correct (pixbuf);
+
+ gdk_cairo_set_source_pixbuf (context, pixbuf, 0.0, 0.0);
+
+ source = cached_source_new (cairo_get_source (context),
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf));
+ g_object_unref (pixbuf);
+ g_hash_table_insert (screen_saver->cached_sources, GINT_TO_POINTER (size),
+ source);
+ }
+
+ cairo_save (context);
+
+ if (screen_saver->should_do_rotations && (abs (floater->angle) > G_MINDOUBLE))
+ {
+ floater->bounds.width = G_SQRT2 * source->width + 2;
+ floater->bounds.height = G_SQRT2 * source->height + 2;
+ floater->bounds.x = (int) (floater->position.x - .5 * G_SQRT2 * source->width) - 1;
+ floater->bounds.y = (int) (floater->position.y - .5 * G_SQRT2 * source->height) - 1;
+
+ cairo_translate (context,
+ trunc (floater->position.x),
+ trunc (floater->position.y));
+ cairo_rotate (context, floater->angle);
+ cairo_translate (context,
+ -trunc (floater->position.x),
+ -trunc (floater->position.y));
+ }
+ else
+ {
+ floater->bounds.width = source->width + 2;
+ floater->bounds.height = source->height + 2;
+ floater->bounds.x = (int) (floater->position.x - .5 * source->width) - 1;
+ floater->bounds.y = (int) (floater->position.y - .5 * source->height) - 1;
+ }
+
+ cairo_translate (context,
+ trunc (floater->position.x - .5 * source->width),
+ trunc (floater->position.y - .5 * source->height));
+
+ cairo_set_source (context, source->pattern);
+
+ cairo_rectangle (context,
+ trunc (.5 * (source->width - floater->bounds.width)),
+ trunc (.5 * (source->height - floater->bounds.height)),
+ floater->bounds.width, floater->bounds.height);
+
+ cairo_clip (context);
+ cairo_paint_with_alpha (context, floater->opacity);
+ cairo_restore (context);
+
+ if (screen_saver->should_show_paths && (floater->path != NULL))
+ {
+ gdouble dash_pattern[] = { 5.0 };
+ gint size;
+
+ size = CLAMP ((int) (FLOATER_MAX_SIZE * floater->path_start_scale),
+ FLOATER_MIN_SIZE, FLOATER_MAX_SIZE);
+
+ cairo_save (context);
+ cairo_set_source_rgba (context, 1.0, 1.0, 1.0, .2 * floater->opacity);
+ cairo_move_to (context,
+ floater->path->start_point.x,
+ floater->path->start_point.y);
+ cairo_curve_to (context,
+ floater->path->start_control_point.x,
+ floater->path->start_control_point.y,
+ floater->path->end_control_point.x,
+ floater->path->end_control_point.y,
+ floater->path->end_point.x,
+ floater->path->end_point.y);
+ cairo_set_line_cap (context, CAIRO_LINE_CAP_ROUND);
+ cairo_stroke (context);
+ cairo_set_source_rgba (context, 1.0, 0.0, 0.0, .5 * floater->opacity);
+ cairo_rectangle (context,
+ floater->path->start_point.x - 3,
+ floater->path->start_point.y - 3,
+ 6, 6);
+ cairo_fill (context);
+ cairo_set_source_rgba (context, 0.0, 0.5, 0.0, .5 * floater->opacity);
+ cairo_arc (context,
+ floater->path->start_control_point.x,
+ floater->path->start_control_point.y,
+ 3, 0.0, 2.0 * G_PI);
+ cairo_stroke (context);
+ cairo_set_source_rgba (context, 0.5, 0.0, 0.5, .5 * floater->opacity);
+ cairo_arc (context,
+ floater->path->end_control_point.x,
+ floater->path->end_control_point.y,
+ 3, 0.0, 2.0 * G_PI);
+ cairo_stroke (context);
+ cairo_set_source_rgba (context, 0.0, 0.0, 1.0, .5 * floater->opacity);
+ cairo_rectangle (context,
+ floater->path->end_point.x - 3,
+ floater->path->end_point.y - 3,
+ 6, 6);
+ cairo_fill (context);
+
+ cairo_set_dash (context, dash_pattern, G_N_ELEMENTS (dash_pattern), 0);
+ cairo_set_source_rgba (context, .5, .5, .5, .2 * floater->scale);
+ cairo_move_to (context, floater->path->start_point.x,
+ floater->path->start_point.y);
+ cairo_line_to (context, floater->path->start_control_point.x,
+ floater->path->start_control_point.y);
+ cairo_stroke (context);
+
+ cairo_move_to (context, floater->path->end_point.x,
+ floater->path->end_point.y);
+ cairo_line_to (context, floater->path->end_control_point.x,
+ floater->path->end_control_point.y);
+ cairo_stroke (context);
+
+ cairo_restore (context);
+ }
+
+ return TRUE;
+}
+
+static ScreenSaver *
+screen_saver_new (GtkDrawingArea *drawing_area,
+ const gchar *filename,
+ gint max_floater_count,
+ gboolean should_do_rotations,
+ gboolean should_show_paths)
+{
+ ScreenSaver *screen_saver;
+
+ screen_saver = g_new (ScreenSaver, 1);
+ screen_saver->filename = g_strdup (filename);
+ screen_saver->drawing_area = GTK_WIDGET (drawing_area);
+ screen_saver->cached_sources =
+ g_hash_table_new_full (NULL, NULL, NULL,
+ (GDestroyNotify) cached_source_free);
+
+ g_signal_connect_swapped (G_OBJECT (drawing_area), "size-allocate",
+ G_CALLBACK (screen_saver_on_size_allocate),
+ screen_saver);
+
+ g_signal_connect_swapped (G_OBJECT (drawing_area), "expose-event",
+ G_CALLBACK (screen_saver_on_expose_event),
+ screen_saver);
+
+ screen_saver->first_update_time = 0.0;
+ screen_saver->current_calculated_stats_time = 0.0;
+ screen_saver->last_calculated_stats_time = 0.0;
+ screen_saver->update_count = 0;
+ screen_saver->frame_count = 0;
+ screen_saver->updates_per_second = 0.0;
+ screen_saver->frames_per_second = 0.0;
+ screen_saver->floaters = NULL;
+ screen_saver->max_floater_count = max_floater_count;
+
+ screen_saver->should_show_paths = should_show_paths;
+ screen_saver->should_do_rotations = should_do_rotations;
+
+ screen_saver_get_initial_state (screen_saver);
+
+ screen_saver->state_update_timeout_id =
+ g_timeout_add (1000 / (2.0 * OPTIMAL_FRAME_RATE),
+ (GSourceFunc) screen_saver_do_update_state, screen_saver);
+
+ screen_saver->stats_update_timeout_id =
+ g_timeout_add (1000, (GSourceFunc) screen_saver_do_update_stats,
+ screen_saver);
+
+ return screen_saver;
+}
+
+static void
+screen_saver_free (ScreenSaver *screen_saver)
+{
+ if (screen_saver == NULL)
+ return;
+
+ g_free (screen_saver->filename);
+
+ g_hash_table_destroy (screen_saver->cached_sources);
+
+ if (screen_saver->state_update_timeout_id != 0)
+ g_source_remove (screen_saver->state_update_timeout_id);
+
+ if (screen_saver->stats_update_timeout_id != 0)
+ g_source_remove (screen_saver->stats_update_timeout_id);
+
+ screen_saver_destroy_floaters (screen_saver);
+
+ g_free (screen_saver);
+}
+
+static gdouble
+screen_saver_get_timestamp (ScreenSaver *screen_saver)
+{
+ const gdouble microseconds_per_second = (gdouble ) G_USEC_PER_SEC;
+ gdouble timestamp;
+ GTimeVal now = { 0L, /* zero-filled */ };
+
+ g_get_current_time (&now);
+ timestamp = ((microseconds_per_second * now.tv_sec) + now.tv_usec) /
+ microseconds_per_second;
+
+ return timestamp;
+}
+
+static void
+screen_saver_create_floaters (ScreenSaver *screen_saver)
+{
+ gint i;
+
+ for (i = 0; i < screen_saver->max_floater_count; i++)
+ {
+ ScreenSaverFloater *floater;
+ Point position;
+ gdouble scale;
+
+ position.x = g_random_double_range (screen_saver->canvas_rectangle.top_left_point.x,
+ screen_saver->canvas_rectangle.bottom_right_point.x);
+ position.y = g_random_double_range (screen_saver->canvas_rectangle.top_left_point.y,
+ screen_saver->canvas_rectangle.bottom_right_point.y);
+
+ scale = g_random_double ();
+
+ floater = screen_saver_floater_new (screen_saver, &position, scale);
+
+ screen_saver->floaters = g_list_prepend (screen_saver->floaters,
+ floater);
+ }
+}
+
+static gdouble
+screen_saver_get_updates_per_second (ScreenSaver *screen_saver)
+{
+ return screen_saver->updates_per_second;
+}
+
+static gdouble
+screen_saver_get_frames_per_second (ScreenSaver *screen_saver)
+{
+ return screen_saver->frames_per_second;
+}
+
+static gdouble
+screen_saver_get_image_cache_usage (ScreenSaver *screen_saver)
+{
+ static const gdouble cache_capacity = (FLOATER_MAX_SIZE - FLOATER_MIN_SIZE + 1);
+
+ return g_hash_table_size (screen_saver->cached_sources) / cache_capacity;
+}
+
+static void
+screen_saver_destroy_floaters (ScreenSaver *screen_saver)
+{
+ if (screen_saver->floaters == NULL)
+ return;
+
+ g_list_foreach (screen_saver->floaters, (GFunc) screen_saver_floater_free,
+ NULL);
+ g_list_free (screen_saver->floaters);
+
+ screen_saver->floaters = NULL;
+}
+
+static void
+screen_saver_on_size_allocate (ScreenSaver *screen_saver,
+ GtkAllocation *allocation)
+{
+ Rectangle canvas_rectangle;
+
+ canvas_rectangle.top_left_point.x = allocation->x - .1 * allocation->width;
+ canvas_rectangle.top_left_point.y = allocation->y - .1 * allocation->height;
+
+ canvas_rectangle.bottom_right_point.x = allocation->x + (1.1 * allocation->width);
+ canvas_rectangle.bottom_right_point.y = allocation->y + (1.1 * allocation->height);
+
+ screen_saver->canvas_rectangle = canvas_rectangle;
+}
+
+static gint
+compare_floaters (ScreenSaverFloater *a,
+ ScreenSaverFloater *b)
+{
+ if (a->scale > b->scale)
+ return 1;
+ else if (abs (a->scale - b->scale) <= G_MINDOUBLE)
+ return 0;
+ else
+ return -1;
+}
+
+static void
+screen_saver_on_expose_event (ScreenSaver *screen_saver,
+ GdkEventExpose *event)
+{
+ GList *tmp;
+ cairo_t *context;
+
+ if (screen_saver->floaters == NULL)
+ screen_saver_create_floaters (screen_saver);
+
+ context = gdk_cairo_create (screen_saver->drawing_area->window);
+
+ cairo_rectangle (context,
+ (double) event->area.x,
+ (double) event->area.y,
+ (double) event->area.width,
+ (double) event->area.height);
+ cairo_clip (context);
+
+ screen_saver->floaters = g_list_sort (screen_saver->floaters,
+ (GCompareFunc)compare_floaters);
+
+ for (tmp = screen_saver->floaters; tmp != NULL; tmp = tmp->next)
+ {
+ ScreenSaverFloater *floater;
+ GdkRectangle rect;
+ gint size;
+
+ floater = (ScreenSaverFloater *) tmp->data;
+
+ size = CLAMP ((int) (FLOATER_MAX_SIZE * floater->scale),
+ FLOATER_MIN_SIZE, FLOATER_MAX_SIZE);
+
+ rect.x = (int) (floater->position.x - .5 * G_SQRT2 * size);
+ rect.y = (int) (floater->position.y - .5 * G_SQRT2 * size);
+ rect.width = G_SQRT2 * size;
+ rect.height = G_SQRT2 * size;
+
+ if (!gdk_region_rect_in (event->region, &rect))
+ continue;
+
+ if (!screen_saver_floater_do_draw (screen_saver, floater, context))
+ {
+ gtk_main_quit ();
+ break;
+ }
+ }
+
+ cairo_destroy (context);
+
+ screen_saver->draw_ops_pending = TRUE;
+ screen_saver->frame_count++;
+}
+
+static void
+screen_saver_update_state (ScreenSaver *screen_saver,
+ gdouble time)
+{
+ GList *tmp;
+
+ tmp = screen_saver->floaters;
+ while (tmp != NULL)
+ {
+ ScreenSaverFloater *floater;
+ floater = (ScreenSaverFloater *) tmp->data;
+
+ screen_saver_floater_update_state (screen_saver, floater, time);
+
+ if (GTK_WIDGET_REALIZED (screen_saver->drawing_area)
+ && (floater->bounds.width > 0) && (floater->bounds.height > 0))
+ {
+ gint size;
+ size = CLAMP ((int) (FLOATER_MAX_SIZE * floater->scale),
+ FLOATER_MIN_SIZE, FLOATER_MAX_SIZE);
+
+ gtk_widget_queue_draw_area (screen_saver->drawing_area,
+ floater->bounds.x,
+ floater->bounds.y,
+ floater->bounds.width,
+ floater->bounds.height);
+
+ /* the edges could concievably be spread across two
+ * pixels so we add +2 to invalidated region
+ */
+ if (screen_saver->should_do_rotations)
+ gtk_widget_queue_draw_area (screen_saver->drawing_area,
+ (int) (floater->position.x -
+ .5 * G_SQRT2 * size),
+ (int) (floater->position.y -
+ .5 * G_SQRT2 * size),
+ G_SQRT2 * size + 2,
+ G_SQRT2 * size + 2);
+ else
+ gtk_widget_queue_draw_area (screen_saver->drawing_area,
+ (int) (floater->position.x -
+ .5 * size),
+ (int) (floater->position.y -
+ .5 * size),
+ size + 2, size + 2);
+
+ if (screen_saver->should_show_paths)
+ gtk_widget_queue_draw (screen_saver->drawing_area);
+ }
+
+ tmp = tmp->next;
+ }
+}
+
+static void
+screen_saver_get_initial_state (ScreenSaver *screen_saver)
+{
+ screen_saver->first_update_time = screen_saver_get_timestamp (screen_saver);
+ screen_saver_update_state (screen_saver, 0.0);
+}
+
+static gboolean
+screen_saver_do_update_state (ScreenSaver *screen_saver)
+{
+ gdouble current_update_time;
+
+ /* flush pending requests to the X server and block for
+ * replies before proceeding to help prevent the X server from
+ * getting overrun with requests
+ */
+ if (screen_saver->draw_ops_pending)
+ {
+ gdk_flush ();
+ screen_saver->draw_ops_pending = FALSE;
+ }
+
+ current_update_time = screen_saver_get_timestamp (screen_saver);
+ screen_saver_update_state (screen_saver, current_update_time -
+ screen_saver->first_update_time);
+ screen_saver->update_count++;
+ return TRUE;
+}
+
+static gboolean
+screen_saver_do_update_stats (ScreenSaver *screen_saver)
+{
+ gdouble last_calculated_stats_time, seconds_since_last_stats_update;
+
+ last_calculated_stats_time = screen_saver->current_calculated_stats_time;
+ screen_saver->current_calculated_stats_time =
+ screen_saver_get_timestamp (screen_saver);
+ screen_saver->last_calculated_stats_time = last_calculated_stats_time;
+
+ if (abs (last_calculated_stats_time) <= G_MINDOUBLE)
+ return TRUE;
+
+ seconds_since_last_stats_update =
+ screen_saver->current_calculated_stats_time - last_calculated_stats_time;
+
+ screen_saver->updates_per_second =
+ screen_saver->update_count / seconds_since_last_stats_update;
+ screen_saver->frames_per_second =
+ screen_saver->frame_count / seconds_since_last_stats_update;
+
+ screen_saver->update_count = 0;
+ screen_saver->frame_count = 0;
+
+ return TRUE;
+}
+
+static gboolean
+do_print_screen_saver_stats (ScreenSaver *screen_saver)
+{
+
+ g_print ("updates per second: %.2f, frames per second: %.2f, "
+ "image cache %.0f%% full\n",
+ screen_saver_get_updates_per_second (screen_saver),
+ screen_saver_get_frames_per_second (screen_saver),
+ screen_saver_get_image_cache_usage (screen_saver) * 100.0);
+
+ return TRUE;
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ ScreenSaver *screen_saver;
+ GtkWidget *window;
+ GtkWidget *drawing_area;
+
+ GtkStateType state;
+
+ GError *error;
+
+ error = NULL;
+
+ bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ gtk_init_with_args (&argc, &argv,
+ /* translators: the word "image" here
+ * represents a command line argument
+ */
+ _("image - floats images around the screen"),
+ options, GETTEXT_PACKAGE, &error);
+
+
+ if (error != NULL)
+ {
+ g_printerr (_("%s. See --help for usage information.\n"),
+ _(error->message));
+ g_error_free (error);
+ return EX_SOFTWARE;
+ }
+
+ if ((filenames == NULL) || (filenames[0] == NULL) ||
+ (filenames[1] != NULL))
+ {
+ g_printerr (_("You must specify one image. See --help for usage "
+ "information.\n"));
+ return EX_USAGE;
+ }
+
+ window = gs_theme_window_new ();
+
+ g_signal_connect (G_OBJECT (window), "delete-event",
+ G_CALLBACK (gtk_main_quit), NULL);
+
+ drawing_area = gtk_drawing_area_new ();
+
+ state = (GtkStateType) 0;
+ while (state < (GtkStateType) G_N_ELEMENTS (drawing_area->style->bg))
+ {
+ gtk_widget_modify_bg (drawing_area, state, &drawing_area->style->mid[state]);
+ state++;
+ }
+
+ gtk_widget_show (drawing_area);
+ gtk_container_add (GTK_CONTAINER (window), drawing_area);
+
+ screen_saver = screen_saver_new (GTK_DRAWING_AREA (drawing_area),
+ filenames[0], max_floater_count,
+ should_do_rotations, should_show_paths);
+ g_strfreev (filenames);
+
+ if (should_print_stats)
+ g_timeout_add (STAT_PRINT_FREQUENCY,
+ (GSourceFunc) do_print_screen_saver_stats,
+ screen_saver);
+
+ if ((geometry == NULL)
+ || !gtk_window_parse_geometry (GTK_WINDOW (window), geometry))
+ gtk_window_set_default_size (GTK_WINDOW (window), 640, 480);
+
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ screen_saver_free (screen_saver);
+
+ return EX_OK;
+}
diff --git a/savers/floaters.c.orig b/savers/floaters.c.orig
new file mode 100644
index 0000000..1fd84f5
--- /dev/null
+++ b/savers/floaters.c.orig
@@ -0,0 +1,1256 @@
+/*
+ * Copyright (C) 2005 Ray Strode <[email protected]>,
+ * Matthias Clasen <[email protected]>,
+ * Søren Sandmann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Originally written by: Ray Strode <[email protected]>
+ *
+ * Later contributions by: Matthias Clasen <[email protected]>
+ * Søren Sandmann <[email protected]>
+ */
+
+#include "config.h"
+
+#include <math.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <time.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+
+#include <gtk/gtk.h>
+
+#include "gs-theme-window.h"
+
+#ifndef trunc
+#define trunc(x) (((x) > 0.0) ? floor((x)) : -floor(-(x)))
+#endif
+
+#ifndef OPTIMAL_FRAME_RATE
+#define OPTIMAL_FRAME_RATE (25.0)
+#endif
+
+#ifndef STAT_PRINT_FREQUENCY
+#define STAT_PRINT_FREQUENCY (2000)
+#endif
+
+#ifndef FLOATER_MAX_SIZE
+#define FLOATER_MAX_SIZE (128.0)
+#endif
+
+#ifndef FLOATER_MIN_SIZE
+#define FLOATER_MIN_SIZE (16.0)
+#endif
+#ifndef FLOATER_DEFAULT_COUNT
+#define FLOATER_DEFAULT_COUNT (5)
+#endif
+
+#ifndef SMALL_ANGLE
+#define SMALL_ANGLE (0.025 * G_PI)
+#endif
+
+#ifndef BIG_ANGLE
+#define BIG_ANGLE (0.125 * G_PI)
+#endif
+
+#ifndef GAMMA
+#define GAMMA 2.2
+#endif
+
+static gboolean should_show_paths = FALSE;
+static gboolean should_do_rotations = FALSE;
+static gboolean should_print_stats = FALSE;
+static gint max_floater_count = FLOATER_DEFAULT_COUNT;
+static gchar *geometry = NULL;
+static gchar **filenames = NULL;
+
+static GOptionEntry options[] = {
+ {"show-paths", 'p', 0, G_OPTION_ARG_NONE, &should_show_paths,
+ N_("Show paths that images follow"), NULL},
+
+ {"do-rotations", 'r', 0, G_OPTION_ARG_NONE, &should_do_rotations,
+ N_("Occasionally rotate images as they move"), NULL},
+
+ {"print-stats", 's', 0, G_OPTION_ARG_NONE, &should_print_stats,
+ N_("Print out frame rate and other statistics"), NULL},
+
+ {"number-of-images", 'n', 0, G_OPTION_ARG_INT, &max_floater_count,
+ N_("The maximum number of images to keep on screen"), N_("MAX_IMAGES")},
+
+ {"geometry", 0, 0, G_OPTION_ARG_STRING, &geometry,
+ N_("The initial size and position of window"), N_("WIDTHxHEIGHT+X+Y")},
+
+ {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames,
+ N_("The source image to use"), NULL},
+
+ {NULL}
+};
+
+
+typedef struct _Point Point;
+typedef struct _Path Path;
+typedef struct _Rectangle Rectangle;
+typedef struct _ScreenSaverFloater ScreenSaverFloater;
+typedef struct _CachedSource CachedSource;
+typedef struct _ScreenSaver ScreenSaver;
+
+struct _Point
+{
+ gdouble x, y;
+};
+
+struct _Path
+{
+ Point start_point;
+ Point start_control_point;
+ Point end_control_point;
+ Point end_point;
+
+ gdouble x_linear_coefficient,
+ y_linear_coefficient;
+
+ gdouble x_quadratic_coefficient,
+ y_quadratic_coefficient;
+
+ gdouble x_cubic_coefficient,
+ y_cubic_coefficient;
+
+ gdouble duration;
+};
+
+struct _CachedSource
+{
+ cairo_pattern_t *pattern;
+ gint width, height;
+};
+
+struct _Rectangle
+{
+ Point top_left_point;
+ Point bottom_right_point;
+};
+
+struct _ScreenSaverFloater
+{
+ GdkRectangle bounds;
+
+ Point start_position;
+ Point position;
+
+ gdouble scale;
+ gdouble opacity;
+
+ Path *path;
+ gdouble path_start_time;
+ gdouble path_start_scale;
+ gdouble path_end_scale;
+
+ gdouble angle;
+ gdouble angle_increment;
+};
+
+struct _ScreenSaver
+{
+ GtkWidget *drawing_area;
+ Rectangle canvas_rectangle;
+ GHashTable *cached_sources;
+
+ char *filename;
+
+ gdouble first_update_time;
+
+ gdouble last_calculated_stats_time,
+ current_calculated_stats_time;
+ gint update_count, frame_count;
+
+ gdouble updates_per_second;
+ gdouble frames_per_second;
+
+ guint state_update_timeout_id;
+ guint stats_update_timeout_id;
+
+ GList *floaters;
+ gint max_floater_count;
+
+ guint should_do_rotations: 1;
+ guint should_show_paths : 1;
+ guint draw_ops_pending : 1;
+};
+
+static Path *path_new (Point *start_point,
+ Point *start_control_point,
+ Point *end_control_point,
+ Point *end_point,
+ gdouble duration);
+static void path_free (Path *path);
+
+static ScreenSaverFloater *screen_saver_floater_new (ScreenSaver *screen_saver,
+ Point *position,
+ gdouble scale);
+static void screen_saver_floater_free (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater);
+static gboolean screen_saver_floater_is_off_canvas (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater);
+static gboolean screen_saver_floater_should_bubble_up (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble performance_ratio,
+ gdouble *duration);
+static gboolean screen_saver_floater_should_come_on_screen (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble performance_ratio,
+ gdouble *duration);
+static Point screen_saver_floater_get_position_from_time (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble time);
+static gdouble screen_saver_floater_get_scale_from_time (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble time);
+static gdouble screen_saver_floater_get_angle_from_time (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble time);
+
+static Path *screen_saver_floater_create_path_to_on_screen (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble duration);
+static Path *screen_saver_floater_create_path_to_bubble_up (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble duration);
+static Path *screen_saver_floater_create_path_to_random_point (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble duration);
+static Path *screen_saver_floater_create_path (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater);
+static void screen_saver_floater_update_state (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble time);
+
+static gboolean screen_saver_floater_do_draw (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ cairo_t *context);
+
+static CachedSource *cached_source_new (cairo_pattern_t *pattern,
+ gint width,
+ gint height);
+static void cached_source_free (CachedSource *source);
+
+static ScreenSaver *screen_saver_new (GtkDrawingArea *drawing_area,
+ const gchar *filename,
+ gint max_floater_count,
+ gboolean should_do_rotations,
+ gboolean should_show_paths);
+static void screen_saver_free (ScreenSaver *screen_saver);
+static gdouble screen_saver_get_timestamp (ScreenSaver *screen_saver);
+static void screen_saver_get_initial_state (ScreenSaver *screen_saver);
+static void screen_saver_update_state (ScreenSaver *screen_saver,
+ gdouble time);
+static gboolean screen_saver_do_update_state (ScreenSaver *screen_saver);
+static gboolean screen_saver_do_update_stats (ScreenSaver *screen_saver);
+static gdouble screen_saver_get_updates_per_second (ScreenSaver *screen_saver);
+static gdouble screen_saver_get_frames_per_second (ScreenSaver *screen_saver);
+static gdouble screen_saver_get_image_cache_usage (ScreenSaver *screen_saver);
+static void screen_saver_create_floaters (ScreenSaver *screen_saver);
+static void screen_saver_destroy_floaters (ScreenSaver *screen_saver);
+static void screen_saver_on_size_allocate (ScreenSaver *screen_saver,
+ GtkAllocation *allocation);
+static void screen_saver_on_expose_event (ScreenSaver *screen_saver,
+ GdkEventExpose *event);
+static gboolean do_print_screen_saver_stats (ScreenSaver *screen_saver);
+static GdkPixbuf *gamma_correct (const GdkPixbuf *input_pixbuf);
+
+static CachedSource*
+cached_source_new (cairo_pattern_t *pattern,
+ gint width,
+ gint height)
+{
+ CachedSource *source;
+
+ source = g_new (CachedSource, 1);
+ source->pattern = cairo_pattern_reference (pattern);
+ source->width = width;
+ source->height = height;
+
+ return source;
+}
+
+static void
+cached_source_free (CachedSource *source)
+{
+ if (source == NULL)
+ return;
+
+ cairo_pattern_destroy (source->pattern);
+
+ g_free (source);
+}
+
+static Path *
+path_new (Point *start_point,
+ Point *start_control_point,
+ Point *end_control_point,
+ Point *end_point,
+ gdouble duration)
+{
+ Path *path;
+
+ path = g_new (Path, 1);
+ path->start_point = *start_point;
+ path->start_control_point = *start_control_point;
+ path->end_control_point = *end_control_point;
+ path->end_point = *end_point;
+ path->duration = duration;
+
+ /* we precompute the coefficients to the cubic bezier curve here
+ * so that we don't have to do it repeatedly later The equation is:
+ *
+ * B(t) = A * t^3 + B * t^2 + C * t + start_point
+ */
+ path->x_linear_coefficient = 3 * (start_control_point->x - start_point->x);
+ path->x_quadratic_coefficient = 3 * (end_control_point->x -
+ start_control_point->x) -
+ path->x_linear_coefficient;
+ path->x_cubic_coefficient = end_point->x - start_point->x -
+ path->x_linear_coefficient -
+ path->x_quadratic_coefficient;
+
+ path->y_linear_coefficient = 3 * (start_control_point->y - start_point->y);
+ path->y_quadratic_coefficient = 3 * (end_control_point->y -
+ start_control_point->y) -
+ path->y_linear_coefficient;
+ path->y_cubic_coefficient = end_point->y - start_point->y -
+ path->y_linear_coefficient -
+ path->y_quadratic_coefficient;
+ return path;
+}
+
+static void
+path_free (Path *path)
+{
+ g_free (path);
+}
+
+static ScreenSaverFloater*
+screen_saver_floater_new (ScreenSaver *screen_saver,
+ Point *position,
+ gdouble scale)
+{
+ ScreenSaverFloater *floater;
+
+ floater = g_new (ScreenSaverFloater, 1);
+ floater->bounds.width = 0;
+ floater->start_position = *position;
+ floater->position = *position;
+ floater->scale = scale;
+ floater->opacity = pow (scale, 1.0 / GAMMA);
+ floater->path = NULL;
+ floater->path_start_time = 0.0;
+ floater->path_start_scale = 1.0;
+ floater->path_end_scale = 0.0;
+
+ floater->angle = 0.0;
+ floater->angle_increment = 0.0;
+
+ return floater;
+}
+
+void
+screen_saver_floater_free (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater)
+{
+ if (floater == NULL)
+ return;
+
+ path_free (floater->path);
+
+ g_free (floater);
+}
+
+static gboolean
+screen_saver_floater_is_off_canvas (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater)
+{
+ if ((floater->position.x < screen_saver->canvas_rectangle.top_left_point.x) ||
+ (floater->position.x > screen_saver->canvas_rectangle.bottom_right_point.x) ||
+ (floater->position.y < screen_saver->canvas_rectangle.top_left_point.y) ||
+ (floater->position.y > screen_saver->canvas_rectangle.bottom_right_point.y))
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+screen_saver_floater_should_come_on_screen (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble performance_ratio,
+ gdouble *duration)
+{
+
+ if (!screen_saver_floater_is_off_canvas (screen_saver, floater))
+ return FALSE;
+
+ if ((abs (performance_ratio - .5) >= G_MINDOUBLE) &&
+ (g_random_double () > .5))
+ {
+ if (duration)
+ *duration = g_random_double_range (3.0, 7.0);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+screen_saver_floater_should_bubble_up (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble performance_ratio,
+ gdouble *duration)
+{
+
+ if ((performance_ratio < .5) && (g_random_double () > .5))
+ {
+ if (duration)
+ *duration = performance_ratio * 30.0;
+
+ return TRUE;
+ }
+
+ if ((floater->scale < .3) && (g_random_double () > .6))
+ {
+ if (duration)
+ *duration = 30.0;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static Point
+screen_saver_floater_get_position_from_time (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble time)
+{
+ Point point;
+
+ time = time / floater->path->duration;
+
+ point.x = floater->path->x_cubic_coefficient * (time * time * time) +
+ floater->path->x_quadratic_coefficient * (time * time) +
+ floater->path->x_linear_coefficient * (time) +
+ floater->path->start_point.x;
+ point.y = floater->path->y_cubic_coefficient * (time * time * time) +
+ floater->path->y_quadratic_coefficient * (time * time) +
+ floater->path->y_linear_coefficient * (time) +
+ floater->path->start_point.y;
+
+ return point;
+}
+
+static gdouble
+screen_saver_floater_get_scale_from_time (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble time)
+{
+ gdouble completion_ratio, total_scale_growth, new_scale;
+
+ completion_ratio = time / floater->path->duration;
+ total_scale_growth = (floater->path_end_scale - floater->path_start_scale);
+ new_scale = floater->path_start_scale + total_scale_growth * completion_ratio;
+
+ return CLAMP (new_scale, 0.0, 1.0);
+}
+
+static gdouble
+screen_saver_floater_get_angle_from_time (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble time)
+{
+ gdouble completion_ratio;
+ gdouble total_rotation;
+
+ completion_ratio = time / floater->path->duration;
+ total_rotation = floater->angle_increment * floater->path->duration;
+
+ return floater->angle + total_rotation * completion_ratio;
+}
+
+static Path *
+screen_saver_floater_create_path_to_on_screen (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble duration)
+{
+ Point start_position, end_position, start_control_point, end_control_point;
+ start_position = floater->position;
+
+ end_position.x = g_random_double_range (.25, .75) *
+ (screen_saver->canvas_rectangle.top_left_point.x +
+ screen_saver->canvas_rectangle.bottom_right_point.x);
+ end_position.y = g_random_double_range (.25, .75) *
+ (screen_saver->canvas_rectangle.top_left_point.y +
+ screen_saver->canvas_rectangle.bottom_right_point.y);
+
+ start_control_point.x = start_position.x + .9 * (end_position.x - start_position.x);
+ start_control_point.y = start_position.y + .9 * (end_position.y - start_position.y);
+
+ end_control_point.x = start_position.x + 1.0 * (end_position.x - start_position.x);
+ end_control_point.y = start_position.y + 1.0 * (end_position.y - start_position.y);
+
+ return path_new (&start_position, &start_control_point, &end_control_point,
+ &end_position, duration);
+}
+
+static Path *
+screen_saver_floater_create_path_to_bubble_up (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble duration)
+{
+ Point start_position, end_position, start_control_point, end_control_point;
+
+ start_position = floater->position;
+ end_position.x = start_position.x;
+ end_position.y = screen_saver->canvas_rectangle.top_left_point.y - FLOATER_MAX_SIZE;
+ start_control_point.x = .5 * start_position.x;
+ start_control_point.y = .5 * start_position.y;
+ end_control_point.x = 1.5 * end_position.x;
+ end_control_point.y = .5 * end_position.y;
+
+ return path_new (&start_position, &start_control_point, &end_control_point,
+ &end_position, duration);
+}
+
+static Path *
+screen_saver_floater_create_path_to_random_point (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble duration)
+{
+ Point start_position, end_position, start_control_point, end_control_point;
+
+ start_position = floater->position;
+
+ end_position.x = start_position.x +
+ (g_random_double_range (-.5, .5) * 4 * FLOATER_MAX_SIZE);
+ end_position.y = start_position.y +
+ (g_random_double_range (-.5, .5) * 4 * FLOATER_MAX_SIZE);
+
+ start_control_point.x = start_position.x + .95 * (end_position.x - start_position.x);
+ start_control_point.y = start_position.y + .95 * (end_position.y - start_position.y);
+
+ end_control_point.x = start_position.x + 1.0 * (end_position.x - start_position.x);
+ end_control_point.y = start_position.y + 1.0 * (end_position.y - start_position.y);
+
+ return path_new (&start_position, &start_control_point, &end_control_point,
+ &end_position, duration);
+}
+
+static Path *
+screen_saver_floater_create_path (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater)
+{
+ gdouble performance_ratio;
+ gdouble duration;
+
+ performance_ratio =
+ screen_saver_get_frames_per_second (screen_saver) / OPTIMAL_FRAME_RATE;
+
+ if (abs (performance_ratio) <= G_MINDOUBLE)
+ performance_ratio = 1.0;
+
+ if (screen_saver_floater_should_bubble_up (screen_saver, floater, performance_ratio, &duration))
+ return screen_saver_floater_create_path_to_bubble_up (screen_saver, floater, duration);
+
+ if (screen_saver_floater_should_come_on_screen (screen_saver, floater, performance_ratio, &duration))
+ return screen_saver_floater_create_path_to_on_screen (screen_saver, floater, duration);
+
+ return screen_saver_floater_create_path_to_random_point (screen_saver, floater,
+ g_random_double_range (3.0, 7.0));
+}
+
+static void
+screen_saver_floater_update_state (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ gdouble time)
+{
+ gdouble performance_ratio;
+
+ performance_ratio =
+ screen_saver_get_frames_per_second (screen_saver) / OPTIMAL_FRAME_RATE;
+
+ if (floater->path == NULL)
+ {
+ floater->path = screen_saver_floater_create_path (screen_saver, floater);
+ floater->path_start_time = time;
+
+ floater->path_start_scale = floater->scale;
+
+ if (g_random_double () > .5)
+ floater->path_end_scale = g_random_double_range (0.10, performance_ratio);
+
+ /* poor man's distribution */
+ if (screen_saver->should_do_rotations &&
+ (g_random_double () < .75 * performance_ratio))
+ {
+ gint r;
+
+ r = g_random_int_range (0, 100);
+ if (r < 80)
+ floater->angle_increment = 0.0;
+ else if (r < 95)
+ floater->angle_increment = g_random_double_range (-SMALL_ANGLE, SMALL_ANGLE);
+ else
+ floater->angle_increment = g_random_double_range (-BIG_ANGLE, BIG_ANGLE);
+ }
+ }
+
+ if (time < (floater->path_start_time + floater->path->duration))
+ {
+ gdouble path_time;
+
+ path_time = time - floater->path_start_time;
+
+ floater->position =
+ screen_saver_floater_get_position_from_time (screen_saver, floater,
+ path_time);
+ floater->scale =
+ screen_saver_floater_get_scale_from_time (screen_saver, floater, path_time);
+
+ floater->angle =
+ screen_saver_floater_get_angle_from_time (screen_saver, floater, path_time);
+
+ floater->opacity = pow (floater->scale, 1.0 / GAMMA);
+ }
+ else
+ {
+ path_free (floater->path);
+
+ floater->path = NULL;
+ floater->path_start_time = 0.0;
+ }
+}
+
+static GdkPixbuf *
+gamma_correct (const GdkPixbuf *input_pixbuf)
+{
+ gint x, y, width, height, rowstride;
+ GdkPixbuf *output_pixbuf;
+ guchar *pixels;
+
+ output_pixbuf = gdk_pixbuf_copy (input_pixbuf);
+ pixels = gdk_pixbuf_get_pixels (output_pixbuf);
+
+ width = gdk_pixbuf_get_width (output_pixbuf);
+ height = gdk_pixbuf_get_height (output_pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (output_pixbuf);
+
+ for (y = 0; y < height; y++)
+ for (x = 0; x < width; x++)
+ {
+ guchar *alpha_channel;
+ guchar opacity;
+
+ alpha_channel = pixels + y * (rowstride / 4) + x + 3;
+ opacity = (guchar) (255 * pow ((*alpha_channel / 255.0), 1.0 / GAMMA));
+
+ *alpha_channel = opacity;
+ }
+
+ return output_pixbuf;
+}
+
+static gboolean
+screen_saver_floater_do_draw (ScreenSaver *screen_saver,
+ ScreenSaverFloater *floater,
+ cairo_t *context)
+{
+ gint size;
+ CachedSource *source;
+
+ size = CLAMP ((int) (FLOATER_MAX_SIZE * floater->scale),
+ FLOATER_MIN_SIZE, FLOATER_MAX_SIZE);
+
+ source = g_hash_table_lookup (screen_saver->cached_sources, GINT_TO_POINTER (size));
+
+ if (source == NULL)
+ {
+ GdkPixbuf *pixbuf;
+ GError *error;
+
+ pixbuf = NULL;
+ error = NULL;
+
+ pixbuf = gdk_pixbuf_new_from_file_at_size (screen_saver->filename, size, -1,
+ &error);
+ if (pixbuf == NULL)
+ {
+ g_assert (error != NULL);
+ g_printerr ("%s", _(error->message));
+ g_error_free (error);
+ return FALSE;
+ }
+
+ if (gdk_pixbuf_get_has_alpha (pixbuf))
+ gamma_correct (pixbuf);
+
+ gdk_cairo_set_source_pixbuf (context, pixbuf, 0.0, 0.0);
+
+ source = cached_source_new (cairo_get_source (context),
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf));
+ g_object_unref (pixbuf);
+ g_hash_table_insert (screen_saver->cached_sources, GINT_TO_POINTER (size),
+ source);
+ }
+
+ cairo_save (context);
+
+ if (screen_saver->should_do_rotations && (abs (floater->angle) > G_MINDOUBLE))
+ {
+ floater->bounds.width = G_SQRT2 * source->width + 2;
+ floater->bounds.height = G_SQRT2 * source->height + 2;
+ floater->bounds.x = (int) (floater->position.x - .5 * G_SQRT2 * source->width) - 1;
+ floater->bounds.y = (int) (floater->position.y - .5 * G_SQRT2 * source->height) - 1;
+
+ cairo_translate (context,
+ trunc (floater->position.x),
+ trunc (floater->position.y));
+ cairo_rotate (context, floater->angle);
+ cairo_translate (context,
+ -trunc (floater->position.x),
+ -trunc (floater->position.y));
+ }
+ else
+ {
+ floater->bounds.width = source->width + 2;
+ floater->bounds.height = source->height + 2;
+ floater->bounds.x = (int) (floater->position.x - .5 * source->width) - 1;
+ floater->bounds.y = (int) (floater->position.y - .5 * source->height) - 1;
+ }
+
+ cairo_translate (context,
+ trunc (floater->position.x - .5 * source->width),
+ trunc (floater->position.y - .5 * source->height));
+
+ cairo_set_source (context, source->pattern);
+
+ cairo_rectangle (context,
+ trunc (.5 * (source->width - floater->bounds.width)),
+ trunc (.5 * (source->height - floater->bounds.height)),
+ floater->bounds.width, floater->bounds.height);
+
+ cairo_clip (context);
+ cairo_paint_with_alpha (context, floater->opacity);
+ cairo_restore (context);
+
+ if (screen_saver->should_show_paths && (floater->path != NULL))
+ {
+ gdouble dash_pattern[] = { 5.0 };
+ gint size;
+
+ size = CLAMP ((int) (FLOATER_MAX_SIZE * floater->path_start_scale),
+ FLOATER_MIN_SIZE, FLOATER_MAX_SIZE);
+
+ cairo_save (context);
+ cairo_set_source_rgba (context, 1.0, 1.0, 1.0, .2 * floater->opacity);
+ cairo_move_to (context,
+ floater->path->start_point.x,
+ floater->path->start_point.y);
+ cairo_curve_to (context,
+ floater->path->start_control_point.x,
+ floater->path->start_control_point.y,
+ floater->path->end_control_point.x,
+ floater->path->end_control_point.y,
+ floater->path->end_point.x,
+ floater->path->end_point.y);
+ cairo_set_line_cap (context, CAIRO_LINE_CAP_ROUND);
+ cairo_stroke (context);
+ cairo_set_source_rgba (context, 1.0, 0.0, 0.0, .5 * floater->opacity);
+ cairo_rectangle (context,
+ floater->path->start_point.x - 3,
+ floater->path->start_point.y - 3,
+ 6, 6);
+ cairo_fill (context);
+ cairo_set_source_rgba (context, 0.0, 0.5, 0.0, .5 * floater->opacity);
+ cairo_arc (context,
+ floater->path->start_control_point.x,
+ floater->path->start_control_point.y,
+ 3, 0.0, 2.0 * G_PI);
+ cairo_stroke (context);
+ cairo_set_source_rgba (context, 0.5, 0.0, 0.5, .5 * floater->opacity);
+ cairo_arc (context,
+ floater->path->end_control_point.x,
+ floater->path->end_control_point.y,
+ 3, 0.0, 2.0 * G_PI);
+ cairo_stroke (context);
+ cairo_set_source_rgba (context, 0.0, 0.0, 1.0, .5 * floater->opacity);
+ cairo_rectangle (context,
+ floater->path->end_point.x - 3,
+ floater->path->end_point.y - 3,
+ 6, 6);
+ cairo_fill (context);
+
+ cairo_set_dash (context, dash_pattern, G_N_ELEMENTS (dash_pattern), 0);
+ cairo_set_source_rgba (context, .5, .5, .5, .2 * floater->scale);
+ cairo_move_to (context, floater->path->start_point.x,
+ floater->path->start_point.y);
+ cairo_line_to (context, floater->path->start_control_point.x,
+ floater->path->start_control_point.y);
+ cairo_stroke (context);
+
+ cairo_move_to (context, floater->path->end_point.x,
+ floater->path->end_point.y);
+ cairo_line_to (context, floater->path->end_control_point.x,
+ floater->path->end_control_point.y);
+ cairo_stroke (context);
+
+ cairo_restore (context);
+ }
+
+ return TRUE;
+}
+
+static ScreenSaver *
+screen_saver_new (GtkDrawingArea *drawing_area,
+ const gchar *filename,
+ gint max_floater_count,
+ gboolean should_do_rotations,
+ gboolean should_show_paths)
+{
+ ScreenSaver *screen_saver;
+
+ screen_saver = g_new (ScreenSaver, 1);
+ screen_saver->filename = g_strdup (filename);
+ screen_saver->drawing_area = GTK_WIDGET (drawing_area);
+ screen_saver->cached_sources =
+ g_hash_table_new_full (NULL, NULL, NULL,
+ (GDestroyNotify) cached_source_free);
+
+ g_signal_connect_swapped (G_OBJECT (drawing_area), "size-allocate",
+ G_CALLBACK (screen_saver_on_size_allocate),
+ screen_saver);
+
+ g_signal_connect_swapped (G_OBJECT (drawing_area), "expose-event",
+ G_CALLBACK (screen_saver_on_expose_event),
+ screen_saver);
+
+ screen_saver->first_update_time = 0.0;
+ screen_saver->current_calculated_stats_time = 0.0;
+ screen_saver->last_calculated_stats_time = 0.0;
+ screen_saver->update_count = 0;
+ screen_saver->frame_count = 0;
+ screen_saver->updates_per_second = 0.0;
+ screen_saver->frames_per_second = 0.0;
+ screen_saver->floaters = NULL;
+ screen_saver->max_floater_count = max_floater_count;
+
+ screen_saver->should_show_paths = should_show_paths;
+ screen_saver->should_do_rotations = should_do_rotations;
+
+ screen_saver_get_initial_state (screen_saver);
+
+ screen_saver->state_update_timeout_id =
+ g_timeout_add (1000 / (2.0 * OPTIMAL_FRAME_RATE),
+ (GSourceFunc) screen_saver_do_update_state, screen_saver);
+
+ screen_saver->stats_update_timeout_id =
+ g_timeout_add (1000, (GSourceFunc) screen_saver_do_update_stats,
+ screen_saver);
+
+ return screen_saver;
+}
+
+static void
+screen_saver_free (ScreenSaver *screen_saver)
+{
+ if (screen_saver == NULL)
+ return;
+
+ g_free (screen_saver->filename);
+
+ g_hash_table_destroy (screen_saver->cached_sources);
+
+ if (screen_saver->state_update_timeout_id != 0)
+ g_source_remove (screen_saver->state_update_timeout_id);
+
+ if (screen_saver->stats_update_timeout_id != 0)
+ g_source_remove (screen_saver->stats_update_timeout_id);
+
+ screen_saver_destroy_floaters (screen_saver);
+
+ g_free (screen_saver);
+}
+
+static gdouble
+screen_saver_get_timestamp (ScreenSaver *screen_saver)
+{
+ const gdouble microseconds_per_second = (gdouble ) G_USEC_PER_SEC;
+ gdouble timestamp;
+ GTimeVal now = { 0L, /* zero-filled */ };
+
+ g_get_current_time (&now);
+ timestamp = ((microseconds_per_second * now.tv_sec) + now.tv_usec) /
+ microseconds_per_second;
+
+ return timestamp;
+}
+
+static void
+screen_saver_create_floaters (ScreenSaver *screen_saver)
+{
+ gint i;
+
+ for (i = 0; i < screen_saver->max_floater_count; i++)
+ {
+ ScreenSaverFloater *floater;
+ Point position;
+ gdouble scale;
+
+ position.x = g_random_double_range (screen_saver->canvas_rectangle.top_left_point.x,
+ screen_saver->canvas_rectangle.bottom_right_point.x);
+ position.y = g_random_double_range (screen_saver->canvas_rectangle.top_left_point.y,
+ screen_saver->canvas_rectangle.bottom_right_point.y);
+
+ scale = g_random_double ();
+
+ floater = screen_saver_floater_new (screen_saver, &position, scale);
+
+ screen_saver->floaters = g_list_prepend (screen_saver->floaters,
+ floater);
+ }
+}
+
+static gdouble
+screen_saver_get_updates_per_second (ScreenSaver *screen_saver)
+{
+ return screen_saver->updates_per_second;
+}
+
+static gdouble
+screen_saver_get_frames_per_second (ScreenSaver *screen_saver)
+{
+ return screen_saver->frames_per_second;
+}
+
+static gdouble
+screen_saver_get_image_cache_usage (ScreenSaver *screen_saver)
+{
+ static const gdouble cache_capacity = (FLOATER_MAX_SIZE - FLOATER_MIN_SIZE + 1);
+
+ return g_hash_table_size (screen_saver->cached_sources) / cache_capacity;
+}
+
+static void
+screen_saver_destroy_floaters (ScreenSaver *screen_saver)
+{
+ if (screen_saver->floaters == NULL)
+ return;
+
+ g_list_foreach (screen_saver->floaters, (GFunc) screen_saver_floater_free,
+ NULL);
+ g_list_free (screen_saver->floaters);
+
+ screen_saver->floaters = NULL;
+}
+
+static void
+screen_saver_on_size_allocate (ScreenSaver *screen_saver,
+ GtkAllocation *allocation)
+{
+ Rectangle canvas_rectangle;
+
+ canvas_rectangle.top_left_point.x = allocation->x - .1 * allocation->width;
+ canvas_rectangle.top_left_point.y = allocation->y - .1 * allocation->height;
+
+ canvas_rectangle.bottom_right_point.x = allocation->x + (1.1 * allocation->width);
+ canvas_rectangle.bottom_right_point.y = allocation->y + (1.1 * allocation->height);
+
+ screen_saver->canvas_rectangle = canvas_rectangle;
+}
+
+static gint
+compare_floaters (ScreenSaverFloater *a,
+ ScreenSaverFloater *b)
+{
+ if (a->scale > b->scale)
+ return 1;
+ else if (abs (a->scale - b->scale) <= G_MINDOUBLE)
+ return 0;
+ else
+ return -1;
+}
+
+static void
+screen_saver_on_expose_event (ScreenSaver *screen_saver,
+ GdkEventExpose *event)
+{
+ GList *tmp;
+ cairo_t *context;
+
+ if (screen_saver->floaters == NULL)
+ screen_saver_create_floaters (screen_saver);
+
+ context = gdk_cairo_create (screen_saver->drawing_area->window);
+
+ cairo_rectangle (context,
+ (double) event->area.x,
+ (double) event->area.y,
+ (double) event->area.width,
+ (double) event->area.height);
+ cairo_clip (context);
+
+ screen_saver->floaters = g_list_sort (screen_saver->floaters,
+ (GCompareFunc)compare_floaters);
+
+ for (tmp = screen_saver->floaters; tmp != NULL; tmp = tmp->next)
+ {
+ ScreenSaverFloater *floater;
+ GdkRectangle rect;
+ gint size;
+
+ floater = (ScreenSaverFloater *) tmp->data;
+
+ size = CLAMP ((int) (FLOATER_MAX_SIZE * floater->scale),
+ FLOATER_MIN_SIZE, FLOATER_MAX_SIZE);
+
+ rect.x = (int) (floater->position.x - .5 * G_SQRT2 * size);
+ rect.y = (int) (floater->position.y - .5 * G_SQRT2 * size);
+ rect.width = G_SQRT2 * size;
+ rect.height = G_SQRT2 * size;
+
+ if (!gdk_region_rect_in (event->region, &rect))
+ continue;
+
+ if (!screen_saver_floater_do_draw (screen_saver, floater, context))
+ {
+ gtk_main_quit ();
+ break;
+ }
+ }
+
+ cairo_destroy (context);
+
+ screen_saver->draw_ops_pending = TRUE;
+ screen_saver->frame_count++;
+}
+
+static void
+screen_saver_update_state (ScreenSaver *screen_saver,
+ gdouble time)
+{
+ GList *tmp;
+
+ tmp = screen_saver->floaters;
+ while (tmp != NULL)
+ {
+ ScreenSaverFloater *floater;
+ floater = (ScreenSaverFloater *) tmp->data;
+
+ screen_saver_floater_update_state (screen_saver, floater, time);
+
+ if (GTK_WIDGET_REALIZED (screen_saver->drawing_area)
+ && (floater->bounds.width > 0) && (floater->bounds.height > 0))
+ {
+ gint size;
+ size = CLAMP ((int) (FLOATER_MAX_SIZE * floater->scale),
+ FLOATER_MIN_SIZE, FLOATER_MAX_SIZE);
+
+ gtk_widget_queue_draw_area (screen_saver->drawing_area,
+ floater->bounds.x,
+ floater->bounds.y,
+ floater->bounds.width,
+ floater->bounds.height);
+
+ /* the edges could concievably be spread across two
+ * pixels so we add +2 to invalidated region
+ */
+ if (screen_saver->should_do_rotations)
+ gtk_widget_queue_draw_area (screen_saver->drawing_area,
+ (int) (floater->position.x -
+ .5 * G_SQRT2 * size),
+ (int) (floater->position.y -
+ .5 * G_SQRT2 * size),
+ G_SQRT2 * size + 2,
+ G_SQRT2 * size + 2);
+ else
+ gtk_widget_queue_draw_area (screen_saver->drawing_area,
+ (int) (floater->position.x -
+ .5 * size),
+ (int) (floater->position.y -
+ .5 * size),
+ size + 2, size + 2);
+
+ if (screen_saver->should_show_paths)
+ gtk_widget_queue_draw (screen_saver->drawing_area);
+ }
+
+ tmp = tmp->next;
+ }
+}
+
+static void
+screen_saver_get_initial_state (ScreenSaver *screen_saver)
+{
+ screen_saver->first_update_time = screen_saver_get_timestamp (screen_saver);
+ screen_saver_update_state (screen_saver, 0.0);
+}
+
+static gboolean
+screen_saver_do_update_state (ScreenSaver *screen_saver)
+{
+ gdouble current_update_time;
+
+ /* flush pending requests to the X server and block for
+ * replies before proceeding to help prevent the X server from
+ * getting overrun with requests
+ */
+ if (screen_saver->draw_ops_pending)
+ {
+ gdk_flush ();
+ screen_saver->draw_ops_pending = FALSE;
+ }
+
+ current_update_time = screen_saver_get_timestamp (screen_saver);
+ screen_saver_update_state (screen_saver, current_update_time -
+ screen_saver->first_update_time);
+ screen_saver->update_count++;
+ return TRUE;
+}
+
+static gboolean
+screen_saver_do_update_stats (ScreenSaver *screen_saver)
+{
+ gdouble last_calculated_stats_time, seconds_since_last_stats_update;
+
+ last_calculated_stats_time = screen_saver->current_calculated_stats_time;
+ screen_saver->current_calculated_stats_time =
+ screen_saver_get_timestamp (screen_saver);
+ screen_saver->last_calculated_stats_time = last_calculated_stats_time;
+
+ if (abs (last_calculated_stats_time) <= G_MINDOUBLE)
+ return TRUE;
+
+ seconds_since_last_stats_update =
+ screen_saver->current_calculated_stats_time - last_calculated_stats_time;
+
+ screen_saver->updates_per_second =
+ screen_saver->update_count / seconds_since_last_stats_update;
+ screen_saver->frames_per_second =
+ screen_saver->frame_count / seconds_since_last_stats_update;
+
+ screen_saver->update_count = 0;
+ screen_saver->frame_count = 0;
+
+ return TRUE;
+}
+
+static gboolean
+do_print_screen_saver_stats (ScreenSaver *screen_saver)
+{
+
+ g_print ("updates per second: %.2f, frames per second: %.2f, "
+ "image cache %.0f%% full\n",
+ screen_saver_get_updates_per_second (screen_saver),
+ screen_saver_get_frames_per_second (screen_saver),
+ screen_saver_get_image_cache_usage (screen_saver) * 100.0);
+
+ return TRUE;
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ ScreenSaver *screen_saver;
+ GtkWidget *window;
+ GtkWidget *drawing_area;
+
+ GtkStateType state;
+
+ GError *error;
+
+ error = NULL;
+
+ bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ gtk_init_with_args (&argc, &argv,
+ /* translators: the word "image" here
+ * represents a command line argument
+ */
+ _("image - floats images around the screen"),
+ options, GETTEXT_PACKAGE, &error);
+
+
+ if (error != NULL)
+ {
+ g_printerr (_("%s. See --help for usage information.\n"),
+ _(error->message));
+ g_error_free (error);
+ return EX_SOFTWARE;
+ }
+
+ if ((filenames == NULL) || (filenames[0] == NULL) ||
+ (filenames[1] != NULL))
+ {
+ g_printerr (_("You must specify one image. See --help for usage "
+ "information.\n"));
+ return EX_USAGE;
+ }
+
+ window = gs_theme_window_new ();
+
+ g_signal_connect (G_OBJECT (window), "delete-event",
+ G_CALLBACK (gtk_main_quit), NULL);
+
+ drawing_area = gtk_drawing_area_new ();
+
+ state = (GtkStateType) 0;
+ while (state < (GtkStateType) G_N_ELEMENTS (drawing_area->style->bg))
+ {
+ gtk_widget_modify_bg (drawing_area, state, &drawing_area->style->mid[state]);
+ state++;
+ }
+
+ gtk_widget_show (drawing_area);
+ gtk_container_add (GTK_CONTAINER (window), drawing_area);
+
+ screen_saver = screen_saver_new (GTK_DRAWING_AREA (drawing_area),
+ filenames[0], max_floater_count,
+ should_do_rotations, should_show_paths);
+ g_strfreev (filenames);
+
+ if (should_print_stats)
+ g_timeout_add (STAT_PRINT_FREQUENCY,
+ (GSourceFunc) do_print_screen_saver_stats,
+ screen_saver);
+
+ if ((geometry == NULL)
+ || !gtk_window_parse_geometry (GTK_WINDOW (window), geometry))
+ gtk_window_set_default_size (GTK_WINDOW (window), 640, 480);
+
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ screen_saver_free (screen_saver);
+
+ return EX_OK;
+}
diff --git a/savers/footlogo-floaters.desktop.in.in b/savers/footlogo-floaters.desktop.in.in
new file mode 100644
index 0000000..03cdad4
--- /dev/null
+++ b/savers/footlogo-floaters.desktop.in.in
@@ -0,0 +1,10 @@
+[Desktop Entry]
+_Name=Floating Feet
+_Comment=Bubbles the MATE foot logo around the screen
+Exec=@SAVERDIR@/floaters @FLOATERS_FOOT_LOGO_IMAGE@
+TryExec=@SAVERDIR@/floaters
+StartupNotify=false
+Terminal=false
+Type=Application
+Categories=MATE;Screensaver;
+OnlyShowIn=MATE;
diff --git a/savers/gs-theme-engine-marshal.list b/savers/gs-theme-engine-marshal.list
new file mode 100644
index 0000000..70e7616
--- /dev/null
+++ b/savers/gs-theme-engine-marshal.list
@@ -0,0 +1 @@
+BOOLEAN:BOXED
diff --git a/savers/gs-theme-engine.c b/savers/gs-theme-engine.c
new file mode 100644
index 0000000..042bc53
--- /dev/null
+++ b/savers/gs-theme-engine.c
@@ -0,0 +1,226 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * Copyright (C) 2005 William Jon McCann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "gs-theme-engine.h"
+#include "gs-theme-engine-marshal.h"
+
+static void gs_theme_engine_class_init (GSThemeEngineClass *klass);
+static void gs_theme_engine_init (GSThemeEngine *engine);
+static void gs_theme_engine_finalize (GObject *object);
+
+struct GSThemeEnginePrivate
+{
+ gpointer reserved;
+};
+
+#define GS_THEME_ENGINE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GS_TYPE_THEME_ENGINE, GSThemeEnginePrivate))
+
+static GObjectClass *parent_class = NULL;
+
+G_DEFINE_ABSTRACT_TYPE (GSThemeEngine, gs_theme_engine, GTK_TYPE_DRAWING_AREA)
+
+void
+_gs_theme_engine_profile_log (const char *func,
+ const char *note,
+ const char *format,
+ ...)
+{
+ va_list args;
+ char *str;
+ char *formatted;
+
+ va_start (args, format);
+ formatted = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ if (func != NULL)
+ {
+ str = g_strdup_printf ("MARK: %s %s: %s %s", g_get_prgname(), func, note ? note : "", formatted);
+ }
+ else
+ {
+ str = g_strdup_printf ("MARK: %s: %s %s", g_get_prgname(), note ? note : "", formatted);
+ }
+
+ g_free (formatted);
+
+ access (str, F_OK);
+ g_free (str);
+}
+
+static void
+gs_theme_engine_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GSThemeEngine *self;
+
+ self = GS_THEME_ENGINE (object);
+
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_theme_engine_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GSThemeEngine *self;
+
+ self = GS_THEME_ENGINE (object);
+
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_theme_engine_clear (GtkWidget *widget)
+{
+ GdkColor color = { 0, 0x0000, 0x0000, 0x0000 };
+ GdkColormap *colormap;
+ GtkStateType state;
+
+ g_return_if_fail (GS_IS_THEME_ENGINE (widget));
+
+ if (! GTK_WIDGET_VISIBLE (widget))
+ {
+ return;
+ }
+
+ state = (GtkStateType) 0;
+ while (state < (GtkStateType) G_N_ELEMENTS (widget->style->bg))
+ {
+ gtk_widget_modify_bg (widget, state, &color);
+ state++;
+ }
+
+ colormap = gdk_drawable_get_colormap (widget->window);
+ gdk_colormap_alloc_color (colormap, &color, FALSE, TRUE);
+ gdk_window_set_background (widget->window, &color);
+ gdk_window_clear (widget->window);
+ gdk_flush ();
+}
+
+static gboolean
+gs_theme_engine_real_map_event (GtkWidget *widget,
+ GdkEventAny *event)
+{
+ gboolean handled = FALSE;
+
+ gs_theme_engine_clear (widget);
+
+ return handled;
+}
+
+static void
+gs_theme_engine_class_init (GSThemeEngineClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = gs_theme_engine_finalize;
+ object_class->get_property = gs_theme_engine_get_property;
+ object_class->set_property = gs_theme_engine_set_property;
+
+ widget_class->map_event = gs_theme_engine_real_map_event;
+
+ g_type_class_add_private (klass, sizeof (GSThemeEnginePrivate));
+}
+
+static void
+gs_theme_engine_init (GSThemeEngine *engine)
+{
+ engine->priv = GS_THEME_ENGINE_GET_PRIVATE (engine);
+}
+
+static void
+gs_theme_engine_finalize (GObject *object)
+{
+ GSThemeEngine *engine;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GS_IS_THEME_ENGINE (object));
+
+ engine = GS_THEME_ENGINE (object);
+
+ g_return_if_fail (engine->priv != NULL);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+void
+gs_theme_engine_get_window_size (GSThemeEngine *engine,
+ int *width,
+ int *height)
+{
+ if (width != NULL)
+ {
+ *width = 0;
+ }
+ if (height != NULL)
+ {
+ *height = 0;
+ }
+
+ g_return_if_fail (GS_IS_THEME_ENGINE (engine));
+
+ if (! GTK_WIDGET_VISIBLE (GTK_WIDGET (engine)))
+ {
+ return;
+ }
+
+ gdk_window_get_geometry (GTK_WIDGET (engine)->window,
+ NULL,
+ NULL,
+ width,
+ height,
+ NULL);
+}
+
+GdkWindow *
+gs_theme_engine_get_window (GSThemeEngine *engine)
+{
+ g_return_val_if_fail (GS_IS_THEME_ENGINE (engine), NULL);
+
+ return GTK_WIDGET (engine)->window;
+}
diff --git a/savers/gs-theme-engine.c.orig b/savers/gs-theme-engine.c.orig
new file mode 100644
index 0000000..9724e5b
--- /dev/null
+++ b/savers/gs-theme-engine.c.orig
@@ -0,0 +1,216 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * Copyright (C) 2005 William Jon McCann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "gs-theme-engine.h"
+#include "gs-theme-engine-marshal.h"
+
+static void gs_theme_engine_class_init (GSThemeEngineClass *klass);
+static void gs_theme_engine_init (GSThemeEngine *engine);
+static void gs_theme_engine_finalize (GObject *object);
+
+struct GSThemeEnginePrivate
+{
+ gpointer reserved;
+};
+
+#define GS_THEME_ENGINE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GS_TYPE_THEME_ENGINE, GSThemeEnginePrivate))
+
+static GObjectClass *parent_class = NULL;
+
+G_DEFINE_ABSTRACT_TYPE (GSThemeEngine, gs_theme_engine, GTK_TYPE_DRAWING_AREA)
+
+void
+_gs_theme_engine_profile_log (const char *func,
+ const char *note,
+ const char *format,
+ ...)
+{
+ va_list args;
+ char *str;
+ char *formatted;
+
+ va_start (args, format);
+ formatted = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ if (func != NULL) {
+ str = g_strdup_printf ("MARK: %s %s: %s %s", g_get_prgname(), func, note ? note : "", formatted);
+ } else {
+ str = g_strdup_printf ("MARK: %s: %s %s", g_get_prgname(), note ? note : "", formatted);
+ }
+
+ g_free (formatted);
+
+ access (str, F_OK);
+ g_free (str);
+}
+
+static void
+gs_theme_engine_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GSThemeEngine *self;
+
+ self = GS_THEME_ENGINE (object);
+
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_theme_engine_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GSThemeEngine *self;
+
+ self = GS_THEME_ENGINE (object);
+
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_theme_engine_clear (GtkWidget *widget)
+{
+ GdkColor color = { 0, 0x0000, 0x0000, 0x0000 };
+ GdkColormap *colormap;
+ GtkStateType state;
+
+ g_return_if_fail (GS_IS_THEME_ENGINE (widget));
+
+ if (! GTK_WIDGET_VISIBLE (widget)) {
+ return;
+ }
+
+ state = (GtkStateType) 0;
+ while (state < (GtkStateType) G_N_ELEMENTS (widget->style->bg)) {
+ gtk_widget_modify_bg (widget, state, &color);
+ state++;
+ }
+
+ colormap = gdk_drawable_get_colormap (widget->window);
+ gdk_colormap_alloc_color (colormap, &color, FALSE, TRUE);
+ gdk_window_set_background (widget->window, &color);
+ gdk_window_clear (widget->window);
+ gdk_flush ();
+}
+
+static gboolean
+gs_theme_engine_real_map_event (GtkWidget *widget,
+ GdkEventAny *event)
+{
+ gboolean handled = FALSE;
+
+ gs_theme_engine_clear (widget);
+
+ return handled;
+}
+
+static void
+gs_theme_engine_class_init (GSThemeEngineClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = gs_theme_engine_finalize;
+ object_class->get_property = gs_theme_engine_get_property;
+ object_class->set_property = gs_theme_engine_set_property;
+
+ widget_class->map_event = gs_theme_engine_real_map_event;
+
+ g_type_class_add_private (klass, sizeof (GSThemeEnginePrivate));
+}
+
+static void
+gs_theme_engine_init (GSThemeEngine *engine)
+{
+ engine->priv = GS_THEME_ENGINE_GET_PRIVATE (engine);
+}
+
+static void
+gs_theme_engine_finalize (GObject *object)
+{
+ GSThemeEngine *engine;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GS_IS_THEME_ENGINE (object));
+
+ engine = GS_THEME_ENGINE (object);
+
+ g_return_if_fail (engine->priv != NULL);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+void
+gs_theme_engine_get_window_size (GSThemeEngine *engine,
+ int *width,
+ int *height)
+{
+ if (width != NULL) {
+ *width = 0;
+ }
+ if (height != NULL) {
+ *height = 0;
+ }
+
+ g_return_if_fail (GS_IS_THEME_ENGINE (engine));
+
+ if (! GTK_WIDGET_VISIBLE (GTK_WIDGET (engine))) {
+ return;
+ }
+
+ gdk_window_get_geometry (GTK_WIDGET (engine)->window,
+ NULL,
+ NULL,
+ width,
+ height,
+ NULL);
+}
+
+GdkWindow *
+gs_theme_engine_get_window (GSThemeEngine *engine)
+{
+ g_return_val_if_fail (GS_IS_THEME_ENGINE (engine), NULL);
+
+ return GTK_WIDGET (engine)->window;
+}
diff --git a/savers/gs-theme-engine.h b/savers/gs-theme-engine.h
new file mode 100644
index 0000000..b4dda3a
--- /dev/null
+++ b/savers/gs-theme-engine.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2005 William Jon McCann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authors: William Jon McCann <[email protected]>
+ *
+ */
+
+#ifndef __GS_THEME_ENGINE_H
+#define __GS_THEME_ENGINE_H
+
+#include <glib.h>
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_THEME_ENGINE (gs_theme_engine_get_type ())
+#define GS_THEME_ENGINE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GS_TYPE_THEME_ENGINE, GSThemeEngine))
+#define GS_THEME_ENGINE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GS_TYPE_THEME_ENGINE, GSThemeEngineClass))
+#define GS_IS_THEME_ENGINE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GS_TYPE_THEME_ENGINE))
+#define GS_IS_THEME_ENGINE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GS_TYPE_THEME_ENGINE))
+#define GS_THEME_ENGINE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GS_TYPE_THEME_ENGINE, GSThemeEngineClass))
+
+typedef struct GSThemeEnginePrivate GSThemeEnginePrivate;
+
+typedef struct
+{
+ GtkDrawingArea parent;
+ GSThemeEnginePrivate *priv;
+} GSThemeEngine;
+
+typedef struct
+{
+ GtkDrawingAreaClass parent_class;
+
+ /* for signals later if needed */
+ gpointer reserved_1;
+ gpointer reserved_2;
+ gpointer reserved_3;
+ gpointer reserved_4;
+} GSThemeEngineClass;
+
+GType gs_theme_engine_get_type (void);
+
+void gs_theme_engine_get_window_size (GSThemeEngine *engine,
+ int *width,
+ int *height);
+GdkWindow *gs_theme_engine_get_window (GSThemeEngine *engine);
+
+#define ENABLE_PROFILING 1
+#ifdef ENABLE_PROFILING
+#ifdef G_HAVE_ISO_VARARGS
+#define gs_theme_engine_profile_start(...) _gs_theme_engine_profile_log (G_STRFUNC, "start", __VA_ARGS__)
+#define gs_theme_engine_profile_end(...) _gs_theme_engine_profile_log (G_STRFUNC, "end", __VA_ARGS__)
+#define gs_theme_engine_profile_msg(...) _gs_theme_engine_profile_log (NULL, NULL, __VA_ARGS__)
+#elif defined(G_HAVE_GNUC_VARARGS)
+#define gs_theme_engine_profile_start(format...) _gs_theme_engine_profile_log (G_STRFUNC, "start", format)
+#define gs_theme_engine_profile_end(format...) _gs_theme_engine_profile_log (G_STRFUNC, "end", format)
+#define gs_theme_engine_profile_msg(format...) _gs_theme_engine_profile_log (NULL, NULL, format)
+#endif
+#else
+#define gs_theme_engine_profile_start(...)
+#define gs_theme_engine_profile_end(...)
+#define gs_theme_engine_profile_msg(...)
+#endif
+
+void _gs_theme_engine_profile_log (const char *func,
+ const char *note,
+ const char *format,
+ ...) G_GNUC_PRINTF (3, 4);
+
+G_END_DECLS
+
+#endif /* __GS_THEME_ENGINE_H */
diff --git a/savers/gs-theme-engine.h.orig b/savers/gs-theme-engine.h.orig
new file mode 100644
index 0000000..5c6fd30
--- /dev/null
+++ b/savers/gs-theme-engine.h.orig
@@ -0,0 +1,88 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2005 William Jon McCann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authors: William Jon McCann <[email protected]>
+ *
+ */
+
+#ifndef __GS_THEME_ENGINE_H
+#define __GS_THEME_ENGINE_H
+
+#include <glib.h>
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_THEME_ENGINE (gs_theme_engine_get_type ())
+#define GS_THEME_ENGINE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GS_TYPE_THEME_ENGINE, GSThemeEngine))
+#define GS_THEME_ENGINE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GS_TYPE_THEME_ENGINE, GSThemeEngineClass))
+#define GS_IS_THEME_ENGINE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GS_TYPE_THEME_ENGINE))
+#define GS_IS_THEME_ENGINE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GS_TYPE_THEME_ENGINE))
+#define GS_THEME_ENGINE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GS_TYPE_THEME_ENGINE, GSThemeEngineClass))
+
+typedef struct GSThemeEnginePrivate GSThemeEnginePrivate;
+
+typedef struct
+{
+ GtkDrawingArea parent;
+ GSThemeEnginePrivate *priv;
+} GSThemeEngine;
+
+typedef struct
+{
+ GtkDrawingAreaClass parent_class;
+
+ /* for signals later if needed */
+ gpointer reserved_1;
+ gpointer reserved_2;
+ gpointer reserved_3;
+ gpointer reserved_4;
+} GSThemeEngineClass;
+
+GType gs_theme_engine_get_type (void);
+
+void gs_theme_engine_get_window_size (GSThemeEngine *engine,
+ int *width,
+ int *height);
+GdkWindow *gs_theme_engine_get_window (GSThemeEngine *engine);
+
+#define ENABLE_PROFILING 1
+#ifdef ENABLE_PROFILING
+#ifdef G_HAVE_ISO_VARARGS
+#define gs_theme_engine_profile_start(...) _gs_theme_engine_profile_log (G_STRFUNC, "start", __VA_ARGS__)
+#define gs_theme_engine_profile_end(...) _gs_theme_engine_profile_log (G_STRFUNC, "end", __VA_ARGS__)
+#define gs_theme_engine_profile_msg(...) _gs_theme_engine_profile_log (NULL, NULL, __VA_ARGS__)
+#elif defined(G_HAVE_GNUC_VARARGS)
+#define gs_theme_engine_profile_start(format...) _gs_theme_engine_profile_log (G_STRFUNC, "start", format)
+#define gs_theme_engine_profile_end(format...) _gs_theme_engine_profile_log (G_STRFUNC, "end", format)
+#define gs_theme_engine_profile_msg(format...) _gs_theme_engine_profile_log (NULL, NULL, format)
+#endif
+#else
+#define gs_theme_engine_profile_start(...)
+#define gs_theme_engine_profile_end(...)
+#define gs_theme_engine_profile_msg(...)
+#endif
+
+void _gs_theme_engine_profile_log (const char *func,
+ const char *note,
+ const char *format,
+ ...) G_GNUC_PRINTF (3, 4);
+
+G_END_DECLS
+
+#endif /* __GS_THEME_ENGINE_H */
diff --git a/savers/gs-theme-window.c b/savers/gs-theme-window.c
new file mode 100644
index 0000000..bdc1c88
--- /dev/null
+++ b/savers/gs-theme-window.c
@@ -0,0 +1,214 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * gs-theme-window.c - special toplevel for screensavers
+ *
+ * Copyright (C) 2005 Ray Strode <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Originally written by: Ray Strode <[email protected]>
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gi18n.h>
+
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include "gs-theme-window.h"
+
+static void gs_theme_window_finalize (GObject *object);
+static void gs_theme_window_real_realize (GtkWidget *widget);
+
+static GObjectClass *parent_class = NULL;
+
+G_DEFINE_TYPE (GSThemeWindow, gs_theme_window, GTK_TYPE_WINDOW)
+
+#define MIN_SIZE 10
+
+static void
+gs_theme_window_class_init (GSThemeWindowClass *klass)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ widget_class = GTK_WIDGET_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = gs_theme_window_finalize;
+
+ widget_class->realize = gs_theme_window_real_realize;
+}
+
+static void
+force_no_pixmap_background (GtkWidget *widget)
+{
+ static gboolean first_time = TRUE;
+
+ if (first_time)
+ {
+ gtk_rc_parse_string ("\n"
+ " style \"gs-theme-engine-style\"\n"
+ " {\n"
+ " bg_pixmap[NORMAL] = \"<none>\"\n"
+ " bg_pixmap[ACTIVE] = \"<none>\"\n"
+ " bg_pixmap[PRELIGHT] = \"<none>\"\n"
+ " bg_pixmap[SELECTED] = \"<none>\"\n"
+ " bg_pixmap[INSENSITIVE] = \"<none>\"\n"
+ " bg[NORMAL] = \"#000000\"\n"
+ " bg[ACTIVE] = \"#000000\"\n"
+ " bg[PRELIGHT] = \"#000000\"\n"
+ " bg[SELECTED] = \"#000000\"\n"
+ " bg[INSENSITIVE] = \"#000000\"\n"
+ " }\n"
+ " widget \"gs-window*\" style : highest \"gs-theme-engine-style\"\n"
+ "\n");
+ first_time = FALSE;
+ }
+
+ gtk_widget_set_name (widget, "gs-window");
+}
+
+static void
+gs_theme_window_init (GSThemeWindow *window)
+{
+ force_no_pixmap_background (GTK_WIDGET (window));
+}
+
+static void
+gs_theme_window_finalize (GObject *object)
+{
+ GSThemeWindow *window;
+ GObjectClass *parent_class;
+
+ window = GS_THEME_WINDOW (object);
+
+ parent_class = G_OBJECT_CLASS (gs_theme_window_parent_class);
+
+ if (parent_class->finalize != NULL)
+ parent_class->finalize (object);
+}
+
+static void
+gs_theme_window_real_realize (GtkWidget *widget)
+{
+ GdkWindow *window;
+ Window remote_xwindow;
+ GtkRequisition requisition;
+ GtkAllocation allocation;
+ const char *preview_xid;
+ int x;
+ int y;
+ int width;
+ int height;
+ int event_mask;
+
+ event_mask = 0;
+ window = NULL;
+ preview_xid = g_getenv ("XSCREENSAVER_WINDOW");
+
+ if (preview_xid != NULL)
+ {
+ char *end;
+
+ remote_xwindow = (Window) strtoul (preview_xid, &end, 0);
+
+ if ((remote_xwindow != 0) && (end != NULL) &&
+ ((*end == ' ') || (*end == '\0')) &&
+ ((remote_xwindow < G_MAXULONG) || (errno != ERANGE)))
+ {
+ window = gdk_window_foreign_new (remote_xwindow);
+
+ if (window != NULL)
+ {
+ /* This is a kludge; we need to set the same
+ * flags gs-window-x11.c does, to ensure they
+ * don't get unset by gtk_window_map() later.
+ */
+ gtk_window_set_decorated (GTK_WINDOW (widget), FALSE);
+
+ gtk_window_set_skip_taskbar_hint (GTK_WINDOW (widget), TRUE);
+ gtk_window_set_skip_pager_hint (GTK_WINDOW (widget), TRUE);
+
+ gtk_window_set_keep_above (GTK_WINDOW (widget), TRUE);
+
+ gtk_window_fullscreen (GTK_WINDOW (widget));
+
+ event_mask = GDK_EXPOSURE_MASK | GDK_STRUCTURE_MASK;
+ gtk_widget_set_events (widget, gtk_widget_get_events (widget) | event_mask);
+ }
+ }
+ }
+
+ if (window == NULL)
+ {
+ GtkWidgetClass *parent_class;
+
+ parent_class = GTK_WIDGET_CLASS (gs_theme_window_parent_class);
+
+ if (parent_class->realize != NULL)
+ parent_class->realize (widget);
+
+ return;
+ }
+
+ gtk_style_set_background (widget->style,
+ window,
+ GTK_STATE_NORMAL);
+ gdk_window_set_decorations (window, (GdkWMDecoration) 0);
+ gdk_window_set_events (window, gdk_window_get_events (window) | event_mask);
+
+ widget->window = window;
+ gdk_window_set_user_data (window, widget);
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+ gdk_window_get_geometry (window, &x, &y, &width, &height, NULL);
+
+ if (width < MIN_SIZE || height < MIN_SIZE)
+ {
+ g_critical ("This window is way too small to use");
+ exit (1);
+ }
+
+ gtk_widget_size_request (widget, &requisition);
+ allocation.x = x;
+ allocation.y = y;
+ allocation.width = width;
+ allocation.height = height;
+ gtk_widget_size_allocate (widget, &allocation);
+ gtk_window_resize (GTK_WINDOW (widget), width, height);
+}
+
+GtkWidget *
+gs_theme_window_new (void)
+{
+ GSThemeWindow *window;
+
+ window = g_object_new (GS_TYPE_THEME_WINDOW,
+ "type", GTK_WINDOW_TOPLEVEL,
+ NULL);
+
+ return GTK_WIDGET (window);
+}
diff --git a/savers/gs-theme-window.c.orig b/savers/gs-theme-window.c.orig
new file mode 100644
index 0000000..805387c
--- /dev/null
+++ b/savers/gs-theme-window.c.orig
@@ -0,0 +1,208 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * gs-theme-window.c - special toplevel for screensavers
+ *
+ * Copyright (C) 2005 Ray Strode <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Originally written by: Ray Strode <[email protected]>
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gi18n.h>
+
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include "gs-theme-window.h"
+
+static void gs_theme_window_finalize (GObject *object);
+static void gs_theme_window_real_realize (GtkWidget *widget);
+
+static GObjectClass *parent_class = NULL;
+
+G_DEFINE_TYPE (GSThemeWindow, gs_theme_window, GTK_TYPE_WINDOW)
+
+#define MIN_SIZE 10
+
+static void
+gs_theme_window_class_init (GSThemeWindowClass *klass)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ widget_class = GTK_WIDGET_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = gs_theme_window_finalize;
+
+ widget_class->realize = gs_theme_window_real_realize;
+}
+
+static void
+force_no_pixmap_background (GtkWidget *widget)
+{
+ static gboolean first_time = TRUE;
+
+ if (first_time) {
+ gtk_rc_parse_string ("\n"
+ " style \"gs-theme-engine-style\"\n"
+ " {\n"
+ " bg_pixmap[NORMAL] = \"<none>\"\n"
+ " bg_pixmap[ACTIVE] = \"<none>\"\n"
+ " bg_pixmap[PRELIGHT] = \"<none>\"\n"
+ " bg_pixmap[SELECTED] = \"<none>\"\n"
+ " bg_pixmap[INSENSITIVE] = \"<none>\"\n"
+ " bg[NORMAL] = \"#000000\"\n"
+ " bg[ACTIVE] = \"#000000\"\n"
+ " bg[PRELIGHT] = \"#000000\"\n"
+ " bg[SELECTED] = \"#000000\"\n"
+ " bg[INSENSITIVE] = \"#000000\"\n"
+ " }\n"
+ " widget \"gs-window*\" style : highest \"gs-theme-engine-style\"\n"
+ "\n");
+ first_time = FALSE;
+ }
+
+ gtk_widget_set_name (widget, "gs-window");
+}
+
+static void
+gs_theme_window_init (GSThemeWindow *window)
+{
+ force_no_pixmap_background (GTK_WIDGET (window));
+}
+
+static void
+gs_theme_window_finalize (GObject *object)
+{
+ GSThemeWindow *window;
+ GObjectClass *parent_class;
+
+ window = GS_THEME_WINDOW (object);
+
+ parent_class = G_OBJECT_CLASS (gs_theme_window_parent_class);
+
+ if (parent_class->finalize != NULL)
+ parent_class->finalize (object);
+}
+
+static void
+gs_theme_window_real_realize (GtkWidget *widget)
+{
+ GdkWindow *window;
+ Window remote_xwindow;
+ GtkRequisition requisition;
+ GtkAllocation allocation;
+ const char *preview_xid;
+ int x;
+ int y;
+ int width;
+ int height;
+ int event_mask;
+
+ event_mask = 0;
+ window = NULL;
+ preview_xid = g_getenv ("XSCREENSAVER_WINDOW");
+
+ if (preview_xid != NULL) {
+ char *end;
+
+ remote_xwindow = (Window) strtoul (preview_xid, &end, 0);
+
+ if ((remote_xwindow != 0) && (end != NULL) &&
+ ((*end == ' ') || (*end == '\0')) &&
+ ((remote_xwindow < G_MAXULONG) || (errno != ERANGE))) {
+ window = gdk_window_foreign_new (remote_xwindow);
+
+ if (window != NULL) {
+ /* This is a kludge; we need to set the same
+ * flags gs-window-x11.c does, to ensure they
+ * don't get unset by gtk_window_map() later.
+ */
+ gtk_window_set_decorated (GTK_WINDOW (widget), FALSE);
+
+ gtk_window_set_skip_taskbar_hint (GTK_WINDOW (widget), TRUE);
+ gtk_window_set_skip_pager_hint (GTK_WINDOW (widget), TRUE);
+
+ gtk_window_set_keep_above (GTK_WINDOW (widget), TRUE);
+
+ gtk_window_fullscreen (GTK_WINDOW (widget));
+
+ event_mask = GDK_EXPOSURE_MASK | GDK_STRUCTURE_MASK;
+ gtk_widget_set_events (widget, gtk_widget_get_events (widget) | event_mask);
+ }
+ }
+ }
+
+ if (window == NULL) {
+ GtkWidgetClass *parent_class;
+
+ parent_class = GTK_WIDGET_CLASS (gs_theme_window_parent_class);
+
+ if (parent_class->realize != NULL)
+ parent_class->realize (widget);
+
+ return;
+ }
+
+ gtk_style_set_background (widget->style,
+ window,
+ GTK_STATE_NORMAL);
+ gdk_window_set_decorations (window, (GdkWMDecoration) 0);
+ gdk_window_set_events (window, gdk_window_get_events (window) | event_mask);
+
+ widget->window = window;
+ gdk_window_set_user_data (window, widget);
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+ gdk_window_get_geometry (window, &x, &y, &width, &height, NULL);
+
+ if (width < MIN_SIZE || height < MIN_SIZE) {
+ g_critical ("This window is way too small to use");
+ exit (1);
+ }
+
+ gtk_widget_size_request (widget, &requisition);
+ allocation.x = x;
+ allocation.y = y;
+ allocation.width = width;
+ allocation.height = height;
+ gtk_widget_size_allocate (widget, &allocation);
+ gtk_window_resize (GTK_WINDOW (widget), width, height);
+}
+
+GtkWidget *
+gs_theme_window_new (void)
+{
+ GSThemeWindow *window;
+
+ window = g_object_new (GS_TYPE_THEME_WINDOW,
+ "type", GTK_WINDOW_TOPLEVEL,
+ NULL);
+
+ return GTK_WIDGET (window);
+}
diff --git a/savers/gs-theme-window.h b/savers/gs-theme-window.h
new file mode 100644
index 0000000..8223828
--- /dev/null
+++ b/savers/gs-theme-window.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * gs-theme-window.h - special toplevel for screensavers
+ *
+ * Copyright (C) 2005 Ray Strode <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Originally written by: Ray Strode <[email protected]>
+ */
+
+#ifndef GS_THEME_WINDOW_H
+#define GS_THEME_WINDOW_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_THEME_WINDOW (gs_theme_window_get_type ())
+#define GS_THEME_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GS_TYPE_THEME_WINDOW, GSThemeWindow))
+#define GS_THEME_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GS_TYPE_THEME_WINDOW, GSThemeWindowClass))
+#define GS_IS_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GS_TYPE_THEME_WINDOW))
+#define GS_IS_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GS_TYPE_THEME_WINDOW))
+#define GS_THEME_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GS_TYPE_THEME_WINDOW, GSThemeWindowClass))
+
+typedef struct _GSThemeWindow GSThemeWindow;
+typedef struct _GSThemeWindowClass GSThemeWindowClass;
+
+struct _GSThemeWindow
+{
+ GtkWindow parent;
+
+ /*< private >*/
+ /* reserved for priv pointer */
+ gpointer reserved;
+};
+
+struct _GSThemeWindowClass
+{
+ GtkWindowClass parent_class;
+
+ /* for signals later if needed */
+ gpointer reserved_1;
+ gpointer reserved_2;
+ gpointer reserved_3;
+ gpointer reserved_4;
+};
+
+#ifndef GS_HIDE_FUNCTION_DECLARATIONS
+GType gs_theme_window_get_type (void);
+GtkWidget *gs_theme_window_new (void);
+#endif
+
+G_END_DECLS
+#endif /* GS_THEME_WINDOW_H */
diff --git a/savers/gs-theme-window.h.orig b/savers/gs-theme-window.h.orig
new file mode 100644
index 0000000..cfc5edf
--- /dev/null
+++ b/savers/gs-theme-window.h.orig
@@ -0,0 +1,72 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * gs-theme-window.h - special toplevel for screensavers
+ *
+ * Copyright (C) 2005 Ray Strode <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Originally written by: Ray Strode <[email protected]>
+ */
+
+#ifndef GS_THEME_WINDOW_H
+#define GS_THEME_WINDOW_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_THEME_WINDOW (gs_theme_window_get_type ())
+#define GS_THEME_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GS_TYPE_THEME_WINDOW, GSThemeWindow))
+#define GS_THEME_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GS_TYPE_THEME_WINDOW, GSThemeWindowClass))
+#define GS_IS_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GS_TYPE_THEME_WINDOW))
+#define GS_IS_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GS_TYPE_THEME_WINDOW))
+#define GS_THEME_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GS_TYPE_THEME_WINDOW, GSThemeWindowClass))
+
+typedef struct _GSThemeWindow GSThemeWindow;
+typedef struct _GSThemeWindowClass GSThemeWindowClass;
+
+struct _GSThemeWindow
+{
+ GtkWindow parent;
+
+ /*< private >*/
+ /* reserved for priv pointer */
+ gpointer reserved;
+};
+
+struct _GSThemeWindowClass
+{
+ GtkWindowClass parent_class;
+
+ /* for signals later if needed */
+ gpointer reserved_1;
+ gpointer reserved_2;
+ gpointer reserved_3;
+ gpointer reserved_4;
+};
+
+#ifndef GS_HIDE_FUNCTION_DECLARATIONS
+GType gs_theme_window_get_type (void);
+GtkWidget *gs_theme_window_new (void);
+#endif
+
+G_END_DECLS
+#endif /* GS_THEME_WINDOW_H */
diff --git a/savers/gste-popsquares.c b/savers/gste-popsquares.c
new file mode 100644
index 0000000..6a6f2e3
--- /dev/null
+++ b/savers/gste-popsquares.c
@@ -0,0 +1,622 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * Copyright (C) 2005 William Jon McCann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "gs-theme-engine.h"
+#include "gste-popsquares.h"
+
+static void gste_popsquares_class_init (GSTEPopsquaresClass *klass);
+static void gste_popsquares_init (GSTEPopsquares *engine);
+static void gste_popsquares_finalize (GObject *object);
+
+typedef struct _square
+{
+ int x, y, w, h;
+ int color;
+} square;
+
+struct GSTEPopsquaresPrivate
+{
+ guint timeout_id;
+
+ int ncolors;
+ int subdivision;
+
+ GdkGC *gc;
+ GdkColor *colors;
+ square *squares;
+
+};
+
+#define GSTE_POPSQUARES_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSTE_TYPE_POPSQUARES, GSTEPopsquaresPrivate))
+
+static GObjectClass *parent_class = NULL;
+
+G_DEFINE_TYPE (GSTEPopsquares, gste_popsquares, GS_TYPE_THEME_ENGINE)
+
+static void
+hsv_to_rgb (int h,
+ double s,
+ double v,
+ unsigned short *r,
+ unsigned short *g,
+ unsigned short *b)
+{
+ double H, S, V, R, G, B;
+ double p1, p2, p3;
+ double f;
+ int i;
+
+ if (s < 0)
+ {
+ s = 0;
+ }
+ if (v < 0)
+ {
+ v = 0;
+ }
+ if (s > 1)
+ {
+ s = 1;
+ }
+ if (v > 1)
+ {
+ v = 1;
+ }
+
+ S = s;
+ V = v;
+ H = (h % 360) / 60.0;
+ i = H;
+ f = H - i;
+ p1 = V * (1 - S);
+ p2 = V * (1 - (S * f));
+ p3 = V * (1 - (S * (1 - f)));
+
+ if (i == 0)
+ {
+ R = V;
+ G = p3;
+ B = p1;
+ }
+ else if (i == 1)
+ {
+ R = p2;
+ G = V;
+ B = p1;
+ }
+ else if (i == 2)
+ {
+ R = p1;
+ G = V;
+ B = p3;
+ }
+ else if (i == 3)
+ {
+ R = p1;
+ G = p2;
+ B = V;
+ }
+ else if (i == 4)
+ {
+ R = p3;
+ G = p1;
+ B = V;
+ }
+ else
+ {
+ R = V;
+ G = p1;
+ B = p2;
+ }
+
+ *r = R * 65535;
+ *g = G * 65535;
+ *b = B * 65535;
+}
+
+static void
+rgb_to_hsv (unsigned short r,
+ unsigned short g,
+ unsigned short b,
+ int *h,
+ double *s,
+ double *v)
+{
+ double R, G, B, H, S, V;
+ double cmax, cmin;
+ double cmm;
+ int imax;
+
+ R = ((double) r) / 65535.0;
+ G = ((double) g) / 65535.0;
+ B = ((double) b) / 65535.0;
+ cmax = R;
+ cmin = G;
+ imax = 1;
+
+ if (cmax < G)
+ {
+ cmax = G;
+ cmin = R;
+ imax = 2;
+ }
+ if (cmax < B)
+ {
+ cmax = B;
+ imax = 3;
+ }
+ if (cmin > B)
+ {
+ cmin = B;
+ }
+
+ cmm = cmax - cmin;
+ V = cmax;
+
+ if (cmm == 0)
+ {
+ S = H = 0;
+ }
+ else
+ {
+ S = cmm / cmax;
+ if (imax == 1)
+ {
+ H = (G - B) / cmm;
+ }
+ else if (imax == 2)
+ {
+ H = 2.0 + (B - R) / cmm;
+ }
+ else
+ {
+ /*if (imax == 3)*/
+ H = 4.0 + (R - G) / cmm;
+ }
+
+ if (H < 0)
+ {
+ H += 6.0;
+ }
+ }
+
+ *h = (H * 60.0);
+ *s = S;
+ *v = V;
+}
+
+static void
+make_color_ramp (GdkColormap *colormap,
+ int h1,
+ double s1,
+ double v1,
+ int h2,
+ double s2,
+ double v2,
+ GdkColor *colors,
+ int n_colors,
+ gboolean closed,
+ gboolean allocate,
+ gboolean writable)
+{
+ double dh, ds, dv; /* deltas */
+ int i;
+ int ncolors, wanted;
+ int total_ncolors = n_colors;
+
+ wanted = total_ncolors;
+ if (closed)
+ {
+ wanted = (wanted / 2) + 1;
+ }
+
+ ncolors = total_ncolors;
+
+ memset (colors, 0, n_colors * sizeof (*colors));
+
+ if (closed)
+ {
+ ncolors = (ncolors / 2) + 1;
+ }
+
+ /* Note: unlike other routines in this module, this function assumes that
+ if h1 and h2 are more than 180 degrees apart, then the desired direction
+ is always from h1 to h2 (rather than the shorter path.) make_uniform
+ depends on this.
+ */
+ dh = ((double)h2 - (double)h1) / ncolors;
+ ds = (s2 - s1) / ncolors;
+ dv = (v2 - v1) / ncolors;
+
+ for (i = 0; i < ncolors; i++)
+ {
+ hsv_to_rgb ((int) (h1 + (i * dh)),
+ (s1 + (i * ds)),
+ (v1 + (i * dv)),
+ &colors [i].red,
+ &colors [i].green,
+ &colors [i].blue);
+ if (allocate)
+ {
+ gdk_colormap_alloc_color (colormap,
+ &colors [i],
+ writable,
+ TRUE);
+ }
+ }
+
+ if (closed)
+ {
+ for (i = ncolors; i < n_colors; i++)
+ {
+ colors [i] = colors [n_colors - i];
+ }
+ }
+
+}
+
+static void
+randomize_square_colors (square *squares,
+ int nsquares,
+ int ncolors)
+{
+ int i;
+ square *s;
+
+ s = squares;
+
+ for (i = 0; i < nsquares; i++)
+ {
+ s[i].color = g_random_int_range (0, ncolors);
+ }
+}
+
+static void
+set_colors (GdkWindow *window,
+ GdkColor *fg,
+ GdkColor *bg)
+{
+ GtkWidget *widget;
+ GdkColor color;
+
+ widget = gtk_invisible_new ();
+
+ color = widget->style->dark [GTK_STATE_SELECTED];
+ fg->red = color.red;
+ fg->green = color.green;
+ fg->blue = color.blue;
+ color = widget->style->bg [GTK_STATE_SELECTED];
+ bg->red = color.red;
+ bg->green = color.green;
+ bg->blue = color.blue;
+}
+
+static void
+gste_popsquares_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GSTEPopsquares *self;
+
+ self = GSTE_POPSQUARES (object);
+
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gste_popsquares_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GSTEPopsquares *self;
+
+ self = GSTE_POPSQUARES (object);
+
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+setup_squares (GSTEPopsquares *pop)
+{
+ int window_width;
+ int window_height;
+ int nsquares;
+ int x, y;
+ int sw, sh, gw, gh;
+ GdkWindow *window;
+
+ window = gs_theme_engine_get_window (GS_THEME_ENGINE (pop));
+
+ if (window == NULL)
+ {
+ return;
+ }
+
+ gs_theme_engine_get_window_size (GS_THEME_ENGINE (pop), &window_width, &window_height);
+
+ sw = window_width / pop->priv->subdivision;
+ sh = window_height / pop->priv->subdivision;
+
+ gw = pop->priv->subdivision;
+ gh = pop->priv->subdivision;
+ nsquares = gw * gh;
+
+ if (pop->priv->squares)
+ {
+ g_free (pop->priv->squares);
+ }
+ pop->priv->squares = g_new0 (square, nsquares);
+
+ for (y = 0; y < gh; y++)
+ {
+ for (x = 0; x < gw; x++)
+ {
+ square *s = (square *) &pop->priv->squares [gw * y + x];
+ s->w = sw;
+ s->h = sh;
+ s->x = x * sw;
+ s->y = y * sh;
+ }
+ }
+}
+
+static void
+setup_colors (GSTEPopsquares *pop)
+{
+ double s1, v1, s2, v2 = 0;
+ int h1, h2 = 0;
+ int nsquares;
+ GdkColor fg;
+ GdkColor bg;
+ GdkWindow *window;
+
+ window = gs_theme_engine_get_window (GS_THEME_ENGINE (pop));
+
+ if (window == NULL)
+ {
+ return;
+ }
+
+ set_colors (window, &fg, &bg);
+
+ if (pop->priv->gc)
+ {
+ g_object_unref (pop->priv->gc);
+ }
+ pop->priv->gc = gdk_gc_new (window);
+
+ if (pop->priv->colors)
+ {
+ g_free (pop->priv->colors);
+ }
+ pop->priv->colors = g_new0 (GdkColor, pop->priv->ncolors);
+
+ rgb_to_hsv (fg.red, fg.green, fg.blue, &h1, &s1, &v1);
+ rgb_to_hsv (bg.red, bg.green, bg.blue, &h2, &s2, &v2);
+
+ make_color_ramp (gtk_widget_get_colormap (GTK_WIDGET (pop)),
+ h1, s1, v1,
+ h2, s2, v2,
+ pop->priv->colors,
+ pop->priv->ncolors,
+ TRUE,
+ TRUE,
+ FALSE);
+
+ nsquares = pop->priv->subdivision * pop->priv->subdivision;
+
+ randomize_square_colors (pop->priv->squares, nsquares, pop->priv->ncolors);
+}
+
+static void
+gste_popsquares_real_show (GtkWidget *widget)
+{
+ GSTEPopsquares *pop = GSTE_POPSQUARES (widget);
+
+ /* start */
+ setup_squares (pop);
+ setup_colors (pop);
+
+ if (GTK_WIDGET_CLASS (parent_class)->show)
+ {
+ GTK_WIDGET_CLASS (parent_class)->show (widget);
+ }
+}
+
+static gboolean
+gste_popsquares_real_expose (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ gboolean handled = FALSE;
+
+ /* draw */
+
+ /* FIXME: should double buffer? */
+
+ if (GTK_WIDGET_CLASS (parent_class)->expose_event)
+ {
+ handled = GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
+ }
+
+ return handled;
+}
+
+static gboolean
+gste_popsquares_real_configure (GtkWidget *widget,
+ GdkEventConfigure *event)
+{
+ GSTEPopsquares *pop = GSTE_POPSQUARES (widget);
+ gboolean handled = FALSE;
+
+ /* resize */
+
+ /* just reset everything */
+ setup_squares (pop);
+ setup_colors (pop);
+
+ /* schedule a redraw */
+ gtk_widget_queue_draw (widget);
+
+ if (GTK_WIDGET_CLASS (parent_class)->configure_event)
+ {
+ handled = GTK_WIDGET_CLASS (parent_class)->configure_event (widget, event);
+ }
+
+ return handled;
+}
+
+static void
+gste_popsquares_class_init (GSTEPopsquaresClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = gste_popsquares_finalize;
+ object_class->get_property = gste_popsquares_get_property;
+ object_class->set_property = gste_popsquares_set_property;
+
+ widget_class->show = gste_popsquares_real_show;
+ widget_class->expose_event = gste_popsquares_real_expose;
+ widget_class->configure_event = gste_popsquares_real_configure;
+
+ g_type_class_add_private (klass, sizeof (GSTEPopsquaresPrivate));
+}
+
+static gboolean
+draw_iter (GSTEPopsquares *pop)
+{
+ int border = 1;
+ gboolean twitch = FALSE;
+ int x, y;
+ int sw, sh, gw, gh;
+ int nsquares;
+ int window_width;
+ int window_height;
+ GdkWindow *window;
+
+ window = gs_theme_engine_get_window (GS_THEME_ENGINE (pop));
+
+ if (window == NULL)
+ {
+ return TRUE;
+ }
+
+ gs_theme_engine_get_window_size (GS_THEME_ENGINE (pop),
+ &window_width,
+ &window_height);
+ sw = window_width / pop->priv->subdivision;
+ sh = window_height / pop->priv->subdivision;
+
+ gw = pop->priv->subdivision;
+ gh = pop->priv->subdivision;
+ nsquares = gw * gh;
+
+ for (y = 0; y < gh; y++)
+ {
+ for (x = 0; x < gw; x++)
+ {
+ square *s = (square *) &pop->priv->squares [gw * y + x];
+
+ gdk_gc_set_foreground (pop->priv->gc, &(pop->priv->colors [s->color]));
+ gdk_draw_rectangle (window, pop->priv->gc, TRUE, s->x, s->y,
+ border ? s->w - border : s->w,
+ border ? s->h - border : s->h);
+ s->color++;
+
+ if (s->color == pop->priv->ncolors)
+ {
+ if (twitch && ((g_random_int_range (0, 4)) == 0))
+ {
+ randomize_square_colors (pop->priv->squares, nsquares, pop->priv->ncolors);
+ }
+ else
+ {
+ s->color = g_random_int_range (0, pop->priv->ncolors);
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+gste_popsquares_init (GSTEPopsquares *pop)
+{
+ int delay;
+
+ pop->priv = GSTE_POPSQUARES_GET_PRIVATE (pop);
+
+ pop->priv->ncolors = 128;
+ pop->priv->subdivision = 5;
+
+ delay = 25;
+ pop->priv->timeout_id = g_timeout_add (delay, (GSourceFunc)draw_iter, pop);
+}
+
+static void
+gste_popsquares_finalize (GObject *object)
+{
+ GSTEPopsquares *pop;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSTE_IS_POPSQUARES (object));
+
+ pop = GSTE_POPSQUARES (object);
+
+ g_return_if_fail (pop->priv != NULL);
+
+ if (pop->priv->timeout_id > 0)
+ {
+ g_source_remove (pop->priv->timeout_id);
+ pop->priv->timeout_id = 0;
+ }
+
+ g_free (pop->priv->squares);
+ g_free (pop->priv->colors);
+ g_object_unref (pop->priv->gc);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
diff --git a/savers/gste-popsquares.c.orig b/savers/gste-popsquares.c.orig
new file mode 100644
index 0000000..cda78c7
--- /dev/null
+++ b/savers/gste-popsquares.c.orig
@@ -0,0 +1,563 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * Copyright (C) 2005 William Jon McCann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "gs-theme-engine.h"
+#include "gste-popsquares.h"
+
+static void gste_popsquares_class_init (GSTEPopsquaresClass *klass);
+static void gste_popsquares_init (GSTEPopsquares *engine);
+static void gste_popsquares_finalize (GObject *object);
+
+typedef struct _square {
+ int x, y, w, h;
+ int color;
+} square;
+
+struct GSTEPopsquaresPrivate
+{
+ guint timeout_id;
+
+ int ncolors;
+ int subdivision;
+
+ GdkGC *gc;
+ GdkColor *colors;
+ square *squares;
+
+};
+
+#define GSTE_POPSQUARES_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSTE_TYPE_POPSQUARES, GSTEPopsquaresPrivate))
+
+static GObjectClass *parent_class = NULL;
+
+G_DEFINE_TYPE (GSTEPopsquares, gste_popsquares, GS_TYPE_THEME_ENGINE)
+
+static void
+hsv_to_rgb (int h,
+ double s,
+ double v,
+ unsigned short *r,
+ unsigned short *g,
+ unsigned short *b)
+{
+ double H, S, V, R, G, B;
+ double p1, p2, p3;
+ double f;
+ int i;
+
+ if (s < 0) {
+ s = 0;
+ }
+ if (v < 0) {
+ v = 0;
+ }
+ if (s > 1) {
+ s = 1;
+ }
+ if (v > 1) {
+ v = 1;
+ }
+
+ S = s; V = v;
+ H = (h % 360) / 60.0;
+ i = H;
+ f = H - i;
+ p1 = V * (1 - S);
+ p2 = V * (1 - (S * f));
+ p3 = V * (1 - (S * (1 - f)));
+
+ if (i == 0) {
+ R = V;
+ G = p3;
+ B = p1;
+ } else if (i == 1) {
+ R = p2;
+ G = V;
+ B = p1;
+ } else if (i == 2) {
+ R = p1;
+ G = V;
+ B = p3;
+ } else if (i == 3) {
+ R = p1;
+ G = p2;
+ B = V;
+ } else if (i == 4) {
+ R = p3;
+ G = p1;
+ B = V;
+ } else {
+ R = V;
+ G = p1;
+ B = p2;
+ }
+
+ *r = R * 65535;
+ *g = G * 65535;
+ *b = B * 65535;
+}
+
+static void
+rgb_to_hsv (unsigned short r,
+ unsigned short g,
+ unsigned short b,
+ int *h,
+ double *s,
+ double *v)
+{
+ double R, G, B, H, S, V;
+ double cmax, cmin;
+ double cmm;
+ int imax;
+
+ R = ((double) r) / 65535.0;
+ G = ((double) g) / 65535.0;
+ B = ((double) b) / 65535.0;
+ cmax = R;
+ cmin = G;
+ imax = 1;
+
+ if (cmax < G) {
+ cmax = G; cmin = R; imax = 2;
+ }
+ if (cmax < B) {
+ cmax = B; imax = 3;
+ }
+ if (cmin > B) {
+ cmin = B;
+ }
+
+ cmm = cmax - cmin;
+ V = cmax;
+
+ if (cmm == 0) {
+ S = H = 0;
+ } else {
+ S = cmm / cmax;
+ if (imax == 1) {
+ H = (G - B) / cmm;
+ } else if (imax == 2) {
+ H = 2.0 + (B - R) / cmm;
+ } else {
+ /*if (imax == 3)*/
+ H = 4.0 + (R - G) / cmm;
+ }
+
+ if (H < 0) {
+ H += 6.0;
+ }
+ }
+
+ *h = (H * 60.0);
+ *s = S;
+ *v = V;
+}
+
+static void
+make_color_ramp (GdkColormap *colormap,
+ int h1,
+ double s1,
+ double v1,
+ int h2,
+ double s2,
+ double v2,
+ GdkColor *colors,
+ int n_colors,
+ gboolean closed,
+ gboolean allocate,
+ gboolean writable)
+{
+ double dh, ds, dv; /* deltas */
+ int i;
+ int ncolors, wanted;
+ int total_ncolors = n_colors;
+
+ wanted = total_ncolors;
+ if (closed) {
+ wanted = (wanted / 2) + 1;
+ }
+
+ ncolors = total_ncolors;
+
+ memset (colors, 0, n_colors * sizeof (*colors));
+
+ if (closed) {
+ ncolors = (ncolors / 2) + 1;
+ }
+
+ /* Note: unlike other routines in this module, this function assumes that
+ if h1 and h2 are more than 180 degrees apart, then the desired direction
+ is always from h1 to h2 (rather than the shorter path.) make_uniform
+ depends on this.
+ */
+ dh = ((double)h2 - (double)h1) / ncolors;
+ ds = (s2 - s1) / ncolors;
+ dv = (v2 - v1) / ncolors;
+
+ for (i = 0; i < ncolors; i++) {
+ hsv_to_rgb ((int) (h1 + (i * dh)),
+ (s1 + (i * ds)),
+ (v1 + (i * dv)),
+ &colors [i].red,
+ &colors [i].green,
+ &colors [i].blue);
+ if (allocate) {
+ gdk_colormap_alloc_color (colormap,
+ &colors [i],
+ writable,
+ TRUE);
+ }
+ }
+
+ if (closed) {
+ for (i = ncolors; i < n_colors; i++) {
+ colors [i] = colors [n_colors - i];
+ }
+ }
+
+}
+
+static void
+randomize_square_colors (square *squares,
+ int nsquares,
+ int ncolors)
+{
+ int i;
+ square *s;
+
+ s = squares;
+
+ for (i = 0; i < nsquares; i++) {
+ s[i].color = g_random_int_range (0, ncolors);
+ }
+}
+
+static void
+set_colors (GdkWindow *window,
+ GdkColor *fg,
+ GdkColor *bg)
+{
+ GtkWidget *widget;
+ GdkColor color;
+
+ widget = gtk_invisible_new ();
+
+ color = widget->style->dark [GTK_STATE_SELECTED];
+ fg->red = color.red;
+ fg->green = color.green;
+ fg->blue = color.blue;
+ color = widget->style->bg [GTK_STATE_SELECTED];
+ bg->red = color.red;
+ bg->green = color.green;
+ bg->blue = color.blue;
+}
+
+static void
+gste_popsquares_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GSTEPopsquares *self;
+
+ self = GSTE_POPSQUARES (object);
+
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gste_popsquares_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GSTEPopsquares *self;
+
+ self = GSTE_POPSQUARES (object);
+
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+setup_squares (GSTEPopsquares *pop)
+{
+ int window_width;
+ int window_height;
+ int nsquares;
+ int x, y;
+ int sw, sh, gw, gh;
+ GdkWindow *window;
+
+ window = gs_theme_engine_get_window (GS_THEME_ENGINE (pop));
+
+ if (window == NULL) {
+ return;
+ }
+
+ gs_theme_engine_get_window_size (GS_THEME_ENGINE (pop), &window_width, &window_height);
+
+ sw = window_width / pop->priv->subdivision;
+ sh = window_height / pop->priv->subdivision;
+
+ gw = pop->priv->subdivision;
+ gh = pop->priv->subdivision;
+ nsquares = gw * gh;
+
+ if (pop->priv->squares) {
+ g_free (pop->priv->squares);
+ }
+ pop->priv->squares = g_new0 (square, nsquares);
+
+ for (y = 0; y < gh; y++) {
+ for (x = 0; x < gw; x++) {
+ square *s = (square *) &pop->priv->squares [gw * y + x];
+ s->w = sw;
+ s->h = sh;
+ s->x = x * sw;
+ s->y = y * sh;
+ }
+ }
+}
+
+static void
+setup_colors (GSTEPopsquares *pop)
+{
+ double s1, v1, s2, v2 = 0;
+ int h1, h2 = 0;
+ int nsquares;
+ GdkColor fg;
+ GdkColor bg;
+ GdkWindow *window;
+
+ window = gs_theme_engine_get_window (GS_THEME_ENGINE (pop));
+
+ if (window == NULL) {
+ return;
+ }
+
+ set_colors (window, &fg, &bg);
+
+ if (pop->priv->gc) {
+ g_object_unref (pop->priv->gc);
+ }
+ pop->priv->gc = gdk_gc_new (window);
+
+ if (pop->priv->colors) {
+ g_free (pop->priv->colors);
+ }
+ pop->priv->colors = g_new0 (GdkColor, pop->priv->ncolors);
+
+ rgb_to_hsv (fg.red, fg.green, fg.blue, &h1, &s1, &v1);
+ rgb_to_hsv (bg.red, bg.green, bg.blue, &h2, &s2, &v2);
+
+ make_color_ramp (gtk_widget_get_colormap (GTK_WIDGET (pop)),
+ h1, s1, v1,
+ h2, s2, v2,
+ pop->priv->colors,
+ pop->priv->ncolors,
+ TRUE,
+ TRUE,
+ FALSE);
+
+ nsquares = pop->priv->subdivision * pop->priv->subdivision;
+
+ randomize_square_colors (pop->priv->squares, nsquares, pop->priv->ncolors);
+}
+
+static void
+gste_popsquares_real_show (GtkWidget *widget)
+{
+ GSTEPopsquares *pop = GSTE_POPSQUARES (widget);
+
+ /* start */
+ setup_squares (pop);
+ setup_colors (pop);
+
+ if (GTK_WIDGET_CLASS (parent_class)->show) {
+ GTK_WIDGET_CLASS (parent_class)->show (widget);
+ }
+}
+
+static gboolean
+gste_popsquares_real_expose (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ gboolean handled = FALSE;
+
+ /* draw */
+
+ /* FIXME: should double buffer? */
+
+ if (GTK_WIDGET_CLASS (parent_class)->expose_event) {
+ handled = GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
+ }
+
+ return handled;
+}
+
+static gboolean
+gste_popsquares_real_configure (GtkWidget *widget,
+ GdkEventConfigure *event)
+{
+ GSTEPopsquares *pop = GSTE_POPSQUARES (widget);
+ gboolean handled = FALSE;
+
+ /* resize */
+
+ /* just reset everything */
+ setup_squares (pop);
+ setup_colors (pop);
+
+ /* schedule a redraw */
+ gtk_widget_queue_draw (widget);
+
+ if (GTK_WIDGET_CLASS (parent_class)->configure_event) {
+ handled = GTK_WIDGET_CLASS (parent_class)->configure_event (widget, event);
+ }
+
+ return handled;
+}
+
+static void
+gste_popsquares_class_init (GSTEPopsquaresClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = gste_popsquares_finalize;
+ object_class->get_property = gste_popsquares_get_property;
+ object_class->set_property = gste_popsquares_set_property;
+
+ widget_class->show = gste_popsquares_real_show;
+ widget_class->expose_event = gste_popsquares_real_expose;
+ widget_class->configure_event = gste_popsquares_real_configure;
+
+ g_type_class_add_private (klass, sizeof (GSTEPopsquaresPrivate));
+}
+
+static gboolean
+draw_iter (GSTEPopsquares *pop)
+{
+ int border = 1;
+ gboolean twitch = FALSE;
+ int x, y;
+ int sw, sh, gw, gh;
+ int nsquares;
+ int window_width;
+ int window_height;
+ GdkWindow *window;
+
+ window = gs_theme_engine_get_window (GS_THEME_ENGINE (pop));
+
+ if (window == NULL) {
+ return TRUE;
+ }
+
+ gs_theme_engine_get_window_size (GS_THEME_ENGINE (pop),
+ &window_width,
+ &window_height);
+ sw = window_width / pop->priv->subdivision;
+ sh = window_height / pop->priv->subdivision;
+
+ gw = pop->priv->subdivision;
+ gh = pop->priv->subdivision;
+ nsquares = gw * gh;
+
+ for (y = 0; y < gh; y++) {
+ for (x = 0; x < gw; x++) {
+ square *s = (square *) &pop->priv->squares [gw * y + x];
+
+ gdk_gc_set_foreground (pop->priv->gc, &(pop->priv->colors [s->color]));
+ gdk_draw_rectangle (window, pop->priv->gc, TRUE, s->x, s->y,
+ border ? s->w - border : s->w,
+ border ? s->h - border : s->h);
+ s->color++;
+
+ if (s->color == pop->priv->ncolors) {
+ if (twitch && ((g_random_int_range (0, 4)) == 0)) {
+ randomize_square_colors (pop->priv->squares, nsquares, pop->priv->ncolors);
+ } else {
+ s->color = g_random_int_range (0, pop->priv->ncolors);
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+gste_popsquares_init (GSTEPopsquares *pop)
+{
+ int delay;
+
+ pop->priv = GSTE_POPSQUARES_GET_PRIVATE (pop);
+
+ pop->priv->ncolors = 128;
+ pop->priv->subdivision = 5;
+
+ delay = 25;
+ pop->priv->timeout_id = g_timeout_add (delay, (GSourceFunc)draw_iter, pop);
+}
+
+static void
+gste_popsquares_finalize (GObject *object)
+{
+ GSTEPopsquares *pop;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSTE_IS_POPSQUARES (object));
+
+ pop = GSTE_POPSQUARES (object);
+
+ g_return_if_fail (pop->priv != NULL);
+
+ if (pop->priv->timeout_id > 0) {
+ g_source_remove (pop->priv->timeout_id);
+ pop->priv->timeout_id = 0;
+ }
+
+ g_free (pop->priv->squares);
+ g_free (pop->priv->colors);
+ g_object_unref (pop->priv->gc);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
diff --git a/savers/gste-popsquares.h b/savers/gste-popsquares.h
new file mode 100644
index 0000000..eefb363
--- /dev/null
+++ b/savers/gste-popsquares.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2005 William Jon McCann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authors: William Jon McCann <[email protected]>
+ *
+ */
+
+#ifndef __GSTE_POPSQUARES_H
+#define __GSTE_POPSQUARES_H
+
+#include <glib.h>
+#include <gdk/gdk.h>
+#include "gs-theme-engine.h"
+
+G_BEGIN_DECLS
+
+#define GSTE_TYPE_POPSQUARES (gste_popsquares_get_type ())
+#define GSTE_POPSQUARES(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GSTE_TYPE_POPSQUARES, GSTEPopsquares))
+#define GSTE_POPSQUARES_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GSTE_TYPE_POPSQUARES, GSTEPopsquaresClass))
+#define GSTE_IS_POPSQUARES(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSTE_TYPE_POPSQUARES))
+#define GSTE_IS_POPSQUARES_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GSTE_TYPE_POPSQUARES))
+#define GSTE_POPSQUARES_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSTE_TYPE_POPSQUARES, GSTEPopsquaresClass))
+
+typedef struct GSTEPopsquaresPrivate GSTEPopsquaresPrivate;
+
+typedef struct
+{
+ GSThemeEngine parent;
+ GSTEPopsquaresPrivate *priv;
+} GSTEPopsquares;
+
+typedef struct
+{
+ GSThemeEngineClass parent_class;
+} GSTEPopsquaresClass;
+
+GType gste_popsquares_get_type (void);
+GSThemeEngine *gste_popsquares_new (void);
+
+G_END_DECLS
+
+#endif /* __GSTE_POPSQUARES_H */
diff --git a/savers/gste-popsquares.h.orig b/savers/gste-popsquares.h.orig
new file mode 100644
index 0000000..4139652
--- /dev/null
+++ b/savers/gste-popsquares.h.orig
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2005 William Jon McCann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authors: William Jon McCann <[email protected]>
+ *
+ */
+
+#ifndef __GSTE_POPSQUARES_H
+#define __GSTE_POPSQUARES_H
+
+#include <glib.h>
+#include <gdk/gdk.h>
+#include "gs-theme-engine.h"
+
+G_BEGIN_DECLS
+
+#define GSTE_TYPE_POPSQUARES (gste_popsquares_get_type ())
+#define GSTE_POPSQUARES(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GSTE_TYPE_POPSQUARES, GSTEPopsquares))
+#define GSTE_POPSQUARES_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GSTE_TYPE_POPSQUARES, GSTEPopsquaresClass))
+#define GSTE_IS_POPSQUARES(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSTE_TYPE_POPSQUARES))
+#define GSTE_IS_POPSQUARES_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GSTE_TYPE_POPSQUARES))
+#define GSTE_POPSQUARES_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSTE_TYPE_POPSQUARES, GSTEPopsquaresClass))
+
+typedef struct GSTEPopsquaresPrivate GSTEPopsquaresPrivate;
+
+typedef struct
+{
+ GSThemeEngine parent;
+ GSTEPopsquaresPrivate *priv;
+} GSTEPopsquares;
+
+typedef struct
+{
+ GSThemeEngineClass parent_class;
+} GSTEPopsquaresClass;
+
+GType gste_popsquares_get_type (void);
+GSThemeEngine *gste_popsquares_new (void);
+
+G_END_DECLS
+
+#endif /* __GSTE_POPSQUARES_H */
diff --git a/savers/gste-slideshow.c b/savers/gste-slideshow.c
new file mode 100644
index 0000000..9180be8
--- /dev/null
+++ b/savers/gste-slideshow.c
@@ -0,0 +1,1082 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * Copyright (C) 2005-2006 William Jon McCann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "gs-theme-engine.h"
+#include "gste-slideshow.h"
+
+static void gste_slideshow_class_init (GSTESlideshowClass *klass);
+static void gste_slideshow_init (GSTESlideshow *engine);
+static void gste_slideshow_finalize (GObject *object);
+
+struct GSTESlideshowPrivate
+{
+ /* Image at full opacity */
+ cairo_pattern_t *pat1;
+ /* Image at partial opacity */
+ cairo_pattern_t *pat2;
+ /* Alpha of pat2 */
+ gdouble alpha2;
+ /* edges of pat2 */
+ int pat2top;
+ int pat2bottom;
+ int pat2left;
+ int pat2right;
+
+ /* backbuffer that we do all the alpha drawing into (no round
+ * trips to the X server when the server doesn't support drawing
+ * pixmaps with alpha?) */
+ cairo_surface_t *surf;
+
+ gint64 fade_ticks;
+
+ GThread *load_thread;
+ GAsyncQueue *op_q;
+ GAsyncQueue *results_q;
+
+ guint results_pull_id;
+ guint update_image_id;
+
+ GSList *filename_list;
+ char *images_location;
+ gboolean sort_images;
+ int window_width;
+ int window_height;
+ PangoColor *background_color;
+ gboolean no_stretch_hint;
+
+ guint timeout_id;
+
+ GTimer *timer;
+ gboolean fade_disabled;
+};
+
+enum
+{
+ PROP_0,
+ PROP_IMAGES_LOCATION,
+ PROP_SORT_IMAGES,
+ PROP_SOLID_BACKGROUND,
+ PROP_NO_STRETCH_HINT
+};
+
+#define GSTE_SLIDESHOW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSTE_TYPE_SLIDESHOW, GSTESlideshowPrivate))
+
+static GObjectClass *parent_class = NULL;
+
+G_DEFINE_TYPE (GSTESlideshow, gste_slideshow, GS_TYPE_THEME_ENGINE)
+
+#define N_FADE_TICKS 10
+#define MINIMUM_FPS 3.0
+#define DEFAULT_IMAGES_LOCATION DATADIR "/pixmaps/backgrounds"
+#define IMAGE_LOAD_TIMEOUT 10000
+
+typedef struct _Op
+{
+ char *location;
+ GSTESlideshow *slideshow;
+} Op;
+
+typedef struct _OpResult
+{
+ GdkPixbuf *pixbuf;
+} OpResult;
+
+static gboolean
+push_load_image_func (GSTESlideshow *show)
+{
+ Op *op;
+
+ gs_theme_engine_profile_msg ("Starting a new image load");
+
+ op = g_new (Op, 1);
+
+ op->location = g_strdup (show->priv->images_location);
+ op->slideshow = g_object_ref (show);
+
+ g_async_queue_push (show->priv->op_q, op);
+
+ show->priv->update_image_id = 0;
+
+ return FALSE;
+}
+
+static void
+start_new_load (GSTESlideshow *show,
+ guint timeout)
+{
+ gs_theme_engine_profile_msg ("Scheduling a new image load");
+
+ /* queue a new load */
+ if (show->priv->update_image_id <= 0)
+ {
+ show->priv->update_image_id = g_timeout_add_full (G_PRIORITY_LOW, timeout,
+ (GSourceFunc)push_load_image_func,
+ show, NULL);
+ }
+}
+
+static void
+start_fade (GSTESlideshow *show,
+ GdkPixbuf *pixbuf)
+{
+ int pw;
+ int ph;
+ int x;
+ int y;
+ cairo_t *cr;
+ int window_width;
+ int window_height;
+
+ gs_theme_engine_profile_start ("start");
+
+ window_width = show->priv->window_width;
+ window_height = show->priv->window_height;
+
+ if (show->priv->pat2 != NULL)
+ {
+ cairo_pattern_destroy (show->priv->pat2);
+ }
+
+ pw = gdk_pixbuf_get_width (pixbuf);
+ ph = gdk_pixbuf_get_height (pixbuf);
+ x = (window_width - pw) / 2;
+ y = (window_height - ph) / 2;
+
+ if (gdk_pixbuf_get_has_alpha (pixbuf) && show->priv->background_color)
+ {
+ GdkPixbuf *colored;
+ guint32 color;
+ GdkPixmap *pixmap;
+
+ color = (show->priv->background_color->red << 16)
+ + (show->priv->background_color->green / 256 << 8)
+ + show->priv->background_color->blue / 256;
+ colored = gdk_pixbuf_composite_color_simple (pixbuf,
+ pw, ph,
+ GDK_INTERP_BILINEAR,
+ 255,
+ 256,
+ color,
+ color);
+ pixmap = gdk_pixmap_new (NULL, ph, pw, gdk_visual_get_system ()->depth);
+
+ gdk_draw_pixbuf (pixmap, NULL, colored, 0, 0, 0, 0, -1, -1, GDK_RGB_DITHER_MAX, 0, 0);
+ gdk_pixbuf_get_from_drawable (pixbuf, pixmap, NULL, 0, 0, 0, 0, -1, -1);
+
+ g_object_unref (pixmap);
+
+ g_object_unref(colored);
+ }
+
+ cr = cairo_create (show->priv->surf);
+
+ /* XXX Handle out of memory? */
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, x, y);
+ show->priv->pat2 = cairo_pattern_reference (cairo_get_source (cr));
+ show->priv->pat2top = y;
+ show->priv->pat2bottom = y + ph;
+ show->priv->pat2left = x;
+ show->priv->pat2right = x + pw;
+
+ cairo_destroy (cr);
+
+ show->priv->fade_ticks = 0;
+ g_timer_start (show->priv->timer);
+
+ gs_theme_engine_profile_end ("end");
+}
+
+static void
+finish_fade (GSTESlideshow *show)
+{
+ gs_theme_engine_profile_start ("start");
+
+ if (show->priv->pat1 != NULL)
+ {
+ cairo_pattern_destroy (show->priv->pat1);
+ }
+
+ show->priv->pat1 = show->priv->pat2;
+ show->priv->pat2 = NULL;
+
+ start_new_load (show, IMAGE_LOAD_TIMEOUT);
+
+ gs_theme_engine_profile_end ("end");
+}
+
+static void
+update_display (GSTESlideshow *show)
+{
+ int window_width;
+ int window_height;
+ cairo_t *cr;
+
+ gs_theme_engine_profile_start ("start");
+
+ cr = cairo_create (show->priv->surf);
+
+ gs_theme_engine_get_window_size (GS_THEME_ENGINE (show),
+ &window_width,
+ &window_height);
+
+ if (show->priv->pat2 != NULL)
+ {
+ /* fade out areas not covered by the new image */
+ /* top */
+ cairo_rectangle (cr, 0, 0, window_width, show->priv->pat2top);
+ if (show->priv->background_color)
+ {
+ cairo_set_source_rgba (cr, show->priv->background_color->red / 65535.0,
+ show->priv->background_color->green / 65535.0,
+ show->priv->background_color->blue / 65535.0, show->priv->alpha2);
+ }
+ else
+ {
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, show->priv->alpha2);
+ }
+ cairo_fill (cr);
+ /* left (excluding what's covered by top and bottom) */
+ cairo_rectangle (cr, 0, show->priv->pat2top,
+ show->priv->pat2left,
+ show->priv->pat2bottom - show->priv->pat2top);
+ if (show->priv->background_color)
+ {
+ cairo_set_source_rgba (cr, show->priv->background_color->red / 65535.0,
+ show->priv->background_color->green / 65535.0,
+ show->priv->background_color->blue / 65535.0, show->priv->alpha2);
+ }
+ else
+ {
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, show->priv->alpha2);
+ }
+ cairo_fill (cr);
+ /* bottom */
+ cairo_rectangle (cr, 0, show->priv->pat2bottom, window_width,
+ window_height - show->priv->pat2bottom);
+ if (show->priv->background_color)
+ {
+ cairo_set_source_rgba (cr, show->priv->background_color->red / 65535.0,
+ show->priv->background_color->green / 65535.0,
+ show->priv->background_color->blue / 65535.0, show->priv->alpha2);
+ }
+ else
+ {
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, show->priv->alpha2);
+ }
+ cairo_fill (cr);
+ /* right (excluding what's covered by top and bottom) */
+ cairo_rectangle (cr, show->priv->pat2right,
+ show->priv->pat2top,
+ window_width - show->priv->pat2right,
+ show->priv->pat2bottom - show->priv->pat2top);
+ if (show->priv->background_color)
+ {
+ cairo_set_source_rgba (cr, show->priv->background_color->red / 65535.0,
+ show->priv->background_color->green / 65535.0,
+ show->priv->background_color->blue / 65535.0, show->priv->alpha2);
+ }
+ else
+ {
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, show->priv->alpha2);
+ }
+ cairo_fill (cr);
+
+ gs_theme_engine_profile_start ("paint pattern to surface");
+ cairo_set_source (cr, show->priv->pat2);
+
+ cairo_paint_with_alpha (cr, show->priv->alpha2);
+ gs_theme_engine_profile_end ("paint pattern to surface");
+ }
+ else
+ {
+ if (show->priv->pat1 != NULL)
+ {
+ cairo_set_source (cr, show->priv->pat1);
+ cairo_paint (cr);
+ }
+ }
+
+ cairo_destroy (cr);
+
+ /* paint the image buffer into the window */
+ cr = gdk_cairo_create (GTK_WIDGET (show)->window);
+
+ cairo_set_source_surface (cr, show->priv->surf, 0, 0);
+
+ gs_theme_engine_profile_start ("paint surface to window");
+ cairo_paint (cr);
+ gs_theme_engine_profile_end ("paint surface to window");
+
+ cairo_destroy (cr);
+
+ gs_theme_engine_profile_end ("end");
+}
+
+static gboolean
+draw_iter (GSTESlideshow *show)
+{
+ double old_opacity;
+ double new_opacity;
+
+ if (show->priv->pat2 != NULL)
+ {
+ gdouble fps;
+ gdouble elapsed;
+
+ if (show->priv->fade_disabled)
+ {
+ show->priv->alpha2 = 1.0;
+ update_display (show);
+ finish_fade (show);
+ return TRUE;
+ }
+
+ /* we are in a fade */
+ show->priv->fade_ticks++;
+
+ /*
+ * We have currently drawn pat2 with old_opacity, and we
+ * want to set alpha2 so that drawing pat2 at alpha2
+ * yields it drawn with new_opacity
+ *
+ * Solving
+ * new_opacity = 1 - (1 - alpha2) * (1 - old_opacity)
+ * yields
+ * alpha2 = 1 - (1 - new_opacity) / (1 - old_opacity)
+ *
+ * XXX This assumes that cairo doesn't correct alpha for
+ * the color profile. However, any error is guaranteed
+ * to be cleaned up by the last iteration, where alpha2
+ * becomes 1 because new_opacity is 1.
+ */
+ old_opacity = (double) (show->priv->fade_ticks - 1) /
+ (double) N_FADE_TICKS;
+ new_opacity = (double) show->priv->fade_ticks /
+ (double) N_FADE_TICKS;
+ show->priv->alpha2 = 1.0 - (1.0 - new_opacity) /
+ (1.0 - old_opacity);
+
+ update_display (show);
+
+ elapsed = g_timer_elapsed (show->priv->timer, NULL);
+ fps = (gdouble)show->priv->fade_ticks / elapsed;
+ if (fps < MINIMUM_FPS)
+ {
+ g_warning ("Getting less than %.2f frames per second, disabling fade", MINIMUM_FPS);
+ show->priv->fade_ticks = N_FADE_TICKS - 1;
+ show->priv->fade_disabled = TRUE;
+ }
+
+ if (show->priv->fade_ticks >= N_FADE_TICKS)
+ {
+ finish_fade (show);
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+process_new_pixbuf (GSTESlideshow *show,
+ GdkPixbuf *pixbuf)
+{
+ gs_theme_engine_profile_msg ("Processing a new image");
+
+ if (pixbuf != NULL)
+ {
+ start_fade (show, pixbuf);
+ }
+ else
+ {
+ start_new_load (show, 10);
+ }
+}
+
+static void
+op_result_free (OpResult *result)
+{
+ if (result == NULL)
+ {
+ return;
+ }
+
+ if (result->pixbuf != NULL)
+ {
+ g_object_unref (result->pixbuf);
+ }
+
+ g_free (result);
+}
+
+static gboolean
+results_pull_func (GSTESlideshow *show)
+{
+ OpResult *result;
+
+ GDK_THREADS_ENTER ();
+
+ g_async_queue_lock (show->priv->results_q);
+
+ result = g_async_queue_try_pop_unlocked (show->priv->results_q);
+ g_assert (result);
+
+ while (result != NULL)
+ {
+ process_new_pixbuf (show, result->pixbuf);
+ op_result_free (result);
+
+ result = g_async_queue_try_pop_unlocked (show->priv->results_q);
+ }
+
+ show->priv->results_pull_id = 0;
+
+ g_async_queue_unlock (show->priv->results_q);
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+static GdkPixbuf *
+scale_pixbuf (GdkPixbuf *pixbuf,
+ int max_width,
+ int max_height,
+ gboolean no_stretch_hint)
+{
+ int pw;
+ int ph;
+ float scale_factor_x = 1.0;
+ float scale_factor_y = 1.0;
+ float scale_factor = 1.0;
+
+ pw = gdk_pixbuf_get_width (pixbuf);
+ ph = gdk_pixbuf_get_height (pixbuf);
+
+ /* If the image is less than 256 wide or high then it
+ is probably a thumbnail and we should ignore it */
+ if (pw < 256 || ph < 256)
+ {
+ return NULL;
+ }
+
+ /* Determine which dimension requires the smallest scale. */
+ scale_factor_x = (float) max_width / (float) pw;
+ scale_factor_y = (float) max_height / (float) ph;
+
+ if (scale_factor_x > scale_factor_y)
+ {
+ scale_factor = scale_factor_y;
+ }
+ else
+ {
+ scale_factor = scale_factor_x;
+ }
+
+ /* always scale down, allow to disable scaling up */
+ if (scale_factor < 1.0 || !no_stretch_hint)
+ {
+ int scale_x;
+ int scale_y;
+
+ scale_x = (int) (pw * scale_factor);
+ scale_y = (int) (ph * scale_factor);
+ return gdk_pixbuf_scale_simple (pixbuf,
+ scale_x,
+ scale_y,
+ GDK_INTERP_BILINEAR);
+ }
+ else
+ {
+ return g_object_ref (pixbuf);
+ }
+}
+
+static void
+add_files_to_list (GSList **list,
+ const char *base)
+{
+ GDir *d;
+ const char *d_name;
+
+ d = g_dir_open (base, 0, NULL);
+ if (d == NULL)
+ {
+ g_warning ("Could not open directory: %s", base);
+ return;
+ }
+
+ while ((d_name = g_dir_read_name (d)) != NULL)
+ {
+ char *path;
+
+ /* skip hidden files */
+ if (d_name[0] == '.')
+ {
+ continue;
+ }
+
+ path = g_build_filename (base, d_name, NULL);
+ if (g_file_test (path, G_FILE_TEST_IS_DIR))
+ {
+ add_files_to_list (list, path);
+ g_free (path);
+ }
+ else
+ {
+ *list = g_slist_prepend (*list, path);
+ }
+ }
+
+ g_dir_close (d);
+}
+
+static GSList *
+build_filename_list_local_dir (const char *base)
+{
+ GSList *list = NULL;
+
+ add_files_to_list (&list, base);
+
+ return list;
+}
+
+static int
+gste_strcmp_compare_func (gconstpointer string_a, gconstpointer string_b)
+{
+ return strcmp (string_a == NULL ? "" : string_a,
+ string_b == NULL ? "" : string_b);
+}
+
+
+static GdkPixbuf *
+get_pixbuf_from_local_dir (GSTESlideshow *show,
+ const char *location)
+{
+ GdkPixbuf *pixbuf, *transformed_pixbuf;
+ char *filename;
+ int i;
+ GSList *l;
+
+ /* rebuild the cache */
+ if (show->priv->filename_list == NULL)
+ {
+ show->priv->filename_list = build_filename_list_local_dir (location);
+ }
+
+ if (show->priv->filename_list == NULL)
+ {
+ return NULL;
+ }
+ else
+ {
+ if (show->priv->sort_images)
+ {
+ show->priv->filename_list = g_slist_sort (show->priv->filename_list, gste_strcmp_compare_func);
+ }
+ }
+
+ /* get a random filename if needed */
+ if (! show->priv->sort_images)
+ {
+ i = g_random_int_range (0, g_slist_length (show->priv->filename_list));
+ l = g_slist_nth (show->priv->filename_list, i);
+ }
+ else
+ {
+ l = show->priv->filename_list;
+ }
+ filename = l->data;
+
+ pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
+
+ if (pixbuf != NULL)
+ {
+ transformed_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ g_object_unref (pixbuf);
+ }
+ else
+ {
+ transformed_pixbuf = NULL;
+ }
+
+ g_free (filename);
+ show->priv->filename_list = g_slist_delete_link (show->priv->filename_list, l);
+
+ return transformed_pixbuf;
+}
+
+static GdkPixbuf *
+get_pixbuf_from_location (GSTESlideshow *show,
+ const char *location)
+{
+ GdkPixbuf *pixbuf = NULL;
+ gboolean is_dir;
+
+ if (location == NULL)
+ {
+ return NULL;
+ }
+
+ is_dir = g_file_test (location, G_FILE_TEST_IS_DIR);
+
+ if (is_dir)
+ {
+ pixbuf = get_pixbuf_from_local_dir (show, location);
+ }
+
+ return pixbuf;
+}
+
+static GdkPixbuf *
+get_pixbuf (GSTESlideshow *show,
+ const char *location,
+ int width,
+ int height)
+{
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *scaled = NULL;
+
+ if (location == NULL)
+ {
+ return NULL;
+ }
+
+ pixbuf = get_pixbuf_from_location (show, location);
+
+ if (pixbuf != NULL)
+ {
+ scaled = scale_pixbuf (pixbuf, width, height, show->priv->no_stretch_hint);
+ g_object_unref (pixbuf);
+ }
+
+ return scaled;
+}
+
+static void
+op_load_image (GSTESlideshow *show,
+ const char *location)
+{
+ OpResult *op_result;
+ int window_width;
+ int window_height;
+
+ window_width = show->priv->window_width;
+ window_height = show->priv->window_height;
+
+ op_result = g_new0 (OpResult, 1);
+
+ op_result->pixbuf = get_pixbuf (show,
+ location,
+ window_width,
+ window_height);
+
+ GDK_THREADS_ENTER ();
+ g_async_queue_lock (show->priv->results_q);
+ g_async_queue_push_unlocked (show->priv->results_q, op_result);
+
+ if (show->priv->results_pull_id == 0)
+ {
+ show->priv->results_pull_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
+ (GSourceFunc)results_pull_func,
+ show, NULL);
+ }
+
+ g_async_queue_unlock (show->priv->results_q);
+ GDK_THREADS_LEAVE ();
+}
+
+static gpointer
+load_threadfunc (GAsyncQueue *op_q)
+{
+ Op *op;
+
+ op = g_async_queue_pop (op_q);
+ while (op)
+ {
+ op_load_image (op->slideshow,
+ op->location);
+
+ if (op->slideshow != NULL)
+ {
+ g_object_unref (op->slideshow);
+ }
+ g_free (op->location);
+ g_free (op);
+
+ op = g_async_queue_pop (op_q);
+ }
+
+ return NULL;
+}
+
+void
+gste_slideshow_set_images_location (GSTESlideshow *show,
+ const char *location)
+{
+ g_return_if_fail (GSTE_IS_SLIDESHOW (show));
+
+ g_free (show->priv->images_location);
+ show->priv->images_location = g_strdup (location);
+}
+
+
+void
+gste_slideshow_set_sort_images (GSTESlideshow *show,
+ gboolean sort_images)
+{
+ g_return_if_fail (GSTE_IS_SLIDESHOW (show));
+
+ show->priv->sort_images = sort_images;
+}
+
+void
+gste_slideshow_set_no_stretch_hint (GSTESlideshow *show,
+ gboolean no_stretch_hint)
+{
+ g_return_if_fail (GSTE_IS_SLIDESHOW (show));
+
+ show->priv->no_stretch_hint = no_stretch_hint;
+}
+
+void
+gste_slideshow_set_background_color (GSTESlideshow *show,
+ const char *background_color)
+{
+ g_return_if_fail (GSTE_IS_SLIDESHOW (show));
+
+ if (show->priv->background_color != NULL)
+ {
+ g_slice_free (PangoColor, show->priv->background_color);
+ show->priv->background_color = NULL;
+ }
+
+ if (background_color != NULL)
+ {
+ show->priv->background_color = g_slice_new (PangoColor);
+
+ if (pango_color_parse (show->priv->background_color, background_color) == FALSE)
+ {
+ g_slice_free (PangoColor, show->priv->background_color);
+ show->priv->background_color = NULL;
+ }
+ }
+}
+
+static void
+gste_slideshow_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GSTESlideshow *self;
+
+ self = GSTE_SLIDESHOW (object);
+
+ switch (prop_id)
+ {
+ case PROP_IMAGES_LOCATION:
+ gste_slideshow_set_images_location (self, g_value_get_string (value));
+ break;
+ case PROP_SORT_IMAGES:
+ gste_slideshow_set_sort_images (self, g_value_get_boolean (value));
+ break;
+ case PROP_SOLID_BACKGROUND:
+ gste_slideshow_set_background_color (self, g_value_get_string (value));
+ break;
+ case PROP_NO_STRETCH_HINT:
+ gste_slideshow_set_no_stretch_hint (self, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gste_slideshow_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GSTESlideshow *self;
+
+ self = GSTE_SLIDESHOW (object);
+
+ switch (prop_id)
+ {
+ case PROP_IMAGES_LOCATION:
+ g_value_set_string (value, self->priv->images_location);
+ break;
+ case PROP_SORT_IMAGES:
+ g_value_set_boolean (value, self->priv->sort_images);
+ break;
+ case PROP_SOLID_BACKGROUND:
+ {
+ char *color = NULL;
+ color = pango_color_to_string (self->priv->background_color);
+ g_value_set_string (value, color);
+ g_free (color);
+ break;
+ }
+ case PROP_NO_STRETCH_HINT:
+ g_value_set_boolean (value, self->priv->no_stretch_hint);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gste_slideshow_real_show (GtkWidget *widget)
+{
+ GSTESlideshow *show = GSTE_SLIDESHOW (widget);
+ int delay;
+
+ if (GTK_WIDGET_CLASS (parent_class)->show)
+ {
+ GTK_WIDGET_CLASS (parent_class)->show (widget);
+ }
+
+ start_new_load (show, 10);
+
+ delay = 25;
+ show->priv->timeout_id = g_timeout_add (delay, (GSourceFunc)draw_iter, show);
+
+ if (show->priv->timer != NULL)
+ {
+ g_timer_destroy (show->priv->timer);
+ }
+ show->priv->timer = g_timer_new ();
+}
+
+static gboolean
+gste_slideshow_real_expose (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ GSTESlideshow *show = GSTE_SLIDESHOW (widget);
+ gboolean handled = FALSE;
+
+ update_display (show);
+
+ if (GTK_WIDGET_CLASS (parent_class)->expose_event)
+ {
+ handled = GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
+ }
+
+ return handled;
+}
+
+static gboolean
+gste_slideshow_real_configure (GtkWidget *widget,
+ GdkEventConfigure *event)
+{
+ GSTESlideshow *show = GSTE_SLIDESHOW (widget);
+ gboolean handled = FALSE;
+ cairo_t *cr;
+
+ /* resize */
+ gs_theme_engine_get_window_size (GS_THEME_ENGINE (show),
+ &show->priv->window_width,
+ &show->priv->window_height);
+
+ gs_theme_engine_profile_msg ("Resize to x:%d y:%d",
+ show->priv->window_width,
+ show->priv->window_height);
+
+ if (show->priv->surf != NULL)
+ {
+ cairo_surface_destroy (show->priv->surf);
+ }
+
+ cr = gdk_cairo_create (widget->window);
+ show->priv->surf = cairo_surface_create_similar (cairo_get_target (cr),
+ CAIRO_CONTENT_COLOR,
+ show->priv->window_width,
+ show->priv->window_height);
+ cairo_destroy (cr);
+
+ /* schedule a redraw */
+ gtk_widget_queue_draw (widget);
+
+ if (GTK_WIDGET_CLASS (parent_class)->configure_event)
+ {
+ handled = GTK_WIDGET_CLASS (parent_class)->configure_event (widget, event);
+ }
+
+ return handled;
+}
+
+static void
+gste_slideshow_class_init (GSTESlideshowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = gste_slideshow_finalize;
+ object_class->get_property = gste_slideshow_get_property;
+ object_class->set_property = gste_slideshow_set_property;
+
+ widget_class->show = gste_slideshow_real_show;
+ widget_class->expose_event = gste_slideshow_real_expose;
+ widget_class->configure_event = gste_slideshow_real_configure;
+
+ g_type_class_add_private (klass, sizeof (GSTESlideshowPrivate));
+
+ g_object_class_install_property (object_class,
+ PROP_IMAGES_LOCATION,
+ g_param_spec_string ("images-location",
+ NULL,
+ NULL,
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_SORT_IMAGES,
+ g_param_spec_boolean ("sort-images",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_SOLID_BACKGROUND,
+ g_param_spec_string ("background-color",
+ NULL,
+ NULL,
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_NO_STRETCH_HINT,
+ g_param_spec_boolean ("no-stretch",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE));
+}
+
+static void
+set_colormap (GtkWidget *widget)
+{
+ GdkScreen *screen;
+ GdkColormap *colormap;
+
+ screen = gtk_widget_get_screen (widget);
+ colormap = gdk_screen_get_rgba_colormap (screen);
+ if (colormap == NULL)
+ {
+ colormap = gdk_screen_get_rgb_colormap (screen);
+ }
+
+ gtk_widget_set_colormap (widget, colormap);
+}
+
+static void
+gste_slideshow_init (GSTESlideshow *show)
+{
+ GError *error;
+
+ show->priv = GSTE_SLIDESHOW_GET_PRIVATE (show);
+
+ show->priv->images_location = g_strdup (DEFAULT_IMAGES_LOCATION);
+
+ show->priv->op_q = g_async_queue_new ();
+ show->priv->results_q = g_async_queue_new ();
+
+ error = NULL;
+ show->priv->load_thread = g_thread_create ((GThreadFunc)load_threadfunc, show->priv->op_q, FALSE, &error);
+ if (show->priv->load_thread == NULL)
+ {
+ g_error ("Could not create a thread to load images: %s",
+ error->message);
+ g_error_free (error);
+ exit (-1);
+ }
+
+ set_colormap (GTK_WIDGET (show));
+}
+
+static void
+gste_slideshow_finalize (GObject *object)
+{
+ GSTESlideshow *show;
+ gpointer result;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSTE_IS_SLIDESHOW (object));
+
+ show = GSTE_SLIDESHOW (object);
+
+ g_return_if_fail (show->priv != NULL);
+
+ if (show->priv->surf)
+ {
+ cairo_surface_destroy (show->priv->surf);
+ }
+
+ if (show->priv->timeout_id > 0)
+ {
+ g_source_remove (show->priv->timeout_id);
+ show->priv->timeout_id = 0;
+ }
+
+ if (show->priv->results_pull_id > 0)
+ {
+ g_source_remove (show->priv->results_pull_id);
+ }
+
+ if (show->priv->results_q != NULL)
+ {
+ result = g_async_queue_try_pop (show->priv->results_q);
+
+ while (result)
+ {
+ result = g_async_queue_try_pop (show->priv->results_q);
+ }
+ g_async_queue_unref (show->priv->results_q);
+ }
+
+ g_free (show->priv->images_location);
+ show->priv->images_location = NULL;
+
+ if (show->priv->background_color)
+ {
+ g_slice_free (PangoColor, show->priv->background_color);
+ show->priv->background_color = NULL;
+ }
+
+ if (show->priv->timer != NULL)
+ {
+ g_timer_destroy (show->priv->timer);
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
diff --git a/savers/gste-slideshow.c.orig b/savers/gste-slideshow.c.orig
new file mode 100644
index 0000000..8ea83bd
--- /dev/null
+++ b/savers/gste-slideshow.c.orig
@@ -0,0 +1,1001 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * Copyright (C) 2005-2006 William Jon McCann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "gs-theme-engine.h"
+#include "gste-slideshow.h"
+
+static void gste_slideshow_class_init (GSTESlideshowClass *klass);
+static void gste_slideshow_init (GSTESlideshow *engine);
+static void gste_slideshow_finalize (GObject *object);
+
+struct GSTESlideshowPrivate
+{
+ /* Image at full opacity */
+ cairo_pattern_t *pat1;
+ /* Image at partial opacity */
+ cairo_pattern_t *pat2;
+ /* Alpha of pat2 */
+ gdouble alpha2;
+ /* edges of pat2 */
+ int pat2top;
+ int pat2bottom;
+ int pat2left;
+ int pat2right;
+
+ /* backbuffer that we do all the alpha drawing into (no round
+ * trips to the X server when the server doesn't support drawing
+ * pixmaps with alpha?) */
+ cairo_surface_t *surf;
+
+ gint64 fade_ticks;
+
+ GThread *load_thread;
+ GAsyncQueue *op_q;
+ GAsyncQueue *results_q;
+
+ guint results_pull_id;
+ guint update_image_id;
+
+ GSList *filename_list;
+ char *images_location;
+ gboolean sort_images;
+ int window_width;
+ int window_height;
+ PangoColor *background_color;
+ gboolean no_stretch_hint;
+
+ guint timeout_id;
+
+ GTimer *timer;
+ gboolean fade_disabled;
+};
+
+enum {
+ PROP_0,
+ PROP_IMAGES_LOCATION,
+ PROP_SORT_IMAGES,
+ PROP_SOLID_BACKGROUND,
+ PROP_NO_STRETCH_HINT
+};
+
+#define GSTE_SLIDESHOW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSTE_TYPE_SLIDESHOW, GSTESlideshowPrivate))
+
+static GObjectClass *parent_class = NULL;
+
+G_DEFINE_TYPE (GSTESlideshow, gste_slideshow, GS_TYPE_THEME_ENGINE)
+
+#define N_FADE_TICKS 10
+#define MINIMUM_FPS 3.0
+#define DEFAULT_IMAGES_LOCATION DATADIR "/pixmaps/backgrounds"
+#define IMAGE_LOAD_TIMEOUT 10000
+
+typedef struct _Op
+{
+ char *location;
+ GSTESlideshow *slideshow;
+} Op;
+
+typedef struct _OpResult
+{
+ GdkPixbuf *pixbuf;
+} OpResult;
+
+static gboolean
+push_load_image_func (GSTESlideshow *show)
+{
+ Op *op;
+
+ gs_theme_engine_profile_msg ("Starting a new image load");
+
+ op = g_new (Op, 1);
+
+ op->location = g_strdup (show->priv->images_location);
+ op->slideshow = g_object_ref (show);
+
+ g_async_queue_push (show->priv->op_q, op);
+
+ show->priv->update_image_id = 0;
+
+ return FALSE;
+}
+
+static void
+start_new_load (GSTESlideshow *show,
+ guint timeout)
+{
+ gs_theme_engine_profile_msg ("Scheduling a new image load");
+
+ /* queue a new load */
+ if (show->priv->update_image_id <= 0) {
+ show->priv->update_image_id = g_timeout_add_full (G_PRIORITY_LOW, timeout,
+ (GSourceFunc)push_load_image_func,
+ show, NULL);
+ }
+}
+
+static void
+start_fade (GSTESlideshow *show,
+ GdkPixbuf *pixbuf)
+{
+ int pw;
+ int ph;
+ int x;
+ int y;
+ cairo_t *cr;
+ int window_width;
+ int window_height;
+
+ gs_theme_engine_profile_start ("start");
+
+ window_width = show->priv->window_width;
+ window_height = show->priv->window_height;
+
+ if (show->priv->pat2 != NULL) {
+ cairo_pattern_destroy (show->priv->pat2);
+ }
+
+ pw = gdk_pixbuf_get_width (pixbuf);
+ ph = gdk_pixbuf_get_height (pixbuf);
+ x = (window_width - pw) / 2;
+ y = (window_height - ph) / 2;
+
+ if (gdk_pixbuf_get_has_alpha (pixbuf) && show->priv->background_color) {
+ GdkPixbuf *colored;
+ guint32 color;
+ GdkPixmap *pixmap;
+
+ color = (show->priv->background_color->red << 16)
+ + (show->priv->background_color->green / 256 << 8)
+ + show->priv->background_color->blue / 256;
+ colored = gdk_pixbuf_composite_color_simple (pixbuf,
+ pw, ph,
+ GDK_INTERP_BILINEAR,
+ 255,
+ 256,
+ color,
+ color);
+ pixmap = gdk_pixmap_new (NULL, ph, pw, gdk_visual_get_system ()->depth);
+
+ gdk_draw_pixbuf (pixmap, NULL, colored, 0, 0, 0, 0, -1, -1, GDK_RGB_DITHER_MAX, 0, 0);
+ gdk_pixbuf_get_from_drawable (pixbuf, pixmap, NULL, 0, 0, 0, 0, -1, -1);
+
+ g_object_unref (pixmap);
+
+ g_object_unref(colored);
+ }
+
+ cr = cairo_create (show->priv->surf);
+
+ /* XXX Handle out of memory? */
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, x, y);
+ show->priv->pat2 = cairo_pattern_reference (cairo_get_source (cr));
+ show->priv->pat2top = y;
+ show->priv->pat2bottom = y + ph;
+ show->priv->pat2left = x;
+ show->priv->pat2right = x + pw;
+
+ cairo_destroy (cr);
+
+ show->priv->fade_ticks = 0;
+ g_timer_start (show->priv->timer);
+
+ gs_theme_engine_profile_end ("end");
+}
+
+static void
+finish_fade (GSTESlideshow *show)
+{
+ gs_theme_engine_profile_start ("start");
+
+ if (show->priv->pat1 != NULL) {
+ cairo_pattern_destroy (show->priv->pat1);
+ }
+
+ show->priv->pat1 = show->priv->pat2;
+ show->priv->pat2 = NULL;
+
+ start_new_load (show, IMAGE_LOAD_TIMEOUT);
+
+ gs_theme_engine_profile_end ("end");
+}
+
+static void
+update_display (GSTESlideshow *show)
+{
+ int window_width;
+ int window_height;
+ cairo_t *cr;
+
+ gs_theme_engine_profile_start ("start");
+
+ cr = cairo_create (show->priv->surf);
+
+ gs_theme_engine_get_window_size (GS_THEME_ENGINE (show),
+ &window_width,
+ &window_height);
+
+ if (show->priv->pat2 != NULL) {
+ /* fade out areas not covered by the new image */
+ /* top */
+ cairo_rectangle (cr, 0, 0, window_width, show->priv->pat2top);
+ if (show->priv->background_color) {
+ cairo_set_source_rgba (cr, show->priv->background_color->red / 65535.0,
+ show->priv->background_color->green / 65535.0,
+ show->priv->background_color->blue / 65535.0, show->priv->alpha2);
+ } else {
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, show->priv->alpha2);
+ }
+ cairo_fill (cr);
+ /* left (excluding what's covered by top and bottom) */
+ cairo_rectangle (cr, 0, show->priv->pat2top,
+ show->priv->pat2left,
+ show->priv->pat2bottom - show->priv->pat2top);
+ if (show->priv->background_color) {
+ cairo_set_source_rgba (cr, show->priv->background_color->red / 65535.0,
+ show->priv->background_color->green / 65535.0,
+ show->priv->background_color->blue / 65535.0, show->priv->alpha2);
+ } else {
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, show->priv->alpha2);
+ }
+ cairo_fill (cr);
+ /* bottom */
+ cairo_rectangle (cr, 0, show->priv->pat2bottom, window_width,
+ window_height - show->priv->pat2bottom);
+ if (show->priv->background_color) {
+ cairo_set_source_rgba (cr, show->priv->background_color->red / 65535.0,
+ show->priv->background_color->green / 65535.0,
+ show->priv->background_color->blue / 65535.0, show->priv->alpha2);
+ } else {
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, show->priv->alpha2);
+ }
+ cairo_fill (cr);
+ /* right (excluding what's covered by top and bottom) */
+ cairo_rectangle (cr, show->priv->pat2right,
+ show->priv->pat2top,
+ window_width - show->priv->pat2right,
+ show->priv->pat2bottom - show->priv->pat2top);
+ if (show->priv->background_color) {
+ cairo_set_source_rgba (cr, show->priv->background_color->red / 65535.0,
+ show->priv->background_color->green / 65535.0,
+ show->priv->background_color->blue / 65535.0, show->priv->alpha2);
+ } else {
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, show->priv->alpha2);
+ }
+ cairo_fill (cr);
+
+ gs_theme_engine_profile_start ("paint pattern to surface");
+ cairo_set_source (cr, show->priv->pat2);
+
+ cairo_paint_with_alpha (cr, show->priv->alpha2);
+ gs_theme_engine_profile_end ("paint pattern to surface");
+ } else {
+ if (show->priv->pat1 != NULL) {
+ cairo_set_source (cr, show->priv->pat1);
+ cairo_paint (cr);
+ }
+ }
+
+ cairo_destroy (cr);
+
+ /* paint the image buffer into the window */
+ cr = gdk_cairo_create (GTK_WIDGET (show)->window);
+
+ cairo_set_source_surface (cr, show->priv->surf, 0, 0);
+
+ gs_theme_engine_profile_start ("paint surface to window");
+ cairo_paint (cr);
+ gs_theme_engine_profile_end ("paint surface to window");
+
+ cairo_destroy (cr);
+
+ gs_theme_engine_profile_end ("end");
+}
+
+static gboolean
+draw_iter (GSTESlideshow *show)
+{
+ double old_opacity;
+ double new_opacity;
+
+ if (show->priv->pat2 != NULL) {
+ gdouble fps;
+ gdouble elapsed;
+
+ if (show->priv->fade_disabled) {
+ show->priv->alpha2 = 1.0;
+ update_display (show);
+ finish_fade (show);
+ return TRUE;
+ }
+
+ /* we are in a fade */
+ show->priv->fade_ticks++;
+
+ /*
+ * We have currently drawn pat2 with old_opacity, and we
+ * want to set alpha2 so that drawing pat2 at alpha2
+ * yields it drawn with new_opacity
+ *
+ * Solving
+ * new_opacity = 1 - (1 - alpha2) * (1 - old_opacity)
+ * yields
+ * alpha2 = 1 - (1 - new_opacity) / (1 - old_opacity)
+ *
+ * XXX This assumes that cairo doesn't correct alpha for
+ * the color profile. However, any error is guaranteed
+ * to be cleaned up by the last iteration, where alpha2
+ * becomes 1 because new_opacity is 1.
+ */
+ old_opacity = (double) (show->priv->fade_ticks - 1) /
+ (double) N_FADE_TICKS;
+ new_opacity = (double) show->priv->fade_ticks /
+ (double) N_FADE_TICKS;
+ show->priv->alpha2 = 1.0 - (1.0 - new_opacity) /
+ (1.0 - old_opacity);
+
+ update_display (show);
+
+ elapsed = g_timer_elapsed (show->priv->timer, NULL);
+ fps = (gdouble)show->priv->fade_ticks / elapsed;
+ if (fps < MINIMUM_FPS) {
+ g_warning ("Getting less than %.2f frames per second, disabling fade", MINIMUM_FPS);
+ show->priv->fade_ticks = N_FADE_TICKS - 1;
+ show->priv->fade_disabled = TRUE;
+ }
+
+ if (show->priv->fade_ticks >= N_FADE_TICKS) {
+ finish_fade (show);
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+process_new_pixbuf (GSTESlideshow *show,
+ GdkPixbuf *pixbuf)
+{
+ gs_theme_engine_profile_msg ("Processing a new image");
+
+ if (pixbuf != NULL) {
+ start_fade (show, pixbuf);
+ } else {
+ start_new_load (show, 10);
+ }
+}
+
+static void
+op_result_free (OpResult *result)
+{
+ if (result == NULL) {
+ return;
+ }
+
+ if (result->pixbuf != NULL) {
+ g_object_unref (result->pixbuf);
+ }
+
+ g_free (result);
+}
+
+static gboolean
+results_pull_func (GSTESlideshow *show)
+{
+ OpResult *result;
+
+ GDK_THREADS_ENTER ();
+
+ g_async_queue_lock (show->priv->results_q);
+
+ result = g_async_queue_try_pop_unlocked (show->priv->results_q);
+ g_assert (result);
+
+ while (result != NULL) {
+ process_new_pixbuf (show, result->pixbuf);
+ op_result_free (result);
+
+ result = g_async_queue_try_pop_unlocked (show->priv->results_q);
+ }
+
+ show->priv->results_pull_id = 0;
+
+ g_async_queue_unlock (show->priv->results_q);
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+static GdkPixbuf *
+scale_pixbuf (GdkPixbuf *pixbuf,
+ int max_width,
+ int max_height,
+ gboolean no_stretch_hint)
+{
+ int pw;
+ int ph;
+ float scale_factor_x = 1.0;
+ float scale_factor_y = 1.0;
+ float scale_factor = 1.0;
+
+ pw = gdk_pixbuf_get_width (pixbuf);
+ ph = gdk_pixbuf_get_height (pixbuf);
+
+ /* If the image is less than 256 wide or high then it
+ is probably a thumbnail and we should ignore it */
+ if (pw < 256 || ph < 256) {
+ return NULL;
+ }
+
+ /* Determine which dimension requires the smallest scale. */
+ scale_factor_x = (float) max_width / (float) pw;
+ scale_factor_y = (float) max_height / (float) ph;
+
+ if (scale_factor_x > scale_factor_y) {
+ scale_factor = scale_factor_y;
+ } else {
+ scale_factor = scale_factor_x;
+ }
+
+ /* always scale down, allow to disable scaling up */
+ if (scale_factor < 1.0 || !no_stretch_hint) {
+ int scale_x;
+ int scale_y;
+
+ scale_x = (int) (pw * scale_factor);
+ scale_y = (int) (ph * scale_factor);
+ return gdk_pixbuf_scale_simple (pixbuf,
+ scale_x,
+ scale_y,
+ GDK_INTERP_BILINEAR);
+ } else {
+ return g_object_ref (pixbuf);
+ }
+}
+
+static void
+add_files_to_list (GSList **list,
+ const char *base)
+{
+ GDir *d;
+ const char *d_name;
+
+ d = g_dir_open (base, 0, NULL);
+ if (d == NULL) {
+ g_warning ("Could not open directory: %s", base);
+ return;
+ }
+
+ while ((d_name = g_dir_read_name (d)) != NULL) {
+ char *path;
+
+ /* skip hidden files */
+ if (d_name[0] == '.') {
+ continue;
+ }
+
+ path = g_build_filename (base, d_name, NULL);
+ if (g_file_test (path, G_FILE_TEST_IS_DIR)) {
+ add_files_to_list (list, path);
+ g_free (path);
+ } else {
+ *list = g_slist_prepend (*list, path);
+ }
+ }
+
+ g_dir_close (d);
+}
+
+static GSList *
+build_filename_list_local_dir (const char *base)
+{
+ GSList *list = NULL;
+
+ add_files_to_list (&list, base);
+
+ return list;
+}
+
+static int
+gste_strcmp_compare_func (gconstpointer string_a, gconstpointer string_b)
+{
+ return strcmp (string_a == NULL ? "" : string_a,
+ string_b == NULL ? "" : string_b);
+}
+
+
+static GdkPixbuf *
+get_pixbuf_from_local_dir (GSTESlideshow *show,
+ const char *location)
+{
+ GdkPixbuf *pixbuf, *transformed_pixbuf;
+ char *filename;
+ int i;
+ GSList *l;
+
+ /* rebuild the cache */
+ if (show->priv->filename_list == NULL) {
+ show->priv->filename_list = build_filename_list_local_dir (location);
+ }
+
+ if (show->priv->filename_list == NULL) {
+ return NULL;
+ } else {
+ if (show->priv->sort_images) {
+ show->priv->filename_list = g_slist_sort (show->priv->filename_list, gste_strcmp_compare_func);
+ }
+ }
+
+ /* get a random filename if needed */
+ if (! show->priv->sort_images) {
+ i = g_random_int_range (0, g_slist_length (show->priv->filename_list));
+ l = g_slist_nth (show->priv->filename_list, i);
+ } else {
+ l = show->priv->filename_list;
+ }
+ filename = l->data;
+
+ pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
+
+ if (pixbuf != NULL) {
+ transformed_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ g_object_unref (pixbuf);
+ } else {
+ transformed_pixbuf = NULL;
+ }
+
+ g_free (filename);
+ show->priv->filename_list = g_slist_delete_link (show->priv->filename_list, l);
+
+ return transformed_pixbuf;
+}
+
+static GdkPixbuf *
+get_pixbuf_from_location (GSTESlideshow *show,
+ const char *location)
+{
+ GdkPixbuf *pixbuf = NULL;
+ gboolean is_dir;
+
+ if (location == NULL) {
+ return NULL;
+ }
+
+ is_dir = g_file_test (location, G_FILE_TEST_IS_DIR);
+
+ if (is_dir) {
+ pixbuf = get_pixbuf_from_local_dir (show, location);
+ }
+
+ return pixbuf;
+}
+
+static GdkPixbuf *
+get_pixbuf (GSTESlideshow *show,
+ const char *location,
+ int width,
+ int height)
+{
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *scaled = NULL;
+
+ if (location == NULL) {
+ return NULL;
+ }
+
+ pixbuf = get_pixbuf_from_location (show, location);
+
+ if (pixbuf != NULL) {
+ scaled = scale_pixbuf (pixbuf, width, height, show->priv->no_stretch_hint);
+ g_object_unref (pixbuf);
+ }
+
+ return scaled;
+}
+
+static void
+op_load_image (GSTESlideshow *show,
+ const char *location)
+{
+ OpResult *op_result;
+ int window_width;
+ int window_height;
+
+ window_width = show->priv->window_width;
+ window_height = show->priv->window_height;
+
+ op_result = g_new0 (OpResult, 1);
+
+ op_result->pixbuf = get_pixbuf (show,
+ location,
+ window_width,
+ window_height);
+
+ GDK_THREADS_ENTER ();
+ g_async_queue_lock (show->priv->results_q);
+ g_async_queue_push_unlocked (show->priv->results_q, op_result);
+
+ if (show->priv->results_pull_id == 0) {
+ show->priv->results_pull_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
+ (GSourceFunc)results_pull_func,
+ show, NULL);
+ }
+
+ g_async_queue_unlock (show->priv->results_q);
+ GDK_THREADS_LEAVE ();
+}
+
+static gpointer
+load_threadfunc (GAsyncQueue *op_q)
+{
+ Op *op;
+
+ op = g_async_queue_pop (op_q);
+ while (op) {
+ op_load_image (op->slideshow,
+ op->location);
+
+ if (op->slideshow != NULL) {
+ g_object_unref (op->slideshow);
+ }
+ g_free (op->location);
+ g_free (op);
+
+ op = g_async_queue_pop (op_q);
+ }
+
+ return NULL;
+}
+
+void
+gste_slideshow_set_images_location (GSTESlideshow *show,
+ const char *location)
+{
+ g_return_if_fail (GSTE_IS_SLIDESHOW (show));
+
+ g_free (show->priv->images_location);
+ show->priv->images_location = g_strdup (location);
+}
+
+
+void
+gste_slideshow_set_sort_images (GSTESlideshow *show,
+ gboolean sort_images)
+{
+ g_return_if_fail (GSTE_IS_SLIDESHOW (show));
+
+ show->priv->sort_images = sort_images;
+}
+
+void
+gste_slideshow_set_no_stretch_hint (GSTESlideshow *show,
+ gboolean no_stretch_hint)
+{
+ g_return_if_fail (GSTE_IS_SLIDESHOW (show));
+
+ show->priv->no_stretch_hint = no_stretch_hint;
+}
+
+void
+gste_slideshow_set_background_color (GSTESlideshow *show,
+ const char *background_color)
+{
+ g_return_if_fail (GSTE_IS_SLIDESHOW (show));
+
+ if (show->priv->background_color != NULL) {
+ g_slice_free (PangoColor, show->priv->background_color);
+ show->priv->background_color = NULL;
+ }
+
+ if (background_color != NULL) {
+ show->priv->background_color = g_slice_new (PangoColor);
+
+ if (pango_color_parse (show->priv->background_color, background_color) == FALSE) {
+ g_slice_free (PangoColor, show->priv->background_color);
+ show->priv->background_color = NULL;
+ }
+ }
+}
+
+static void
+gste_slideshow_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GSTESlideshow *self;
+
+ self = GSTE_SLIDESHOW (object);
+
+ switch (prop_id) {
+ case PROP_IMAGES_LOCATION:
+ gste_slideshow_set_images_location (self, g_value_get_string (value));
+ break;
+ case PROP_SORT_IMAGES:
+ gste_slideshow_set_sort_images (self, g_value_get_boolean (value));
+ break;
+ case PROP_SOLID_BACKGROUND:
+ gste_slideshow_set_background_color (self, g_value_get_string (value));
+ break;
+ case PROP_NO_STRETCH_HINT:
+ gste_slideshow_set_no_stretch_hint (self, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gste_slideshow_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GSTESlideshow *self;
+
+ self = GSTE_SLIDESHOW (object);
+
+ switch (prop_id) {
+ case PROP_IMAGES_LOCATION:
+ g_value_set_string (value, self->priv->images_location);
+ break;
+ case PROP_SORT_IMAGES:
+ g_value_set_boolean (value, self->priv->sort_images);
+ break;
+ case PROP_SOLID_BACKGROUND:
+ {
+ char *color = NULL;
+ color = pango_color_to_string (self->priv->background_color);
+ g_value_set_string (value, color);
+ g_free (color);
+ break;
+ }
+ case PROP_NO_STRETCH_HINT:
+ g_value_set_boolean (value, self->priv->no_stretch_hint);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gste_slideshow_real_show (GtkWidget *widget)
+{
+ GSTESlideshow *show = GSTE_SLIDESHOW (widget);
+ int delay;
+
+ if (GTK_WIDGET_CLASS (parent_class)->show) {
+ GTK_WIDGET_CLASS (parent_class)->show (widget);
+ }
+
+ start_new_load (show, 10);
+
+ delay = 25;
+ show->priv->timeout_id = g_timeout_add (delay, (GSourceFunc)draw_iter, show);
+
+ if (show->priv->timer != NULL) {
+ g_timer_destroy (show->priv->timer);
+ }
+ show->priv->timer = g_timer_new ();
+}
+
+static gboolean
+gste_slideshow_real_expose (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ GSTESlideshow *show = GSTE_SLIDESHOW (widget);
+ gboolean handled = FALSE;
+
+ update_display (show);
+
+ if (GTK_WIDGET_CLASS (parent_class)->expose_event) {
+ handled = GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
+ }
+
+ return handled;
+}
+
+static gboolean
+gste_slideshow_real_configure (GtkWidget *widget,
+ GdkEventConfigure *event)
+{
+ GSTESlideshow *show = GSTE_SLIDESHOW (widget);
+ gboolean handled = FALSE;
+ cairo_t *cr;
+
+ /* resize */
+ gs_theme_engine_get_window_size (GS_THEME_ENGINE (show),
+ &show->priv->window_width,
+ &show->priv->window_height);
+
+ gs_theme_engine_profile_msg ("Resize to x:%d y:%d",
+ show->priv->window_width,
+ show->priv->window_height);
+
+ if (show->priv->surf != NULL) {
+ cairo_surface_destroy (show->priv->surf);
+ }
+
+ cr = gdk_cairo_create (widget->window);
+ show->priv->surf = cairo_surface_create_similar (cairo_get_target (cr),
+ CAIRO_CONTENT_COLOR,
+ show->priv->window_width,
+ show->priv->window_height);
+ cairo_destroy (cr);
+
+ /* schedule a redraw */
+ gtk_widget_queue_draw (widget);
+
+ if (GTK_WIDGET_CLASS (parent_class)->configure_event) {
+ handled = GTK_WIDGET_CLASS (parent_class)->configure_event (widget, event);
+ }
+
+ return handled;
+}
+
+static void
+gste_slideshow_class_init (GSTESlideshowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = gste_slideshow_finalize;
+ object_class->get_property = gste_slideshow_get_property;
+ object_class->set_property = gste_slideshow_set_property;
+
+ widget_class->show = gste_slideshow_real_show;
+ widget_class->expose_event = gste_slideshow_real_expose;
+ widget_class->configure_event = gste_slideshow_real_configure;
+
+ g_type_class_add_private (klass, sizeof (GSTESlideshowPrivate));
+
+ g_object_class_install_property (object_class,
+ PROP_IMAGES_LOCATION,
+ g_param_spec_string ("images-location",
+ NULL,
+ NULL,
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_SORT_IMAGES,
+ g_param_spec_boolean ("sort-images",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_SOLID_BACKGROUND,
+ g_param_spec_string ("background-color",
+ NULL,
+ NULL,
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_NO_STRETCH_HINT,
+ g_param_spec_boolean ("no-stretch",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE));
+}
+
+static void
+set_colormap (GtkWidget *widget)
+{
+ GdkScreen *screen;
+ GdkColormap *colormap;
+
+ screen = gtk_widget_get_screen (widget);
+ colormap = gdk_screen_get_rgba_colormap (screen);
+ if (colormap == NULL) {
+ colormap = gdk_screen_get_rgb_colormap (screen);
+ }
+
+ gtk_widget_set_colormap (widget, colormap);
+}
+
+static void
+gste_slideshow_init (GSTESlideshow *show)
+{
+ GError *error;
+
+ show->priv = GSTE_SLIDESHOW_GET_PRIVATE (show);
+
+ show->priv->images_location = g_strdup (DEFAULT_IMAGES_LOCATION);
+
+ show->priv->op_q = g_async_queue_new ();
+ show->priv->results_q = g_async_queue_new ();
+
+ error = NULL;
+ show->priv->load_thread = g_thread_create ((GThreadFunc)load_threadfunc, show->priv->op_q, FALSE, &error);
+ if (show->priv->load_thread == NULL) {
+ g_error ("Could not create a thread to load images: %s",
+ error->message);
+ g_error_free (error);
+ exit (-1);
+ }
+
+ set_colormap (GTK_WIDGET (show));
+}
+
+static void
+gste_slideshow_finalize (GObject *object)
+{
+ GSTESlideshow *show;
+ gpointer result;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSTE_IS_SLIDESHOW (object));
+
+ show = GSTE_SLIDESHOW (object);
+
+ g_return_if_fail (show->priv != NULL);
+
+ if (show->priv->surf) {
+ cairo_surface_destroy (show->priv->surf);
+ }
+
+ if (show->priv->timeout_id > 0) {
+ g_source_remove (show->priv->timeout_id);
+ show->priv->timeout_id = 0;
+ }
+
+ if (show->priv->results_pull_id > 0) {
+ g_source_remove (show->priv->results_pull_id);
+ }
+
+ if (show->priv->results_q != NULL) {
+ result = g_async_queue_try_pop (show->priv->results_q);
+
+ while (result) {
+ result = g_async_queue_try_pop (show->priv->results_q);
+ }
+ g_async_queue_unref (show->priv->results_q);
+ }
+
+ g_free (show->priv->images_location);
+ show->priv->images_location = NULL;
+
+ if (show->priv->background_color) {
+ g_slice_free (PangoColor, show->priv->background_color);
+ show->priv->background_color = NULL;
+ }
+
+ if (show->priv->timer != NULL) {
+ g_timer_destroy (show->priv->timer);
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
diff --git a/savers/gste-slideshow.h b/savers/gste-slideshow.h
new file mode 100644
index 0000000..b76794f
--- /dev/null
+++ b/savers/gste-slideshow.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2005 William Jon McCann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authors: William Jon McCann <[email protected]>
+ *
+ */
+
+#ifndef __GSTE_SLIDESHOW_H
+#define __GSTE_SLIDESHOW_H
+
+#include <glib.h>
+#include <gdk/gdk.h>
+#include "gs-theme-engine.h"
+
+G_BEGIN_DECLS
+
+#define GSTE_TYPE_SLIDESHOW (gste_slideshow_get_type ())
+#define GSTE_SLIDESHOW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GSTE_TYPE_SLIDESHOW, GSTESlideshow))
+#define GSTE_SLIDESHOW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GSTE_TYPE_SLIDESHOW, GSTESlideshowClass))
+#define GSTE_IS_SLIDESHOW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSTE_TYPE_SLIDESHOW))
+#define GSTE_IS_SLIDESHOW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GSTE_TYPE_SLIDESHOW))
+#define GSTE_SLIDESHOW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSTE_TYPE_SLIDESHOW, GSTESlideshowClass))
+
+typedef struct GSTESlideshowPrivate GSTESlideshowPrivate;
+
+typedef struct
+{
+ GSThemeEngine parent;
+ GSTESlideshowPrivate *priv;
+} GSTESlideshow;
+
+typedef struct
+{
+ GSThemeEngineClass parent_class;
+} GSTESlideshowClass;
+
+GType gste_slideshow_get_type (void);
+GSThemeEngine *gste_slideshow_new (void);
+
+void gste_slideshow_set_images_location (GSTESlideshow *show,
+ const char *location);
+
+void gste_slideshow_set_sort_images (GSTESlideshow *show,
+ gboolean sort_image);
+
+void gste_slideshow_set_background_color (GSTESlideshow *show,
+ const char *background_color);
+
+void gste_slideshow_set_no_stretch_hint (GSTESlideshow *show,
+ gboolean no_stretch_hint);
+
+G_END_DECLS
+
+#endif /* __GSTE_SLIDESHOW_H */
diff --git a/savers/gste-slideshow.h.orig b/savers/gste-slideshow.h.orig
new file mode 100644
index 0000000..5d7ec7c
--- /dev/null
+++ b/savers/gste-slideshow.h.orig
@@ -0,0 +1,69 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2005 William Jon McCann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authors: William Jon McCann <[email protected]>
+ *
+ */
+
+#ifndef __GSTE_SLIDESHOW_H
+#define __GSTE_SLIDESHOW_H
+
+#include <glib.h>
+#include <gdk/gdk.h>
+#include "gs-theme-engine.h"
+
+G_BEGIN_DECLS
+
+#define GSTE_TYPE_SLIDESHOW (gste_slideshow_get_type ())
+#define GSTE_SLIDESHOW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GSTE_TYPE_SLIDESHOW, GSTESlideshow))
+#define GSTE_SLIDESHOW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GSTE_TYPE_SLIDESHOW, GSTESlideshowClass))
+#define GSTE_IS_SLIDESHOW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSTE_TYPE_SLIDESHOW))
+#define GSTE_IS_SLIDESHOW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GSTE_TYPE_SLIDESHOW))
+#define GSTE_SLIDESHOW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSTE_TYPE_SLIDESHOW, GSTESlideshowClass))
+
+typedef struct GSTESlideshowPrivate GSTESlideshowPrivate;
+
+typedef struct
+{
+ GSThemeEngine parent;
+ GSTESlideshowPrivate *priv;
+} GSTESlideshow;
+
+typedef struct
+{
+ GSThemeEngineClass parent_class;
+} GSTESlideshowClass;
+
+GType gste_slideshow_get_type (void);
+GSThemeEngine *gste_slideshow_new (void);
+
+void gste_slideshow_set_images_location (GSTESlideshow *show,
+ const char *location);
+
+void gste_slideshow_set_sort_images (GSTESlideshow *show,
+ gboolean sort_image);
+
+void gste_slideshow_set_background_color (GSTESlideshow *show,
+ const char *background_color);
+
+void gste_slideshow_set_no_stretch_hint (GSTESlideshow *show,
+ gboolean no_stretch_hint);
+
+G_END_DECLS
+
+#endif /* __GSTE_SLIDESHOW_H */
diff --git a/savers/personal-slideshow.desktop.in.in b/savers/personal-slideshow.desktop.in.in
new file mode 100644
index 0000000..80d38bd
--- /dev/null
+++ b/savers/personal-slideshow.desktop.in.in
@@ -0,0 +1,10 @@
+[Desktop Entry]
+_Name=Pictures folder
+_Comment=Display a slideshow from your Pictures folder
+Exec=@SAVERDIR@/slideshow
+TryExec=@SAVERDIR@/slideshow
+StartupNotify=false
+Terminal=false
+Type=Application
+Categories=MATE;Screensaver;
+OnlyShowIn=MATE;
diff --git a/savers/popsquares.c b/savers/popsquares.c
new file mode 100644
index 0000000..22533b6
--- /dev/null
+++ b/savers/popsquares.c
@@ -0,0 +1,73 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * Copyright (C) 2005 William Jon McCann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include <glib/gi18n.h>
+
+#include "gs-theme-window.h"
+#include "gs-theme-engine.h"
+#include "gste-popsquares.h"
+
+int
+main (int argc, char **argv)
+{
+ GSThemeEngine *engine;
+ GtkWidget *window;
+ GError *error;
+
+ bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ error = NULL;
+
+ if (!gtk_init_with_args (&argc, &argv, NULL, NULL, NULL, &error))
+ {
+ g_printerr (_("%s. See --help for usage information.\n"),
+ error->message);
+ g_error_free (error);
+ exit (1);
+ }
+
+ window = gs_theme_window_new ();
+ g_signal_connect (G_OBJECT (window), "delete-event",
+ G_CALLBACK (gtk_main_quit), NULL);
+
+ g_set_prgname ("popsquares");
+
+ engine = g_object_new (GSTE_TYPE_POPSQUARES, NULL);
+ gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (engine));
+
+ gtk_widget_show (GTK_WIDGET (engine));
+
+ gtk_window_set_default_size (GTK_WINDOW (window), 640, 480);
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/savers/popsquares.c.orig b/savers/popsquares.c.orig
new file mode 100644
index 0000000..04ea19d
--- /dev/null
+++ b/savers/popsquares.c.orig
@@ -0,0 +1,72 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * Copyright (C) 2005 William Jon McCann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include <glib/gi18n.h>
+
+#include "gs-theme-window.h"
+#include "gs-theme-engine.h"
+#include "gste-popsquares.h"
+
+int
+main (int argc, char **argv)
+{
+ GSThemeEngine *engine;
+ GtkWidget *window;
+ GError *error;
+
+ bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ error = NULL;
+
+ if (!gtk_init_with_args (&argc, &argv, NULL, NULL, NULL, &error)) {
+ g_printerr (_("%s. See --help for usage information.\n"),
+ error->message);
+ g_error_free (error);
+ exit (1);
+ }
+
+ window = gs_theme_window_new ();
+ g_signal_connect (G_OBJECT (window), "delete-event",
+ G_CALLBACK (gtk_main_quit), NULL);
+
+ g_set_prgname ("popsquares");
+
+ engine = g_object_new (GSTE_TYPE_POPSQUARES, NULL);
+ gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (engine));
+
+ gtk_widget_show (GTK_WIDGET (engine));
+
+ gtk_window_set_default_size (GTK_WINDOW (window), 640, 480);
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/savers/popsquares.desktop.in.in b/savers/popsquares.desktop.in.in
new file mode 100644
index 0000000..261d4d1
--- /dev/null
+++ b/savers/popsquares.desktop.in.in
@@ -0,0 +1,10 @@
+[Desktop Entry]
+_Name=Pop art squares
+_Comment=A pop-art-ish grid of pulsing colors.
+Exec=@SAVERDIR@/popsquares
+TryExec=@SAVERDIR@/popsquares
+StartupNotify=false
+Terminal=false
+Type=Application
+Categories=MATE;Screensaver;
+OnlyShowIn=MATE;
diff --git a/savers/slideshow.c b/savers/slideshow.c
new file mode 100644
index 0000000..e406d34
--- /dev/null
+++ b/savers/slideshow.c
@@ -0,0 +1,143 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * Copyright (C) 2005 William Jon McCann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include "gs-theme-window.h"
+#include "gs-theme-engine.h"
+#include "gste-slideshow.h"
+
+#include "xdg-user-dir-lookup.h"
+
+int
+main (int argc, char **argv)
+{
+ GSThemeEngine *engine;
+ GtkWidget *window;
+ GError *error;
+ gboolean ret;
+ char *location = NULL;
+ char *background_color = NULL;
+ gboolean sort_images = FALSE;
+ gboolean no_stretch = FALSE;
+ GOptionEntry entries [] =
+ {
+ {
+ "location", 0, 0, G_OPTION_ARG_STRING, &location,
+ N_("Location to get images from"), N_("PATH")
+ },
+ {
+ "background-color", 0, 0, G_OPTION_ARG_STRING, &background_color,
+ N_("Color to use for images background"), N_("\"#rrggbb\"")
+ },
+ {
+ "sort-images", 0, 0, G_OPTION_ARG_NONE, &sort_images,
+ N_("Do not randomize pictures from location"), NULL
+ },
+ {
+ "no-stretch", 0, 0, G_OPTION_ARG_NONE, &no_stretch,
+ N_("Do not try to stretch images on screen"), NULL
+ },
+ { NULL }
+ };
+
+ bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ error = NULL;
+
+ g_thread_init (NULL);
+ ret = gtk_init_with_args (&argc, &argv,
+ NULL,
+ entries,
+ NULL,
+ &error);
+ if (! ret)
+ {
+ g_message ("%s", error->message);
+ g_error_free (error);
+ exit (1);
+ }
+
+ g_chdir (g_get_home_dir ());
+
+ g_set_prgname ("slideshow");
+
+ window = gs_theme_window_new ();
+ g_signal_connect (G_OBJECT (window), "delete-event",
+ G_CALLBACK (gtk_main_quit), NULL);
+
+ engine = g_object_new (GSTE_TYPE_SLIDESHOW, NULL);
+
+ if (location == NULL)
+ {
+ location = xdg_user_dir_lookup ("PICTURES");
+ if (location == NULL ||
+ strcmp (location, "/tmp") == 0 ||
+ strcmp (location, g_get_home_dir ()) == 0)
+ {
+ free (location);
+ location = g_build_filename (g_get_home_dir (), "Pictures", NULL);
+ }
+ }
+
+ if (location != NULL)
+ {
+ g_object_set (engine, "images-location", location, NULL);
+ }
+
+ if (sort_images)
+ {
+ g_object_set (engine, "sort-images", sort_images, NULL);
+ }
+
+ if (background_color != NULL)
+ {
+ g_object_set (engine, "background-color", background_color, NULL);
+ }
+
+ if (no_stretch)
+ {
+ g_object_set (engine, "no-stretch", no_stretch, NULL);
+ }
+
+ gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (engine));
+
+ gtk_widget_show (GTK_WIDGET (engine));
+
+ gtk_window_set_default_size (GTK_WINDOW (window), 640, 480);
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/savers/slideshow.c.orig b/savers/slideshow.c.orig
new file mode 100644
index 0000000..f18dc5c
--- /dev/null
+++ b/savers/slideshow.c.orig
@@ -0,0 +1,127 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * Copyright (C) 2005 William Jon McCann <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include "gs-theme-window.h"
+#include "gs-theme-engine.h"
+#include "gste-slideshow.h"
+
+#include "xdg-user-dir-lookup.h"
+
+int
+main (int argc, char **argv)
+{
+ GSThemeEngine *engine;
+ GtkWidget *window;
+ GError *error;
+ gboolean ret;
+ char *location = NULL;
+ char *background_color = NULL;
+ gboolean sort_images = FALSE;
+ gboolean no_stretch = FALSE;
+ GOptionEntry entries [] = {
+ { "location", 0, 0, G_OPTION_ARG_STRING, &location,
+ N_("Location to get images from"), N_("PATH") },
+ { "background-color", 0, 0, G_OPTION_ARG_STRING, &background_color,
+ N_("Color to use for images background"), N_("\"#rrggbb\"") },
+ { "sort-images", 0, 0, G_OPTION_ARG_NONE, &sort_images,
+ N_("Do not randomize pictures from location"), NULL },
+ { "no-stretch", 0, 0, G_OPTION_ARG_NONE, &no_stretch,
+ N_("Do not try to stretch images on screen"), NULL },
+ { NULL }
+ };
+
+ bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ error = NULL;
+
+ g_thread_init (NULL);
+ ret = gtk_init_with_args (&argc, &argv,
+ NULL,
+ entries,
+ NULL,
+ &error);
+ if (! ret) {
+ g_message ("%s", error->message);
+ g_error_free (error);
+ exit (1);
+ }
+
+ g_chdir (g_get_home_dir ());
+
+ g_set_prgname ("slideshow");
+
+ window = gs_theme_window_new ();
+ g_signal_connect (G_OBJECT (window), "delete-event",
+ G_CALLBACK (gtk_main_quit), NULL);
+
+ engine = g_object_new (GSTE_TYPE_SLIDESHOW, NULL);
+
+ if (location == NULL) {
+ location = xdg_user_dir_lookup ("PICTURES");
+ if (location == NULL ||
+ strcmp (location, "/tmp") == 0 ||
+ strcmp (location, g_get_home_dir ()) == 0) {
+ free (location);
+ location = g_build_filename (g_get_home_dir (), "Pictures", NULL);
+ }
+ }
+
+ if (location != NULL) {
+ g_object_set (engine, "images-location", location, NULL);
+ }
+
+ if (sort_images) {
+ g_object_set (engine, "sort-images", sort_images, NULL);
+ }
+
+ if (background_color != NULL) {
+ g_object_set (engine, "background-color", background_color, NULL);
+ }
+
+ if (no_stretch) {
+ g_object_set (engine, "no-stretch", no_stretch, NULL);
+ }
+
+ gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (engine));
+
+ gtk_widget_show (GTK_WIDGET (engine));
+
+ gtk_window_set_default_size (GTK_WINDOW (window), 640, 480);
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/savers/xdg-user-dir-lookup.c b/savers/xdg-user-dir-lookup.c
new file mode 100644
index 0000000..c9e8fe2
--- /dev/null
+++ b/savers/xdg-user-dir-lookup.c
@@ -0,0 +1,168 @@
+/*
+ This file is not licenced under the GPL like the rest of the code.
+ Its is under the MIT license, to encourage reuse by cut-and-paste.
+
+ Copyright (c) 2007 Red Hat, inc
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "xdg-user-dir-lookup.h"
+
+char *
+xdg_user_dir_lookup (const char *type)
+{
+ FILE *file;
+ char *home_dir, *config_home, *config_file;
+ char buffer[512];
+ char *user_dir;
+ char *p, *d;
+ int len;
+ int relative;
+
+ home_dir = getenv ("HOME");
+
+ if (home_dir == NULL)
+ return strdup ("/tmp");
+
+ config_home = getenv ("XDG_CONFIG_HOME");
+ if (config_home == NULL || config_home[0] == 0)
+ {
+ config_file = malloc (strlen (home_dir) + strlen ("/.config/user-dirs.dirs") + 1);
+ strcpy (config_file, home_dir);
+ strcat (config_file, "/.config/user-dirs.dirs");
+ }
+ else
+ {
+ config_file = malloc (strlen (config_home) + strlen ("/user-dirs.dirs") + 1);
+ strcpy (config_file, config_home);
+ strcat (config_file, "/user-dirs.dirs");
+ }
+
+ file = fopen (config_file, "r");
+ free (config_file);
+ if (file == NULL)
+ goto error;
+
+ user_dir = NULL;
+ while (fgets (buffer, sizeof (buffer), file))
+ {
+ /* Remove newline at end */
+ len = strlen (buffer);
+ if (len > 0 && buffer[len-1] == '\n')
+ buffer[len-1] = 0;
+
+ p = buffer;
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ if (strncmp (p, "XDG_", 4) != 0)
+ continue;
+ p += 4;
+ if (strncmp (p, type, strlen (type)) != 0)
+ continue;
+ p += strlen (type);
+ if (strncmp (p, "_DIR", 4) != 0)
+ continue;
+ p += 4;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ if (*p != '=')
+ continue;
+ p++;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ if (*p != '"')
+ continue;
+ p++;
+
+ relative = 0;
+ if (strncmp (p, "$HOME/", 6) == 0)
+ {
+ p += 6;
+ relative = 1;
+ }
+ else if (*p != '/')
+ continue;
+
+ if (relative)
+ {
+ user_dir = malloc (strlen (home_dir) + 1 + strlen (p) + 1);
+ strcpy (user_dir, home_dir);
+ strcat (user_dir, "/");
+ }
+ else
+ {
+ user_dir = malloc (strlen (p) + 1);
+ *user_dir = 0;
+ }
+
+ d = user_dir + strlen (user_dir);
+ while (*p && *p != '"')
+ {
+ if ((*p == '\\') && (*(p+1) != 0))
+ p++;
+ *d++ = *p++;
+ }
+ *d = 0;
+ }
+ fclose (file);
+
+ if (user_dir)
+ return user_dir;
+
+error:
+ /* Special case desktop for historical compatibility */
+ if (strcmp (type, "DESKTOP") == 0)
+ {
+ user_dir = malloc (strlen (home_dir) + strlen ("/Desktop") + 1);
+ strcpy (user_dir, home_dir);
+ strcat (user_dir, "/Desktop");
+ return user_dir;
+ }
+ else
+ return strdup (home_dir);
+}
+
+#ifdef STANDALONE
+
+int
+main (int argc, char *argv[])
+{
+ if (argc != 2)
+ {
+ fprintf (stderr, "Usage %s <dir-type>\n", argv[0]);
+ exit (1);
+ }
+
+ printf ("%s\n", xdg_user_dir_lookup (argv[1]));
+ return 0;
+}
+
+#endif
diff --git a/savers/xdg-user-dir-lookup.c.orig b/savers/xdg-user-dir-lookup.c.orig
new file mode 100644
index 0000000..a6b2fb1
--- /dev/null
+++ b/savers/xdg-user-dir-lookup.c.orig
@@ -0,0 +1,168 @@
+/*
+ This file is not licenced under the GPL like the rest of the code.
+ Its is under the MIT license, to encourage reuse by cut-and-paste.
+
+ Copyright (c) 2007 Red Hat, inc
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "xdg-user-dir-lookup.h"
+
+char *
+xdg_user_dir_lookup (const char *type)
+{
+ FILE *file;
+ char *home_dir, *config_home, *config_file;
+ char buffer[512];
+ char *user_dir;
+ char *p, *d;
+ int len;
+ int relative;
+
+ home_dir = getenv ("HOME");
+
+ if (home_dir == NULL)
+ return strdup ("/tmp");
+
+ config_home = getenv ("XDG_CONFIG_HOME");
+ if (config_home == NULL || config_home[0] == 0)
+ {
+ config_file = malloc (strlen (home_dir) + strlen ("/.config/user-dirs.dirs") + 1);
+ strcpy (config_file, home_dir);
+ strcat (config_file, "/.config/user-dirs.dirs");
+ }
+ else
+ {
+ config_file = malloc (strlen (config_home) + strlen ("/user-dirs.dirs") + 1);
+ strcpy (config_file, config_home);
+ strcat (config_file, "/user-dirs.dirs");
+ }
+
+ file = fopen (config_file, "r");
+ free (config_file);
+ if (file == NULL)
+ goto error;
+
+ user_dir = NULL;
+ while (fgets (buffer, sizeof (buffer), file))
+ {
+ /* Remove newline at end */
+ len = strlen (buffer);
+ if (len > 0 && buffer[len-1] == '\n')
+ buffer[len-1] = 0;
+
+ p = buffer;
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ if (strncmp (p, "XDG_", 4) != 0)
+ continue;
+ p += 4;
+ if (strncmp (p, type, strlen (type)) != 0)
+ continue;
+ p += strlen (type);
+ if (strncmp (p, "_DIR", 4) != 0)
+ continue;
+ p += 4;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ if (*p != '=')
+ continue;
+ p++;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ if (*p != '"')
+ continue;
+ p++;
+
+ relative = 0;
+ if (strncmp (p, "$HOME/", 6) == 0)
+ {
+ p += 6;
+ relative = 1;
+ }
+ else if (*p != '/')
+ continue;
+
+ if (relative)
+ {
+ user_dir = malloc (strlen (home_dir) + 1 + strlen (p) + 1);
+ strcpy (user_dir, home_dir);
+ strcat (user_dir, "/");
+ }
+ else
+ {
+ user_dir = malloc (strlen (p) + 1);
+ *user_dir = 0;
+ }
+
+ d = user_dir + strlen (user_dir);
+ while (*p && *p != '"')
+ {
+ if ((*p == '\\') && (*(p+1) != 0))
+ p++;
+ *d++ = *p++;
+ }
+ *d = 0;
+ }
+ fclose (file);
+
+ if (user_dir)
+ return user_dir;
+
+ error:
+ /* Special case desktop for historical compatibility */
+ if (strcmp (type, "DESKTOP") == 0)
+ {
+ user_dir = malloc (strlen (home_dir) + strlen ("/Desktop") + 1);
+ strcpy (user_dir, home_dir);
+ strcat (user_dir, "/Desktop");
+ return user_dir;
+ }
+ else
+ return strdup (home_dir);
+}
+
+#ifdef STANDALONE
+
+int
+main (int argc, char *argv[])
+{
+ if (argc != 2)
+ {
+ fprintf (stderr, "Usage %s <dir-type>\n", argv[0]);
+ exit (1);
+ }
+
+ printf ("%s\n", xdg_user_dir_lookup (argv[1]));
+ return 0;
+}
+
+#endif
diff --git a/savers/xdg-user-dir-lookup.h b/savers/xdg-user-dir-lookup.h
new file mode 100644
index 0000000..2803be3
--- /dev/null
+++ b/savers/xdg-user-dir-lookup.h
@@ -0,0 +1,28 @@
+/*
+ This file is not licenced under the GPL like the rest of the code.
+ Its is under the MIT license, to encourage reuse by cut-and-paste.
+
+ Copyright (c) 2007 Red Hat, inc
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+char * xdg_user_dir_lookup (const char *type);