[Xfce4-commits] <xfdesktop:master> Thumbnail service support to xfdesktop-settings (Bug #6536)

Eric Koegel noreply at xfce.org
Sun Aug 4 10:36:02 CEST 2013


Updating branch refs/heads/master
         to 59921a68c40f8115460553ae69bde5bda3bc7d76 (commit)
       from 3cec6972530e4e4af47dbb84358fa8fedcc00f82 (commit)

commit 59921a68c40f8115460553ae69bde5bda3bc7d76
Author: Eric Koegel <eric.koegel at gmail.com>
Date:   Mon Feb 25 11:49:16 2013 +0300

    Thumbnail service support to xfdesktop-settings (Bug #6536)

 common/xfdesktop-common.c            |   61 ++++++++-
 common/xfdesktop-common.h            |    2 +
 common/xfdesktop-thumbnailer.c       |   49 +++----
 common/xfdesktop-thumbnailer.h       |    3 +
 settings/main.c                      |  235 ++++++++++++++++++++++++++++++----
 settings/xfdesktop-settings-ui.glade |    4 +-
 6 files changed, 298 insertions(+), 56 deletions(-)

diff --git a/common/xfdesktop-common.c b/common/xfdesktop-common.c
index 344611a..755e273 100644
--- a/common/xfdesktop-common.c
+++ b/common/xfdesktop-common.c
@@ -188,13 +188,70 @@ xfdesktop_backdrop_choose_random(const gchar *filename)
     return file;
 }
 
+gchar *
+xfdesktop_get_file_mimetype(const gchar *file)
+{
+    GFile *temp_file;
+    GFileInfo *file_info;
+    gchar *mime_type = NULL;
+
+    g_return_val_if_fail(file != NULL, NULL);
+
+    temp_file = g_file_new_for_path(file);
+
+    g_return_val_if_fail(temp_file != NULL, NULL);
+
+    file_info = g_file_query_info(temp_file,
+                                  "standard::content-type",
+                                  0,
+                                  NULL,
+                                  NULL);
+
+    if(file_info != NULL) {
+        mime_type = g_strdup(g_file_info_get_content_type(file_info));
+
+        g_object_unref(file_info);
+    }
+
+    g_object_unref(temp_file);
+
+    return mime_type;
+}
+
 gboolean
 xfdesktop_image_file_is_valid(const gchar *filename)
 {
+    static GSList *pixbuf_formats = NULL;
+    GSList *l;
+    gboolean image_valid = FALSE;
+    gchar *file_mimetype;
+
     g_return_val_if_fail(filename, FALSE);
 
-    /* if gdk can get pixbuf info from the file then it's an image file */
-    return (gdk_pixbuf_get_file_info(filename, NULL, NULL) == NULL ? FALSE : TRUE);
+    if(pixbuf_formats == NULL) {
+        pixbuf_formats = gdk_pixbuf_get_formats();
+    }
+
+    file_mimetype = xfdesktop_get_file_mimetype(filename);
+
+    if(file_mimetype == NULL)
+        return FALSE;
+
+    /* Every pixbuf format has a list of mime types we can compare against */
+    for(l = pixbuf_formats; l != NULL && image_valid == FALSE; l = g_slist_next(l)) {
+        gint i;
+        gchar ** mimetypes = gdk_pixbuf_format_get_mime_types(l->data);
+
+        for(i = 0; mimetypes[i] != NULL && image_valid == FALSE; i++) {
+            if(g_strcmp0(file_mimetype, mimetypes[i]) == 0)
+                image_valid = TRUE;
+        }
+         g_strfreev(mimetypes);
+    }
+
+    g_free(file_mimetype);
+
+    return image_valid;
 }
 
 gboolean
diff --git a/common/xfdesktop-common.h b/common/xfdesktop-common.h
index 7c7570d..5334ba8 100644
--- a/common/xfdesktop-common.h
+++ b/common/xfdesktop-common.h
@@ -76,6 +76,8 @@ gchar *xfdesktop_backdrop_choose_random(const gchar *filename);
 
 gboolean xfdesktop_image_file_is_valid(const gchar *filename);
 
+gchar *xfdesktop_get_file_mimetype(const gchar *file);
+
 gboolean xfdesktop_check_is_running(Window *xid);
 void xfdesktop_send_client_message(Window xid, const gchar *msg);
 
diff --git a/common/xfdesktop-thumbnailer.c b/common/xfdesktop-thumbnailer.c
index 8f39fe9..2c735df 100644
--- a/common/xfdesktop-thumbnailer.c
+++ b/common/xfdesktop-thumbnailer.c
@@ -41,6 +41,7 @@
 #include <libxfce4util/libxfce4util.h>
 #include "xfdesktop-thumbnailer.h"
 #include "xfdesktop-marshal.h"
+#include "xfdesktop-common.h"
 
 static void xfdesktop_thumbnailer_init(GObject *);
 static void xfdesktop_thumbnailer_class_init(GObjectClass *);
@@ -257,34 +258,14 @@ xfdesktop_thumbnailer_new(void)
     return thumbnailer_object;
 }
 
