summaryrefslogtreecommitdiff
path: root/trashapplet/src
diff options
context:
space:
mode:
authorPerberos <[email protected]>2011-11-14 18:24:48 -0300
committerPerberos <[email protected]>2011-11-14 18:24:48 -0300
commit312ba610a1e98fc656fb58178227d7d45a64494e (patch)
tree54a3c2b6084c80e63fb0526c6e7b8e01627acbd7 /trashapplet/src
downloadmate-applets-312ba610a1e98fc656fb58178227d7d45a64494e.tar.bz2
mate-applets-312ba610a1e98fc656fb58178227d7d45a64494e.tar.xz
initial
Diffstat (limited to 'trashapplet/src')
-rw-r--r--trashapplet/src/Makefile.am19
-rw-r--r--trashapplet/src/trash-empty.c378
-rw-r--r--trashapplet/src/trash-empty.h29
-rw-r--r--trashapplet/src/trashapplet.c645
-rw-r--r--trashapplet/src/xstuff.c536
-rw-r--r--trashapplet/src/xstuff.h35
6 files changed, 1642 insertions, 0 deletions
diff --git a/trashapplet/src/Makefile.am b/trashapplet/src/Makefile.am
new file mode 100644
index 00000000..37c83dfb
--- /dev/null
+++ b/trashapplet/src/Makefile.am
@@ -0,0 +1,19 @@
+INCLUDES = -I$(top_srcdir) \
+ $(MATE_APPLETS3_CFLAGS) \
+ $(GIO_CFLAGS) \
+ -DTRASH_MENU_UI_DIR=\""$(datadir)/mate-2.0/ui"\"
+
+libexec_PROGRAMS = trashapplet
+
+trashapplet_SOURCES = \
+ trashapplet.c \
+ trash-empty.h \
+ trash-empty.c \
+ xstuff.c \
+ xstuff.h
+
+trashapplet_LDADD = \
+ $(MATE_APPLETS3_LIBS) \
+ $(GIO_LIBS)
+
+-include $(top_srcdir)/git.mk
diff --git a/trashapplet/src/trash-empty.c b/trashapplet/src/trash-empty.c
new file mode 100644
index 00000000..1c311e5e
--- /dev/null
+++ b/trashapplet/src/trash-empty.c
@@ -0,0 +1,378 @@
+/*
+ * trash-empty.c: a routine to empty the trash
+ *
+ * Copyright © 2008 Ryan Lortie
+ *
+ * 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 <mateconf/mateconf-client.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+#include "trash-empty.h"
+#include "config.h"
+
+/* only one concurrent trash empty operation can occur */
+static GtkDialog *trash_empty_confirm_dialog;
+static GtkDialog *trash_empty_dialog;
+static GtkProgressBar *trash_empty_progress_bar;
+static GtkLabel *trash_empty_location;
+static GtkLabel *trash_empty_file;
+
+/* the rules:
+ * 1) nothing here may be modified while trash_empty_update_pending.
+ * 2) an idle may only be scheduled if trash_empty_update_pending.
+ * 3) only the worker may set trash_empty_update_pending = TRUE.
+ * 4) only the UI updater may set trash_empty_update_pending = FALSE.
+ *
+ * i -think- this is threadsafe... ((famous last words...))
+ */
+static GFile * volatile trash_empty_current_file;
+static gsize volatile trash_empty_deleted_files;
+static gsize volatile trash_empty_total_files;
+static gboolean volatile trash_empty_update_pending;
+
+static gboolean
+trash_empty_clear_pending (gpointer user_data)
+{
+ trash_empty_update_pending = FALSE;
+
+ return FALSE;
+}
+
+static gboolean
+trash_empty_update_dialog (gpointer user_data)
+{
+ gsize deleted, total;
+ GFile *file;
+
+ g_assert (trash_empty_update_pending);
+
+ deleted = trash_empty_deleted_files;
+ total = trash_empty_total_files;
+ file = trash_empty_current_file;
+
+ /* maybe the done() got processed first. */
+ if (trash_empty_dialog)
+ {
+ char *index_str, *total_str;
+ char *text_tmp, *text;
+ char *tmp;
+
+ /* The i18n tools can't handle a direct embedding of the
+ * size format using a macro. This is a work-around. */
+ index_str = g_strdup_printf ("%"G_GSIZE_FORMAT, deleted + 1);
+ total_str = g_strdup_printf ("%"G_GSIZE_FORMAT, total);
+ /* Translators: the %s in this string should be read as %d. */
+ text = g_strdup_printf (_("Removing item %s of %s"),
+ index_str, total_str);
+ gtk_progress_bar_set_text (trash_empty_progress_bar, text);
+ g_free (total_str);
+ g_free (index_str);
+ g_free (text);
+
+ if (deleted > total)
+ gtk_progress_bar_set_fraction (trash_empty_progress_bar, 1.0);
+ else
+ gtk_progress_bar_set_fraction (trash_empty_progress_bar,
+ (gdouble) deleted / (gdouble) total);
+
+ /* no g_file_get_basename? */
+ {
+ GFile *parent;
+
+ parent = g_file_get_parent (file);
+ text = g_file_get_uri (parent);
+ g_object_unref (parent);
+ }
+ gtk_label_set_text (trash_empty_location, text);
+ g_free (text);
+
+ tmp = g_file_get_basename (file);
+ /* Translators: %s is a file name */
+ text_tmp = g_strdup_printf (_("Removing: %s"), tmp);
+ text = g_markup_printf_escaped ("<i>%s</i>", text_tmp);
+ gtk_label_set_markup (trash_empty_file, text);
+ g_free (text);
+ g_free (text_tmp);
+ g_free (tmp);
+
+ /* unhide the labels */
+ gtk_widget_show_all (GTK_WIDGET (trash_empty_dialog));
+ }
+
+ trash_empty_current_file = NULL;
+ g_object_unref (file);
+
+ trash_empty_update_pending = FALSE;
+
+ return FALSE;
+}
+
+static gboolean
+trash_empty_done (gpointer user_data)
+{
+ gtk_object_destroy (GTK_OBJECT (trash_empty_dialog));
+
+ g_assert (trash_empty_dialog == NULL);
+
+ return FALSE;
+}
+
+/* =============== worker thread code begins here =============== */
+static void
+trash_empty_maybe_schedule_update (GIOSchedulerJob *job,
+ GFile *file,
+ gsize deleted)
+{
+ if (!trash_empty_update_pending)
+ {
+ g_assert (trash_empty_current_file == NULL);
+
+ trash_empty_current_file = g_object_ref (file);
+ trash_empty_deleted_files = deleted;
+
+ trash_empty_update_pending = TRUE;
+ g_io_scheduler_job_send_to_mainloop_async (job,
+ trash_empty_update_dialog,
+ NULL, NULL);
+ }
+}
+
+static void
+trash_empty_delete_contents (GIOSchedulerJob *job,
+ GCancellable *cancellable,
+ GFile *file,
+ gboolean actually_delete,
+ gsize *deleted)
+{
+ GFileEnumerator *enumerator;
+ GFileInfo *info;
+ GFile *child;
+
+ if (g_cancellable_is_cancelled (cancellable))
+ return;
+
+ enumerator = g_file_enumerate_children (file,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable, NULL);
+ if (enumerator)
+ {
+ while ((info = g_file_enumerator_next_file (enumerator,
+ cancellable, NULL)) != NULL)
+ {
+ child = g_file_get_child (file, g_file_info_get_name (info));
+
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+ trash_empty_delete_contents (job, cancellable, child,
+ actually_delete, deleted);
+
+ if (actually_delete)
+ {
+ trash_empty_maybe_schedule_update (job, child, *deleted);
+ g_file_delete (child, cancellable, NULL);
+ }
+
+ (*deleted)++;
+
+ g_object_unref (child);
+ g_object_unref (info);
+
+ if (g_cancellable_is_cancelled (cancellable))
+ break;
+ }
+
+ g_object_unref (enumerator);
+ }
+}
+
+static gboolean
+trash_empty_job (GIOSchedulerJob *job,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ gsize deleted;
+ GFile *trash;
+
+ trash = g_file_new_for_uri ("trash:///");
+
+ /* first do a dry run to count the number of files */
+ deleted = 0;
+ trash_empty_delete_contents (job, cancellable, trash, FALSE, &deleted);
+ trash_empty_total_files = deleted;
+
+ /* now do the real thing */
+ deleted = 0;
+ trash_empty_delete_contents (job, cancellable, trash, TRUE, &deleted);
+
+ /* done */
+ g_object_unref (trash);
+ g_io_scheduler_job_send_to_mainloop_async (job,
+ trash_empty_done,
+ NULL, NULL);
+
+ return FALSE;
+}
+/* ================ worker thread code ends here ================ */
+
+static void
+trash_empty_start (GtkWidget *parent)
+{
+ struct { const char *name; gpointer *pointer; } widgets[] =
+ {
+ { "empty_trash", (gpointer *) &trash_empty_dialog },
+ { "progressbar", (gpointer *) &trash_empty_progress_bar },
+ { "location_label", (gpointer *) &trash_empty_location },
+ { "file_label", (gpointer *) &trash_empty_file }
+ };
+ GCancellable *cancellable;
+ GtkBuilder *builder;
+ gint i;
+
+ builder = gtk_builder_new ();
+ gtk_builder_add_from_file (builder,
+ GTK_BUILDERDIR "/trashapplet-empty-progress.ui",
+ NULL);
+
+ for (i = 0; i < G_N_ELEMENTS (widgets); i++)
+ {
+ GObject *object;
+
+ object = gtk_builder_get_object (builder, widgets[i].name);
+
+ if (object == NULL)
+ {
+ g_critical ("failed to parse trash-empty dialog markup");
+
+ if (trash_empty_dialog)
+ gtk_object_destroy (GTK_OBJECT (trash_empty_dialog));
+
+ g_object_unref (builder);
+ return;
+ }
+
+ *widgets[i].pointer = object;
+ g_object_add_weak_pointer (object, widgets[i].pointer);
+ }
+ g_object_unref (builder);
+
+ cancellable = g_cancellable_new ();
+ g_signal_connect_object (trash_empty_dialog, "response",
+ G_CALLBACK (g_cancellable_cancel),
+ cancellable, G_CONNECT_SWAPPED);
+ g_io_scheduler_push_job (trash_empty_job, NULL, NULL, 0, cancellable);
+ g_object_unref (cancellable);
+
+ gtk_window_set_screen (GTK_WINDOW (trash_empty_dialog),
+ gtk_widget_get_screen (parent));
+ gtk_widget_show (GTK_WIDGET (trash_empty_dialog));
+}
+
+static gboolean
+trash_empty_require_confirmation (void)
+{
+ return mateconf_client_get_bool (mateconf_client_get_default (),
+ "/apps/caja/preferences/confirm_trash",
+ NULL);
+}
+
+static void
+trash_empty_confirmation_response (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ if (response_id == GTK_RESPONSE_YES)
+ trash_empty_start (GTK_WIDGET (dialog));
+
+ gtk_object_destroy (GTK_OBJECT (dialog));
+ g_assert (trash_empty_confirm_dialog == NULL);
+}
+
+/*
+ * The code in trash_empty_show_confirmation_dialog() was taken from
+ * libcaja-private/caja-file-operations.c (confirm_empty_trash)
+ * by Michiel Sikkes <[email protected]> and adapted for the applet.
+ */
+static void
+trash_empty_show_confirmation_dialog (GtkWidget *parent)
+{
+ GtkWidget *dialog;
+ GtkWidget *button;
+ GdkScreen *screen;
+
+ if (!trash_empty_require_confirmation ())
+ {
+ trash_empty_start (parent);
+ return;
+ }
+
+ screen = gtk_widget_get_screen (parent);
+
+ dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_NONE,
+ _("Empty all of the items from "
+ "the trash?"));
+ trash_empty_confirm_dialog = GTK_DIALOG (dialog);
+ g_object_add_weak_pointer (G_OBJECT (dialog),
+ (gpointer *) &trash_empty_confirm_dialog);
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("If you choose to empty "
+ "the trash, all items in "
+ "it will be permanently "
+ "lost. Please note that "
+ "you can also delete them "
+ "separately."));
+
+ gtk_window_set_screen (GTK_WINDOW (dialog), screen);
+ atk_object_set_role (gtk_widget_get_accessible (dialog), ATK_ROLE_ALERT);
+ gtk_window_set_wmclass (GTK_WINDOW (dialog), "empty_trash", "Caja");
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+
+ button = gtk_button_new_with_mnemonic (_("_Empty Trash"));
+ gtk_widget_show (button);
+ gtk_widget_set_can_default (button, TRUE);
+
+ gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
+ GTK_RESPONSE_YES);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ GTK_RESPONSE_YES);
+
+ gtk_widget_show (dialog);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (trash_empty_confirmation_response), NULL);
+}
+
+void
+trash_empty (GtkWidget *parent)
+{
+ if (trash_empty_confirm_dialog)
+ gtk_window_present (GTK_WINDOW (trash_empty_confirm_dialog));
+ else if (trash_empty_dialog)
+ gtk_window_present (GTK_WINDOW (trash_empty_dialog));
+
+ /* theoretically possible that an update is pending, but very unlikely. */
+ else if (!trash_empty_update_pending)
+ trash_empty_show_confirmation_dialog (parent);
+}
diff --git a/trashapplet/src/trash-empty.h b/trashapplet/src/trash-empty.h
new file mode 100644
index 00000000..1bfbdc52
--- /dev/null
+++ b/trashapplet/src/trash-empty.h
@@ -0,0 +1,29 @@
+/*
+ * trash-empty.h: empty the trash
+ *
+ * Copyright © 2008 Ryan Lortie
+ *
+ * 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.
+ */
+
+#ifndef _trash_empty_h_
+#define _trash_empty_h_
+
+#include <gtk/gtk.h>
+
+void trash_empty (GtkWidget *parent);
+
+#endif /* _trash_empty_h_ */
diff --git a/trashapplet/src/trashapplet.c b/trashapplet/src/trashapplet.c
new file mode 100644
index 00000000..2294c7ac
--- /dev/null
+++ b/trashapplet/src/trashapplet.c
@@ -0,0 +1,645 @@
+/* -*- mode: c; c-basic-offset: 8 -*-
+ * trashapplet.c
+ *
+ * Copyright (c) 2004 Michiel Sikkes <[email protected]>,
+ * 2004 Emmanuele Bassi <[email protected]>
+ * 2008 Ryan Lortie <[email protected]>
+ * Matthias Clasen <[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
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <mateconf/mateconf-client.h>
+#include <gio/gio.h>
+#include <mate-panel-applet.h>
+
+#include "trash-empty.h"
+#include "xstuff.h"
+
+typedef MatePanelAppletClass TrashAppletClass;
+
+typedef struct
+{
+ MatePanelApplet applet;
+
+ GFileMonitor *trash_monitor;
+ GFile *trash;
+
+ GtkImage *image;
+ GIcon *icon;
+ gint items;
+} TrashApplet;
+
+G_DEFINE_TYPE (TrashApplet, trash_applet, PANEL_TYPE_APPLET);
+#define TRASH_TYPE_APPLET (trash_applet_get_type ())
+#define TRASH_APPLET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TRASH_TYPE_APPLET, TrashApplet))
+
+static void trash_applet_do_empty (GtkAction *action,
+ TrashApplet *applet);
+static void trash_applet_show_about (GtkAction *action,
+ TrashApplet *applet);
+static void trash_applet_open_folder (GtkAction *action,
+ TrashApplet *applet);
+static void trash_applet_show_help (GtkAction *action,
+ TrashApplet *applet);
+
+static const GtkActionEntry trash_applet_menu_actions [] = {
+ { "EmptyTrash", GTK_STOCK_CLEAR, N_("_Empty Trash"),
+ NULL, NULL,
+ G_CALLBACK (trash_applet_do_empty) },
+ { "OpenTrash", GTK_STOCK_OPEN, N_("_Open Trash"),
+ NULL, NULL,
+ G_CALLBACK (trash_applet_open_folder) },
+ { "HelpTrash", GTK_STOCK_HELP, N_("_Help"),
+ NULL, NULL,
+ G_CALLBACK (trash_applet_show_help) },
+ { "AboutTrash", GTK_STOCK_ABOUT, N_("_About"),
+ NULL, NULL,
+ G_CALLBACK (trash_applet_show_about) }
+};
+
+static void
+trash_applet_monitor_changed (TrashApplet *applet)
+{
+ GError *error = NULL;
+ GFileInfo *info;
+ GIcon *icon;
+ gint items;
+
+ info = g_file_query_info (applet->trash,
+ G_FILE_ATTRIBUTE_STANDARD_ICON","
+ G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT,
+ 0, NULL, &error);
+
+ if (!info)
+ {
+ g_critical ("could not query trash:/: '%s'", error->message);
+ g_error_free (error);
+
+ return;
+ }
+
+ icon = g_file_info_get_icon (info);
+ items = g_file_info_get_attribute_uint32 (info,
+ G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT);
+
+ if (!g_icon_equal (icon, applet->icon))
+ {
+ /* note: the size is meaningless here,
+ * since we do set_pixel_size() later
+ */
+ gtk_image_set_from_gicon (GTK_IMAGE (applet->image),
+ icon, GTK_ICON_SIZE_MENU);
+
+ if (applet->icon)
+ g_object_unref (applet->icon);
+
+ applet->icon = g_object_ref (icon);
+ }
+
+ if (items != applet->items)
+ {
+ if (items)
+ {
+ char *text;
+
+ text = g_strdup_printf (ngettext ("%d Item in Trash",
+ "%d Items in Trash",
+ items), items);
+ gtk_widget_set_tooltip_text (GTK_WIDGET (applet), text);
+ g_free (text);
+ }
+ else
+ gtk_widget_set_tooltip_text (GTK_WIDGET (applet),
+ _("No Items in Trash"));
+
+ applet->items = items;
+ }
+
+ g_object_unref (info);
+}
+
+static void
+trash_applet_set_icon_size (TrashApplet *applet,
+ gint size)
+{
+ /* copied from button-widget.c in the panel */
+ if (size < 22)
+ size = 16;
+ else if (size < 24)
+ size = 22;
+ else if (size < 32)
+ size = 24;
+ else if (size < 48)
+ size = 32;
+ else
+ size = 48;
+
+ /* GtkImage already contains a check to do nothing if it's the same */
+ gtk_image_set_pixel_size (applet->image, size);
+}
+
+static void
+trash_applet_size_allocate (GtkWidget *widget,
+ GdkRectangle *allocation)
+{
+ TrashApplet *applet = TRASH_APPLET (widget);
+
+ switch (mate_panel_applet_get_orient (MATE_PANEL_APPLET (applet)))
+ {
+ case MATE_PANEL_APPLET_ORIENT_LEFT:
+ case MATE_PANEL_APPLET_ORIENT_RIGHT:
+ trash_applet_set_icon_size (applet, allocation->width);
+ break;
+
+ case MATE_PANEL_APPLET_ORIENT_UP:
+ case MATE_PANEL_APPLET_ORIENT_DOWN:
+ trash_applet_set_icon_size (applet, allocation->height);
+ break;
+ }
+
+ GTK_WIDGET_CLASS (trash_applet_parent_class)
+ ->size_allocate (widget, allocation);
+}
+
+static void
+trash_applet_destroy (GtkObject *object)
+{
+ TrashApplet *applet = TRASH_APPLET (object);
+
+ if (applet->trash_monitor)
+ g_object_unref (applet->trash_monitor);
+ applet->trash_monitor = NULL;
+
+ if (applet->trash)
+ g_object_unref (applet->trash);
+ applet->trash = NULL;
+
+ if (applet->image)
+ g_object_unref (applet->image);
+ applet->image = NULL;
+
+ if (applet->icon)
+ g_object_unref (applet->icon);
+ applet->icon = NULL;
+
+ GTK_OBJECT_CLASS (trash_applet_parent_class)
+ ->destroy (object);
+}
+
+static void
+trash_applet_init (TrashApplet *applet)
+{
+ const GtkTargetEntry drop_types[] = { { "text/uri-list" } };
+
+ /* needed to clamp ourselves to the panel size */
+ mate_panel_applet_set_flags (MATE_PANEL_APPLET (applet), MATE_PANEL_APPLET_EXPAND_MINOR);
+
+ /* enable transparency hack */
+ mate_panel_applet_set_background_widget (MATE_PANEL_APPLET (applet),
+ GTK_WIDGET (applet));
+
+ /* setup the image */
+ applet->image = g_object_ref_sink (gtk_image_new ());
+ gtk_container_add (GTK_CONTAINER (applet),
+ GTK_WIDGET (applet->image));
+ gtk_widget_show (GTK_WIDGET (applet->image));
+
+ /* setup the trash backend */
+ applet->trash = g_file_new_for_uri ("trash:/");
+ applet->trash_monitor = g_file_monitor_file (applet->trash, 0, NULL, NULL);
+ g_signal_connect_swapped (applet->trash_monitor, "changed",
+ G_CALLBACK (trash_applet_monitor_changed),
+ applet);
+
+ /* setup drag and drop */
+ gtk_drag_dest_set (GTK_WIDGET (applet), GTK_DEST_DEFAULT_ALL,
+ drop_types, G_N_ELEMENTS (drop_types),
+ GDK_ACTION_MOVE);
+
+ /* synthesise the first update */
+ applet->items = -1;
+ trash_applet_monitor_changed (applet);
+}
+
+#define PANEL_ENABLE_ANIMATIONS "/apps/panel/global/enable_animations"
+static gboolean
+trash_applet_button_release (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ TrashApplet *applet = TRASH_APPLET (widget);
+ static MateConfClient *client;
+
+ if (client == NULL)
+ client = mateconf_client_get_default ();
+
+ if (event->button == 1)
+ {
+ if (mateconf_client_get_bool (client, PANEL_ENABLE_ANIMATIONS, NULL))
+ xstuff_zoom_animate (widget, NULL);
+
+ trash_applet_open_folder (NULL, applet);
+
+ return TRUE;
+ }
+
+ if (GTK_WIDGET_CLASS (trash_applet_parent_class)->button_release_event)
+ return GTK_WIDGET_CLASS (trash_applet_parent_class)
+ ->button_release_event (widget, event);
+ else
+ return FALSE;
+}
+static gboolean
+trash_applet_key_press (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ TrashApplet *applet = TRASH_APPLET (widget);
+
+ switch (event->keyval)
+ {
+ case GDK_KP_Enter:
+ case GDK_ISO_Enter:
+ case GDK_3270_Enter:
+ case GDK_Return:
+ case GDK_space:
+ case GDK_KP_Space:
+ trash_applet_open_folder (NULL, applet);
+ return TRUE;
+
+ default:
+ break;
+ }
+
+ if (GTK_WIDGET_CLASS (trash_applet_parent_class)->key_press_event)
+ return GTK_WIDGET_CLASS (trash_applet_parent_class)
+ ->key_press_event (widget, event);
+ else
+ return FALSE;
+}
+
+static gboolean
+trash_applet_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ GList *target;
+
+ /* refuse drops of panel applets */
+ for (target = context->targets; target; target = target->next)
+ {
+ const char *name = gdk_atom_name (target->data);
+
+ if (!strcmp (name, "application/x-panel-icon-internal"))
+ break;
+ }
+
+ if (target)
+ gdk_drag_status (context, 0, time);
+ else
+ gdk_drag_status (context, GDK_ACTION_MOVE, time);
+
+ return TRUE;
+}
+
+/* TODO - Must HIGgify this dialog */
+static void
+error_dialog (TrashApplet *applet, const gchar *error, ...)
+{
+ va_list args;
+ gchar *error_string;
+ GtkWidget *dialog;
+
+ g_return_if_fail (error != NULL);
+
+ va_start (args, error);
+ error_string = g_strdup_vprintf (error, args);
+ va_end (args);
+
+ dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ "%s", error_string);
+
+ g_signal_connect (G_OBJECT (dialog), "response",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+ gtk_window_set_screen (GTK_WINDOW(dialog),
+ gtk_widget_get_screen (GTK_WIDGET (applet)));
+ gtk_widget_show (dialog);
+
+ g_free (error_string);
+}
+
+static void
+trash_applet_do_empty (GtkAction *action,
+ TrashApplet *applet)
+{
+ trash_empty (GTK_WIDGET (applet));
+}
+
+static void
+trash_applet_open_folder (GtkAction *action,
+ TrashApplet *applet)
+{
+ GError *err = NULL;
+
+ gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (applet)),
+ "trash:",
+ gtk_get_current_event_time (),
+ &err);
+
+ if (err)
+ {
+ error_dialog (applet, _("Error while spawning caja:\n%s"),
+ err->message);
+ g_error_free (err);
+ }
+}
+
+static void
+trash_applet_show_help (GtkAction *action,
+ TrashApplet *applet)
+{
+ GError *err = NULL;
+
+ /* FIXME - Actually, we need a user guide */
+ gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (applet)),
+ "ghelp:trashapplet",
+ gtk_get_current_event_time (),
+ &err);
+
+ if (err)
+ {
+ error_dialog (applet,
+ _("There was an error displaying help: %s"),
+ err->message);
+ g_error_free (err);
+ }
+}
+
+
+static void
+trash_applet_show_about (GtkAction *action,
+ TrashApplet *applet)
+{
+ static const char *authors[] = {
+ "Michiel Sikkes <[email protected]>",
+ "Emmanuele Bassi <[email protected]>",
+ "Sebastian Bacher <[email protected]>",
+ "James Henstridge <[email protected]>",
+ "Ryan Lortie <[email protected]>",
+ NULL
+ };
+ static const char *documenters[] = {
+ "Michiel Sikkes <[email protected]>",
+ NULL
+ };
+
+ gtk_show_about_dialog (NULL,
+ "version", VERSION,
+ "copyright", "Copyright \xC2\xA9 2004 Michiel Sikkes,"
+ "\xC2\xA9 2008 Ryan Lortie",
+ "comments", _("A MATE trash bin that lives in your panel. "
+ "You can use it to view the trash or drag "
+ "and drop items into the trash."),
+ "authors", authors,
+ "documenters", documenters,
+ "translator-credits", _("translator-credits"),
+ "logo_icon_name", "user-trash-full",
+ NULL);
+}
+
+static gboolean
+confirm_delete_immediately (GtkWidget *parent_view,
+ gint num_files,
+ gboolean all)
+{
+ GdkScreen *screen;
+ GtkWidget *dialog, *hbox, *vbox, *image, *label;
+ gchar *str, *prompt, *detail;
+ int response;
+
+ screen = gtk_widget_get_screen (parent_view);
+
+ dialog = gtk_dialog_new ();
+ gtk_window_set_screen (GTK_WINDOW (dialog), screen);
+ atk_object_set_role (gtk_widget_get_accessible (dialog), ATK_ROLE_ALERT);
+ gtk_window_set_title (GTK_WINDOW (dialog), _("Delete Immediately?"));
+ gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ gtk_widget_realize (dialog);
+ gdk_window_set_transient_for (gtk_widget_get_window (GTK_WIDGET (dialog)),
+ gdk_screen_get_root_window (screen));
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 14);
+
+ hbox = gtk_hbox_new (FALSE, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
+ gtk_widget_show (hbox);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), hbox,
+ FALSE, FALSE, 0);
+
+ image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_QUESTION,
+ GTK_ICON_SIZE_DIALOG);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
+ gtk_widget_show (image);
+ gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+
+ vbox = gtk_vbox_new (FALSE, 12);
+ gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
+ gtk_widget_show (vbox);
+
+ if (all)
+ {
+ prompt = _("Cannot move items to trash, do you want to delete them immediately?");
+ detail = g_strdup_printf ("None of the %d selected items can be moved to the Trash", num_files);
+ }
+ else
+ {
+ prompt = _("Cannot move some items to trash, do you want to delete these immediately?");
+ detail = g_strdup_printf ("%d of the selected items cannot be moved to the Trash", num_files);
+ }
+
+ str = g_strconcat ("<span weight=\"bold\" size=\"larger\">",
+ prompt, "</span>", NULL);
+ label = gtk_label_new (str);
+ gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+ gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+ gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+ g_free (str);
+
+ label = gtk_label_new (detail);
+ gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+ gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+ g_free (detail);
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_DELETE,
+ GTK_RESPONSE_YES);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ GTK_RESPONSE_YES);
+
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_object_destroy (GTK_OBJECT (dialog));
+
+ return response == GTK_RESPONSE_YES;
+}
+
+static void
+trash_applet_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selectiondata,
+ guint info,
+ guint time_)
+{
+ gchar **list;
+ gint i;
+ GList *trashed = NULL;
+ GList *untrashable = NULL;
+ GList *l;
+ GError *error = NULL;
+
+ list = g_uri_list_extract_uris ((gchar *)gtk_selection_data_get_data (selectiondata));
+
+ for (i = 0; list[i]; i++)
+ {
+ GFile *file;
+
+ file = g_file_new_for_uri (list[i]);
+
+ if (!g_file_trash (file, NULL, NULL))
+ {
+ untrashable = g_list_prepend (untrashable, file);
+ }
+ else
+ {
+ trashed = g_list_prepend (trashed, file);
+ }
+ }
+
+ if (untrashable)
+ {
+ if (confirm_delete_immediately (widget,
+ g_list_length (untrashable),
+ trashed == NULL))
+ {
+ for (l = untrashable; l; l = l->next)
+ {
+ if (!g_file_delete (l->data, NULL, &error))
+ {
+/*
+* FIXME: uncomment me after branched (we're string frozen)
+ error_dialog (applet,
+ _("Unable to delete '%s': %s"),
+ g_file_get_uri (l->data),
+ error->message);
+*/
+ g_clear_error (&error);
+ }
+ }
+ }
+ }
+
+ g_list_foreach (untrashable, (GFunc)g_object_unref, NULL);
+ g_list_free (untrashable);
+ g_list_foreach (trashed, (GFunc)g_object_unref, NULL);
+ g_list_free (trashed);
+
+ g_strfreev (list);
+
+ gtk_drag_finish (context, TRUE, FALSE, time_);
+}
+
+static void
+trash_applet_class_init (TrashAppletClass *class)
+{
+ GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+ gtkobject_class->destroy = trash_applet_destroy;
+ widget_class->size_allocate = trash_applet_size_allocate;
+ widget_class->button_release_event = trash_applet_button_release;
+ widget_class->key_press_event = trash_applet_key_press;
+ widget_class->drag_motion = trash_applet_drag_motion;
+ widget_class->drag_data_received = trash_applet_drag_data_received;
+}
+
+static gboolean
+trash_applet_factory (MatePanelApplet *applet,
+ const gchar *iid,
+ gpointer data)
+{
+ gboolean retval = FALSE;
+
+ if (!strcmp (iid, "TrashApplet"))
+ {
+ GtkActionGroup *action_group;
+ gchar *ui_path;
+
+ g_set_application_name (_("Trash Applet"));
+
+ gtk_window_set_default_icon_name ("user-trash");
+
+ /* Set up the menu */
+ action_group = gtk_action_group_new ("Trash Applet Actions");
+ gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
+ gtk_action_group_add_actions (action_group,
+ trash_applet_menu_actions,
+ G_N_ELEMENTS (trash_applet_menu_actions),
+ applet);
+ ui_path = g_build_filename (TRASH_MENU_UI_DIR, "trashapplet-menu.xml", NULL);
+ mate_panel_applet_setup_menu_from_file (applet, ui_path, action_group);
+ g_free (ui_path);
+ g_object_unref (action_group);
+
+ gtk_widget_show (GTK_WIDGET (applet));
+
+ retval = TRUE;
+ }
+
+ return retval;
+}
+
+MATE_PANEL_APPLET_OUT_PROCESS_FACTORY ("TrashAppletFactory",
+ TRASH_TYPE_APPLET,
+ "TrashApplet",
+ trash_applet_factory,
+ NULL)
diff --git a/trashapplet/src/xstuff.c b/trashapplet/src/xstuff.c
new file mode 100644
index 00000000..0458f182
--- /dev/null
+++ b/trashapplet/src/xstuff.c
@@ -0,0 +1,536 @@
+/*
+ * MATE panel x stuff
+ *
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ * 2002 Sun Microsystems Inc.
+ *
+ * Authors: George Lebl <[email protected]>
+ * Mark McLoughlin <[email protected]>
+ *
+ * Contains code from the Window Maker window manager
+ *
+ * Copyright (c) 1997-2002 Alfredo K. Kojima
+
+ */
+#include <config.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include "xstuff.h"
+
+static Atom
+panel_atom_get (const char *atom_name)
+{
+ static GHashTable *atom_hash;
+ Display *xdisplay;
+ Atom retval;
+
+ g_return_val_if_fail (atom_name != NULL, None);
+
+ xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
+
+ if (!atom_hash)
+ atom_hash = g_hash_table_new_full (
+ g_str_hash, g_str_equal, g_free, NULL);
+
+ retval = GPOINTER_TO_UINT (g_hash_table_lookup (atom_hash, atom_name));
+ if (!retval) {
+ retval = XInternAtom (xdisplay, atom_name, FALSE);
+
+ if (retval != None)
+ g_hash_table_insert (atom_hash, g_strdup (atom_name),
+ GUINT_TO_POINTER (retval));
+ }
+
+ return retval;
+}
+
+/* Stolen from deskguide */
+static gpointer
+get_typed_property_data (Display *xdisplay,
+ Window xwindow,
+ Atom property,
+ Atom requested_type,
+ gint *size_p,
+ guint expected_format)
+{
+ static const guint prop_buffer_lengh = 1024 * 1024;
+ unsigned char *prop_data = NULL;
+ Atom type_returned = 0;
+ unsigned long nitems_return = 0, bytes_after_return = 0;
+ int format_returned = 0;
+ gpointer data = NULL;
+ gboolean abort = FALSE;
+
+ g_return_val_if_fail (size_p != NULL, NULL);
+ *size_p = 0;
+
+ gdk_error_trap_push ();
+
+ abort = XGetWindowProperty (xdisplay,
+ xwindow,
+ property,
+ 0, prop_buffer_lengh,
+ False,
+ requested_type,
+ &type_returned, &format_returned,
+ &nitems_return,
+ &bytes_after_return,
+ &prop_data) != Success;
+ if (gdk_error_trap_pop () ||
+ type_returned == None)
+ abort++;
+ if (!abort &&
+ requested_type != AnyPropertyType &&
+ requested_type != type_returned)
+ {
+ g_warning ("%s(): Property has wrong type, probably on crack", G_STRFUNC);
+ abort++;
+ }
+ if (!abort && bytes_after_return)
+ {
+ g_warning ("%s(): Eeek, property has more than %u bytes, stored on harddisk?",
+ G_STRFUNC, prop_buffer_lengh);
+ abort++;
+ }
+ if (!abort && expected_format && expected_format != format_returned)
+ {
+ g_warning ("%s(): Expected format (%u) unmatched (%d), programmer was drunk?",
+ G_STRFUNC, expected_format, format_returned);
+ abort++;
+ }
+ if (!abort && prop_data && nitems_return && format_returned)
+ {
+ switch (format_returned)
+ {
+ case 32:
+ *size_p = nitems_return * 4;
+ if (sizeof (gulong) == 8)
+ {
+ guint32 i, *mem = g_malloc0 (*size_p + 1);
+ gulong *prop_longs = (gulong*) prop_data;
+
+ for (i = 0; i < *size_p / 4; i++)
+ mem[i] = prop_longs[i];
+ data = mem;
+ }
+ break;
+ case 16:
+ *size_p = nitems_return * 2;
+ break;
+ case 8:
+ *size_p = nitems_return;
+ break;
+ default:
+ g_warning ("Unknown property data format with %d bits (extraterrestrial?)",
+ format_returned);
+ break;
+ }
+ if (!data && *size_p)
+ {
+ guint8 *mem = g_malloc (*size_p + 1);
+
+ memcpy (mem, prop_data, *size_p);
+ mem[*size_p] = 0;
+ data = mem;
+ }
+ }
+
+ if (prop_data)
+ XFree (prop_data);
+
+ return data;
+}
+
+gboolean
+xstuff_is_compliant_wm (void)
+{
+ Display *xdisplay;
+ Window root_window;
+ gpointer data;
+ int size;
+
+ xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
+ root_window = GDK_WINDOW_XWINDOW (
+ gdk_get_default_root_window ());
+
+ /* FIXME this is totally broken; should be using
+ * gdk_net_wm_supports() on particular hints when we rely
+ * on those particular hints
+ */
+ data = get_typed_property_data (
+ xdisplay, root_window,
+ panel_atom_get ("_NET_SUPPORTED"),
+ XA_ATOM, &size, 32);
+
+ if (!data)
+ return FALSE;
+
+ /* Actually checks for some of these */
+ g_free (data);
+ return TRUE;
+}
+
+gboolean
+xstuff_net_wm_supports (const char *hint)
+{
+ return gdk_net_wm_supports (gdk_atom_intern (hint, FALSE));
+}
+
+void
+xstuff_set_no_group (GdkWindow *win)
+{
+ XWMHints *old_wmhints;
+ XWMHints wmhints = {0};
+
+ XDeleteProperty (GDK_WINDOW_XDISPLAY (win),
+ GDK_WINDOW_XWINDOW (win),
+ panel_atom_get ("WM_CLIENT_LEADER"));
+
+ old_wmhints = XGetWMHints (GDK_WINDOW_XDISPLAY (win),
+ GDK_WINDOW_XWINDOW (win));
+ /* General paranoia */
+ if (old_wmhints != NULL) {
+ memcpy (&wmhints, old_wmhints, sizeof (XWMHints));
+ XFree (old_wmhints);
+
+ wmhints.flags &= ~WindowGroupHint;
+ wmhints.window_group = 0;
+ } else {
+ /* General paranoia */
+ wmhints.flags = StateHint;
+ wmhints.window_group = 0;
+ wmhints.initial_state = NormalState;
+ }
+
+ XSetWMHints (GDK_WINDOW_XDISPLAY (win),
+ GDK_WINDOW_XWINDOW (win),
+ &wmhints);
+}
+
+/* This is such a broken stupid function. */
+void
+xstuff_set_pos_size (GdkWindow *window, int x, int y, int w, int h)
+{
+ XSizeHints size_hints;
+ int old_x, old_y, old_w, old_h;
+
+ old_x = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (window), "xstuff-cached-x"));
+ old_y = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (window), "xstuff-cached-y"));
+ old_w = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (window), "xstuff-cached-w"));
+ old_h = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (window), "xstuff-cached-h"));
+
+ if (x == old_x && y == old_y && w == old_w && h == old_h)
+ return;
+
+ /* Do not add USPosition / USSize here, fix the damn WM */
+ size_hints.flags = PPosition | PSize | PMaxSize | PMinSize;
+ size_hints.x = 0; /* window managers aren't supposed to and */
+ size_hints.y = 0; /* don't use these fields */
+ size_hints.width = w;
+ size_hints.height = h;
+ size_hints.min_width = w;
+ size_hints.min_height = h;
+ size_hints.max_width = w;
+ size_hints.max_height = h;
+
+ gdk_error_trap_push ();
+
+ XSetWMNormalHints (GDK_WINDOW_XDISPLAY (window),
+ GDK_WINDOW_XWINDOW (window),
+ &size_hints);
+
+ gdk_window_move_resize (window, x, y, w, h);
+
+ gdk_flush ();
+ gdk_error_trap_pop ();
+
+ g_object_set_data (G_OBJECT (window), "xstuff-cached-x", GINT_TO_POINTER (x));
+ g_object_set_data (G_OBJECT (window), "xstuff-cached-y", GINT_TO_POINTER (y));
+ g_object_set_data (G_OBJECT (window), "xstuff-cached-w", GINT_TO_POINTER (w));
+ g_object_set_data (G_OBJECT (window), "xstuff-cached-h", GINT_TO_POINTER (h));
+}
+
+void
+xstuff_set_wmspec_dock_hints (GdkWindow *window,
+ gboolean autohide)
+{
+ Atom atoms [2] = { None, None };
+
+ if (!autohide)
+ atoms [0] = panel_atom_get ("_NET_WM_WINDOW_TYPE_DOCK");
+ else {
+ atoms [0] = panel_atom_get ("_MATE_WINDOW_TYPE_AUTOHIDE_PANEL");
+ atoms [1] = panel_atom_get ("_NET_WM_WINDOW_TYPE_DOCK");
+ }
+
+ XChangeProperty (GDK_WINDOW_XDISPLAY (window),
+ GDK_WINDOW_XWINDOW (window),
+ panel_atom_get ("_NET_WM_WINDOW_TYPE"),
+ XA_ATOM, 32, PropModeReplace,
+ (unsigned char *) atoms,
+ autohide ? 2 : 1);
+}
+
+void
+xstuff_set_wmspec_strut (GdkWindow *window,
+ int left,
+ int right,
+ int top,
+ int bottom)
+{
+ long vals [4];
+
+ vals [0] = left;
+ vals [1] = right;
+ vals [2] = top;
+ vals [3] = bottom;
+
+ XChangeProperty (GDK_WINDOW_XDISPLAY (window),
+ GDK_WINDOW_XWINDOW (window),
+ panel_atom_get ("_NET_WM_STRUT"),
+ XA_CARDINAL, 32, PropModeReplace,
+ (unsigned char *) vals, 4);
+}
+
+void
+xstuff_delete_property (GdkWindow *window, const char *name)
+{
+ Display *xdisplay = GDK_WINDOW_XDISPLAY (window);
+ Window xwindow = GDK_WINDOW_XWINDOW (window);
+
+ XDeleteProperty (xdisplay, xwindow,
+ panel_atom_get (name));
+}
+
+/* Zoom animation */
+#define MINIATURIZE_ANIMATION_FRAMES_Z 1
+#define MINIATURIZE_ANIMATION_STEPS_Z 6
+/* the delay per draw */
+#define MINIATURIZE_ANIMATION_DELAY_Z 10
+
+static void
+draw_zoom_animation (GdkScreen *gscreen,
+ int x, int y, int w, int h,
+ int fx, int fy, int fw, int fh,
+ int steps)
+{
+#define FRAMES (MINIATURIZE_ANIMATION_FRAMES_Z)
+ float cx[FRAMES], cy[FRAMES], cw[FRAMES], ch[FRAMES];
+ int cxi[FRAMES], cyi[FRAMES], cwi[FRAMES], chi[FRAMES];
+ float xstep, ystep, wstep, hstep;
+ int i, j;
+ GC frame_gc;
+ XGCValues gcv;
+ GdkColor color = { 65535, 65535, 65535 };
+ Display *dpy;
+ Window root_win;
+ int screen;
+ int depth;
+
+ dpy = gdk_x11_display_get_xdisplay (gdk_screen_get_display (gscreen));
+ root_win = gdk_x11_drawable_get_xid (gdk_screen_get_root_window (gscreen));
+ screen = gdk_screen_get_number (gscreen);
+ depth = gdk_drawable_get_depth (gdk_screen_get_root_window (gscreen));
+
+ /* frame GC */
+ gdk_colormap_alloc_color (
+ gdk_screen_get_system_colormap (gscreen), &color, FALSE, TRUE);
+ gcv.function = GXxor;
+ /* this will raise the probability of the XORed color being different
+ * of the original color in PseudoColor when not all color cells are
+ * initialized */
+ if (DefaultVisual(dpy, screen)->class==PseudoColor)
+ gcv.plane_mask = (1<<(depth-1))|1;
+ else
+ gcv.plane_mask = AllPlanes;
+ gcv.foreground = color.pixel;
+ if (gcv.foreground == 0)
+ gcv.foreground = 1;
+ gcv.line_width = 1;
+ gcv.subwindow_mode = IncludeInferiors;
+ gcv.graphics_exposures = False;
+
+ frame_gc = XCreateGC(dpy, root_win, GCForeground|GCGraphicsExposures
+ |GCFunction|GCSubwindowMode|GCLineWidth
+ |GCPlaneMask, &gcv);
+
+ xstep = (float)(fx-x)/steps;
+ ystep = (float)(fy-y)/steps;
+ wstep = (float)(fw-w)/steps;
+ hstep = (float)(fh-h)/steps;
+
+ for (j=0; j<FRAMES; j++) {
+ cx[j] = (float)x;
+ cy[j] = (float)y;
+ cw[j] = (float)w;
+ ch[j] = (float)h;
+ cxi[j] = (int)cx[j];
+ cyi[j] = (int)cy[j];
+ cwi[j] = (int)cw[j];
+ chi[j] = (int)ch[j];
+ }
+ XGrabServer(dpy);
+ for (i=0; i<steps; i++) {
+ for (j=0; j<FRAMES; j++) {
+ XDrawRectangle(dpy, root_win, frame_gc, cxi[j], cyi[j], cwi[j], chi[j]);
+ }
+ XFlush(dpy);
+#if (MINIATURIZE_ANIMATION_DELAY_Z > 0)
+ usleep(MINIATURIZE_ANIMATION_DELAY_Z);
+#else
+ usleep(10);
+#endif
+ for (j=0; j<FRAMES; j++) {
+ XDrawRectangle(dpy, root_win, frame_gc,
+ cxi[j], cyi[j], cwi[j], chi[j]);
+ if (j<FRAMES-1) {
+ cx[j]=cx[j+1];
+ cy[j]=cy[j+1];
+ cw[j]=cw[j+1];
+ ch[j]=ch[j+1];
+
+ cxi[j]=cxi[j+1];
+ cyi[j]=cyi[j+1];
+ cwi[j]=cwi[j+1];
+ chi[j]=chi[j+1];
+
+ } else {
+ cx[j]+=xstep;
+ cy[j]+=ystep;
+ cw[j]+=wstep;
+ ch[j]+=hstep;
+
+ cxi[j] = (int)cx[j];
+ cyi[j] = (int)cy[j];
+ cwi[j] = (int)cw[j];
+ chi[j] = (int)ch[j];
+ }
+ }
+ }
+
+ for (j=0; j<FRAMES; j++) {
+ XDrawRectangle(dpy, root_win, frame_gc,
+ cxi[j], cyi[j], cwi[j], chi[j]);
+ }
+ XFlush(dpy);
+#if (MINIATURIZE_ANIMATION_DELAY_Z > 0)
+ usleep(MINIATURIZE_ANIMATION_DELAY_Z);
+#else
+ usleep(10);
+#endif
+ for (j=0; j<FRAMES; j++) {
+ XDrawRectangle(dpy, root_win, frame_gc,
+ cxi[j], cyi[j], cwi[j], chi[j]);
+ }
+
+ XUngrabServer(dpy);
+ XFreeGC (dpy, frame_gc);
+ gdk_colormap_free_colors (gdk_screen_get_system_colormap (gscreen),
+ &color, 1);
+}
+#undef FRAMES
+
+void
+xstuff_zoom_animate (GtkWidget *widget, GdkRectangle *opt_rect)
+{
+ GdkScreen *gscreen;
+ GdkRectangle rect, dest;
+ GtkAllocation allocation;
+ int monitor;
+
+ if (opt_rect)
+ rect = *opt_rect;
+ else {
+ gdk_window_get_origin (gtk_widget_get_window (widget), &rect.x, &rect.y);
+ gtk_widget_get_allocation (widget, &allocation);
+ if (!gtk_widget_get_has_window (widget)) {
+ rect.x += allocation.x;
+ rect.y += allocation.y;
+ }
+ rect.height = allocation.height;
+ rect.width = allocation.width;
+ }
+
+ gscreen = gtk_widget_get_screen (widget);
+ monitor = gdk_screen_get_monitor_at_window (gscreen, gtk_widget_get_window (widget));
+ gdk_screen_get_monitor_geometry (gscreen, monitor, &dest);
+
+ draw_zoom_animation (gscreen,
+ rect.x, rect.y, rect.width, rect.height,
+ dest.x, dest.y, dest.width, dest.height,
+ MINIATURIZE_ANIMATION_STEPS_Z);
+}
+
+int
+xstuff_get_current_workspace (GdkScreen *screen)
+{
+ Window root_window;
+ Atom type = None;
+ gulong nitems;
+ gulong bytes_after;
+ gulong *num;
+ int format;
+ int result;
+ int retval;
+
+ root_window = gdk_x11_drawable_get_xid (
+ gdk_screen_get_root_window (screen));
+
+ gdk_error_trap_push ();
+ result = XGetWindowProperty (gdk_display,
+ root_window,
+ panel_atom_get ("_NET_CURRENT_DESKTOP"),
+ 0, G_MAXLONG,
+ False, XA_CARDINAL, &type, &format, &nitems,
+ &bytes_after, (gpointer) &num);
+ if (gdk_error_trap_pop () || result != Success)
+ return -1;
+
+ if (type != XA_CARDINAL) {
+ XFree (num);
+ return -1;
+ }
+
+ retval = *num;
+
+ XFree (num);
+
+ return retval;
+}
+
+void
+xstuff_grab_key_on_all_screens (int keycode,
+ guint modifiers,
+ gboolean grab)
+{
+ GdkDisplay *display;
+ int n_screens;
+ int i;
+
+ display = gdk_display_get_default ();
+ n_screens = gdk_display_get_n_screens (display);
+
+ for (i = 0; i < n_screens; i++) {
+ GdkWindow *root;
+
+ root = gdk_screen_get_root_window (
+ gdk_display_get_screen (display, i));
+
+ if (grab)
+ XGrabKey (gdk_x11_display_get_xdisplay (display),
+ keycode, modifiers,
+ gdk_x11_drawable_get_xid (root),
+ True, GrabModeAsync, GrabModeAsync);
+ else
+ XUngrabKey (gdk_x11_display_get_xdisplay (display),
+ keycode, modifiers,
+ gdk_x11_drawable_get_xid (root));
+ }
+}
diff --git a/trashapplet/src/xstuff.h b/trashapplet/src/xstuff.h
new file mode 100644
index 00000000..f927c220
--- /dev/null
+++ b/trashapplet/src/xstuff.h
@@ -0,0 +1,35 @@
+#ifndef __XSTUFF_H__
+#define __XSTUFF_H__
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+void xstuff_delete_property (GdkWindow *window,
+ const char *name);
+gboolean xstuff_is_compliant_wm (void);
+gboolean xstuff_net_wm_supports (const char *hint);
+
+void xstuff_set_no_group (GdkWindow *win);
+
+void xstuff_unsetup_desktop_area (void);
+void xstuff_set_pos_size (GdkWindow *window,
+ int x, int y,
+ int w, int h);
+void xstuff_set_wmspec_dock_hints (GdkWindow *window,
+ gboolean autohide);
+void xstuff_set_wmspec_strut (GdkWindow *window,
+ int left,
+ int right,
+ int top,
+ int bottom);
+
+void xstuff_zoom_animate (GtkWidget *widget,
+ GdkRectangle *opt_src_rect);
+
+int xstuff_get_current_workspace (GdkScreen *screen);
+
+void xstuff_grab_key_on_all_screens (int keycode,
+ guint modifiers,
+ gboolean grab);
+
+#endif /* __XSTUFF_H__ */