[mutter/wip/gcampax/background: 67/68] MetaBackground: reintroduce animated backgrounds



commit db7fa793b035d042ecfbd29eec10f2c4925fa018
Author: Giovanni Campagna <gcampagna src gnome org>
Date:   Wed Feb 13 14:46:07 2013 +0100

    MetaBackground: reintroduce animated backgrounds
    
    Replace the previous background loader with a MetaBackgroundSlideshow class,
    which replaces the slideshow code from GnomeBG.

 src/compositor/meta-background-actor-private.h |   42 +-
 src/compositor/meta-background-actor.c         |  113 ++-
 src/compositor/meta-background.c               |  956 +++++++++++++++++++++++-
 3 files changed, 1039 insertions(+), 72 deletions(-)
---
diff --git a/src/compositor/meta-background-actor-private.h b/src/compositor/meta-background-actor-private.h
index 7b1f8b8..8473347 100644
--- a/src/compositor/meta-background-actor-private.h
+++ b/src/compositor/meta-background-actor-private.h
@@ -9,15 +9,39 @@
 void meta_background_actor_set_visible_region  (MetaBackgroundActor *self,
                                                 cairo_region_t      *visible_region);
 
-GTask     *meta_background_draw_async          (MetaScreen          *screen,
-                                                const char          *picture_uri,
-                                                GCancellable        *cancellable,
-                                                GAsyncReadyCallback  callback,
-                                                gpointer             user_data);
-CoglHandle meta_background_draw_finish         (MetaScreen    *screen,
-                                                GAsyncResult  *result,
-                                                char         **picture_uri,
-                                                GError       **error);
+/**
+ * MetaBackgroundSlideshow:
+ *
+ * A class for handling animated backgrounds.
+ */
+
+#define META_TYPE_BACKGROUND_SLIDESHOW            (meta_background_slideshow_get_type ())
+#define META_BACKGROUND_SLIDESHOW(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_BACKGROUND_SLIDESHOW, MetaBackgroundSlideshow))
+#define META_BACKGROUND_SLIDESHOW_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_BACKGROUND_SLIDESHOW, MetaBackgroundSlideshowClass))
+#define META_IS_BACKGROUND_SLIDESHOW(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_BACKGROUND_SLIDESHOW))
+#define META_IS_BACKGROUND_SLIDESHOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_BACKGROUND_SLIDESHOW))
+#define META_BACKGROUND_SLIDESHOW_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), META_TYPE_BACKGROUND_SLIDESHOW, MetaBackgroundSlideshowClass))
+
+typedef struct _MetaBackgroundSlideshow        MetaBackgroundSlideshow;
+typedef struct _MetaBackgroundSlideshowClass   MetaBackgroundSlideshowClass;
+
+GType meta_background_slideshow_get_type (void) G_GNUC_CONST;
+
+MetaBackgroundSlideshow     *meta_background_slideshow_new         (MetaScreen          *screen,
+                                                                    const char          *picture_uri);
+
+const char                  *meta_background_slideshow_get_uri     (MetaBackgroundSlideshow *slideshow);
+
+GTask                       *meta_background_slideshow_draw_async  (MetaBackgroundSlideshow  *slideshow,
+                                                                    GCancellable             *cancellable,
+                                                                    GAsyncReadyCallback       callback,
+                                                                    gpointer                  user_data);
+CoglHandle                   meta_background_slideshow_draw_finish (MetaBackgroundSlideshow  *slideshow,
+                                                                    GAsyncResult             *result,
+                                                                    GError                  **error);
+
+int                          meta_background_slideshow_get_next_timeout (MetaBackgroundSlideshow *slideshow);
+
 
 
 #endif /* META_BACKGROUND_ACTOR_PRIVATE_H */
diff --git a/src/compositor/meta-background-actor.c b/src/compositor/meta-background-actor.c
index 195ddf0..1e0981d 100644
--- a/src/compositor/meta-background-actor.c
+++ b/src/compositor/meta-background-actor.c
@@ -25,6 +25,8 @@
 
 #include <config.h>
 
+#include <string.h>
+
 #include <clutter/clutter.h>
 
 #include "cogl-utils.h"
@@ -43,6 +45,7 @@
 typedef struct _MetaBackground MetaBackground;
 
 typedef struct {
+  MetaBackgroundSlideshow    *slideshow;
   CoglTexture                *texture;
   float                       texture_width;
   float                       texture_height;
@@ -52,7 +55,6 @@ typedef struct {
   ClutterColor                colors[2];
   GDesktopBackgroundStyle     style;
   GDesktopBackgroundShading   shading;
-  char                       *picture_uri;
 } MetaBackgroundState;
 
 struct _MetaBackground
@@ -65,6 +67,8 @@ struct _MetaBackground
   MetaBackgroundState old_state;
   MetaBackgroundState state;
   GSettings   *settings;
+  MetaBackgroundSlideshow *pending_slideshow;
+  GSource                 *timeout;
 
   GTask *rendering_task;
 };
@@ -101,16 +105,17 @@ static GParamSpec *obj_props[PROP_LAST];
 
 G_DEFINE_TYPE (MetaBackgroundActor, meta_background_actor, CLUTTER_TYPE_ACTOR);
 