-static gchar *
-xfdesktop_get_file_mimetype(gchar *file)
+gboolean xfdesktop_thumbnailer_service_available(XfdesktopThumbnailer *thumbnailer)
 {
-    GFile *temp_file;
-    GFileInfo *file_info;
-    gchar *mime_type = NULL;
-
-    g_return_val_if_fail(file != NULL, NULL);
-
-    temp_file = g_file_new_for_path(file);
-
-    g_return_val_if_fail(temp_file != NULL, NULL);
-
-    file_info = g_file_query_info(temp_file,
-                                  "standard::content-type",
-                                  0,
-                                  NULL,
-                                  NULL);
-
-    if(file_info != NULL) {
-        mime_type = g_strdup(g_file_info_get_content_type(file_info));
+    g_return_val_if_fail(XFDESKTOP_IS_THUMBNAILER(thumbnailer), FALSE);
 
-        g_object_unref(file_info);
-    }
+    if(thumbnailer->priv->proxy == NULL)
+        return FALSE;
 
-    g_object_unref(temp_file);
-    
-    return mime_type;
+    return TRUE;
 }
 
 gboolean
@@ -370,6 +351,12 @@ xfdesktop_thumbnailer_queue_thumbnail(XfdesktopThumbnailer *thumbnailer,
     return TRUE;
 }
 