-static void meta_background_update                    (MetaBackground      *background,
+static void meta_background_uri_changed               (MetaBackground      *background,
                                                        const char          *picture_uri);
+static gboolean meta_background_update                (MetaBackground      *background);
 static void meta_background_actor_screen_size_changed (MetaScreen          *screen,
                                                        MetaBackgroundActor *actor);
 static void meta_background_actor_constructed         (GObject             *object);
 
 static void clear_state                (MetaBackgroundState *state);
-static void set_texture                (MetaBackground  *background,
-                                        CoglHandle       texture,
-                                        const char      *picture_uri);
+static void set_texture                (MetaBackground          *background,
+                                        CoglHandle               texture,
+                                        MetaBackgroundSlideshow *slideshow);
 
 static void
 meta_background_free (MetaBackground *background)
@@ -131,11 +136,16 @@ on_settings_changed (GSettings      *settings,
                      const char     *key,
                      MetaBackground *background)
 {
-  char *picture_uri;
+  if (strcmp (key, "picture-uri") == 0)
+    {
+      char *picture_uri;
 
-  picture_uri = g_settings_get_string (settings, "picture-uri");
-  meta_background_update (background, picture_uri);
-  g_free (picture_uri);
+      picture_uri = g_settings_get_string (settings, "picture-uri");
+      meta_background_uri_changed (background, picture_uri);
+      g_free (picture_uri);
+    }
+  else
+    meta_background_update (background);
 }
 
 static GSettings *
@@ -166,7 +176,7 @@ meta_background_get (MetaScreen *screen,
 
       g_signal_connect (settings, "changed",
                         G_CALLBACK (on_settings_changed), background);
-      on_settings_changed (settings, NULL, background);
+      on_settings_changed (settings, "picture-uri", background);
     }
 
   return background;
@@ -186,8 +196,7 @@ static inline void
 clear_state (MetaBackgroundState *state)
 {
   g_clear_pointer (&state->texture, cogl_handle_unref);
-  g_free (state->picture_uri);
-  state->picture_uri = NULL;
+  g_clear_object (&state->slideshow);
 }
 
 static void
@@ -218,13 +227,14 @@ get_color_from_settings (GSettings    *settings,
 }
 
 static void
-set_texture (MetaBackground *background,
-             CoglHandle      texture,
-             const char     *picture_uri)
+set_texture (MetaBackground          *background,
+             CoglHandle               texture,
+             MetaBackgroundSlideshow *slideshow)
 {
   GSList *l;
   gboolean crossfade;
   int width, height;
+  int timeout;
 
   g_assert (texture != COGL_INVALID_HANDLE);
 
@@ -242,7 +252,23 @@ set_texture (MetaBackground *background,
   background->state.shading = g_settings_get_enum (background->settings, "color-shading-type");
   background->state.texture_width = cogl_texture_get_width (background->state.texture);
   background->state.texture_height = cogl_texture_get_height (background->state.texture);
-  background->state.picture_uri = g_strdup (picture_uri);
+  background->state.slideshow = g_object_ref (slideshow);
+
+  timeout = meta_background_slideshow_get_next_timeout (slideshow);
+
+  if (background->timeout != NULL)
+    g_source_destroy (background->timeout);
+
+  if (timeout > 0)
+    {
+      background->timeout = g_timeout_source_new (timeout * 1000);
+      g_source_set_callback (background->timeout,
+                             (GSourceFunc) (meta_background_update),
+                             background, NULL);
+      g_source_attach (background->timeout, NULL);
+    }
+  else
+    background->timeout = NULL;
 
   crossfade = state_is_valid (&background->old_state);
 
@@ -987,13 +1013,14 @@ on_background_drawn (GObject      *object,
                      GAsyncResult *result,
                      gpointer      user_data)
 {
+  MetaBackgroundSlideshow *slideshow;
   MetaBackground *background;
   CoglHandle texture;
   GError *error;
-  char *picture_uri;
 
   error = NULL;
-  texture = meta_background_draw_finish (META_SCREEN (object), result, &picture_uri, &error);
+  slideshow = META_BACKGROUND_SLIDESHOW (object);
+  texture = meta_background_slideshow_draw_finish (slideshow, result, &error);
 
   /* Don't even access user_data if cancelled, it might be already
      freed */
@@ -1010,9 +1037,8 @@ on_background_drawn (GObject      *object,
 
   if (texture != COGL_INVALID_HANDLE)
     {
-      set_texture (background, texture, picture_uri);
+      set_texture (background, texture, slideshow);
       cogl_handle_unref (texture);
-      g_free (picture_uri);
       return;
     }
   else
@@ -1024,7 +1050,7 @@ on_background_drawn (GObject      *object,
 }
 
 /*
- * meta_background_update:
+ * meta_background_uri_changed:
  * @background: a #MetaBackground
  * @picture_uri: the new image URI
  *
@@ -1032,11 +1058,15 @@ on_background_drawn (GObject      *object,
  * a thread, and the actual on screen change is therefore delayed until
  * the redraw is finished.
  */
-void
-meta_background_update (MetaBackground *background,
-                        const char     *picture_uri)
+static void
+meta_background_uri_changed (MetaBackground *background,
+                             const char     *picture_uri)
 {
-  if (g_strcmp0 (background->state.picture_uri, picture_uri) == 0)
+  MetaBackgroundSlideshow *current_slideshow;
+
+  current_slideshow = background->pending_slideshow;
+  if (current_slideshow &&
+      strcmp (meta_background_slideshow_get_uri (current_slideshow), picture_uri) == 0)
     return;
 
   if (background->cancellable)
@@ -1046,13 +1076,38 @@ meta_background_update (MetaBackground *background,
     }
 
   g_clear_object (&background->rendering_task);
+  g_clear_object (&background->pending_slideshow);
 
   background->cancellable = g_cancellable_new ();
+  background->pending_slideshow = meta_background_slideshow_new (background->screen,
+                                                                 picture_uri);
+
+  background->rendering_task = meta_background_slideshow_draw_async (background->pending_slideshow,
+                                                                     background->cancellable,
+                                                                     on_background_drawn, background);
+}
+
+static gboolean
+meta_background_update (MetaBackground *background)
+{
+  if (background->timeout)
+    {
+      g_source_destroy (background->timeout);
+      background->timeout = NULL;
+    }
+
+  if (background->cancellable)
+    {
+      g_cancellable_cancel (background->cancellable);
+      g_object_unref (background->cancellable);
+    }
+
+  g_clear_object (&background->rendering_task);
+  background->rendering_task = meta_background_slideshow_draw_async (background->state.slideshow,
+                                                                     background->cancellable,
+                                                                     on_background_drawn, background);
 
-  background->rendering_task = meta_background_draw_async (background->screen,
-                                                           picture_uri,
-                                                           background->cancellable,
-                                                           on_background_drawn, background);
+  return FALSE;
 }
 
 /**
diff --git a/src/compositor/meta-background.c b/src/compositor/meta-background.c
index d427d15..cc7de07 100644
--- a/src/compositor/meta-background.c
+++ b/src/compositor/meta-background.c
@@ -23,76 +23,890 @@
 
 #include "config.h"
 
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
 #include <gio/gio.h>
 #include <gdk-pixbuf/gdk-pixbuf.h>
 
 #include "meta-background-actor-private.h"
 #include <core/screen-private.h>
 
+/* From and To */
+#define CACHE_SIZE 2
+
+typedef struct _SizedUri {
+  char *picture_uri;
+  int width, height;
+
+  struct _SizedUri *next;
+} SizedUri;
+
+typedef struct {
+  SizedUri *from;
+  SizedUri *to;
+
+  time_t starttime;
+  time_t endtime;
+} Slide;
+
+typedef struct {
+  char *uri;
+  GdkPixbuf *pixbuf;
+
+  int lru_age;
+} CacheEntry;
+
+struct _MetaBackgroundSlideshow {
+  GObject parent_instance;
+
+  /* Immutable once created */
+  MetaScreen *screen;
+  char *picture_uri;
+
+  GMutex cache_mutex;
+  CacheEntry cache[CACHE_SIZE];
+
+  GMutex slides_lock;
+  GQueue slides;
+  time_t total_duration;
+};
+
+struct _MetaBackgroundSlideshowClass {
+  GObjectClass parent_class;
+};
+
+G_DEFINE_TYPE (MetaBackgroundSlideshow, meta_background_slideshow, G_TYPE_OBJECT);
+
+static void
+slide_free (Slide *slide)
+{
+  SizedUri *cur, *next;
+
+  for (cur = slide->from; cur; cur = next)
+    {
+      next = cur->next;
+
+      g_free (cur->picture_uri);
+      g_slice_free (SizedUri, cur);
+    }
+  for (cur = slide->to; cur; cur = next)
+    {
+      next = cur->next;
+
+      g_free (cur->picture_uri);
+      g_slice_free (SizedUri, cur);
+    }
+
+  g_slice_free (Slide, slide);
+}
+
+static void
+clear_cache_entry (CacheEntry *entry)
+{
+  g_free (entry->uri);
+  entry->uri = NULL;
+  g_clear_object (&entry->pixbuf);
+}
+
 static void
-meta_background_draw_thread (GTask        *task,
-                             gpointer      source_object,
-                             gpointer      task_data,
-                             GCancellable *cancellable)
+insert_cache (MetaBackgroundSlideshow *slideshow,
+              const char              *pixbuf_uri,
+              GdkPixbuf               *pixbuf)
+{
+  int i;
+  int min_age, max_age;
+  CacheEntry *candidate;
+
+  g_mutex_lock (&slideshow->cache_mutex);
+
+  candidate = NULL;
+  min_age = INT_MAX;
+  max_age = INT_MIN;
+  for (i = 0; i < CACHE_SIZE; i++)
+    {
+      CacheEntry *entry = &slideshow->cache[i];
+
+      if (entry->uri == NULL)
+        {
+          candidate = entry;
+          min_age = INT_MIN;
+        }
+      else if (entry->lru_age < min_age)
+        {
+          candidate = entry;
+          min_age = entry->lru_age;
+        }
+
+      if (entry->lru_age > max_age)
+        max_age = entry->lru_age;
+    }
+
+  g_assert (candidate != NULL);
+
+  clear_cache_entry (candidate);
+  candidate->uri = g_strdup (pixbuf_uri);
+  candidate->pixbuf = g_object_ref (pixbuf);
+  candidate->lru_age = max_age + 1;
+
+  g_mutex_unlock (&slideshow->cache_mutex);
+}
+
+static GdkPixbuf *
+hit_cache (MetaBackgroundSlideshow *slideshow,
+           const char              *pixbuf_uri)
+{
+  GdkPixbuf *hit;
+  int i;
+
+  g_mutex_lock (&slideshow->cache_mutex);
+
+  hit = NULL;
+
+  for (i = 0; i < CACHE_SIZE; i++)
+    {
+      CacheEntry *entry = &slideshow->cache[i];
+
+      if (entry->uri != NULL &&
+          strcmp (entry->uri, pixbuf_uri) == 0)
+        {
+          entry->lru_age ++;
+          hit = g_object_ref (entry->pixbuf);
+        }
+    }
+
+  g_mutex_unlock (&slideshow->cache_mutex);
+
+  return hit;
+}
+
+typedef enum {
+  STATE_INITIAL,
+  STATE_BACKGROUND,
+  STATE_STARTTIME,
+  STATE_STATIC_SLIDE,
+  STATE_TRANSITION_SLIDE,
+  STATE_FILE,
+  STATE_FILE_SIZE,
+} ParserState;
+
+/* initial -> background -> transition/static -> to/from/file -> size */
+#define STATE_STACK_SIZE 5
+
+typedef struct {
+  GQueue *slides_queue;
+  Slide *current_slide;
+  SizedUri **current_size_list;
+  SizedUri *current_size;
+
+  time_t starttime;
+  struct tm starttime_tm;
+
+  ParserState state_stack[STATE_STACK_SIZE];
+  int         state_stack_len;
+} SlideshowParseContext;
+
+static inline ParserState
+parse_context_get_state (SlideshowParseContext *parser)
+{
+  return parser->state_stack[parser->state_stack_len-1];
+}
+
+static inline void
+parse_context_push_state (SlideshowParseContext *parser,
+                          ParserState            state)
+{
+  g_assert (parser->state_stack_len < STATE_STACK_SIZE);
+
+  parser->state_stack[parser->state_stack_len] = state;
+  parser->state_stack_len++;
+}
+
+static inline void
+parse_context_pop_state (SlideshowParseContext *parser)
+{
+  parser->state_stack_len--;
+}
+
+static void
+slideshow_start_element (GMarkupParseContext  *context,
+                         const char           *element_name,
+                         const char          **attribute_names,
+                         const char          **attribute_values,
+                         gpointer              user_data,
+                         GError              **error)
+{
+  SlideshowParseContext *parser = user_data;
+
+  switch (parse_context_get_state (parser)) {
+  case STATE_INITIAL:
+    if (strcmp (element_name, "background"))
+      g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                  "Invalid root element %s", element_name);
+    else
+      parse_context_push_state (parser, STATE_BACKGROUND);
+    break;
+
+  case STATE_BACKGROUND:
+    if (strcmp (element_name, "starttime") == 0)
+      parse_context_push_state (parser, STATE_STARTTIME);
+    else if (strcmp (element_name, "static") == 0 ||
+             strcmp (element_name, "transition") == 0)
+      {
+        if (strcmp (element_name, "static") == 0)
+          parse_context_push_state (parser, STATE_STATIC_SLIDE);
+        else
+          parse_context_push_state (parser, STATE_TRANSITION_SLIDE);
+
+        parser->current_slide = g_slice_new0 (Slide);
+        parser->current_slide->starttime = parser->starttime;
+
+        g_queue_push_tail (parser->slides_queue, parser->current_slide);
+      }
+    else
+      g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                  "Invalid element %s in state <background>", element_name);
+    break;
+
+  case STATE_STARTTIME:
+    if (strcmp (element_name, "year") &&
+        strcmp (element_name, "month") &&
+        strcmp (element_name, "day") &&
+        strcmp (element_name, "hour") &&
+        strcmp (element_name, "minute") &&
+        strcmp (element_name, "second"))
+      g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                  "Invalid element %s in state <starttime>", element_name);
+    break;
+
+  case STATE_STATIC_SLIDE:
+    if (strcmp (element_name, "file") == 0)
+      {
+        parse_context_push_state (parser, STATE_FILE);
+
+        parser->current_size_list = &parser->current_slide->from;
+      }
+    else if (strcmp (element_name, "duration"))
+      g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                  "Invalid element %s in state <static>", element_name);
+    break;
+
+  case STATE_TRANSITION_SLIDE:
+    if (strcmp (element_name, "from") == 0 ||
+        strcmp (element_name, "to") == 0)
+      {
+        parse_context_push_state (parser, STATE_FILE);
+
+        if (strcmp (element_name, "from") == 0)
+          parser->current_size_list = &parser->current_slide->from;
+        else
+          parser->current_size_list = &parser->current_slide->to;
+      }
+    else if (strcmp (element_name, "duration"))
+      g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                  "Invalid element %s in state <transition>", element_name);
+    break;
+
+  case STATE_FILE:
+    if (strcmp (element_name, "size") == 0)
+      {
+        const char *width, *height;
+
+        if (g_markup_collect_attributes (element_name,
+                                         attribute_names,
+                                         attribute_values,
+                                         error,
+                                         G_MARKUP_COLLECT_STRING,
+                                         "width", &width,
+                                         G_MARKUP_COLLECT_STRING,
+                                         "height", &height,
+                                         G_MARKUP_COLLECT_INVALID))
+          {
+            SizedUri *new_size = g_slice_new (SizedUri);
+
+            new_size->picture_uri = NULL;
+            new_size->width = atoi(width);
+            new_size->height = atoi(height);
+            new_size->next = NULL;
+
+            if (parser->current_size == NULL)
+              {
+                *parser->current_size_list = new_size;
+                parser->current_size = new_size;
+              }
+            else
+              parser->current_size->next = new_size;
+
+            parse_context_push_state (parser, STATE_FILE_SIZE);
+          }
+      }
+    else
+      {
+        g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                    "Invalid element %s in state <file>", element_name);
+      }
+    break;
+
+  case STATE_FILE_SIZE:
+    g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                "Invalid element %s in state <size>", element_name);
+    break;
+  }
+};
+
+static void
+slideshow_end_element (GMarkupParseContext *context,
+                       const gchar         *element_name,
+                       gpointer             user_data,
+                       GError             **error)
+{
+  SlideshowParseContext *parser = user_data;
+
+  switch (parse_context_get_state (parser)) {
+  case STATE_STARTTIME:
+    if (strcmp (element_name, "starttime") == 0)
+      {
+        parser->starttime = mktime (&parser->starttime_tm);
+        parse_context_pop_state (parser);
+      }
+    break;
+
+  case STATE_STATIC_SLIDE:
+  case STATE_TRANSITION_SLIDE:
+    if (strcmp (element_name, "duration"))
+      {
+        parse_context_pop_state (parser);
+        parser->current_slide = NULL;
+        parser->current_size_list = NULL;
+      }
+    break;
+
+  case STATE_FILE:
+    parse_context_pop_state (parser);
+    parser->current_size = NULL;
+    break;
+
+  case STATE_FILE_SIZE:
+  case STATE_BACKGROUND:
+    parse_context_pop_state (parser);
+    break;
+
+  case STATE_INITIAL:
+    g_assert_not_reached ();
+  }
+}
+
+static int
+strntoi(const char *text, size_t text_len)
+{
+  char *v;
+  int i;
+
+  v = g_strndup (text, text_len);
+  i = atoi(v);
+
+  g_free (v);
+  return i;
+}
+
+static gboolean
+is_all_white (const char *text, size_t text_len)
+{
+  size_t i;
+
+  for (i = 0; i < text_len; i++)
+    if (!g_ascii_isspace(text[i]))
+      return FALSE;
+
+  return TRUE;
+}
+
+static void
+slideshow_text (GMarkupParseContext *context,
+                const gchar         *text,
+                gsize                text_len,
+                gpointer             user_data,
+                GError             **error)
+{
+  SlideshowParseContext *parser = user_data;
+  const char *current_element = g_markup_parse_context_get_element (context);
+
+  switch (parse_context_get_state (parser)) {
+  case STATE_STARTTIME:
+    if (strcmp (current_element, "year") == 0)
+      parser->starttime_tm.tm_year = strntoi (text, text_len) - 1900;
+    else if (strcmp (current_element, "month") == 0)
+      parser->starttime_tm.tm_mon = strntoi (text, text_len) - 1;
+    else if (strcmp (current_element, "day") == 0)
+      parser->starttime_tm.tm_mday = strntoi (text, text_len);
+    else if (strcmp (current_element, "hour") == 0)
+      parser->starttime_tm.tm_hour = strntoi (text, text_len);
+    else if (strcmp (current_element, "minute") == 0)
+      parser->starttime_tm.tm_min = strntoi (text, text_len);
+    else if (strcmp (current_element, "second") == 0)
+      parser->starttime_tm.tm_sec = strntoi (text, text_len);
+    else if (!is_all_white (text, text_len))
+      g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                   "Unexpected content in element <%s>", current_element);
+    break;
+
+  case STATE_STATIC_SLIDE:
+  case STATE_TRANSITION_SLIDE:
+    if (strcmp (current_element, "duration") == 0)
+      {
+        /* Values are floating point in the XML, but we only
+           handle integers.
+           Also, some XML files represent infinity as a very high value,
+           so we don't want to overflow reading.
+        */
+        char *value = g_strndup (text, text_len);
+        long long duration = g_ascii_strtod (value, NULL);
+
+        /* We use milliseconds for timeouts, so anything greater than that
+           is impossible (and means infinite)
+        */
+        if ((duration * 1000) > (long long)INT_MAX)
+          {
+            parser->current_slide->endtime = parser->starttime = -1;
+          }
+        else
+          {
+            parser->current_slide->endtime = parser->current_slide->starttime + (int)duration;
+            parser->starttime = parser->current_slide->endtime;
+          }
+
+        g_free (value);
+      }
+    else if (!is_all_white (text, text_len))
+      {
+        g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                     "Unexpected content in element <%s>", current_element);
+      }
+    break;
+
+  case STATE_FILE:
+    {
+      if (parser->current_size != NULL &&
+          !is_all_white (text, text_len))
+        {
+          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                       "Unexpected content in element <%s>", current_element);
+          break;
+        }
+      else
+        {
+          SizedUri *new_size = g_slice_new (SizedUri);
+
+          new_size->picture_uri = NULL;
+          new_size->width = -1;
+          new_size->height = -1;
+          new_size->next = NULL;
+
+          *parser->current_size_list = new_size;
+          parser->current_size = new_size;
+        }
+    }
+    /* Fall through */
+
+  case STATE_FILE_SIZE:
+    parser->current_size->picture_uri = g_strdup_printf("file://%.*s", (int)text_len, text);
+    break;
+
+  case STATE_INITIAL:
+  case STATE_BACKGROUND:
+    if (!is_all_white (text, text_len))
+      g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                   "Unexpected content in element <%s>", current_element);
+  }
+}
+
+static GMarkupParser slideshow_parser = {
+  slideshow_start_element,
+  slideshow_end_element,
+  slideshow_text,
+  NULL,
+  NULL,
+};
+
+/* Based on the default size for GBufferedInputStream */
+#define BUFFER_SIZE 4096
+
+static gboolean
+parse_slideshow (MetaBackgroundSlideshow  *slideshow,
+                 GInputStream             *stream,
+                 GCancellable             *cancellable,
+                 GError                  **error)
+{
+  SlideshowParseContext parser = {
+    NULL, NULL, NULL, NULL, 0, { 0 }, { STATE_INITIAL }, 1
+  };
+  GMarkupParseContext *context;
+  char buffer[BUFFER_SIZE];
+  GError *local_error;
+  gsize read;
+  GList *iter;
+  time_t total_duration;
+
+  parser.slides_queue = &slideshow->slides;
+  parser.starttime_tm.tm_isdst = -1;
+
+  if (!g_seekable_seek (G_SEEKABLE (stream), 0, G_SEEK_SET, cancellable, error))
+    return FALSE;
+
+  context = g_markup_parse_context_new (&slideshow_parser,
+                                        G_MARKUP_TREAT_CDATA_AS_TEXT | G_MARKUP_PREFIX_ERROR_POSITION,
+                                        &parser, NULL);
+
+  local_error = NULL;
+
+  do
+    {
+      g_input_stream_read_all (stream, buffer, sizeof(buffer),
+                               &read, cancellable, error);
+    }
+  while (read > 0 &&
+         g_markup_parse_context_parse (context, buffer, read, &local_error));
+
+  g_markup_parse_context_free (context);
+
+  if (read > 0)
+    {
+      g_message ("Failed to parse slideshow file: %s", local_error->message);
+      g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
+                   _("File format not recognized"));
+
+      g_clear_error (&local_error);
+      g_queue_foreach (&slideshow->slides, (GFunc) slide_free, NULL);
+      g_queue_clear (&slideshow->slides);
+      return FALSE;
+    }
+
+  total_duration = 0;
+
+  for (iter = slideshow->slides.head; iter; iter = iter->next)
+    {
+      Slide *slide = iter->data;
+
+      if (slide->endtime < 0)
+        {
+          slideshow->total_duration = -1;
+          return TRUE;
+        }
+
+      total_duration += slide->endtime - slide->starttime;
+    }
+
+  slideshow->total_duration = total_duration;
+  return TRUE;
+}
+
+static Slide *
+make_single_pixbuf_slide (const char *picture_uri,
+                          GdkPixbuf  *pixbuf)
+{
+  Slide *slide;
+  SizedUri *uri;
+
+  slide = g_slice_new (Slide);
+  uri = g_slice_new (SizedUri);
+
+  uri->picture_uri = g_strdup (picture_uri);
+  uri->width = gdk_pixbuf_get_width (pixbuf);
+  uri->height = gdk_pixbuf_get_height (pixbuf);
+  uri->next = NULL;
+  slide->from = uri;
+  slide->to = NULL;
+  slide->starttime = -1;
+  slide->endtime = -1;
+
+  return slide;
+}
+
+static gboolean
+ensure_slideshow (MetaBackgroundSlideshow  *slideshow,
+                  GCancellable             *cancellable,
+                  GError                  **error)
 {
   GFile *file;
   GInputStream *stream;
   GdkPixbuf *pixbuf;
-  GError *error;
+  GError *local_error;
+  gboolean ok;
 
-  /* TODO: handle slideshows and other complications */
+  if (!g_queue_is_empty (&slideshow->slides))
+    return TRUE;
 
-  file = g_file_new_for_uri (task_data);
+  pixbuf = hit_cache (slideshow, slideshow->picture_uri);
+  if (pixbuf != NULL)
+    {
+      g_queue_push_tail (&slideshow->slides,
+                         make_single_pixbuf_slide (slideshow->picture_uri, pixbuf));
+      slideshow->total_duration = -1;
+      return TRUE;
+    }
 
-  error = NULL;
-  stream = G_INPUT_STREAM (g_file_read (file, cancellable, &error));
+  file = g_file_new_for_uri (slideshow->picture_uri);
+
+  stream = G_INPUT_STREAM (g_file_read (file, cancellable, error));
   if (stream == NULL)
     {
       g_object_unref (file);
-      g_task_return_error (task, error);
-      return;
+      return FALSE;
     }
 
-  pixbuf = gdk_pixbuf_new_from_stream (stream, cancellable, &error);
-  if (pixbuf == NULL)
+  local_error = NULL;
+  pixbuf = gdk_pixbuf_new_from_stream (stream, cancellable, &local_error);
+
+  if (pixbuf != NULL)
+    {
+      g_queue_push_tail (&slideshow->slides,
+                         make_single_pixbuf_slide (slideshow->picture_uri, pixbuf));
+      insert_cache (slideshow, slideshow->picture_uri, pixbuf);
+      slideshow->total_duration = -1;
+
+      ok = TRUE;
+    }
+  else if (g_error_matches (local_error, GDK_PIXBUF_ERROR,
+                            GDK_PIXBUF_ERROR_UNKNOWN_TYPE))
+    {
+      g_clear_error (&local_error);
+      ok = parse_slideshow (slideshow, stream, cancellable, error);
+    }
+  else
+    {
+      g_propagate_error (error, local_error);
+
+      ok = FALSE;
+    }
+
+  if (pixbuf)
+    g_object_unref (pixbuf);
+  g_object_unref (file);
+  g_object_unref (stream);
+
+  return ok;
+}
+
+static Slide *
+find_current_slide (MetaBackgroundSlideshow *slideshow,
+                    time_t                   current_time)
+{
+  Slide *first_slide;
+  GList *iter;
+
+  g_assert (slideshow->slides.head != NULL);
+
+  first_slide = slideshow->slides.head->data;
+
+  if (slideshow->total_duration < 0)
+    return first_slide;
+
+  g_assert (first_slide->starttime >= 0);
+
+  /* Account for loops, looking for the difference from the
+     starttime indicated in the XML */
+  current_time = first_slide->starttime +
+    (current_time - first_slide->starttime) % slideshow->total_duration;
+
+  for (iter = slideshow->slides.head; iter; iter = iter->next)
+    {
+      Slide *slide = iter->data;
+
+      g_assert (slide->endtime >= 0);
+      if (current_time >= slide->starttime &&
+          current_time < slide->endtime)
+        return slide;
+    }
+
+  g_assert_not_reached ();
+}
+
+/*
+ * Find the FileSize that best matches the given size.
+ * Do two passes; the first pass only considers FileSizes
+ * that are larger than the given size.
+ * We are looking for the image that best matches the aspect ratio.
+ * When two images have the same aspect ratio, prefer the one whose
+ * width is closer to the given width.
+ *
+ * Shamelessly taken from gnome-bg.c
+ */
+static SizedUri *
+find_best_size (SizedUri *sizes, gint width, gint height)
+{
+  SizedUri *s, *best;
+  gdouble a, d, distance;
+  gint pass;
+
+  a = width/(gdouble)height;
+  distance = 10000.0;
+  best = NULL;
+
+  for (pass = 0; pass < 2; pass++)
+    {
+      for (s = sizes; s; s = s->next)
+        {
+          if (pass == 0 && (s->width < width || s->height < height))
+            continue;
+
+          d = fabs (a - s->width/(gdouble)s->height);
+          if (d < distance)
+            {
+              distance = d;
+              best = s;
+            }
+          else if (d == distance)
+            {
+              if (abs (s->width - width) < abs (best->width - width))
+                {
+                  best = s;
+                }
+            }
+        }
+
+      if (best)
+        break;
+    }
+
+  return best;
+}
+
+static GdkPixbuf *
+load_best_pixbuf (MetaBackgroundSlideshow  *slideshow,
+                  SizedUri                 *size,
+                  GCancellable             *cancellable,
+                  GError                  **error)
+{
+  GFile *file;
+  GInputStream *stream;
+  GdkPixbuf *pixbuf;
+  int width, height;
+
+  meta_screen_get_size (slideshow->screen, &width, &height);
+  size = find_best_size (size, width, height);
+  pixbuf = hit_cache (slideshow, size->picture_uri);
+
+  if (pixbuf)
+    return pixbuf;
+
+  file = g_file_new_for_uri (size->picture_uri);
+
+  stream = G_INPUT_STREAM (g_file_read (file, cancellable, error));
+  if (stream == NULL)
     {
       g_object_unref (file);
-      g_object_unref (stream);
-      g_task_return_error (task, error);
+      return NULL;
     }
 
-  g_task_return_pointer (task, pixbuf, g_object_unref);
+  pixbuf = gdk_pixbuf_new_from_stream (stream, cancellable, error);
+
+  if (pixbuf)
+    insert_cache (slideshow, size->picture_uri, pixbuf);
+
   g_object_unref (file);
   g_object_unref (stream);
+  return pixbuf;
+}
+
+static void
+meta_background_slideshow_draw_thread (GTask        *task,
+                                       gpointer      source_object,
+                                       gpointer      task_data,
+                                       GCancellable *cancellable)
+{
+  MetaBackgroundSlideshow *self = source_object;
+  GError *error;
+  Slide *slide;
+  time_t current_time;
+
+  g_mutex_lock (&self->slides_lock);
+
+  error = NULL;
+  if (!ensure_slideshow (self, cancellable, &error))
+    goto error;
+
+  current_time = time (NULL);
+  slide = find_current_slide (self, current_time);
+
+  if (slide->to != NULL)
+    {
+      GdkPixbuf *from, *to, *blended;
+      int transition_steps, current_step;
+      Slide *first_slide = self->slides.head->data;
+
+      from = load_best_pixbuf (self, slide->from, cancellable, &error);
+      if (!from)
+        goto error;
+
+      to = load_best_pixbuf (self, slide->to, cancellable, &error);
+      if (!to)
+        goto error;
+
+      g_mutex_unlock (&self->slides_lock);
+
+      /* Round to five minute granularity */
+      current_time = first_slide->starttime +
+        (current_time - first_slide->starttime) % self->total_duration;
+      transition_steps = (slide->endtime - slide->starttime) / 300 * 300;
+      current_step = (current_time - slide->starttime) / 300 * 300;
+
+      blended = gdk_pixbuf_copy (from);
+      gdk_pixbuf_composite (to, blended,
+                            0, 0,
+                            gdk_pixbuf_get_width (blended),
+                            gdk_pixbuf_get_height (blended),
+                            0.0, 0.0, 1.0, 1.0,
+                            GDK_INTERP_BILINEAR,
+                            (255 * ((double)current_step/transition_steps)) + 0.5);
+
+      g_object_unref (from);
+      g_object_unref (to);
+      g_task_return_pointer (task, blended, g_object_unref);
+    }
+  else
+    {
+      GdkPixbuf *static_pixbuf;
+
+      static_pixbuf = load_best_pixbuf (self, slide->from, cancellable, &error);
+      if (!static_pixbuf)
+        goto error;
+
+      g_mutex_unlock (&self->slides_lock);
+      g_task_return_pointer (task, static_pixbuf, g_object_unref);
+    }
+
+  return;
+
+ error:
+  g_task_return_error (task, error);
+  g_mutex_unlock (&self->slides_lock);
 }
 
 GTask *
-meta_background_draw_async (MetaScreen          *screen,
-                            const char          *picture_uri,
-                            GCancellable        *cancellable,
-                            GAsyncReadyCallback  callback,
-                            gpointer             user_data)
+meta_background_slideshow_draw_async (MetaBackgroundSlideshow *slideshow,
+                                      GCancellable            *cancellable,
+                                      GAsyncReadyCallback      callback,
+                                      gpointer                 user_data)
 {
   GTask *task;
 
-  g_return_val_if_fail (META_IS_SCREEN (screen), NULL);
-
-  task = g_task_new (screen, cancellable, callback, user_data);
-  g_task_set_source_tag (task, meta_background_draw_async);
+  task = g_task_new (slideshow, cancellable, callback, user_data);
+  g_task_set_source_tag (task, meta_background_slideshow_draw_async);
   g_task_set_return_on_cancel (task, TRUE);
   g_task_set_check_cancellable (task, TRUE);
 
-  g_task_set_task_data (task, g_strdup (picture_uri), g_free);
-  g_task_run_in_thread (task, meta_background_draw_thread);
+  g_task_run_in_thread (task, meta_background_slideshow_draw_thread);
 
   return task;
 }
 
 CoglHandle
-meta_background_draw_finish (MetaScreen    *screen,
-                             GAsyncResult  *result,
-                             char         **picture_uri,
-                             GError       **error)
+meta_background_slideshow_draw_finish (MetaBackgroundSlideshow  *slideshow,
+                                       GAsyncResult             *result,
+                                       GError                  **error)
 {
   GdkPixbuf *pixbuf;
   CoglHandle handle;
@@ -101,9 +915,6 @@ meta_background_draw_finish (MetaScreen    *screen,
   if (pixbuf == NULL)
     return COGL_INVALID_HANDLE;
 
-  if (picture_uri != NULL)
-    *picture_uri = g_strdup (g_task_get_task_data (G_TASK (result)));
-
   handle = cogl_texture_new_from_data (gdk_pixbuf_get_width (pixbuf),
                                        gdk_pixbuf_get_height (pixbuf),
                                        COGL_TEXTURE_NO_ATLAS | COGL_TEXTURE_NO_SLICING,
@@ -115,3 +926,80 @@ meta_background_draw_finish (MetaScreen    *screen,
   g_object_unref (pixbuf);
   return handle;
 }
+
+static void
+meta_background_slideshow_init (MetaBackgroundSlideshow *self)
+{
+  g_mutex_init (&self->cache_mutex);
+  g_mutex_init (&self->slides_lock);
+}
+
+static void
+meta_background_slideshow_finalize (GObject *object)
+{
+  MetaBackgroundSlideshow *self = META_BACKGROUND_SLIDESHOW (object);
+  int i = 0;
+
+  for (i = 0; i < CACHE_SIZE; i++)
+    clear_cache_entry (&self->cache[i]);
+
+  g_queue_foreach (&self->slides, (GFunc) slide_free, NULL);
+  g_queue_clear (&self->slides);
+
+  g_free (self->picture_uri);
+}
+
+static void
+meta_background_slideshow_class_init (MetaBackgroundSlideshowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = meta_background_slideshow_finalize;
+}
+
+MetaBackgroundSlideshow *
+meta_background_slideshow_new (MetaScreen          *screen,
+                               const char          *picture_uri)
+{
+  MetaBackgroundSlideshow *self;
+
+  self = g_object_new (META_TYPE_BACKGROUND_SLIDESHOW, NULL);
+
+  self->screen = screen;
+  self->picture_uri = g_strdup (picture_uri);
+
+  return self;
+}
+
+const char *
+meta_background_slideshow_get_uri (MetaBackgroundSlideshow *self)
+{
+  return self->picture_uri;
+}
+
+int
+meta_background_slideshow_get_next_timeout (MetaBackgroundSlideshow *slideshow)
+{
+  time_t current_time;
+  Slide *current_slide;
+  int retval;
+
+  g_mutex_lock (&slideshow->slides_lock);
+
+  g_assert (!g_queue_is_empty (&slideshow->slides));
+
+  current_time = time(NULL);
+  current_slide = find_current_slide (slideshow, current_time);
+
+  if (current_slide->endtime < 0)
+    retval = -1;
+  else if (current_slide->to != NULL)
+    /* Translition slides have five minute granularity */
+    retval = 300;
+  else
+    retval = current_slide->endtime - current_time;
+
+  g_mutex_unlock (&slideshow->slides_lock);
+
+  return retval;
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]