+static void
+xfdesktop_thumbnailer_dequeue_foreach(gpointer data, gpointer user_data)
+{
+    xfdesktop_thumbnailer_dequeue_thumbnail(user_data, data);
+}
+
 /**
  * xfdesktop_thumbnailer_dequeue_thumbnail:
  * 
@@ -402,9 +389,8 @@ xfdesktop_thumbnailer_dequeue_thumbnail(XfdesktopThumbnailer *thumbnailer,
     }
 
     if(g_slist_find(thumbnailer->priv->queue, file) != NULL) {
-            thumbnailer->priv->queue = g_slist_remove_all(
-                                                    thumbnailer->priv->queue,
-                                                    file);
+        thumbnailer->priv->queue = g_slist_remove_all(thumbnailer->priv->queue,
+                                                      file);
     }
 
     thumbnailer->priv->request_timer_id = g_timeout_add_full(
@@ -415,6 +401,13 @@ xfdesktop_thumbnailer_dequeue_thumbnail(XfdesktopThumbnailer *thumbnailer,
                         NULL);
 }
 
+void xfdesktop_thumbnailer_dequeue_all_thumbnails(XfdesktopThumbnailer *thumbnailer)
+{
+    g_return_if_fail(XFDESKTOP_IS_THUMBNAILER(thumbnailer));
+
+    g_slist_foreach(thumbnailer->priv->queue, (GFunc)xfdesktop_thumbnailer_dequeue_foreach, thumbnailer);
+}
+
 static gboolean
 xfdesktop_thumbnailer_queue_request_timer(XfdesktopThumbnailer *thumbnailer)
 {
diff --git a/common/xfdesktop-thumbnailer.h b/common/xfdesktop-thumbnailer.h
index 842f155..c7ea4c5 100644
--- a/common/xfdesktop-thumbnailer.h
+++ b/common/xfdesktop-thumbnailer.h
@@ -60,6 +60,8 @@ XfdesktopThumbnailer * xfdesktop_thumbnailer_new(void);
 
 GType xfdesktop_thumbnailer_get_type(void);
 
+gboolean xfdesktop_thumbnailer_service_available(XfdesktopThumbnailer *thumbnailer);
+
 gboolean xfdesktop_thumbnailer_is_supported(XfdesktopThumbnailer *thumbnailer,
                                             gchar *file);
 
@@ -67,6 +69,7 @@ gboolean xfdesktop_thumbnailer_queue_thumbnail(XfdesktopThumbnailer *thumbnailer
                                                gchar *file);
 void xfdesktop_thumbnailer_dequeue_thumbnail(XfdesktopThumbnailer *thumbnailer,
                                              gchar *file);
+void xfdesktop_thumbnailer_dequeue_all_thumbnails(XfdesktopThumbnailer *thumbnailer);
 
 void xfdesktop_thumbnailer_delete_thumbnail(XfdesktopThumbnailer *thumbnailer,
                                             gchar *src_file);
diff --git a/settings/main.c b/settings/main.c
index 1308b21..d0d7996 100644
--- a/settings/main.c
+++ b/settings/main.c
@@ -50,6 +50,7 @@
 #include <libwnck/libwnck.h>
 
 #include "xfdesktop-common.h"
+#include "xfdesktop-thumbnailer.h"
 #include "xfdesktop-settings-ui.h"
 #include "xfdesktop-settings-appearance-frame-ui.h"
 
@@ -82,6 +83,12 @@
 
 typedef struct
 {
+    GtkTreeModel *model;
+    GSList *iters;
+} PreviewData;
+
+typedef struct
+{
     XfconfChannel *channel;
     gint screen;
     gint monitor;
@@ -105,6 +112,11 @@ typedef struct
     GtkWidget *random_backdrop_order_chkbox;
 
     GThread *preview_thread;
+    PreviewData *pdata;
+
+    XfdesktopThumbnailer *thumbnailer;
+
+    gint request_timer_id;
 
 } AppearancePanel;
 
@@ -113,6 +125,7 @@ enum
     COL_PIX = 0,
     COL_NAME,
     COL_FILENAME,
+    COL_THUMBNAIL,
     COL_COLLATE_KEY,
     N_COLS,
 };
@@ -133,15 +146,22 @@ static void
 xfdesktop_settings_do_single_preview(GtkTreeModel *model,
                                      GtkTreeIter *iter)
 {
-    gchar *name = NULL, *new_name = NULL, *filename = NULL;
+    gchar *name = NULL, *new_name = NULL, *filename = NULL, *thumbnail = NULL;
     GdkPixbuf *pix, *pix_scaled = NULL;
 
     gtk_tree_model_get(model, iter,
                        COL_NAME, &name,
                        COL_FILENAME, &filename,
+                       COL_THUMBNAIL, &thumbnail,
                        -1);
 
-    pix = gdk_pixbuf_new_from_file(filename, NULL);
+    if(thumbnail == NULL) {
+        pix = gdk_pixbuf_new_from_file(filename, NULL);
+    } else {
+        pix = gdk_pixbuf_new_from_file(thumbnail, NULL);
+        g_free(thumbnail);
+    }
+
     g_free(filename);
     if(pix) {
         gint width, height;
@@ -185,6 +205,27 @@ xfdesktop_settings_do_single_preview(GtkTreeModel *model,
 }
 
 static gpointer
+xfdesktop_settings_create_some_previews(gpointer data)
+{
+    PreviewData *pdata = data;
+    GSList *l;
+
+    GDK_THREADS_ENTER ();
+
+    for(l = pdata->iters; l && l->data != NULL; l = l->next)
+        xfdesktop_settings_do_single_preview(pdata->model, l->data);
+
+    g_object_unref(G_OBJECT(pdata->model));
+    g_slist_foreach(pdata->iters, (GFunc)gtk_tree_iter_free, NULL);
+    g_slist_free(pdata->iters);
+    g_free(pdata);
+
+    GDK_THREADS_LEAVE ();
+
+    return NULL;
+}
+
+static gpointer
 xfdesktop_settings_create_all_previews(gpointer data)
 {
     GtkTreeModel *model = data;
@@ -221,6 +262,90 @@ xfdesktop_settings_create_all_previews(gpointer data)
 }
 
 static void
+cb_request_timer(gpointer data)
+{
+    AppearancePanel *panel = data;
+    PreviewData *pdata = panel->pdata;
+
+    TRACE("entering");
+
+    if(pdata == NULL)
+        return;
+
+    panel->pdata = NULL;
+    g_source_remove(panel->request_timer_id);
+    panel->request_timer_id = 0;
+
+    if(!g_thread_try_new("xfdesktop_settings_create_some_previews",
+                     xfdesktop_settings_create_some_previews,
+                     pdata, NULL))
+    {
+            g_critical("Unable to create thread for image previews.");
+            g_object_unref(G_OBJECT(pdata->model));
+            g_slist_foreach(pdata->iters, (GFunc)gtk_tree_iter_free, NULL);
+            g_slist_free(pdata->iters);
+            g_free(pdata);
+    }
+}
+
+static void
+cb_thumbnail_ready(XfdesktopThumbnailer *thumbnailer,
+                   gchar *src_file, gchar *thumb_file,
+                   gpointer user_data)
+{
+    AppearancePanel *panel = user_data;
+    GtkTreeModel *model = gtk_icon_view_get_model(GTK_ICON_VIEW(panel->image_iconview));
+    GtkTreeIter iter;
+
+    if(gtk_tree_model_get_iter_first(model, &iter)) {
+        do {
+            gchar *filename = NULL;
+            gtk_tree_model_get(model, &iter, COL_FILENAME, &filename, -1);
+
+            if(g_strcmp0(filename, src_file) == 0) {
+                gtk_list_store_set(GTK_LIST_STORE(model), &iter,
+                                   COL_THUMBNAIL, thumb_file, -1);
+
+                if(panel->request_timer_id != 0) {
+                    g_source_remove(panel->request_timer_id);
+                }
+
+                if(panel->pdata == NULL) {
+                    panel->pdata = g_new0(PreviewData, 1);
+                    panel->pdata->model = g_object_ref(G_OBJECT(model));
+                }
+
+                panel->pdata->iters = g_slist_prepend(panel->pdata->iters, gtk_tree_iter_copy(&iter));
+
+                panel->request_timer_id = g_timeout_add_full(
+                                G_PRIORITY_LOW,
+                                100,
+                                (GSourceFunc)cb_request_timer,
+                                panel,
+                                NULL);
+
+                g_free(filename);
+                return;
+            }
+
+            g_free(filename);
+        } while(gtk_tree_model_iter_next(model, &iter));
+    }
+}
+
+/* Attempts to queue a thumbnail preview */
+static void
+xfdesktop_settings_create_thumbnail_preview(GtkTreeModel *model,
+                                            GtkTreeIter *iter,
+                                            AppearancePanel *panel)
+{
+    gchar *filename;
+
+    gtk_tree_model_get(model, iter, COL_FILENAME, &filename, -1);
+    xfdesktop_thumbnailer_queue_thumbnail(panel->thumbnailer, filename);
+}
+
+static void
 cb_special_icon_toggled(GtkCellRendererToggle *render, gchar *path, gpointer user_data)
 {
     XfconfChannel *channel = g_object_get_data(G_OBJECT(user_data),
@@ -345,7 +470,8 @@ image_list_compare(GtkTreeModel *model,
 
 static GtkTreeIter *
 xfdesktop_settings_image_iconview_add(GtkTreeModel *model,
-                                      const char *path)
+                                      const char *path,
+                                      AppearancePanel *panel)
 {
     gboolean added = FALSE, found = FALSE, valid = FALSE;
     GtkTreeIter iter, search_iter;
@@ -388,6 +514,8 @@ xfdesktop_settings_image_iconview_add(GtkTreeModel *model,
                                COL_COLLATE_KEY, lower,
                                -1);
 
+            xfdesktop_settings_create_thumbnail_preview(model, &iter, panel);
+
             added = TRUE;
         }
     }
@@ -406,13 +534,17 @@ xfdesktop_settings_image_iconview_add(GtkTreeModel *model,
 static GtkTreeIter *
 xfdesktop_image_list_add_dir(GtkListStore *ls,
                              const char *path,
-                             const char *cur_image_file)
+                             const char *cur_image_file,
+                             AppearancePanel *panel)
 {
     GDir *dir;
     gboolean needs_slash = TRUE;
     const gchar *file;
     GtkTreeIter *iter, *iter_ret = NULL;
     gchar buf[PATH_MAX];
+#ifdef G_ENABLE_DEBUG
+    GTimer *timer = g_timer_new();
+#endif
 
     dir = g_dir_open(path, 0, 0);
     if(!dir)
@@ -425,7 +557,7 @@ xfdesktop_image_list_add_dir(GtkListStore *ls,
         g_snprintf(buf, sizeof(buf), needs_slash ? "%s/%s" : "%s%s",
                    path, file);
 
-        iter = xfdesktop_settings_image_iconview_add(GTK_TREE_MODEL(ls), buf);
+        iter = xfdesktop_settings_image_iconview_add(GTK_TREE_MODEL(ls), buf, panel);
         if(iter) {
             if(cur_image_file && !iter_ret && !strcmp(buf, cur_image_file))
                 iter_ret = iter;
@@ -436,6 +568,11 @@ xfdesktop_image_list_add_dir(GtkListStore *ls,
 
     g_dir_close(dir);
 
+#ifdef G_ENABLE_DEBUG
+    DBG("time %f", g_timer_elapsed(timer, NULL));
+    g_timer_destroy(timer);
+#endif
+
     return iter_ret;
 }
 
@@ -606,11 +743,44 @@ cb_xfdesktop_spin_icon_size_changed(GtkSpinButton *button,
 }
 
 static void
+xfdesktop_settings_stop_image_loading(AppearancePanel *panel)
+{
+    /* stop any thumbnailing in progress */
+    xfdesktop_thumbnailer_dequeue_all_thumbnails(panel->thumbnailer);
+
+    /* Remove any pending thumbnail updates */
+    if(panel->request_timer_id != 0) {
+        PreviewData *pdata = panel->pdata;
+        g_source_remove(panel->request_timer_id);
+        panel->request_timer_id = 0;
+        g_object_unref(G_OBJECT(pdata->model));
+        g_slist_foreach(pdata->iters, (GFunc)gtk_tree_iter_free, NULL);
+        g_slist_free(pdata->iters);
+        g_free(pdata);
+    }
+
+    /* Remove any preview threads that may still be running */
+    if(panel->preview_thread != NULL) {
+        g_thread_unref(panel->preview_thread);
+        panel->preview_thread = NULL;
+    }
+}
+
+static void
+cb_xfdesktop_bnt_exit_clicked(GtkButton *button, gpointer user_data)
+{
+    AppearancePanel *panel = user_data;
+
+    xfdesktop_settings_stop_image_loading(panel);
+}
+
+static void
 cb_folder_selection_changed(GtkWidget *button,
                             gpointer user_data)
 {
     AppearancePanel *panel = user_data;
     gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(button));
+    static gchar *previous_filename = NULL;
     GtkListStore *ls;
     GtkTreeIter *iter;
     GtkTreePath *path;
@@ -618,8 +788,17 @@ cb_folder_selection_changed(GtkWidget *button,
 
     TRACE("entering");
 
+    /* Check to see if the folder actually did change */
+    if(g_strcmp0(filename, previous_filename) == 0)
+        return;
+
+    TRACE("folder changed to: %s", filename);
+    previous_filename = filename;
+
+    xfdesktop_settings_stop_image_loading(panel);
+
     ls = gtk_list_store_new(N_COLS, GDK_TYPE_PIXBUF, G_TYPE_STRING,
-                            G_TYPE_STRING, G_TYPE_STRING);
+                            G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
 
     property = xfdesktop_settings_generate_per_workspace_binding_string(panel, "last-image");
 
@@ -628,7 +807,7 @@ cb_folder_selection_changed(GtkWidget *button,
     if(last_image == NULL)
         last_image = DEFAULT_BACKDROP;
 
-    iter = xfdesktop_image_list_add_dir(ls, filename, last_image);
+    iter = xfdesktop_image_list_add_dir(ls, filename, last_image, panel);
 
     gtk_icon_view_set_model(GTK_ICON_VIEW(panel->image_iconview),
                             GTK_TREE_MODEL(ls));
@@ -642,22 +821,18 @@ cb_folder_selection_changed(GtkWidget *button,
         }
      }
 
-    /* remove any preview threads that may still be running since we've probably
-     * changed to a new monitor/workspace */
-    if(panel->preview_thread != NULL) {
-        g_thread_unref(panel->preview_thread);
-        panel->preview_thread = NULL;
-    }
-
-    /* generate previews of each image -- the new thread will own
-     * the reference on the list store, so let's not unref it here */
-    panel->preview_thread = g_thread_try_new("xfdesktop_settings_create_all_previews",
-                                             xfdesktop_settings_create_all_previews,
-                                             ls, NULL);
-
-    if(panel->preview_thread == NULL) {
-        g_critical("Failed to spawn thread; backdrop previews will be unavailable.");
-        g_object_unref(G_OBJECT(ls));
+    /* Thumbnailer not available, fallback to loading the images in a thread */
+    if(!xfdesktop_thumbnailer_service_available(panel->thumbnailer)) {
+        /* generate previews of each image -- the new thread will own
+         * the reference on the list store, so let's not unref it here */
+        panel->preview_thread = g_thread_try_new("xfdesktop_settings_create_all_previews",
+                                                 xfdesktop_settings_create_all_previews,
+                                                 ls, NULL);
+
+        if(panel->preview_thread == NULL) {
+            g_critical("Failed to spawn thread; backdrop previews will be unavailable.");
+            g_object_unref(G_OBJECT(ls));
+        }
     }
 
     g_free(property);
@@ -890,6 +1065,8 @@ xfdesktop_settings_setup_image_iconview(AppearancePanel *panel)
 
     TRACE("entering");
 
+    panel->thumbnailer = xfdesktop_thumbnailer_new();
+
     g_object_set(G_OBJECT(iconview),
                 "pixbuf-column", COL_PIX,
                 "item-width", PREVIEW_WIDTH,
@@ -899,6 +1076,9 @@ xfdesktop_settings_setup_image_iconview(AppearancePanel *panel)
 
     g_signal_connect(G_OBJECT(iconview), "selection-changed",
                      G_CALLBACK(cb_image_selection_changed), panel);
+
+    g_signal_connect(panel->thumbnailer, "thumbnail-ready",
+                     G_CALLBACK(cb_thumbnail_ready), panel);
 }
 
 static void
@@ -909,7 +1089,8 @@ xfdesktop_settings_dialog_setup_tabs(GtkBuilder *main_gxml,
 {
     GtkWidget *appearance_container, *chk_custom_font_size,
               *spin_font_size, *w, *box, *spin_icon_size,
-              *chk_show_thumbnails, *chk_single_click, *appearance_settings;
+              *chk_show_thumbnails, *chk_single_click, *appearance_settings,
+              *bnt_exit;
     GtkBuilder *appearance_gxml;
     AppearancePanel *panel = g_new0(AppearancePanel, 1);
     GError *error = NULL;
@@ -922,6 +1103,12 @@ xfdesktop_settings_dialog_setup_tabs(GtkBuilder *main_gxml,
     appearance_container = GTK_WIDGET(gtk_builder_get_object(main_gxml,
                                                              "notebook_screens"));
 
+    bnt_exit = GTK_WIDGET(gtk_builder_get_object(main_gxml, "bnt_exit"));
+
+    g_signal_connect(G_OBJECT(bnt_exit), "clicked",
+                     G_CALLBACK(cb_xfdesktop_bnt_exit_clicked),
+                     panel);
+
     /* Icons tab */
     /* icon size */
     spin_icon_size = GTK_WIDGET(gtk_builder_get_object(main_gxml, "spin_icon_size"));
diff --git a/settings/xfdesktop-settings-ui.glade b/settings/xfdesktop-settings-ui.glade
index 7bccb6b..b69fe4b 100644
--- a/settings/xfdesktop-settings-ui.glade
+++ b/settings/xfdesktop-settings-ui.glade
@@ -769,7 +769,7 @@
               </object>
             </child>
             <child>
-              <object class="GtkButton" id="button1">
+              <object class="GtkButton" id="bnt_exit">
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="receives_default">True</property>
@@ -791,7 +791,7 @@
     </child>
     <action-widgets>
       <action-widget response="-11">button2</action-widget>
-      <action-widget response="-3">button1</action-widget>
+      <action-widget response="-3">bnt_exit</action-widget>
     </action-widgets>
   </object>
 </interface>


More information about the Xfce4-commits mailing list